Spring5框架入门基础
Spring5框架概述
-
Spring是轻量级的开源的JavaEE框架。
-
Spring可以解决企业应用开发的复杂性。
-
Spring有两个核心部分:
IOC
和AOP
- IOC:控制反转,把创建对象过程交给Spring进行管理
- AOP:面向切面,不修改源代码进行功能增强
-
Spring特点
- 方便解耦,简化开发(IOC可以降低耦合性)
- AOP编程支持
- 方便程序测试(Spring对Junit4支持,可以通过注解方便的测试Spring程序)
- 方便和其他框架进行整合
- 方便进行事务操作
- 降低API开发难度
-
Spring5系统结构
IOC
概念和原理
什么是IOC
- 控制反转,把对象创建和对象之间的调用过程,交给Spring进行管理
- 使用IOC目的:为了降低耦合度
IOC底层原理
xml解析、工厂模式、反射
-
原始方式与工厂模式的对比
-
画图讲解IOC底层原理
IOC之BeanFactory接口
-
IOC思想基于IOC容器完成,IOC底层就是对象工厂
-
Spring提供IOC容器实现两种方式:(两个接口)
-
BeanFactory:IOC容器基本实现,是Spring内部的使用接口,不提供开发人员进行使用
加载配置文件时不会创建对象,在获取对象(使用)才去创建对象
-
ApplicationContext:BeanFactory接口的子接口,提供更多更强大的功能,一般由开发人人员进行调用
加载配置文件时会把在配置文件对象进行创建
-
-
ApplicationContext
接口有实现类
-
FileSystemXmlApplicationContext
configLocation
:要写上配置文件在系统盘(某个盘)里的路径 -
ClassPathXmlApplicationContext
configLocation
:要写上类路径
-
IOC操作Bean管理
概念
-
什么是Bean管理
Bean管理指的是两个操作:
Spring创建对象
和Spring注入属性
-
Bean管理操作的方式:
- 基于xml配置文件方式实现
- 基于注释方式实现
基于xml方式
-
创建对象
<!--配置User对象创建--> <bean id="user" class="com.atguigu.spring.User"></bean>
- 在Spring配置文件中,使用
bean标签
,标签里面添加对应属性,就可以实现对象创建 bean标签
常用的属性id
属性:唯一标识class
属性:类全路径(包类路径)
- 创建对象时候,默认执行无参构造方法
- 在Spring配置文件中,使用
-
注入属性
-
DI:依赖注入(Dependency Injection),就是注入属性
DI是一种设计模式,用于管理对象之间的依赖关系,它将创建和管理对象的责任转移给了第三方的容器或框架,从而降低了系统的耦合度。
-
第一种注入方式:使用
set方法
进行注入-
创建类,定义属性和对应的set方法
/** * 演示set方法注入属性 */ public class Book { //创建属性 private String bname; private String bauthor; //创建属性对应set方法 public void setBauthor(String bauthor) { this.bauthor = bauthor; } public void setBname(String bname) { this.bname = bname; } }
-
在
Spring配置文件
配置对象创建,配置属性注入<!--set方法注入属性--> <bean id="book" class="com.atguigu.spring5.Book"> <!--使用property完成属性注入 name:类里面属性名称 value:向属性注入的值 --> <property name="bname" value="易筋经"></property> <property name="bauthor" value="达摩老祖"></property> </bean>
-
-
第二种注入方式:使用
有参构造
进行注入-
创建类,定义属性,创建属性对应的有参构造方法
/** *使用有参数构造注入 */ public class Orders { //属性 private String oname; private String address; //有参构造 public Orders(String oname, String address) { this.oname = oname; this.address = address; } }
-
在spring配置文件中进行配置
<!--用有参构造注入属性--> <bean id="orders" class="com.atguigu.spring5.Orders"> <constructor-arg name="oname" value="电脑"></constructor-arg> <constructor-arg name="address"value="China"></constructor-arg> </bean>
-
-
p名称空间注入(了解)
使用p名称空间注入,可以简化基于xml配置方式
-
添加p名称空间在配置文件中
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"></beans>
-
进行属性注入,在bean标签里面进行操作
<!--set方法注入属性--> <bean id="book" class="com.atguigu.spring5.Book" p:bname="九阳神功" p:bauthor="无名氏"> </bean>
-
-
xml注入其他类型属性
-
字面量
-
null值
<!--null值--> <property name="address"> <null/> </property>
-
属性值包含特殊符号
<!--属性值包含特殊符号 1. 把<>进行转义 <> 2. 把带特殊符号内容写到CDATA --> <property name="address"> <value><![CDATA[<<南京>>]]></value> </property>
-
-
注入属性——外部bean
-
创建两个类
service类
和dao类
package com.atguigu.spring5.dao; public interface UserDao{ public void update(); }
package com.atguigu.spring5.dao; public class UserDaoImpl implements UserDao{ @Override public void update(){ System.out.println("dao update..........."); } }
-
在
service
调用dao
里面的方法package com.atguigu.spring5.service; public class UserService { //创建UserDao类型属性,生成set方法 private UserDao userDao; public void setUserDao(UserDao userDao) { this.userDao = userDao; } public void add(){ System.out.println("service add................."); userDao.update(); } }
-
在
spring配置文件
中进行配置<!--1 service和dao对象的创建--> <bean id="userService" class="com.atguigu.spring5.service.UserService"> <!--注入userDao对象 name属性值:类里面属性名称 ref属性:创建userDao对象的bean标签id值 --> <property name="userDao" ref="userDaoImpl"></property> </bean> <bean id="userDaoImpl" class="com.atguigu.spring5.dao.UserDaoImpl"/></bean>
-
-
注入属性——内部bean
- 一对多关系
比如:部门和员工,一个部门有多个员工,一个员工属于一个部门。
-
在实体类之间表示一对多关系:员工使用对象类型属性进行表示所属部门
//部门类 public static Dept{ private String dname; public void setDname(String dname){ this.dname=dnamel } }
//员工类 public static Emp{ private String name; private String gender; private Dept dept; //用对象表示员工所属的部门 public void setName(String name){ this.name = name; } public void setGender(String gender){ this.gender = gender; } }
-
在
Spring配置文件
<!--内部bean--> <bean id="Emp" class="com.gtguigu.spring5.bean.Emp"> <!--设置两个普通属性--> <property name="ename" value="lucy"></property> <property name="gender" value="女"></property> <!--设置对象类型属性--> <property name="dept"> <bean id="dept" class="com.atguigu.spring5.bean.Dept"> <property name="dname" value="安保部"></property> </bean> </property> </bean>
-
注入属性——级联赋值
-
第一种写法
<!--级联赋值--> <bean id="emp" class="com.atguigu.spring5.bean.Emp"> <!--设置两个普通属性--> <property name="ename" value="lucy"></property> <property name="gender" value="女"></property> <!--级联赋值--> <property name="dept" ref="dept"></property> </bean> <bean id="dept" class="com.atguigu.spring5.bean.Dept"> <property name="dname" value="财务部"></property> </bean>
-
第二种写法
//使用对象形式表示员工属于某一个部门 private Dept dept; //生成dept的get方法 public Dept getDept(){ return dept; } public void setDept(Dept dept){ this.dept = dept; }
<!--级联赋值--> <bean id="emp" class="com.atguigu.spring5.bean.Emp"> <!--设置两个普通属性--> <property name="ename" value="lucy"></property> <property name="gender" value="女"></property> <!--级联赋值--> <property name="dept" ref="dept"></property> <property name="dept.dname" value="技术部"></property> </bean> <bean id="dept" class="com.atguigu.spring5.bean.Dept"> <property name="dname" value="财务部"></property> </bean>
-
xml注入集合属性
- 注入数组类型属性
- 注入List集合类型属性
- 注入Map集合类型属性
- 注入Set集合类型属性
第一步,创建类,定义数组
、List
、Map
、Set
类型属性,生成对应set方法
public class Stu {
//1 数组类型的属性
private String[] courses;
//2 List集合类型
private List<String> list;
//3 Map集合类型
private Map<String,String> maps;
//4 set集合类型
private Set<String> sets;
public void setCourses(String[] courses) {
this.courses = courses;
}
public void setList(List<String> list) {
this.list = list;
}
public void setMaps(Map<String, String> maps) {
this.maps = maps;
}
public void setSets(Set<String> sets) {
this.sets = sets;
}
}
第二步,在Spring配置文件
进行配置
<!--1 集合类型属性的注入-->
<bean id="stu" class="com.atguigu.spring5.collectiontype.Stu">
<!--数组类型的属性注入-->
<property name="courses">
<array>
<value>java课程</value>
<value>数据库课程</value>
</array>
</property>
<!--list属性注入-->
<property name="list">
<list>
<value>张三</value>
<value>李四</value>
</list>
</property>
<!--Map类型注入-->
<property name="maps">
<map>
<entry key="JAVA" value="java" ></entry>
<entry key="PHP" value="php"></entry>
</map>
</property>
<!--set集合注入-->
<property name="sets">
<set>
<value>MySQL</value>
<value>redis</value>
</set>
</property>
</bean>
-
在集合里面设置对象类型值
<!--创建多个course对象--> <bean id="course1" class="com.atguigu.spring5.collectiontype.Course"> <property name="cname" value="Spring5框架"></property> </bean> <bean id="course2" class="com.atguigu.spring5.collectiontype.Course"> <property name="cname" value="Mybatis框架"></property> </bean> <!--注入list集合类型,值是对象--> <property name="courseList"> <list> <ref bean="course1"></ref> <ref bean="course2"></ref> </list> </property>
-
把集合注入部分提取出来
-
在
Spring配置文件
中引入名称空间util<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:util="http://www.springframework.org/schema/util" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/util/spring-beans.xsd http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd"> <!--把集合注入部分提取出来--> </beans>
-
使用
util标签
完成list集合
注入提取<!--1 把提取list集合类型属性注入--> <util:list id="bookList"> <value>易筋经</value> <value>九阳神功</value> <value>九阴真经</value> </util:list> <!--2 提取list集合类型属性注入使用--> <bean id="book" class="com.atguigu.spring5.collectiontype.Book"> <property name="list" ref="bookList"></property> </bean>
-
FactoryBean
- Spring有两种类型
bean
,一种普通bean
,另一种工厂bean(FactoryBean)
- 普通bean:在配置文件中定义bean类型就是返回类型
- 工厂bean:在配置文件中定义bean类型可以和返回类型不一样
第一步,创建类,让这个类作为工厂bean,实现接口FactoryBean
第二步,实现接口里面的方法,在实现的方法中定义返回的bean类型
public class MyBean implements FactoryBean<Course> {
//定义返回bean
@Override
public Course getObject() throws Exception {
Course course = new Course();
course.setCname("abc");
return course;
}
@Override
public Class<?> getObjectType() {
return null;
}
@Override
public boolean isSingleton() {
return false;
}
}
第三步,配置spring配置文件
<bean id="myBean" class="com.atguigu.spring5.factorybean.MyBean"></bean>
测试类:
public void test(){
ApplicationContext context = new ClassPathXmlApplicationContext("bean.xml");
Course course = context.getBean("mybean", Course.class);
System.out.println(course);
}
bean作用域
-
在Spring里面,设置创建bean实例可以是单实例,也可以是多实例
-
在Spring里面,默认情况下,bean是单实例对象
@Test public void testCollection2(){ ApplicationContext context=new ClassPathXmlApplicationContext("bean2.xml"); Book book =context.getBean("book", Book.class); Book book1 =context.getBean("book", Book.class); System.out.println(book); //com.atguigu.sqpring5.collectiontype.Book@5d11346a System.out.println(book1); //com.atguigu.sqpring5.collectiontype.Book@5d11346a }
以上例子的两个输出是相同的,说明bean默认是单实例对象。
-
如何设置单实例or多实例
- 在Spring配置文件
bean标签
里面有属性scope
,用于设置单实例还是多实例 scope
属性值singleton
:表示单实例对象(默认值)prototype
:表示多实例对象
<!--2 提取list集合类型属性注入使用--> <bean id="myBean" class="com.frx01.spring5.factorybean.MyBean" scope="prototype"> <property name="list" ref="bookList"></property> </bean>
再次测试:
@Test public void testCollection2(){ ApplicationContext context=new ClassPathXmlApplicationContext("bean2.xml"); Book book =context.getBean("book", Book.class); Book book1 =context.getBean("book", Book.class); System.out.println(book); //com.atguigu.sqpring5.collectiontype.Book@5d11346a System.out.println(book1); //com.atguigu.sqpring5.collectiontype.Book@7a36aefa }
以上例子的两个输出不同,说明此时bean是多实例对象。
- 在Spring配置文件
singleton
和prototype
区别- singleton表示单实例,prototype表示多实例
- 设置scope值singleton时候,加载spring配置文件就会创建一个单实例对象
- 设置scope值是prototype时候,不是在加载spring配置文件时候创建对象,在调用getBean方法时候创建多实例对象
bean生命周期
-
生命周期:从对象创建到对象销毁的过程
-
bean生命周期
- 通过构造器创建bean实例(无参构造)
- 为bean的属性设置值和对其他bean的引用(调用set方法)
- 调用bean的初始化的方法(需要进行配置初始化的方法)
- bean可以使用了(对象获取到了)
- 当容器关闭的时候,调用销毁bean的方法(需要配置销毁的方法)
-
演示bean生命周期
public class Orders { //无参数构造 public Orders() { System.out.println("第一步 执行无参构造创建bean实例"); } //创建set方法设置属性的值和对其他bean的引用 private String oname; public void setOname(String oname) { this.oname = oname; System.out.println("第二步 调用set方法设置属性的值"); } //创建执行的初始化方法 public void initMethod(){ System.out.println("第三步 执行初始化方法"); } //创建执行的销毁方法 public void destroyMethod(){ System.out.println("第五步 执行销毁方法"); } }
在Spring配置文件中使用
init-method
指定初始化方法,用destroy-method
指定销毁方法<bean id="orders" class="com.atguigu.spring5.bean.Orders" init-method="initMethod" destroy-method="destroyMethod"> <property name="oname" value="手机"></property> </bean>
测试:
@Test public void testCollection4() { // ApplicationContext context = // new ClassPathXmlApplicationContext("bean4.xml"); ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("bean4.xml"); Orders orders = context.getBean("orders", Orders.class); System.out.println("第四步 获取创建bean实例对象"); System.out.println(orders); //手动让bean实例销毁 context.close(); }
输出:
第一步 执行无参构造创建bean实例
第二步 调用set方法设置属性的值
第三步 执行初始化方法
第四步 获取创建bean实例对象
com.atguigu.spring5.bean.Order@192d3247
第五步 执行销毁方法
-
bean的后置处理器,bean生命周期有七步
- 通过构造器创建bean实例(无参构造)
- 为bean的属性设置值和对其他bean的引用(调用set方法)
- 把bean实例传递到bean后置处理器的方法
postProcessBeforeInitialization
- 调用bean的初始化的方法(需要进行配置初始化的方法)
- 把bean实例传递到bean后置处理器的方法
postProcessAfterInitialization
- bean可以使用了(对象获取到了)
- 当容器关闭的时候,调用销毁bean的方法(需要配置销毁的方法)
-
演示添加后置处理器效果
-
创建类,实现接口
BeanPostProcessor
,创建后置处理器public class MyBeanPost implements BeanPostProcessor { @Override public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { System.out.println("在初始化之前执行的方法"); return bean; } @Override public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { System.out.println("在初始化之后执行的方法"); return bean; } }
<bean id="orders" class="com.atguigu.spring5.bean.Orders" init-method="initMethod" destroy-method="destroyMethod"> <property name="oname" value="手机"></property> </bean> <!-- 配置后置处理器--> <bean id="MyBeanPost" class="com.atguigu.spring5.bean.MyBeanPost"></bean> </beans>
测试:
@Test public void testCollection4() { // ApplicationContext context = // new ClassPathXmlApplicationContext("bean4.xml"); ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("bean4.xml"); Orders orders = context.getBean("orders", Orders.class); System.out.println("第四步 获取创建bean实例对象"); System.out.println(orders); //手动让bean实例销毁 context.close(); }
输出:
第一步 执行无参构造创建bean实例
第二步 调用set方法设置属性的值
在初始化之前执行的方法
第三步 执行初始化方法
在初始化之后执行的方法
第四步 获取创建bean实例对象
com.atguigu.spring5.bean.Order@192d3247
第五步 执行销毁方法
-
xml自动装配
-
自动装配:根据指定的装配规则(属性名称或者属性类型),Spring自动将匹配的属性值注入
-
实现自动装配:
bean标签
属性autowire
,配置自动装配
autowire
属性常用两个值:
byName
根据属性名称注入,注入值bean的id值和类属性名称一样
byType
根据属性类型注入 -
演示自动装配过程
-
根据属性名称自动注入
<bean id="emp" class="com.atguigu.spring5.autowire.Emp" autowire="byName"> <!--<property name="dept" ref="dept"></property>--> </bean> <bean id="dept" class="com.atguigu.spring5.autowire.Dept"></bean>
-
根据属性类型自动注入
<bean id="emp" class="com.atguigu.spring5.autowire.Emp" autowire="byType"> <!--<property name="dept" ref="dept"></property>--> </bean> <bean id="dept" class="com.atguigu.spring5.autowire.Dept"></bean>
外部属性文件
-
直接配置数据库信息
-
配置德鲁伊连接池
-
引入德鲁伊连接池依赖jar包(druid)
<!--直接配置连接池--> <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"> <property name="driverClassName" value="com.mysql.jdbc.Driver"></property> <property name="url" value="jdbc:mysql://localhost:3306/userDb"></property> <property name="username" value="root"></property> <property name="password" value="root"></property> </bean>
-
-
引入外部属性文件配置数据库连接池
-
创建外部属性文件,
properties
格式文件,写数据库信息 -
把外部
properties属性文件
引入到spring配置文件
中-
引入context名称空间
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p" xmlns:util="http://www.springframework.org/schema/util" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/util/spring-util.context.xsd">
-
在Spring配置文件使用标签引入外部属性文件
<!--引入外部属性文件--> <context:property-placeholder location="classpath:jdbc.properties"/> <!--配置连接池--> <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"> <property name="driverClassName" value="${prop.driverClass}"></property> <property name="url" value="${prop.url}"></property> <property name="username" value="${prop.userName}"></property> <property name="password" value="${prop.passwd}"></property> </bean>
-
-
-
基于注解方式
-
什么是注解
- 注解是代码特殊标记,格式:
@注解名称(属性名称=属性值,属性名称=属性值)
- 使用注解,注解作用在类上面,方法上面,属性上面
- 使用注解目的:简化xml配置
- 注解是代码特殊标记,格式:
-
Spring针对Bean管理中创建对象提供注解
@Component
@Service
@Controller
@Repository
以上四个注解功能是一样的,都可以用来创建bean实例
-
基于注解方式实现对象创建
-
引入依赖
-
开启组件扫描
<!--开启组件扫描 1 如果扫描多个包 使用逗号隔开 2 扫描包上层目录 --> <context:component-scan base-package="com.atguigu"></context:component-scan>
-
创建类,在类上面添加创建对象注解
//在注解里面value属性值可以省略不写 //默认值是类名称,首字母小写 //UserService --> userService @Component(value = "userService") //<bean id="userService" class=".."/> public class UserService { public void add(){ System.out.println("service add......"); } }
-
开启组件扫描细节配置
<!--示例1 use-default-filters="false 表示现在不使用默认filter,不扫描全部,自己配置filter context:include-filler,设置扫描哪些内容 --> <context:component-scan base-package="com.atguigu" use-default-filters="false"> <context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/> </context:component-scan> <!--示例2 下面配置扫描包所有内容 context:exclude-filter:设置哪些内容不进行扫描 --> <context:component-scan base-package="com.atguigu"> <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/> </context:component-scan>
-
基于注解方式实现属性注入
-
@AutoWired
:根据属性类型自动装配第一步,把service和dao对象创建,在service和dao类添加创建对象注解
第二步,在service注入dao对象,在service类添加dao类型属性,在属性上面使用注解
@Service public class UserService { //定义dao类型的属性 //不需要添加set方法 //添加注入属性注解 @Autowired private UserDao userDao; public void add(){ System.out.println("service add......"); userDao.add(); } }
-
@Qualifier
:根据属性名称注入这个@Qualifier注解的使用,和上面@Autowired一起使用
//定义dao类型的属性 //不需要添加set方法 //添加注入属性注解 @Autowired //根据类型进行注入 @Qualifier(value = "userDaoImpl1") //根据名称注入 private UserDao userDao;
-
@Resource
:可以根据类型注入,可以根据名称注入// @Resource //根据类型进行注入 @Resource(name="userDaoImpl1") //根据名称进行注入 private UserDao userDao;
-
@Value
:注入普通类型属性@Value(value = "abc") private String name;
-
-
完全注释开发
-
创建配置类,代替xml配置文件
@Configuration //作为配置类,替代xml配置文件 @ComponentScan(basePackages = "com.atguigu") public class SpringConfig { }
-
编写测试类
@Test public void testService2(){ //加载配置类 ApplicationContext context = new AnnotationConfigApplicationContext(SpringConfig.class); UserService userService = context.getBean("userService", UserService.class); System.out.println(userService); userService.add(); }
-
-
AOP
概念
什么是AOP?
- 面向切面编程(方面),利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合性降低,提高程序的可重用性,同时提高了开发的效率。
- 通俗描述:不通过修改源代码方式,在主干功能里面添加新功能
- 使用登录例子说明AOP
底层原理
AOP底层使用动态代理
- 有接口代理,使用JDK动态代理
- 创建接口实现类代理对象,增强类的方法
- 没有接口情况,使用CGLIB动态代理
- 创建子类的代理对象,增强类的方法
JDK动态代理
-
使用JDK动态代理,使用
Proxy类
里面的方法创建代理对象Proxy类
是属于java.lang
包中的-
调用
newProxyInstance方法
static Object newProxyInstance(ClassLoader loader, 类<?>[] interfaces, InvocationHandler h)
返回指定接口的代理类的实例,该接口将调用分派给指定的调用处理程序。
参数:
第一个参数:类加载器
第二个参数:增强方法所在类,这个类实现的接口,支持多个接口
第三个参数:实现这个接口
InvocationHandler
,创建代理对象,写增强的部分
-
-
编写JDK动态代理代码
-
创建接口,定义方法
public interface UserDao { public int add(int a,int b); public String update(String id); }
-
创建接口实现类,实现方法
public class UserDaoImpl implements UserDao{ @Override public int add(int a, int b) { return a+b; } @Override public String update(String id) { return id; } }
-
使用Proxy类创建接口代理对象
public class JDKProxy { public static void main(String[] args) { //创建接口实现类代理对象 Class[] interfaces={UserDao.class}; // Proxy.newProxyInstance(JDKProxy.class.getClassLoader(), interfaces, new InvocationHandler() { // @Override // public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { // return null; // } // }); UserDaoImpl userDao=new UserDaoImpl(); UserDao dao=(UserDao)Proxy.newProxyInstance(JDKProxy.class.getClassLoader(), interfaces, new UserDaoProxy(userDao)); int result = dao.add(1, 2); System.out.println("result"+result); } } //创建代理对象代码 class UserDaoProxy implements InvocationHandler{ //1 把创建的是谁的代理对象 把谁传递过来 //有参数构造器 private Object obj; public UserDaoProxy(Object obj){ this.obj=obj; } //增强的逻辑 @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { //方法之前 System.out.println("方法之前执行......"+method.getName()+" :传递的参数..."+ Arrays.toString(args)); //被增强的方法执行 Object res= method.invoke(obj,args); //方法之后 System.out.println("方法之后执行......"+obj); return res; } }
-
术语
-
连接点(Jointpoint):类里面哪些方法可以被增强,这些方法被称为连接点。(类中可以被增强的方法)
-
切入点(Pointcut):实际被真正增强的方法
-
增强(Advice):指的是在目标对象的方法调用前、后或抛出异常时,通过动态代理技术在目标对象的方法周围插入拦截器的逻辑代码,从而实现对目标对象方法的增强和定制。
-
通知(增强):实际增强的逻辑部分。通知定义了增强在何时被调用,并指定了增强的类型。
通知有多种类型:
- 前置通知
- 后置通知
- 返回增强
- 环绕通知
- 异常通知
- 最终通知
-
切面/方面(Aspect):把通知应用到切入点的过程(动作)
AOP操作——准备工作
-
Spring框架一般基于
AspectJ
实现AOP操作AspectJ
不是Spring组成部分,独立AOP框架,一般把AspectJ和Spring框架一起使用,进行AOP操作
-
基于AspectJ实现AOP操作
- 基于xml配置文件实现
- 基于注解方式实现(使用)
-
在项目工程里面引入AOP相关依赖
-
切入点表达式
-
作用:知道对哪个类里面的哪个方法进行增强
-
语法结构:
execution([权限修饰符] [返回类型] [类全路径] [方法名称] [参数列表])
举例1:
对com.atguigu.dao.BookDao类里面的add进行增强:
execution(* com.atguigu.dao.BookDao.add(..))
举例2:
对com.atguigu.dao.BookDao类里面的所有方法进行增强:
execution(* com.atguigu.dao.BookDao.*(..))
举例3:
对com.atguigu.dao包里面所有类,类里面的所有方法进行增强:
execution(* com.atguigu.dao*.*(..))
AOP操作——AspectJ注解
-
创建类,在类里面定义方法
public class User { public void add(){ System.out.println("add.............."); } }
-
创建增强类(编写增强逻辑)
在增强类里面,创建方法,让不同方法代表不同通知类型
public class UserProxy { public void before(){ //前置通知 System.out.println("before......."); } }
-
进行通知的配置
-
在
Spring配置文件
中,开启注解扫描<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xmlns:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/beans/spring-context.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/beans/spring-aop.xsd"> <!-- 开启全盘扫描--> <context:component-scan base-package="com.frx01.spring5.aopanno"></context:component-scan> </beans>
-
使用注解创建
User
和UserProxy
对象//被增强的类 @Component public class User { } //增强的类 @Component public class UserProxy { }
-
在增强类上面添加注解
@Aspect
//增强的类 @Component @Aspect //生成代理对象 public class UserProxy { }
-
在
spring配置文件
中开启生成代理对象<!--开启Aspect生成代理对象--> <aop:aspectj-autoproxy></aop:aspectj-autoproxy>
-
-
配置不同类型的通知
在增强类里面,在作为通知方法上面添加通知类型注解,使用切入点表达式配置
//增强的类 @Component @Aspect //生成代理对象 public class UserProxy { //前置通知 //@Before注解表示作为前置通知 @Before(value = "execution(* com.atguigu.spring5.aopanno.User.add(..))") public void before(){ System.out.println("before......."); } //后置通知(返回通知) @AfterReturning(value = "execution(* com.atguigu.spring5.aopanno.User.add(..))") public void afterReturning(){ System.out.println("afterReturning....."); } //最终通知 @After(value = "execution(* com.atguigu.spring5.aopanno.User.add(..))") public void after(){ System.out.println("after....."); } //异常通知 @AfterThrowing(value = "execution(* com.atguigu.spring5.aopanno.User.add(..))") public void afterThrowing(){ System.out.println("afterThrowing....."); } //环绕通知 @Around(value = "execution(* com.frx01.spring5.aopanno.User.add(..))") public void around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable{ System.out.println("环绕之前......."); //被增强的方法执行 proceedingJoinPoint.proceed(); System.out.println("环绕之后......."); } }
-
相同切入点抽取
//相同切入点抽取 @Pointcut(value ="execution(* com.atguigu.spring5.aopanno.User.add(..))") public void pointdemo(){ } //前置通知 //@Before注解表示作为前置通知 @Before(value = "pointdemo()") //可直接使用pointdemo作为value public void before(){ System.out.println("before......."); }
-
有多个增强类多同一个方法进行增强,设置增强类优先级
在增强类上面添加注解
@Order(数字类型值)
,数字类型值越小优先级越高@Component @Aspect @Order(1) public class PersonProxy { }
-
完全使用注解开发
创建配置类,不需要创建xml配置文件
@Configuration @ComponentScan(basePackages = {"com.atguigu"}) @EnableAspectJAutoProxy(proxyTargetClass = true) public class ConfigAop { }
-
AOP操作——AspectJ配置文件
-
创建两个类,
增强类
和被增强类
,创建方法 -
在
Spring配置文件
中创建两个类对象<!--创建对象--> <bean id="book" class="com.atguigu.spring5.aopxml.Book"></bean> <bean id="bookProxy" class="com.atguigu.spring5.aopxml.BookProxy"></bean>
-
在
Spring配置文件
中配置切入点<!--配置aop增强--> <aop:config> <!--切入点--> <aop:pointcut id="p" expression="execution(* com.frx01.spring5.aopxml.Book.buy(..))"/> <!--配置切面--> <aop:aspect ref="bookProxy"> <!--配置增强作用在哪个方法上--> <aop:before method="before" pointcut-ref="p"/> </aop:aspect> </aop:config>
JdbcTemplate
概念和准备
-
什么是
JdbcTemplate
Spring框架对JDBC进行封装,使用JdbcTemplate方便实现对数据库操作
-
准备工作
-
创建数据库和表
CREATE DATABASE user_db CREATE TABLE t_book( userId BIGINT PRIMARY KEY, username VARCHAR(100) NOT NULL, ustatus VARCHAR(50) NOT NULL)
-
引入相关的jar包
-
在
Spring配置文件
中配置数据库的连接池<!-- 数据库连接池 --> <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" destroy-method="close"> <property name="url" value="jdbc:mysql:///user_db" /> <property name="username" value="root" /> <property name="password" value="root" /> <property name="driverClassName" value="com.mysql.jdbc.Driver" /> </bean>
-
配置
JdbcTemplate对象
,注入DataSource
<!--JdbcTemplate对象--> <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate"> <!--注入DataSource--> <property name="dataSource" ref="dataSource"></property> </bean>
-
创建
service类
,创建dao类
,在dao
注入jdbcTemplate对象
配置文件:
<!--开启组件扫描--> <context:component-scan base-package="com.atguigu"></context:component-scan>
Service类:
@Service public class BookService { //注入dao @Autowired private BookDao bookDao; }
Dao类:
@Repository public class BookDaoImpl implements BookDao{ //注入JdbcTemplate @Autowired private JdbcTemplate jdbcTemplate; }
-
JdbcTemplate操作数据库
添加
-
对应数据库创建实体类
public class User { private String userId; private String username; private String ustatus; public String getUserId() { return userId; } public String getUsername() { return username; } public String getUstatus() { return ustatus; } public void setUserId(String userId) { this.userId = userId; } public void setUsername(String username) { this.username = username; } public void setUstatus(String ustatus) { this.ustatus = ustatus; } }
-
编写
Service
和Dao
-
在
dao
进行数据库添加操作 -
调用
JdbcTemplate对象
里面update方法
实现添加操作update(String sql, Object... args)
参数:
String sql
:sql语句Object... args
:可变参数,设置sql语句值
@Repository public class BookDaoImpl implements BookDao{ //注入JdbcTemplate @Autowired private JdbcTemplate jdbcTemplate; //添加方法 @Override public void add(Book book) { //1.创建sql语句 String sql="insert into t_book values(?,?,?)"; //2.调用方法实现 Object[] args={ book.getUserId(), book.getUsername(), book.getUstatus()}; int update = jdbcTemplate.update(sql,args); System.out.println(update); } }
-
-
测试
@Test public void testJdbcTemplate(){ ApplicationContext context = new ClassPathXmlApplicationContext("bean1.xml"); BookService bookService = context.getBean("bookService", BookService.class); Book book = new Book(); book.setUserId("1"); book.setUsername("java"); book.setUstatus("A"); bookService.addBook(book); }
结果:
修改和删除
-
修改
@Override public void update(Book book) { String sql="update t_book set username=?,ustate=? where userId=?"; Object[] args={ book.getUsername(), book.getUstatus(), book.getUserId()}; int update = jdbcTemplate.update(sql,args); System.out.println(update>0?"修改成功":"修改失败"); }
-
删除
@Override public void delete(String id) { String sql="delete from t_book where userId=?)"; int update = jdbcTemplate.update(sql, id); System.out.println(update>0?"删除成功":"删除失败"); }
查询返回某个值
-
查询表里面有多少条记录,返回是某个值
-
使用
JdbcTemplate对象
里面的queryForOcject方法
实现查询操作queryForObject(String sql, Class<T> requiredType)
参数:
String sql
:sql语句Class<T> requiredType
:返回类型Class
//查询表记录数
@Override
public int selectCount(){
String sql="select count(*) from t_book";
Integer count = jdbcTemplate.queryForObject(sql, Integer.class);
return count;
}
查询返回对象
-
场景:查询图书详情
-
JdbcTemplate
实现查询返回对象的方法:queryForObject
queryForObject(String sql, RowMapper<T> rowMapper, Object... args)
参数:
String sql
:sql语句RowMapper<T> rowMapper
:RowMapper
是接口,返回不同类型的数据,使用这个接口里面实现类完成数据封装Object... args
:sql语句值
//查询返回对象
@Override
public Book findBookInfo(String id) {
String sql="select * from t_book where userId=?";
//调用方法
Book book = jdbcTemplate.queryForObject(sql, new BeanPropertyRowMapper<Book>(Book.class),id);
return book;
}
查询返回集合
-
场景:查询图书列表分页
-
调用
JdbcTemplate
的query
方法实现查询返回集合query(String sql, RowMapper<T> rowMapper, Object... args)
参数:
String sql
:sql语句RowMapper<T> rowMapper
:RowMapper
是接口,返回不同类型的数据,使用这个接口里面实现类完成数据封装Object... args
:sql语句值
//查询返回集合
@Override
public List<Book> findAllBook() {
String sql="select * from t_book";
//调用方法
List<Book> bookList = jdbcTemplate.query(sql, new BeanPropertyRowMapper<Book>(Book.class));
return bookList;
}
批量操作
-
批量操作:操作表里面的多条记录
-
JdbcTemplate
实现批量添加操作batchUpdate(String sql, List<Object[]> batchArgs)
参数:
String sql
:sql语句List<Object[]> batchArgs
:List集合,添加多条记录数据//批量添加 @Override public void batchAddBook(List<Object[]> batchArgs) { String sql="insert into t_book values(?,?,?)"; int[] ints=jdbcTemplate.batchUpdate(sql,batchArgs); System.out.println(Arrays.toString(ints)); }
测试:
//批量添加_测试 List<Object[]> batchArgs=new ArrayList<>(); Object[] o1={"3","java","a"}; Object[] o2={"4","c++","b"}; Object[] o3={"5","MySQL","e"}; batchArgs.add(o1); batchArgs.add(o2); batchArgs.add(o3); //调用批量添加 bookService.batchAdd(batchArgs);
-
JdbcTemplate
实现批量修改操作//批量修改 @Override public void batchUpdateBook(List<Object[]> batchArgs) { String sql="update t_book set username=?,ustatus=? where userId=?"; int[] ints = jdbcTemplate.batchUpdate(sql, batchArgs); System.out.println(Arrays.toString(ints)); }
测试:
//批量修改_测试 List<Object[]> batchArgs=new ArrayList<>(); Object[] obj1={"java111","a3","3"}; Object[] obj2={"c++1010","b4","4"}; Object[] obj3={"MySQL11","c5","5"}; batchArgs.add(obj1); batchArgs.add(obj2); batchArgs.add(obj3); //调用方法 bookService.batchUpdate(batchArgs);
-
JdbcTemplate
实现批量删除操作//批量删除 @Override public void batchDeleteBook(List<Object[]> batchArgs) { String sql="delete from t_book where userId=?"; int[] ints = jdbcTemplate.batchUpdate(sql, batchArgs); System.out.println(Arrays.toString(ints)); }
测试:
//批量修改 List<Object[]> batchArgs=new ArrayList<>(); Object[] obj1={"3"}; Object[] obj2={"4"}; batchArgs.add(obj1); batchArgs.add(obj2); //调用方法实现批量删除 bookService.batchDelete(batchArgs);
事务管理
概念
-
什么是事务?
事务是数据库操作最基本的单元,逻辑上一组操作,要么都成功,如果有一个失败所有操作都失败
- 典型场景:银行转账
- lucy转账100元给mary
- lucy少100,mary多100
-
事务的四个特性(ACID)
- 原子性
- 一致性
- 隔离性
- 持久性
搭建事务操作环境
-
创建数据库表,添加数据
CREATE TABLE t_account( id VARCHAR(20), username VARCHAR(50), money VARCHAR(50)) INSERT INTO t_account VALUES('1','lucy',1000) INSERT INTO t_account VALUES('2','mary',1000)
-
创建
service
,搭建dao
,完成对象创建和注入关系service
注入dao
,在dao
注入JdbcTemplate
,在JdbcTemplate
注入DataSource
@Service public class UserService { //注入dao @Autowired private UserDao userDao; }
@Repository public class UserDaoImpl implements UserDao{ @Autowired private JdbcTemplate jdbcTemplate; }
-
在
dao
创建两个方法,多钱和少钱的方法,在service
创建方法(转账的方法)@Repository public class UserDaoImpl implements UserDao{ @Autowired private JdbcTemplate jdbcTemplate; //少钱 @Override public void reduceMoney() { String sql="update t_account set money=money-? where username=?"; jdbcTemplate.update(sql,100,"lucy"); } //多钱 @Override public void addMoney() { String sql="update t_account set money=money+? where username=?"; jdbcTemplate.update(sql,100,"mary"); } }
@Service public class UserService { //注入dao @Autowired private UserDao userDao; //转账的方法 public void accountMoney(){ //lucy少100 userDao.reduceMoney(); //mary多100 userDao.addMoney(); } }
-
上面代码,如果正常执行没有问题的,但是如果代码执行过程中出现异常,有问题
//转账的方法 public void accountMoney(){ //lucy少100 userDao.reduceMoney(); //模拟异常 int i =10/0; //mary多100 userDao.addMoney(); } //结果lucy少了100,而mary并没有增加100
-
以上的问题如何解决呢?
- 使用事务进行解决
-
事务操作过程
//转账的方法 public void accountMoney(){ try { //第一步 开启事务 //第二步 进行业务操作 //lucy少100 userDao.reduceMoney(); //模拟异常 int i = 10 / 0; //mary多100 userDao.addMoney(); //第三步 没有发生异常,提交事务 }catch (Exception e){ //第四步 出现异常,事务回滚 } }
-
Spring事务管理介绍
-
事务添加到JavaEE三层结构里面Service层(业务逻辑层)
-
在Spring进行事务管理操作有两种方式:编程式事务管理和声明式事务管理(使用)
-
声明式事务管理
- 基于注解方式(使用)
- 基于xml配置文件方式
-
在Spring进行声明式事务管理,底层使用
AOP
原理 -
Spring事务管理API:
PlatformTransactionManager
PlatformTransactionManager
接口,代表事务管理器,这个接口针对不同的框架提供不同的实现类
注解声明式事务管理
-
在
Spring配置文件
配置事务管理器<!--创建事务管理器--> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <!--注入数据源--> <property name="dataSource" ref="dataSource"></property> </bean>
-
在
Spring配置文件
,开启事务注解-
在
Spring配置文件
引入名称空间tx
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd">
-
开启事务注解
<!--开启事务注解--> <tx:annotation-driven transaction-manager="transactionManager"></tx:annotation-driven>
-
-
在
service类
上面(获取service类里面方法上面)添加事务注解@Transactional
,这个注解添加到类上面,也可以添加到方法上面- 如果把这个注解添加到类上面,这个类里面的所有的方法都添加事务
- 如果把这个注解添加到方法上面,为这个方法添加事务
@Service @Transactional public class UserService { }
声明式事务管理参数配置
在service类
上面添加注解@Transactional
,在这个注解里面可以配置事务相关参数。
-
propagation
:事务传播行为多事务方法直接进行调用,这个过程中事务是如何进行管理的
事务的传播行为可以由传播属性指定。Spring定义了7种类传播行为。
传播属性 描述 REQUIRED 如果有事务在运行,当前的方法就在这个事务内运行,否则,就启动一个新的事物,并在自己的事务内运行 REQUIRED_NEW 当前的方法必须启动新事物,并在它自己的事务内运行,如果有事务正在运行,应该将它挂起 SUPPORTS 如果有事务在运行,当前的方法就在这个事务内运行,否则它可以不运行在事务中 NOT_SUPPROTS 当前的方法不应该运行在事务中,如果有运行的事务,将它挂起 MANDATORY 当前的方法必须运行在事物内部,如果没有正在运行的事务,就抛出异常 NEVER 当前的方法不应该运行在事务中,如果有运行的事务,就抛出异常 NESTED 如果有事务在运行,当前的方法就应该在这个事务的嵌套事务内运行。否则,就启动一个新的事务,并在它自己的事务内运行 @Service @Transactional(propagation = Propagation.REQUIRED) public class UserService { }
-
isolation
:事务隔离级别-
事务有特性称为隔离性,多事务操作之间不会产生影响。不考虑隔离性产生很多问题
-
有三个读的问题:脏读、不可重复读、虚读/幻读
- 脏读:一个未提交事务读取到另一个未提交事务的数据
- 不可重复读:一个未提交事务读取到另一提交事务修改的数据
- 虚读:一个未提交事务读取到另一个提交事务添加数据
-
解决:通过设置事务隔离性,解决读问题
脏读 不可重复度 幻读 READ UNCOMMITTED(读未提交) 有 有 有 READ COMMITTED(读已提交) 无 有 有 REPEATABLE READ(可重复读) 无 无 有 SERIALIZABLE(串行化) 无 无 无
@Service @Transactional(propagation = Propagation.REQUIRED, isolation = Isolation.REPEATABLE_READ) public class UserService { }
-
-
timeout
:超时时间- 事务需要在一定时间内提交,如果不提交进行回滚
- 默认值是
-1
,设置时间以秒为单位进行计算
@Service @Transactional(timeout = -1, propagation = Propagation.REQUIRED, isolation = Isolation.REPEATABLE_READ) public class UserService { }
-
readOnly
:是否只读- 读:查询操作,写:添加修改删除操作
readOnly
默认值false
,表示可以查询,可以添加修改删除操作- 设置
readOnly
值成true
之后,只能查询
@Service @Transactional(readOnly = true, timeout = -1, propagation = Propagation.REQUIRED,isolation = Isolation.REPEATABLE_READ) public class UserService { }
-
rollbackFor
:回滚设置出现哪些异常进行事务回滚
-
noRollbackFor
:不回滚设置出现哪些异常不进行事务回滚
XML声明式事务管理(了解)
-
在
Spring配置文件
中进行配置第一步,配置事务管理器
第二步,配置通知
第三步,配置切入点和切面
<!--1 创建事务管理器--> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <!--注入数据源--> <property name="dataSource" ref="dataSource"></property> </bean> <!--2 配置通知--> <tx:advice id="txadvice"> <!--配置事务参数--> <tx:attributes> <!--指定哪种规则的方法上面添加事务--> <tx:method name="accountMoney" propagation="REQUIRED"/> <!--<tx:method name="account*"/>--> </tx:attributes> </tx:advice> <!--3 配置切入点和切面--> <aop:config> <!--配置切入点--> <aop:pointcut id="pt" expression="execution(* com.frx01.spring5.service.UserService.*(..))"/> <!--配置切面--> <aop:advisor advice-ref="txadvice" pointcut-ref="pt"></aop:advisor> </aop:config>
完全注解声明式事务管理
创建配置类,使用配置类代替xml配置文件
@Configuration //配置类
@ComponentScan(basePackages = "com.atguigu") //组件扫描
@EnableTransactionManagement //开启事务
public class TxConfig {
//创建数据库连接池
@Bean
public DruidDataSource getDruidDataSource(){
DruidDataSource dataSource = new DruidDataSource();
dataSource.setDriverClassName("com.mysql.jdbc.Driver");
dataSource.setUrl("jdbc:mysql:///user_db");
dataSource.setUsername("root");
dataSource.setPassword("root");
return dataSource;
}
//创建JdbcTemplate对象
@Bean
public JdbcTemplate getJdbcTemplate(DataSource dataSource){
//到ioc容器中根据类型找到dataSource
JdbcTemplate jdbcTemplate = new JdbcTemplate();
//注入dataSource
jdbcTemplate.setDataSource(dataSource);
return jdbcTemplate;
}
//创建事务事务管理器
@Bean
public DataSourceTransactionManager getDataSourceTransactionManager(DataSource dataSource){
DataSourceTransactionManager transactionManager = new DataSourceTransactionManager();
transactionManager.setDataSource(dataSource);
return transactionManager;
}
}
Spring5框架新功能
整个Spring5框架的代码基于Java8,运行时兼容JDK9,许多不建议使用的类和方法在代码库中删除。
Spring5.0框架自带了通用的日志封装
- Spring5已经移除
Log4jConfigListener
,官方建议使用Log4j2
- Spring5框架整合
Log4j2
第一步,引入jar包
第二步,创建log4j2.xml
配置文件
<?xml version="1.0" encoding="UTF-8"?>
<!--日志级别以及优先级排序: OFF > FATAL > ERROR > WARN > INFO > DEBUG > TRACE > ALL -->
<!--Configuration后面的status用于设置log4j2自身内部的信息输出,可以不设置,当设置成trace时,可以看到log4j2内部各种详细输出-->
<configuration status="INFO">
<!--先定义所有的appender-->
<appenders>
<!--输出日志信息到控制台-->
<console name="Console" target="SYSTEM_OUT">
<!--控制日志输出的格式-->
<PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"/>
</console>
</appenders>
<!--然后定义logger,只有定义了logger并引入的appender,appender才会生效-->
<!--root:用于指定项目的根日志,如果没有单独指定Logger,则会使用root作为默认的日志输出-->
<loggers>
<root level="info">
<appender-ref ref="Console"/>
</root>
</loggers>
</configuration>
Spring5 框架核心容器支持@Nullable 注解
@Nullable
注解可以使用在方法上面,属性上面,参数上面,表示方法返回可以为空,属性值可以为空,参数值可以为空
-
注解用在方法上面,方法返回值可以为空
@Nullable String getId();
-
注解使用在方法参数里面,方法参数可以为空
public <T> void registerBean(@Nullable String beanName, Class<T> beanClass, @Nullable Supplier<T> supplier, BeanDefinitionCustomizer... customizers) { this.reader.registerBean(beanClass, beanName, supplier, customizers); }
-
注解使用在属性上面,属性可以为空
@Nullable private String bookName;
Spring5 核心容器函数式风格GenericApplicationContext
//函数式风格创建对象,交给spring进行管理
@Test
public void testGenericApplicationContext(){
//1 创建GenericApplicationContext对象
GenericApplicationContext context = new GenericApplicationContext();
//2 调用context的方法进行对象注册
context.refresh();
context.registerBean("user1", User.class, ()-> new User());
//3 获取在spring注册到的对象
// User user = (User)context.getBean("com.atguigu.spring5.test.User");
User user = (User)context.getBean("user1");
System.out.println(user);
}
Spring5 支持整合JUnit5
-
整合
JUnit4
第一步,引入Spring相关针对测试依赖
第二步,创建测试类,使用注解方式完成
@RunWith(SpringJUnit4ClassRunner.class) //指定单元测试框架 @ContextConfiguration("classpath:bean1.xml") //加载配置文件 public class JTest4 { @Autowired private UserService userService; @Test public void test1(){ userService.accountMoney(); } }
-
Spring5整合
JUnit5
Junit5的用途:
- 单元测试:JUnit 5 可以用于编写和运行单元测试,而 Spring 框架提供了对依赖注入、AOP、事务管理等特性的支持,可以帮助开发人员编写更加全面和真实的单元测试。
- 集成测试:在使用 Spring 框架开发应用程序时,通常需要进行集成测试以确保不同组件之间的协作正常运行。JUnit 5 可以与 Spring 的测试支持模块结合,提供对 Spring 上下文加载、自动装配、事务管理等功能的支持,从而实现更加全面的集成测试。
- 测试驱动开发(TDD):通过使用 JUnit 5 和 Spring 框架,开发人员可以实践测试驱动开发方法,即先编写测试用例,然后根据测试用例编写实际的业务逻辑代码。这有助于提高代码的质量和稳定性。
- 参数化测试:JUnit 5 提供了对参数化测试的支持,而 Spring 框架中的一些组件和特性可能需要进行各种参数化测试以验证其行为。结合 JUnit 5 的参数化测试功能,可以更方便地进行这些测试。
第一步,引入JUnit5的jar包
第二步,创建测试类,使用注解完成
@ExtendWith(SpringExtension.class) @ContextConfiguration("classpath:bean1.xml") public class JTest5 { @Autowired private UserService userService; @Test public void test1(){ userService.accountMoney(); } }
-
使用一个复合注解替代上面两个注解完成整合
@SpringJUnitConfig(locations = "classpath:bean1.xml") public class JTest5 { @Autowired private UserService userService; @Test public void test1(){ userService.accountMoney(); } }
Spring5框架新功能——Webflux
SpringWebflux介绍
SpringWebflux
是Spring5添加新的模块,用于web开发的,功能和SpringMVC类似的,Webflux是使用了当前一种比较流行的响应式编程而出现的框架。- 传统 web 框架,比如 SpringMVC,是基于 Servlet 容器的。而
Webflux
是一种异步非阻塞的框架,异步非阻塞的框架在 Servlet3.1 以后才支持,核心是基于Reactor
的相关 API 实现的。 - 异步非阻塞:
- 异步和同步针对调用者,调用者发送请求,如果等着对方回应之后才去做其他事情就是同步,如果发送请求之后不等着对方回应就去做其他事情就是异步。
- 阻塞和非阻塞针对被调用者,被调用者收到请求之后,做完了请求任务才给出反馈,收到请求之后马上给出犯规然后再去做事情就是非阻塞。
- Webflux 特点:
- 非阻塞式:在有限资源下,提高系统吞吐量和伸缩性,以
Reactor
为基础实现响应式编程 - 函数式编程:Spring5 框架基于 java8,Webflux 使用 Java8 函数式编程方式实现路由请求
- 非阻塞式:在有限资源下,提高系统吞吐量和伸缩性,以
- 与SpringMVC作比较
- 两个框架都可以使用注解方式,都运行在
Tomet
等容器中 SpringMVC
采用命令式编程,Webflux
采用异步响应式编程
- 两个框架都可以使用注解方式,都运行在
响应式编程(Java实现)
响应式编程的介绍
响应式编程是一种面向数据流和变化传播的编程范式。这意味着可以在编程语言中很方便地表达静态或动态的数据流,而相关的计算模型会自动将变化的值通过数据流进行传播。电子表格程序就是响应式编程的一个例子。单元格可以包含字面值或类似"=B1+C1
"的公式,而包含公式的单元格的值会依据其他单元格的值的变化而变化。
Java8及其之前版本
提供的观察者模式两个类 Observer
和 Observable
public class ObserverDemo extends Observable {
public static void main(String[] args) {
ObserverDemo observer = new ObserverDemo();
//添加观察者
observer.addObserver((o,arg)->{
System.out.println("发生变化");
});
observer.addObserver((o,arg)->{
System.out.println("手动被观察者通知,准备改变");
});
observer.setChanged();//数据变化
observer.notifyObservers();//通知
}
}
响应式编程(Reachor实现)
-
响应式编程操作中,
Reachor
是满足Reactive
规范框架的 -
Reachor
有两个核心类:Mono
和Flux
,这两个类实现接口Publisher
,提供丰富操作符。Flux
对象实现发布者,返回N
个元素;Mono
实现发布者,返回0
或者1
个元素 -
Flux
和Mono
都是数据流的发布者,使用Flux
和Mono
都可以发出三种数据信号:元素值、错误信号、完成信号,其中错误信号和完成信号都代表终止信号,终止信号用于告诉订阅者数据流结束了,错误信号终止数据流的同时会把错误信息传递给订阅者 -
代码演示
Flux
和Mono
第一步,引入依赖
<dependency> <groupId>io.projectreactor</groupId> <artifactId>reactor-core</artifactId> <version>3.1.5.RELEASE</version> </dependency>
第二步,编程代码
public static void main(String[] args){ //just方法直接声明 Flux.just(1, 2, 3, 4); Mono.just(1); //其他方法 Integer[] array = {1, 2, 3, 4}; Flux.fromArray(array); List<Integer> list = Arrays.asList(array); Flux.fromIterable(list); Stream<Integer> stream = list.stream(); Flux.fromStream(stream); }
-
三种信号特点
- 错误信号和完成信号都是终止信号,不能共存
- 如果没有发送任何元素值,而是直接发送错误或者完成信号,表示是空数据流
- 如果没有错误信号,没有完成信号,表示是无限数据流
-
调用
just
或者其他方法只是声明数据流,数据流并没有发出,只有进行订阅之后才会触发数据流,不订阅什么都不会发生的//just方法直接声明 Flux.just(1, 2, 3, 4).subscribe(System.out::print); Mono.just(1).subscribe(System.out::print);
-
操作符:对数据流进行一道道操作,称为操作符,比如工厂流水线
map
元素映射为新元素flatmap
元素映射为流(把每个元素转换成流,把转换之后的多个流合并成大的流)
SpringWebflux执行流程和核心API
SpringWebflux
基于Reactor
,默认使用容器时Netty
,Netty
是高性能的NIO
框架,异步非阻塞的框架
-
Netty
- BIO
- NIO
-
SpringWebflux
执行过程和SpringMVC
相似-
SpringWebflux
核心控制器DispatchHandler
,实现接口WebHandler
-
接口
WebHandler
有一个方法:public interface WebHandler{ Mono<Void> handle(ServerWebExchange var1); }
-
SpringWebflux
里面DispatcherHandler
,负责请求的处理HandlerMapping
:请求查询到处理的方法HandlerAdapter
:真正负责请求处理HandlerResultHandler
:响应结果处理
-
SpringWebflux
实现函数式编程,两个接口:RouterFunction
(路由处理)和HandlerFunction
(处理函数)
SpringWebflux(基于注解编程模型)
SpringWebflux
实现方式有两种:注解编程模型和函数式编程模型使用注解编程模型方式,和之前
SpringMVC
使用相似的,只需要把相关依赖配置到项目中,SpringBoot
自动配置相关运行容器,默认情况下使用Netty
服务器第一步,创建
SpringBoot
工程,引入Webflux
依赖<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-webflux</artifactId> </dependency>
第二步,配置启动端口号
在
application.properties
中server.port=8081
第三步,创建包和相关类
实体类
//实体类 public class User{ private String name; private String gender; private Integer age; public User(String name, String gender, Integer age){ this.name = name; this.gender = gender; this.age = age; } public void setName(String name){ ...... }
创建接口定义操作的方法
//用户操作接口 public interface UserService { //根据id查询用户 Mono<User> getUserById(int id); //查询所有用户 Flux<User> getAllUser(); //添加用户 Mono<Void> saveUserInfo(Mono<User> user); }
接口实现类
public class UserServiceImpl implements UserService { //创建 map 集合存储数据 private final Map<Integer,User> users = new HashMap<>(); public UserServiceImpl() { this.users.put(1,new User("lucy","nan",20)); this.users.put(2,new User("mary","nv",30)); this.users.put(3,new User("jack","nv",50)); } //根据id查询 @Override public Mono<User> getUserById(int id) { return Mono.justOrEmpty(this.users.get(id)); } //查询多个用户 @Override public Flux<User> getAllUser() { return Flux.fromIterable(this.users.values()); } //添加用户 @Override public Mono<Void> saveUserInfo(Mono<User> userMono) { return userMono.doOnNext(person -> { //向map集合里面放值 int id = users.size()+1; users.put(id,person); }).thenEmpty(Mono.empty()); } }
创建controller
@RestController public class UserController { //注入 service @Autowired private UserService userService; //id 查询 @GetMapping("/user/{id}") public Mono<User> geetUserId(@PathVariable int id) { return userService.getUserById(id); } //查询所有 @GetMapping("/user") public Flux<User> getUsers() { return userService.getAllUser(); } //添加 @PostMapping("/saveuser") public Mono<Void> saveUser(@RequestBody User user) { Mono<User> userMono = Mono.just(user); return userService.saveUserInfo(userMono); } }
SpringMVC
方式实现,同步阻塞的方式,基于SpringMVC+Servlet+Tomcat
SpringWebflux
方式实现,异步非阻塞的方式,基于SpringWebflux+Reactor+Netty
-
SpringWebflux(基于函数式编程模型)
- 在使用函数式编程模型操作时候,需要自己初始化服务器
- 基于函数式编程模型时候,有两个核心接口:
RouterFunction
(实现路由功能,请求转发给对应的 handler)和HandlerFunction
(处理请求生成响应的函数)。核心任务定义两个函数式接口的实现并且启动需要的服务器。 SpringWebflux
请求和响应不再是ServletRequest
和ServletResponse
,而是ServerRequest
和ServerResponse
第一步 把注解编程模型工程复制一份 ,保留 entity
和 service
内容
第二步 创建 Handler
(具体实现方法)
public class UserHandler {
private final UserService userService;
public UserHandler(UserService userService) {
this.userService = userService;
}
//根据 id 查询
public Mono<ServerResponse> getUserById(ServerRequest request) {
//获取 id 值
int userId = Integer.valueOf(request.pathVariable("id"));
//空值处理
Mono<ServerResponse> notFound = ServerResponse.notFound().build();
//调用 service 方法得到数据
Mono<User> userMono = this.userService.getUserById(userId);
//把 userMono 进行转换返回
//使用 Reactor 操作符 flatMap
return userMono.flatMap(person -> ServerResponse.ok().contentType(MediaType.APPLICATION_JSON)
.body(fromObject(person)))
.switchIfEmpty(notFound);
}
//查询所有
public Mono<ServerResponse> getAllUsers() {
//调用 service 得到结果
Flux<User> users = this.userService.getAllUser();
return ServerResponse.ok().contentType(MediaType.APPLICATION_JSON).body(users,User.class);
}
//添加
public Mono<ServerResponse> saveUser(ServerRequest request) {
//得到 user 对象
Mono<User> userMono = request.bodyToMono(User.class);
return ServerResponse.ok().build(this.userService.saveUserInfo(userMono));
}
}
第三步 初始化服务器,编写 Router
-
创建路由的方法
//1 创建 Router 路由 public RouterFunction<ServerResponse> routingFunction() { //创建 hanler 对象 UserService userService = new UserServiceImpl(); UserHandler handler = new UserHandler(userService); //设置路由 return RouterFunctions.route( GET("/users/{id}").and(accept(APPLICATION_JSON)),handler::getUserById) .andRoute(GET("/users").and(accept(APPLICATION_JSON)), handler::getAllUsers); }
-
创建服务器完成适配
//2 创建服务器完成适配 public void createReactorServer() { //路由和 handler 适配 RouterFunction<ServerResponse> route = routingFunction(); HttpHandler httpHandler = toHttpHandler(route); ReactorHttpHandlerAdapter adapter = new ReactorHttpHandlerAdapter(httpHandler); //创建服务器 HttpServer httpServer = HttpServer.create(); httpServer.handle(adapter).bindNow(); }
-
最终调用
public static void main(String[] args) throws Exception{ Server server = new Server(); server.createReactorServer(); System.out.println("enter to exit"); System.in.read(); }
第四步 使用 WebClient 调用
public class Client {
public static void main(String[] args) {
//调用服务器地址
WebClient webClient = WebClient.create("http://127.0.0.1:5794");
//根据 id 查询
String id = "1";
User userresult = webClient.get().uri("/users/{id}", id)
.accept(MediaType.APPLICATION_JSON).retrieve().bodyToMono(User.class)
.block();
System.out.println(userresult.getName());
//查询所有
Flux<User> results = webClient.get().uri("/users")
.accept(MediaType.APPLICATION_JSON).retrieve().bodyToFlux(User.class);
results.map(stu -> stu.getName())
.buffer().doOnNext(System.out::println).blockFirst();
}
}
参考资料:https://www.bilibili.com/video/BV1Vf4y127N5?p=1&vd_source=cf21268954e139179e71f046bac01e56