Spring
个人总结:Spring框架的两个核心是IOC-Bean管理和AOP,它们都是用来减少耦合度,IOC把对象创建以及属性赋值交给spring处理以减少耦合度,AOP是增强功能不需要修改原来的代码。对于IOC-Bean管理有两种方式:基于xml、基于注解。可能最终注解用的更多,xml只是学习注解的铺垫?后面的webflux听的云里雾里如有需要再去补吧!
一、IOC容器
1.什么是IOC(控制反转)
a)把对象创建和对象之间的调用过程,交给spring进行管理
b)使用IOC目的:为了降低耦合度
2.IOC底层
xml解析、工厂模式、反射
3.Spring提供的IOC容器实现的两种方式(两个接口)
a)BeanFactory接口:IOC容器基本实现是Spring内部接口的使用接口,不提供给开发人员进行使用(加载配置文件时候不会创建对象,在获取对象时才会创建对象。)
b)ApplicationContext接口(推荐使用!):BeanFactory接口的子接口,提供更多更强大的功能,提供给开发人员使用(加载配置文件时候就会把在配置文件对象进行创建)
二、IOC容器-Bean管理(基于xml)
a)IOC操作Bean管理(基于xml)
b)IOC操作Bean管理(基于注解)
1、IOC操作Bean管理
a)Bean管理就是两个操作:(1)Spring创建对象;(2)Spring注入属性
2、基于XML配置文件创建对象并注入属性
IOC操作Bean管理(基于xml)包括简单来说包括三个部分:
- 创建普通Bean类
- xml配置:创建Bean类对象,并进行属性赋值(注入属性)
- 代码测试类(即使用)
a)set方式注入
- Book类
//(1)创建类,定义属性和对应的set方法
public class Book {
private String bookName;
private int bookPrice;
public void setBookName(String bookName){
this.bookName = bookName;
}
public void setBookPrice(int bookPrice){
this.bookPrice = bookPrice;
}
public void sout(){
System.out.println(bookName+"::"+bookPrice);
}
}
- xml配置:创建Book类对象,并进行属性赋值
<!--1 配置Book对象创建-->
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean name="book" class="com.liu.bean.Book">
<property name="bookName" value="左耳"></property>
<property name="bookPrice" value="30"></property>
</bean>
</beans>
- 代码测试类(具体使用)
<!--(3)代码测试-->
public class TestBook {
@Test
public void test(){
//1 加载 spring 配置文件
ApplicationContext context = new ClassPathXmlApplicationContext("bean1.xml");
//2 获取配置创建的对象,相当于将xml中的name="book"和Book类绑定
Book book = context.getBean("book", Book.class);
book.sout();
}
}
输出:左耳::30
b)有参构造函数注入
//(1)创建类,构建有参函数
public class Book {
private String bookName;
private int bookPrice;
public Book(String bookName,int bookPrice){
this.bookName = bookName;
this.bookPrice = bookPrice;
}
public void sout(){
System.out.println(bookName+"::"+bookPrice);
}
}
<!--(2)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"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean name="book" class="com.liu.bean.Book">
<constructor-arg name="bookName" value="左耳"></constructor-arg>
<constructor-arg name="bookPrice" value="20"></constructor-arg>
</bean>
</beans>
//(3)测试类
public class TestBook {
@Test
public void test(){
ApplicationContext context = new ClassPathXmlApplicationContext("bean1.xml");
Book book = context.getBean("book", Book.class);
book.sout();
}
}
输出:左耳::20
c)p名称空间注入(略)
d)注入空值和特殊符号
注意:整形属性需要包装成Integer才能==null
//1.创建类
public class Book {
private String bookName;
private String otherName;
private Integer bookPrice;
public void setBookName(String bookName) {
this.bookName = bookName;
}
public void setOtherName(String otherName){
this.otherName = otherName;
}
public void setBookPrice(Integer bookPrice) {
this.bookPrice = bookPrice;
}
public void sout(){
System.out.println(bookName+"or"+otherName+"::"+bookPrice);
}
}
//2.配置xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean name="book" class="com.liu.bean.Book">
<!--(1)特殊符号赋值-->
<property name="bookName">
<value><![CDATA[<<左耳>>]]></value>
</property>
<property name="otherName">
<value><<左耳2>></value>
</property>
<!--(2)null值-->
<property name="bookPrice">
<null></null>
</property>
</bean>
</beans>
//3.测试类public class TestBook { @Test public void test(){ ApplicationContext context = new ClassPathXmlApplicationContext("bean1.xml"); Book book = context.getBean("book", Book.class); book.sout(); }}输出:<<左耳>>or<<左耳2>>::null
2.1 注入属性-外部bean
简而言之,外部bean就是注入的可以是对象类型属性的方式
- 创建两个类service和dao类
//dao类public interface UserDao { public void update();}public class UserDaoImpl implements UserDao{ @Override public void update() { System.out.println("dao add......."); }}
//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(); //原始方法:创建UserDao对象 //UserDao userDao = new UserDaoImpl(); //userDao.update(); }}
- 配置xml
<?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/beans/spring-beans.xsd http://www.springframework.org/schema/util https://www.springframework.org/schema/util/spring-util.xsd"><!-- 1.创建service和dao对象--> <bean id="userService" class="com.liu.service.UserService"><!-- 2.注入userDao对象 name属性:类里面属性名称 ref属性:创建userDao对象bean标签id值,要对应--> <property name="userDao" ref="userDaoImpl"></property> </bean> <bean id="userDaoImpl" class="com.liu.dao.UserDaoImpl"></bean></beans>
- 测试类
public class TestServiceDao{ @Test public void test(){ ApplicationContext context = new ClassPathXmlApplicationContext("bean2.xml"); UserService userService = context.getBean("userService", UserService.class); userService.add(); }}输出:service add........ dao add.......
2.2注入属性-内部bean
内外部bean本质都是一个对象拥有对象类型属性,在xml中内部或者外部进行对象类型属性赋值
- 两个类:员工和部门,部门是员工的属性之一
//员工类public class Emp { private String ename; private String gender; //员工属于一个部门,使用对象属性表示 private Dept dept; public void setEname(String ename){ this.ename = ename; } public void setGender(String gender){ this.gender = gender; } public void setDept(Dept dept){ this.dept = dept; } public void add(){ System.out.println(ename+":"+gender+":"+dept); }}//部门类public class Dept { private String dname; public void setDname(String dname){ this.dname = dname; } @Override public String toString() { return "Dept{" + "dname='" + dname + '\'' + '}'; }}
- 配置xml
<?xml version="1.0" encoding="UTF-8"?><beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <bean id="emp" class="com.liu.bean.Emp"><!-- 两个普通属性--> <property name="ename" value="Jon Snow"></property> <property name="gender" value="boy"></property><!-- 对象类型属性--> <property name="dept"> <bean id="dept" class="com.liu.bean.Dept"> <property name="dname" value="临冬城"></property> </bean> </property> </bean></beans>
- 测试类
public class TestInner { @Test public void test(){ ApplicationContext context = new ClassPathXmlApplicationContext("bean3.xml"); Emp emp = context.getBean("emp", Emp.class); emp.add(); }}输出:Jon Snow:boy:Dept{dname='临冬城'}
2.3注入属性-级联赋值
第一种方式是在外部bean的基础上,进行属性赋值
第二种方式必须要有dept的get方法
<!--方式一:级联赋值--> <bean id="emp" class="com.atguigu.spring5.bean.Emp"> <!--设置两个普通属性--> <property name="ename" value="Andy"></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>
<!--方式二:级联赋值--> <bean id="emp" class="com.atguigu.spring5.bean.Emp"> <!--设置两个普通属性--> <property name="ename" value="jams"></property> <property name="gender" value="男"></property> <!--级联赋值--> <property name="dept.dname" value="技术部门"></property> </bean> <bean id="dept" class="com.atguigu.spring5.bean.Dept"> </bean>
2.4xml 注入集合属性
- bean类
//注入:数组类型属性、List 集合类型属性、Map 集合类型属性、Set 集合类型属性public class Stu { private String[] courses; private List<String> list; private Map<String,String> maps; 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; } public void test(){ System.out.println(Arrays.toString(courses)); System.out.println(list+"|"+maps+"|"+sets); }}
- 配置xml
<?xml version="1.0" encoding="UTF-8"?><beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"><!--集合类型属性的注入--> <bean id="stu" class="com.liu.bean.Stu"> <!-- 数组类型属性注入:array或者list--> <property name="courses"> <array> <value>java课程</value> <value>python课程</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></beans>
- 测试类
@Testpublic void test1(){ ApplicationContext context = new ClassPathXmlApplicationContext("bean5.xml"); Stu stu = context.getBean("stu", Stu.class); stu.test();} 输出:[java课程, python课程] [张三, 李四]|{JAVA=java, PHP=php}|[MySQL, Redis]
2.4.1、在集合里面设置对象类型值
- bean类
//学生所学多门课程 private List<Course> courseList;//创建集合 public void setCourseList(List<Course> courseList) { this.courseList = courseList; }
- 配置xml
<!--创建多个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>
2.4.2、把集合注入部分提取出来
- bean类
public class Book { private List<String> list; public void setList(List<String> list) { this.list = list; } public void test(){ System.out.println(list); }}
- 在spring配置文件xml中引入名称空间util(第一步与p命名空间类似)
<?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:p="http://www.springframework.org/schema/p" xmlns:util="http://www.springframework.org/schema/util" 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"><!-- 1.提取list集合类型属性注入--> <util:list id="bookList"> <value>易筋经</value> <value>九阴真经</value> <value>九阳神功</value> </util:list><!-- 2.提取list属性使用--> <bean id="book" class="com.liu.bean.Book"> <property name="list" ref="bookList"></property> </bean></beans>
- 测试类
@Testpublic void test2(){ ApplicationContext context = new ClassPathXmlApplicationContext("bean6.xml"); Book book = context.getBean("book", Book.class); book.test();}输出:[易筋经, 九阴真经, 九阳神功]
3、工厂Bean
-
Spring有两种类型bean,一种普通bean,另外一种工厂bean(FactoryBean)
-
普通bean:在配置文件中定义的bean类型就是测试类中返回的类型
-
工厂bean:在配置文件中定义bean类型可以和返回类型不一样
-
第一步 创建类,让这个类作为工厂bean,实现接口FactoryBean
-
第二步 实现接口里面的方法,在实现的方法中定义返回的bean类型
-
bean类
public class MyBean implements FactoryBean<Course> { //定义返回类型 @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; }}
- 配置xml
<?xml version="1.0" encoding="UTF-8"?><beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <bean id="myBean" class="com.liu.factorybean.MyBean"></bean></beans>
- 测试类
@Testpublic void test3(){ ApplicationContext context = new ClassPathXmlApplicationContext("bean7.xml"); Course course = context.getBean("myBean", Course.class); System.out.println(course);}输出:Course{cname='abc'}
4、bean作用域
- 在Spring里面,默认情况下,bean是单实例对象
@Testpublic void test2(){ ApplicationContext context = new ClassPathXmlApplicationContext("bean6.xml"); Book book1 = context.getBean("book", Book.class); Book book2 = context.getBean("book", Book.class); System.out.println(book1); System.out.println(book2);}输出:com.liu.bean.Book@548ad73b com.liu.bean.Book@548ad73b
- 在spring配置文件xml中bean标签有属性(scope)用于设置单实例还是多实例
<bean id="book" class="com.liu.bean.Book" scope="prototype">
输出:com.liu.bean.Book@548ad73b com.liu.bean.Book@4c762604
scope属性值:
-
默认值,singleton,表示是单实例对象
-
prototype,表示是多实例对象
- singleton和prototype区别
- singleton单实例,prototype多实例
- 设置scope值是singleton时,加载spring配置文件时就会创建单实例对象
- 设置scope值是prototype时,调用getBean方法时创建多实例对象
5、bean生命周期
-
通过构造器创建bean实例(无参数构造器)
-
为bean的属性设置值和对其他bean引用(即外部bean)(调用set方法)
-
调用bean的初始化的方法(需要进行配置初始化的方法)
-
bean可以使用了(对象获取到了)
-
当容器关闭时,调用bean的销毁的方法(需要进行配置销毁的方法)
-
bean类
public class Orders { //无参构造 public Orders(){ System.out.println("第一步 执行无参数构造创建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("第五步 执行销毁的方法"); }}
- 配置xml
<?xml version="1.0" encoding="UTF-8"?><beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <bean id="orders" class="com.liu.bean.Orders" init-method="initMethod" destroy-method="destroyMethod"> <property name="oname" value="显示器"></property> </bean></beans>
- 测试类
@Test public void test4(){ ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("bean8.xml"); Orders orders = context.getBean("orders", Orders.class); System.out.println("第四步 获取创建bean实例对象"); System.out.println(orders); //第五步 手动让bean实例销毁 context.close(); }输出:第一步 执行无参数构造创建bean实例第二部 调用set方法设置属性值第三步 执行初始化的方法第四步 获取创建bean实例对象com.liu.bean.Orders@e720b71第五步 执行销毁的方法
bean后置处理器
bean生命周期有七步:
(1)通过构造器创建 bean 实例(无参数构造)
(2)为 bean 的属性设置值和对其他 bean 引用(调用 set 方法)
(3)把 bean 实例传递 bean 后置处理器的方法 postProcessBeforeInitialization
(4)调用 bean 的初始化的方法(需要进行配置初始化的方法)
(5)把 bean 实例传递 bean 后置处理器的方法 postProcessAfterInitialization
(6)bean 可以使用了(对象获取到了)
(7)当容器关闭时候,调用 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; }}
- 在xml中配置后置处理器
<bean id="myBeanPost" class="com.liu.bean.MyBeanPost"></bean>输出:第一步 执行无参数构造创建bean实例第二部 调用set方法设置属性值在初始化之前执行的方法第三步 执行初始化的方法在初始化之后执行的方法第四步 获取创建bean实例对象com.liu.bean.Orders@74ad1f1f第五步 执行销毁的方法
6、xml自动装配
什么是自动装配?
- 根据指定装配规则(属性名称或者属性类型),Spring自动将匹配的属性值进行注入
实现自动装配:
- bean标签属性autowire,配置自动装配
autowire属性常用两个值:- byName根据属性名称注入,注入值bean的id值和类属性名称一样
- byType根据属性类型注入
<!-- 根据属性名称注入--><bean id="emp" class="com.liu.autowire.Emp" autowire="byName"></bean><bean id="dept" class="com.liu.autowire.Dept"></bean><!-- 根据属性类型注入--><bean id="emp" class="com.liu.autowire.Emp" autowire="byType"></bean><bean id="dept" class="com.liu.autowire.Dept"></bean>
7、外部属性文件
- 直接配置德鲁伊连接池
<!--引入德鲁伊连接池依赖jar包--><!--直接配置连接池--> <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>
- 引入外部属性文件配置数据库连接池
- 创建jdbc.properties格式文件,写数据库信息
prop.driverClass=com.mysql.jdbc.Driverprop.url=jdbc:mysql://localhost:3306/userDbprop.userName=rootprop.password=root
- 把外部 properties 属性文件引入到 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: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/context/spring-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.password}"></property></bean>
三、IOC容器-Bean管理(基于注解)
1.什么是注解?
a)注解是代码特殊标记,格式:@注解名称(属性名称=属性值,属性名称=属性值…)
b)使用注解,注解作用在类上面,方法上面,属性上面
c)使用注解目的:简化xml配置
2.Spring针对Bean管理中创建对象提供注解
a)@Component
b)@Service
c)@Controller
d)@Repository
上面四个注解功能是一样的,都可以用来创建bean实例
3.基于注解方式实现对象创建
-
第一步 引入依赖
-
开启组件扫描
<!-- 开启组件扫描 1.如果扫描多个包,多个包使用逗号隔开 2.扫描包上层目录--><context:component-scan base-package="com.liu"></context:component-scan>
- 创建bean类,在类上面添加创建对象注解
//在注解里面value属性值可以省略不写,默认值是类名称,首字母小写。UserService--userService@Component(value = "userService")public class UserService { public void add(){ System.out.println("service add...."); }}
- 测试类
@Testpublic void testService(){ ApplicationContext context = new ClassPathXmlApplicationContext("bean11.xml"); UserService userService = context.getBean("userService", UserService.class); userService.add();}输出:service add....
4.开启组件扫描细节配置
<!--示例 1 use-default-filters="false" 表示现在不使用默认 filter,自己配置 filter context:include-filter ,设置扫描哪些内容--> <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>
5.基于注解方式实现属性注入
a)@Autowired:根据属性类型进行自动装配
- 第一步 把service和dao对象创建,在service和dao类添加创建对象注解
- 第二步 在service注入dao对象,在service类添加dao类型属性,在属性上面使用注解@Autowired
@Componentpublic class UserService { //定义dao类型属性 不需要添加set方法 添加注入属性注解 @Autowired private UserDao userDao; public void add(){ System.out.println("service add...."); userDao.add(); }}
b)@Qualifier:根据属性名称进行注入
- 这个@Qualifier注解的使用,和上面@Autowired一起使用
//定义dao类型属性 不需要添加set方法 添加注入属性注解 @Autowired //根据类型进行注入 @Qualifier(value = "userDaoImpl")//根据名称进行注入 private UserDao userDao;
c)@Resource:可以根据类型注入,可以根据名称注入
// @Resource //根据类型进行注入 @Resource(name = "userDaoImpl") //根据名称进行注入 private UserDao userDao;
d)@Value:注入普通类型属性
@Value(value = "abc")private String name;
6.完全注解开发
- 创建配置类,替代xml配置文件
@Configuration //作为配置类,替代xml配置文件@ComponentScan(basePackages = {"com.liu"})public class SpringConfig {}
- 测试类
@Testpublic void testService2(){ //加载配置类 ApplicationContext context = new AnnotationConfigApplicationContext(SpringConfig.class); UserService userService = context.getBean("userService", UserService.class); userService.add();}
四、AOP(面向切面编程)
1.什么是AOP?
-
面向切面(方面)编程,利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率
-
通俗描述:不通过修改源代码方式,在主干功能里面添加新功能
-
例如,完整的登录功能,额外添加权限控制模块
2.AOP底层原理
-
AOP底层使用动态代理,有两种情况动态代理:
-
第一种 有接口情况,使用JDK动态代理:创建接口实现类代理对象,增强类的方法
-
第二种 没有接口情况,使用CGLIB动态代理:创建子类的代理对象,增强类的方法
-
3.AOP底层原理(JDK动态代理)
使用JDK动态代理,使用Proxy类里面的方法创建代理对象(在java.lang.reflect.Proxy)
-
调用newProxyInstance方法
方法有三个参数: 第一参数,类加载器
第二参数,增强方法所在的类,这个类实现的接口,支持多个接口
第三参数,实现这个接口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}; 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; }}
4.AOP(术语)
-
连接点:类里面哪些方法可以被增强,这些方法称为连接点
-
切入点:实际被真正增强的方法,称为切入点
-
通知(增强):实际增强的逻辑部分称为通知(增强)
通知有多种类型:前置通知、后置通知、环绕通知、异常通知、最终通知
-
切面:是动作,把通知应用到切入点过程
5.AOP操作(准备)
-
Spring框架一般都是基于AspectJ实现AOP操作
-
什么是AspectJ?
- 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. * .*(…))
6.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/context/spring-context.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd"><!--开启注解扫描--> <context:component-scan base-package="com.atguigu.spring5.aopannotation"></context:component-scan></beans>
- 使用注解创建User和UserProxy对象
//被增强类@Componentpublic class User { public void add(){ System.out.println("add......"); }}//增强类@Componentpublic class UserProxy { //前置通知 public void before(){ System.out.println("before....."); }}
- 在增强类上面添加注解@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.aopannotation.User.add(..))") public void before(){ System.out.println("before....."); } //最终通知:有异常也执行 @After(value = "execution(* com.atguigu.spring5.aopannotation.User.add(..))") public void after(){ System.out.println("after....."); } //异常通知 @AfterThrowing(value = "execution(* com.atguigu.spring5.aopannotation.User.add(..))") public void afterThrowing(){ System.out.println("afterThrowing....."); } //后置通知(返回通知):有异常不执行 @AfterReturning(value = "execution(* com.atguigu.spring5.aopannotation.User.add(..))") public void afterReturning(){ System.out.println("afterReturning....."); } //环绕通知 @Around(value = "execution(* com.atguigu.spring5.aopannotation.User.add(..))") public void around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable{ System.out.println("环绕之前.........."); //被增强方法执行 proceedingJoinPoint.proceed(); System.out.println("环绕之后.........."); }}
- 相同的切入点抽取
//相同切入点抽取@Pointcut(value = "execution(* com.atguigu.spring5.aopannotation.User.add(..))")public void pointdemo(){}//前置通知//@Before注解表示作为前置通知@Before(value = "pointdemo()")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 {}
7.AOP操作(AspectJ配置文件)
- 创建两个类,增强类和被增强类,创建方法
//被增强类public class Book { public void buy(){ System.out.println("buy........"); }}//增强类public class BookProxy { public void before(){ System.out.println("before........."); }}
- 在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.atguigu.spring5.aopxml.Book.buy(..))"/><!-- 配置切面--> <aop:aspect ref="bookProxy"><!-- 增强作用在具体的方法上--> <aop:before method="before" pointcut-ref="p"></aop:before> </aop:aspect> </aop:config>
五、JdbcTemplate(概念和准备)
1.什么是JdbcTemplate?
- Spring框架对JDBC进行封装,使用JdbcTemplate方便实现对数据库操作
2.准备工作
-
引入相关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.spring5"></context:component-scan>
- Service类
@Servicepublic class BookService { //注入dao @Autowired private BookDao bookDao;}
- Dao类
@Repositorypublic class BookDaoImpl implements BookDao{ //注入jdbcTemplate @Autowired private JdbcTemplate jdbcTemplate;}
3.JdbcTemplate操作数据库(添加)
-
对应数据库创建实体类
public class User { private String userId; private String username; private String ustatus; public String getUserId() { return userId; } public void setUserId(String userId) { this.userId = userId; } public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public String getUstatus() { return ustatus; } public void setUstatus(String ustatus) { this.ustatus = ustatus; }}
-
编写service和dao
-
在dao进行数据库添加操作
-
调用jdbcTemplate对象里面update方法实现添加操作update(String sql,Object… args)
- 有两个参数:sql语句,可变参数
@Repositorypublic 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.调用方法实现 int update = jdbcTemplate.update(sql, book.getUserId(), book.getUsername(), book.getUstatus()); System.out.println(update); }}
-
-
测试类
public class TestBook { @Test public void testJdbcTemplate(){ ApplicationContext context = new ClassPathXmlApplicationContext("bean14.xml"); BookService bookService = context.getBean("bookService", BookService.class); Book book = new Book(); book.setUserId("1"); book.setUsername("java"); book.setUstatus("a"); bookService.addBook(book); }}
4.JdbcTemplate操作数据库(修改和删除)
//修改 @Override public void updateBook(Book book) { String sql = "update t_book set username=?,ustatus=? where user_id=?"; int update = jdbcTemplate.update(sql, book.getUsername(), book.getUstatus(), book.getUserId()); System.out.println(update); } //删除 @Override public void delete(String id) { String sql = "delete from t_book where user_id=?"; int update = jdbcTemplate.update(sql,id); System.out.println(update); }
5.JdbcTemplate操作数据库(查询返回某个值)
- 查询表里面有多少条记录,返回是某个值
- 使用JdbcTemplate实现查询返回某个值代码:jdbcTemplate.queryForObject(sql, Integer.class);
- 有两个参数,sql语句,返回类型class
@Override public int selectCount() { String sql = "select count(*) from t_book"; Integer count = jdbcTemplate.queryForObject(sql, Integer.class); return count; }
6.JdbcTemplate操作数据库(查询返回对象)
-
场景:查询图书详情
-
JdbcTemplate实现查询返回对象:
jdbcTemplate.queryForObject(sql, new BeanPropertyRowMapper(Book.class), id);
- 有三个参数,第一个 sql语句,第三个 sql语句值,第二个 RowMapper是接口,针对返回不同类型数据,使用这个接口里面实现类完成数据封装
//查询返回对象 @Override public Book findBookInfo(String id) { String sql = "select * from t_book where user_id=?"; //调用方法 Book book = jdbcTemplate.queryForObject(sql, new BeanPropertyRowMapper<Book>(Book.class), id); return book; }
7.JdbcTemplate操作数据库(查询返回集合)
-
场景:查询图书列表分页。。。
-
调用JdbcTemplate方法实现查询返回集合:
jdbcTemplate.query(sql, new BeanPropertyRowMapper(Book.class));
- 同样有三个参数,参数意义与查询返回对象一样
@Overridepublic List<Book> findAllBook() { String sql = "select * from t_book"; //调用方法 List<Book> bookList = jdbcTemplate.query(sql, new BeanPropertyRowMapper<Book>(Book.class)); return bookList;}
8.JdbcTemplate操作数据库(批量添加)
- 批量操作:操作表里面多条数据
- JdbcTemplate实现批量添加操作:jdbcTemplate.batchUpdate(sql, batchArgs);
- 有两个参数,sql语句、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","c"}; batchArgs.add(o1); batchArgs.add(o2); batchArgs.add(o3); bookService.batchAdd(batchArgs);
9.JdbcTemplate操作数据库(批量修改)
//批量修改 @Override public void batchUpdateBook(List<Object[]> batchArgs) { String sql = "update t_book set username=?,ustatus=? where user_id=?"; int[] ints = jdbcTemplate.batchUpdate(sql, batchArgs); System.out.println(Arrays.toString(ints)); } //批量修改 List<Object[]> batchArgs = new ArrayList<>(); Object[] o1 = {"java22","a2","3"}; Object[] o2 = {"c++123","b3","4"}; Object[] o3 = {"MySQL66","c4","5"}; batchArgs.add(o1); batchArgs.add(o2); batchArgs.add(o3); bookService.batchUpdate(batchArgs);
10.JdbcTemplate操作数据库(批量删除)
//批量删除 @Override public void batchDeleteBook(List<Object[]> batchArgs) { String sql = "delete from t_book where user_id=?"; int[] ints = jdbcTemplate.batchUpdate(sql, batchArgs); System.out.println(Arrays.toString(ints)); } //批量删除 List<Object[]> batchArgs = new ArrayList<>(); Object[] o1 = {"3"}; Object[] o2 = {"4"}; batchArgs.add(o1); batchArgs.add(o2); bookService.batchDelete(batchArgs);
六、事务概念
-
什么是事务?
- 事务是数据库操作最基本单元,逻辑上的一组操作,要么都成功,如果有一个失败所有操作都失败
- 典型场景:银行转账:joy转账100元给lucy—>joy少100,lucy多100
-
事务四个特性(ACID)
- 原子性:要么都成功,要么失败都失败
- 一致性:操作之前和操作之后总量不变
- 隔离性:多事务操作时,它们之间不会产生影响
- 持久性:操作之后,保持状态不变
1.事务操作(搭建事务操作环境)
dao用来写数据库操作,不写业务
service用来写业务操作
-
创建数据库表,添加记录
t_account表里lucy和mary各有1000元
-
创建service,搭建dao,完成对象创建和注入关系
- service注入dao,在dao注入JdbcTemplate,在JdbcTemplate注入DataSource
@Servicepublic class UserService { //注入Dao @Autowired private UserDao userDao;}
@Repositorypublic class UserDaoImpl implements UserDao { @Autowired private JdbcTemplate jdbcTemplate;}
-
在dao创建两个方法:多钱和少钱的方法,在service创建方法(转账的方法)
//dao类@Repositorypublic class UserDaoImpl implements UserDao { @Autowired private JdbcTemplate jdbcTemplate; //多钱 @Override public void addMoney() { String sql = "update t_account set money=money+? where username=?"; jdbcTemplate.update(sql, 100,"mary"); } //少钱 @Override public void reduceMoney() { String sql = "update t_account set money=money-? where username=?"; jdbcTemplate.update(sql, 100,"lucy"); }}
//service类@Servicepublic class UserService { //注入Dao @Autowired private UserDao userDao; //转账的方法 public void accountMoney(){ //lucy少100 userDao.reduceMoney(); //mary多100 userDao.addMoney(); }}
2.事务场景的引入
-
如果出现了以下异常,需要用事务解决
@Servicepublic class UserService { //注入Dao @Autowired private UserDao userDao; //转账的方法 public void accountMoney(){ //lucy少100 userDao.reduceMoney(); //模拟异常 int i = 10/0; //mary多100 userDao.addMoney(); }}
-
事务如何解决?
@Servicepublic class UserService { //注入Dao @Autowired private UserDao userDao; //转账的方法 public void accountMoney(){ try{ //第一步 开启事务 //第二部 进行业务操作 //lucy少100 userDao.reduceMoney(); //模拟异常 int i = 10/0; //mary多100 userDao.addMoney(); //第三步 没有发生异常,提交事务 }catch (Exception e){ //第四步 出现异常,事务回滚 } }}
3.事务操作(Spring事务管理介绍)
-
事务添加到JavaEE三层结构里面Service层(业务逻辑层)
-
在Spring进行事务管理操作
- 有两种方式:编程式事务管理和声明式事务管理(一般使用)
-
声明式事务管理
- 基于注解方式(一般使用)
- 基于xml配置文件方式
-
在Spring进行声明式事务管理,底层使用AOP原理
-
Spring事务管理API提供了一个接口PlatformTransactionManager,代表事务管理器,这个接口针对不同的框架提供不同的实现类。Spring和MyBatis都是使用DataSourceTransactionManager实现类。
4.事务操作(注解声明式事务管理)
-
在spring配置文件配置事务管理器
<!-- 创建事务管理器--> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"><!-- 注入数据源--> <property name="dataSource" ref="dataSource"></property> </bean>
-
在spring配置文件,开启事务注解
- 在spring配置文件引入名称空间tx
<?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: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/tx http://www.springframework.org/schema/tx/spring-tx.xsd">
- 开启事务注解
<!-- 开启事务注解--> <tx:annotation-driven transaction-manager="transactionManager"></tx:annotation-driven>
-
在service类上面(或者service类里面方法上面)添加事务注解
- @Transactional,这个注解添加到类上面,也可以添加方法上面
- 如果把这个注解添加到类上面,这个类里面所有的方法都添加事务
- 如果把这个注解添加方法上面,为这个方法添加事务
@Service@Transactionalpublic class UserService {
5.事务操作(声明式事务管理参数配置)
-
在service类上面添加注解@Transactional,在这个注解里面可以配置事务相关参数
//事务方法:对数据库表数据进行变化的操作@Transactionalpublic void add(){ //调用update方法 update();}public void update(){}
-
propagation:事务传播行为,Spring框架事务传播行为有7种(这里只记录最常用的两种)
-
REQUIRED:默认属性。如果add方法本身有事务,调用update方法之后,update使用当前add方法里面事务;如果add方法本身没有事务,调用update方法之后,创建新事务。
-
REQUIRED_NEW:使用add方法调用update方法,如果add无论是否有事务,都创建新的事务。
@Service@Transactional(propagation = Propagation.REQUIRED)public class UserService {
-
-
ioslation:事务隔离级别
-
事务有特性称为隔离性,多事务操作之间不会产生影响。
-
不考虑隔离性会产生三个读问题:脏读、不可重复读、虚(幻)读
-
脏读:一个未提交事务读取到另一个未提交事务的数据。岳不群将余额从100改到200,如果此时事务回滚,又在此期间东方不败读取到了200,而实际岳不群事务回滚,实际数额100.
-
不可重复读:一个未提交事务读取到另一提交事务修改的数据。岳不群将余额从100改到200,并提交,东方不败在岳不群提交前后两次查看,而两次数值不一样的现象。
-
虚读:一个未提交事务读取到另一提交事务添加的数据
@Service@Transactional(propagation = Propagation.REQUIRED,isolation = Isolation.REPEATABLE_READ)public class UserService {
-
-
隔离级别 脏读 不可重复读 幻读
READ UNCOMMITTED(读未提交) 有 有 有
READ COMMITTED(读已提交) 无 有 有
REPEATABLE READ(可重复读) 无 无 有
SERIALIZABLE(串行化) 无 无 无
-
mysql数据库默认隔离级别是可重复读。
-
timeout:超时时间
-
事务需要在一定时间内进行提交,如果不提交进行回滚
-
默认值是-1(不超时),设置时间以秒单位进行计算
-
-
readOnly:是否只读
- readOnly默认值false,表示可以查询,可以添加修改删除操作;设置true,只能查询
-
rollbackFor:回滚
- 设置出现哪些异常进行事务回滚
-
noRollbackFor:不回滚
- 设置出现哪些异常不进行事务回滚
6.事务操作(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*" propagation="REQUIRED"/>--> </tx:attributes> </tx:advice> <!--配置切入点和切面--> <aop:config><!-- 配置切入点--> <aop:pointcut id="pt" expression="execution(* com.liuliu.service.UserService.*(..))"/><!-- 配置切面--> <aop:advisor advice-ref="txadvice" pointcut-ref="pt"></aop:advisor> </aop:config>
7.事务操作(完全注解声明式事务管理)
-
创建配置类,使用配置类替代xml配置文件
@Configuration//配置类@ComponentScan(basePackages = "com.liuliu")//组件扫描@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("123456"); 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 dataSourceTransactionManager = new DataSourceTransactionManager(); dataSourceTransactionManager.setDataSource(dataSource); return dataSourceTransactionManager; }}
-
测试类
@org.junit.Test public void testAccount2(){ ApplicationContext context = new AnnotationConfigApplicationContext(TxConfig.class); UserService userService = context.getBean("userService", UserService.class); userService.accountMoney(); }
七、Spring5框架新功能
整个Spring5框架的代码基于Java8,运行时兼容JDK9,许多不建议使用的类和方法在代码库中删除
1.Spring5框架自带了通用的日志封装
-
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>
2.Spring5框架核心容器支持@Nullable注解
-
@Nullable注解可以使用在方法上面,属性上面,参数上面。表示方法返回可以为空,属性值可以为空,参数值可以为空。
//注解用在方法上面,方法返回值可以为空@NullableString getId();//注解使用在方法参数里面,方法参数可以为空public <T> void registerBean(@Nullable String beanName, Class<T> beanClass, @Nullable Supplier<T> supplier, BeanDefinitionCustomizer... customizers){}//注解使用在属性上面,属性值可以为空@Nullableprivate String bookName;
3.Spring5核心容器支持函数式风格GenericApplicationContext
//函数式风格创建对象,交给spring进行管理 @org.junit.Test public void testGenericApplicationContext(){ //1.创建GenericApplicationContext对象 GenericApplicationContext context = new GenericApplicationContext(); //2.调用context对象的方法进行注册,用Lambda表达式 context.refresh(); context.registerBean("user1",User.class, () -> new User()); //3.获取在spring注册的对象// User user = (User)context.getBean("com.liuliu.test.User"); User user = (User)context.getBean("user1"); System.out.println(user); }
4.Spring5支持整合JUnit5
-
整合JUnit4
-
第一步 引入Spring相关针对测试依赖
-
第二步 创建测试类,使用注解方式完成
@RunWith(SpringJUnit4ClassRunner.class)//单元测试框架@ContextConfiguration("classpath:bean15.xml")//加载配置文件public class JTest4 { @Autowired private UserService userService; @Test public void test1(){ userService.accountMoney(); }}
-
-
Spring5整合JUnit5
-
第一步 引入JUnit5的jar包
-
第二步 创建测试类,使用注解完成
@ExtendWith(SpringExtension.class)@ContextConfiguration("classpath:bean15.xml")public class JTest5 { @Autowired private UserService userService; @Test public void test1(){ userService.accountMoney(); }}
-
-
使用一个复合注解替代上面两个注解完成整合
@SpringJUnitConfig(locations = "classpath:bean15.xml")public class JTest5 { @Autowired private UserService userService; @Test public void test1(){ userService.accountMoney(); }}
5.Spring5框架新功能(Webflux)
- SpringWebflux介绍
-
是Spring5添加新的模块,用于web开发的,功能和SpringMVC类似的,Webflux使用当前一种比较流行响应式编程出现的框架
-
使用传统web框架,比如SpringMVC,这些基于Servlet容器,Webflux是一种异步非阻塞的框架,异步非阻塞的框架在Servlet3.1以后才支持,核心是基于Reactor的相关API实现的
-
解释什么是异步非阻塞?
- 异步和同步针对调用者,调用者发送请求,如果等着对方回应之后才去做其他事情就是同步,如果发送请求之后不等着对方回应就去做其他事情就是异步
- 阻塞和非阻塞针对被调用者,被调用者收到请求之后,做完请求任务之后才给出反馈就是阻塞,收到请求之后马上给出反馈然后再去做其他事情就是非阻塞
-
Webflux特点:
- 第一 非阻塞式:在有限资源下,提高系统吞吐量和伸缩性,以Reactor为基础实现响应式编程
- 第二 函数式编程:Spring5框架基于java8,Webflux使用java8函数式编程方式实现路由请求
-
比较SpringMVC
- 第一 两个框架都可以使用注解方式,都运行在Tomcat等容器中
- 第二 SpringMVC采用命令式编程,Webflux采用异步响应式编程
- 响应式编程
-
什么是响应式编程?
- 响应式编程是一种面向数据流和变化传播的编程范式。这意味着可以在编程语言中很方便地表达静态或动态的数据流,而相关的计算模型会自动将变化的值通过数据流进行传播。
- 电子表格程序就是响应式编程的一个例子。单元格可以包含字面值或类似“=B1+C1”的公式,而包含公式的单元格的值会依据其他单元格的值的变化而变化。
-
Java8及其之前版本
- 提供的观察者模式两个类Observer和Observable
public class ObserverDemo extends Observable { public static void main(String[] args) { ObserverDemo observerDemo = new ObserverDemo(); //添加观察者 observerDemo.addObserver((o,arg)->{ System.out.println("发生变化!"); }); //添加观察者 observerDemo.addObserver((o,arg)->{ System.out.println("手动被观察者通知,准备改变!"); }); observerDemo.setChanged();//监测数据变化 observerDemo.notifyObservers();//通知 }}
- 响应式编程(Reactor实现)
-
响应式编程操作中,Reactor是满足Reactive规范框架
-
Reactor有两个核心类,Mono和Flux,这两个类实现接口Publisher,提供丰富操作符。Flux对象实现发布者,返回N个元素;Mono实现翻发布者,返回0个或者1个元素
-
Flux和Mono都是数据流的发布者,使用Flux和Mono都可以发出三种数据信号:元素值、错误信号、完成信号。错误信号和完成信号都代表终止信号,终止信号用于告诉订阅者数据流结束了,错误信号终止数据流同时把错误信息传递给订阅者
-
代码演示Flux和Mono
- 第一步 引入依赖
<dependency> <groupId>io.projectreactor</groupId> <artifactId>reactor-bom</artifactId> <version>2020.0.10</version></dependency>
- 第二步 编写代码
public class TestReactor { 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或者其他方法只是声明数据流,数据流并没有发出,只有进行订阅之后才会触发数据流,不订阅什么都不会发生的
Flux.just(1,2,3,4).subscribe(System.out::println);Mono.just(1).subscribe(System.out::println);
-
操作符:对数据流进行一道道操作,成为操作符,比如工厂流水线
- 第一 map元素映射为新元素
- 第二 flatMap元素映射为流:把每个元素转换流,把转换之后多个流合并大的流
-
SpringWebflux执行流程和核心API
-
SpringWebflux基于Reactor,默认使用容器是Netty,Netty是高性能的NIO框架,异步非阻塞的框架
-
SpringWebflux核心控制器DispatchHandler,实现接口WebHandler
-
WebHandler有一个方法
public interface WebHandler{ Mono<Void> handle(ServerWebExchange var1);}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mEAHLuqB-1632230589428)(C:\Users\The King\AppData\Roaming\Typora\typora-user-images\image-20210920212656883.png)]
-
SpringWebflux里面DispatchHandler,负责请求的处理
- 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 String getName() {return name;} public void setName(String name) {this.name = name;} public String getGender() {return gender;} public void setGender(String gender) {this.gender = gender;} public Integer getAge() {return age;} public void setAge(Integer age) {this.age = age;}}
-
创建接口定义操作的方法
//用户操作接口public interface UserService { //根据id查询用户 Mono<User> getUserById(int id); //查询所有用户 Flux<User> getAllUser(); //添加用户 Mono<Void> saveUserInfo(Mono<User> user);}
-
接口实现类
@Repositorypublic class UserServiceImpl implements UserService{ //创建Map集合存储数据 private final Map<Integer,User> users = new HashMap<>(); public UserServiceImpl(){ this.users.put(1,new User("lucy","girl",20)); this.users.put(2,new User("snow","boy",30)); this.users.put(3,new User("jack","boy",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
@RestControllerpublic class UserController { //注入service @Autowired private UserService userService; //id查询 @GetMapping("/user/{id}") public Mono<User> getUserId(@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。
-
第一步 复制注解编程模型工程
-
第二步 创建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> routerFunction(){ //创建handler对象 UserServiceImpl userService = new UserServiceImpl(); UserHandler handler = new UserHandler(userService); //设置路由 return RouterFunctions.route( GET("/users/{id}").and(accept(MediaType.APPLICATION_JSON)),handler::getUserById) .andRoute(GET("/users").and(accept(MediaType.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(); } }
八、课程总结
1、Spring 框架概述
(1)轻量级开源 JavaEE 框架,为了解决企业复杂性,两个核心组成:IOC 和 AOP
(2)Spring5.2.6 版本
2、IOC 容器
(1)IOC 底层原理(工厂、反射等)
(2)IOC 接口(BeanFactory)
(3)IOC 操作 Bean 管理(基于 xml)
(4)IOC 操作 Bean 管理(基于注解)
3、Aop
(1)AOP 底层原理:动态代理,有接口(JDK 动态代理),没有接口(CGLIB 动态代理)
(2)术语:切入点、增强(通知)、切面
(3)基于 AspectJ 实现 AOP 操作
4、JdbcTemplate
(1)使用 JdbcTemplate 实现数据库 curd 操作
(2)使用 JdbcTemplate 实现数据库批量操作
5、事务管理
(1)事务概念
(2)重要概念(传播行为和隔离级别)
(3)基于注解实现声明式事务管理
(4)完全注解方式实现声明式事务管理
6、Spring5 新功能
(1)整合日志框架
(2)@Nullable 注解
(3)函数式注册对象
(4)整合 JUnit5 单元测试框架
(5)SpringWebflux 使用