• Spring


    Spring

    1.初始Spring

    在这里插入图片描述
    版本发展节点

    在这里插入图片描述

    2. IOC

    2.1 为什么要有IOC

    请添加图片描述

    2.2 底层原理

    工厂(设计模式)+反射(机制) + 配置文件(xml)。
    在这里插入图片描述
    在Spring 3.0之前, 主流使用xml配置 + 注解开发, xml配置开发, 可以帮我们理解代码的本质和底层逻辑, 但随着Spring 3.0的到来, 纯注解开发成为了主流

    2.3 DI注入

    学习了IOC后, 我们虽然解决了类和类之间的耦合关系, 但需要却在获取对象的时候创建Spring工厂, 那有没有更方便获取对象的依赖的方法呢?

    DI注入: Dependency Injection 依赖注入,在Spring框架负责创建Bean对象时,动态的将依赖对象注入到Bean组件(简单的说,可以将另外一个bean对象动态的注入到另外一个bean中。)

    2.3.1 DI注入的思路

    由Spring容器创建了Service、Dao对象,并且在配置中将Dao传入Servcie,那么Service对象就包含了Dao对象的引用。

    2.3.2 DI注入的实现

    1.将service对象也交给spring容器管理

    <bean id="userDao" class="cn.itcast.spring.a_quickstart.UserDaoImpl">bean>
    	
    <bean id="userService" class="cn.itcast.spring.a_quickstart.UserServiceImpl">
    	
    	
    	
    	
    	<property name="userDao" ref="userDao">property>
    bean>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    2.在程序中定义属性提供setter方法:

    public class UserServiceImpl implements IUserService {
    
    	private IUserDao userDao;
    	
    	public void setUserDao(IUserDao userDao) {
    		this.userDao = userDao;
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    2.3.3 DI注入的两种方式

    一、手动注入

    1. 方法注入
    <bean name="userService" class="com.mtb.service.UserService">
            <property name="orderService" ref="orderService"/>
    bean>
    
    • 1
    • 2
    • 3
    1. 构造器注入
    <bean name="userService" class="com.mtb.service.UserService">
            <constructor-arg index="0" ref="orderService"/>
    bean>
    
    • 1
    • 2
    • 3

    二、自动注入
    自动注入也可以分为两种:@Autowired注解注入 XML中配置autowire注入(不常见)

    这里我们简单聊一下@Autowired注解注入(在后续的篇章中会展开了讲), @Autowired可以作用在属性、构造方法、set方法上

    • 属性上:先根据属性类型去找Bean,如果找到多个再根据属性名确定一个
    • 构造方法上:先根据方法参数类型去找Bean,如果找到多个再根据参数名确定一个
    • set方法上:先根据方法参数类型去找Bean,如果找到多个再根据参数名确定一个

    2.3.4 IOC和DI小结

    IOC经常会和DI一起提起,有些同学还会把这两者混为一谈。实际上IOC和DI并不是同一个概念。IOC是一种设计模式,DI是IOC的实现方式。

    • IOC:控制反转,将对象创建管理的权利交给spring容器,获取对象通过spring工厂创建
    • DI:在spring容器中创建管理多个对象,通过 property标签将对象注入到需要依赖的对象中

    2.4 Spring工厂

    ApplicationContext工厂, 直译为应用上下文, 是用来加载Spring框架配置文件,来构建Spring的工厂对象, 也称之为Spring的容器。

    ApplicationContext 只是BeanFactory(Bean工厂,Bean就是一个java对象) 一个子接口:

    在这里插入图片描述
    在这里插入图片描述

    2.4.1 ApplicationContext和BeanFactory的区别

    1. BeanFactory创建完毕后,所有的Bean均为延迟加载,也就是说我们调用getBean()方法获取Bean对象时才创建Bean对象并返回给我们
    2. ApplicationContext是对BeanFactory扩展,提供了更多功能(国际化处理、事件传递、Bean自动装配、各种不同应用层的Context实现)

    ApplicationContext 更加强大, 所以现在开发基本没人使用BeanFactory。

    2.4.2 从Spring工厂中获取bean的两种方式

    1. 通过spring容器的bean的id/name来获取
    ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");
    //1.通过spring容器的bean的id/name来获取
    IUserService userService = (IUserService) ac.getBean("userService");
    
    • 1
    • 2
    • 3
    1. 根据bean的类型或者bean接口的类型获取,一般使用接口类型
    ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");
    //1.通过spring容器的bean的id/name来获取
    IUserService userService2 = (IUserService) ac.getBean(IUserService.class);
    
    • 1
    • 2
    • 3

    3 AOP

    AOP 思想: 基于代理思想,对原来目标对象,创建代理对象,在不修改原对象代码情况下,通过代理对象,调用增强功能的代码,从而对原有业务方法进行增强 !

    一、AOP核心概念
    请添加图片描述

    二、专业名词

    • 连接点(JoinPoint):正在执行的方法,例如:update()、delete()、select()等都是连接点。
    • 切入点(Pointcut):进行功能增强了的方法,例如:update()、delete()方法,而select()方法没有被增强所以不是切入点,但是是连接点。
      • 在SpringAOP中,一个切入点可以只描述一个具体方法,也可以匹配多个方法
        • 一个具体方法:com.itheima.dao包下的BookDao接口中的无形参无返回值的save方法
        • 匹配多个方法:所有的save方法,所有的get开头的方法,所有以Dao结尾的接口中的任意方法,所有带有一个参数的方法
    • 通知(Advice):在切入点前后执行的操作,也就是增强的共性功能
      • 在SpringAOP中,功能最终以方法的形式呈现
    • 通知类:通知方法所在的类叫做通知类
    • 切面(Aspect):描述通知与切入点的对应关系,也就是哪些通知方法对应哪些切入点方法。

    3.1 Spring AOP 编程原理

    一、底层实现
    Spring AOP是基于动态代理的,基于两种动态代理机制:

    • JDK动态代理
    • CGLIB动态代理

    二、动态代理和静态代理区别?

    • 静态代理: 实际存在的代理类
    • 动态代理: 代理类在程序运行时创建的代理方式被成为动态代理。并不是真正存在的类

    参考: 说说动态代理与静态代理区别

    3.1.1 JDK动态代理

    JDK动态代理,针对目标对象的接口进行代理 ,动态生成接口的实现类 !(必须有接口)

    1. 定义接口以及接口实现类
    2. 通过动态代理的方式, 生成代理对象
      动态代理需要三个参数:类加载器、目标对象实现的接口、 回调方法对象
    //接口(表示代理的目标接口)
    public interface ICustomerService {
    	//保存
    	public void save();
    	//查询
    	public int find();
    }
    
    //实现类
    public class CustomerServiceImpl implements ICustomerService{
    
    	public void save() {
    		System.out.println("客户保存了。。。。。");
    	}
    
    	public int find() {
    		System.out.println("客户查询数量了。。。。。");
    		return 100;
    	}
    }
    
    //专门用来生成jdk的动态代理对象的-通用
    public class JdkProxyFactory{
    	//成员变量
    	private Object target;
    	//注入target目标对象
    	public JdkProxyFactory(Object target) {
    		this.target = target;
    	}
    	
    	public Object getProxyObject(){
    		//参数1:目标对象的类加载器
    		//参数2:目标对象实现的接口
    		//参数3:回调方法对象
    		/**方案一:在内部实现new InvocationHandler(),指定匿名类*/
    		return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), new InvocationHandler(){
    
    			public Object invoke(Object proxy, Method method, Object[] args)
    					throws Throwable {
    				//如果是保存的方法,执行记录日志操作
    				if(method.getName().equals("save")){
    					writeLog();
    				}
    				//目标对象原来的方法执行
    				Object object = method.invoke(target, args);//调用目标对象的某个方法,并且返回目标对象方法的返回值
    				return object;
    			}
    			
    		});
    	}
    	
    	//记录日志
    	private static void writeLog(){
    		System.out.println("增强代码:写日志了。。。");
    	}
    
    }
    
    public static void main(String[] args) {
       //target(目标对象)
       ICustomerService target = new CustomerServiceImpl();
       //实例化注入目标对象
       JdkProxyFactory jdkProxyFactory = new JdkProxyFactory(target);
       //获取Proxy Object代理对象:基于目标对象类型的接口的类型的子类型的对象
       ICustomerService proxy = (ICustomerService) jdkProxyFactory.getProxyObject();
       //调用目标对象的方法
       proxy.save();
       System.out.println("————————————————————————————————————————");
       proxy.find();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70

    从结果上看出:在保存方法的前面,输入了日志增强。
    在这里插入图片描述
    JDK动态代理的缺点:
    只能面向接口代理,不能直接对目标类进行代理 ,如果没有接口,则不能使用JDK代理。

    3.1.2 CGLIB动态代理

    Cglib的引入为了解决类的直接代理问题(生成代理子类),不需要接口也可以代理 !

    将spring的核心jar(spring-core)导入进来,因为spring的包,包含了cglib的包

    //没有接口的类
    public class ProductService {
    	public void save() {
    		System.out.println("商品保存了。。。。。");
    		
    	}
    
    	public int find() {
    		System.out.println("商品查询数量了。。。。。");
    		return 99;
    	}
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    //cglib动态代理工厂:用来生成cglib代理对象
    public class CglibProxyFactory implements MethodInterceptor{
    	//声明一个代理对象引用
    	private Object target;
    	//注入代理对象
    	public CglibProxyFactory(Object target) {
    		this.target = target;
    	}
    	
    	//获取代理对象
    	public Object getProxyObject(){
    		//1.代理对象生成器(工厂思想)
    		Enhancer enhancer = new Enhancer();
    		//2.在增强器上设置两个属性
    		//设置要生成代理对象的目标对象:生成的目标对象类型的子类型
    		enhancer.setSuperclass(target.getClass());
    		//设置回调方法
    		enhancer.setCallback(this);
    //		Callback
    		//3.创建获取对象
    		return enhancer.create();
    	}
    
    	
    	//回调方法(代理对象的方法)
    	//参数1:代理对象
    	//参数2:目标对象的方法对象
    	//参数3:目标对象的方法的参数的值
    	//参数4:代理对象的方法对象
    	public Object intercept(Object proxy, Method method, Object[] args,
    			MethodProxy methodProxy) throws Throwable {
    		//如果是保存的方法,执行记录日志操作
    		if(method.getName().equals("save")){
    			writeLog();
    		}
    		//目标对象原来的方法执行
    		Object object = method.invoke(target, args);//调用目标对象的某个方法,并且返回目标对象方法的执行结果
    		return object;
    	}
    	
    	//写日志的增强功能
    	//Advice通知、增强
    	//记录日志
    	private static void writeLog(){
    		System.out.println("增强代码:写日志了。。。");
    	}
    	
    }
    
    //cglib动态代理:可以基于类(无需实现接口)生成代理对象
    @Test
    public void testCglibProxy(){
    	//target目标:
    	ProductService target = new ProductService();
    	//weave织入,生成proxy代理对象
    	//代理工厂对象,注入目标
    	CglibProxyFactory cglibProxyFactory = new CglibProxyFactory(target);
    	//获取proxy:思考:对象的类型
    	//代理对象,其实是目标对象类型的子类型
    	ProductService proxy=(ProductService) cglibProxyFactory.getProxyObject();
    	//调用代理对象的方法
    	proxy.save();
    	System.out.println("————————————————————————————————————————");
    	proxy.find();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65

    控制台输出结果
    在这里插入图片描述最后,使用断点查看cglib代理,生成的代理对象
    在这里插入图片描述

    3.1.3 Spring代理知识总结

    • JDK代理:基于接口的代理,一定是基于接口,会生成目标对象的接口类型的子对象。
    • CGLIB代理:基于类的代理,不需要基于接口,会生成目标对象类型的子对象。

    如图:
    CustomerServiceImpl(基于接口)的代理对象是$Proxy
    ProductService(基于一般类)的代理对象是ProductService的子类
    在这里插入图片描述
    代理知识总结:

    • spring在运行期,生成动态代理对象,不需要特殊的编译器.
    • spring有两种代理方式:
      • 若目标对象实现了若干接口,spring使用JDK的java.lang.reflect.Proxy类代理
      • 若目标对象没有实现任何接口,spring使用CGLIB库生成目标对象的子类。
    • 使用该方式时需要注意:
      • 对接口创建代理优于对类创建代理,因为会产生更加松耦合的系统,所以spring默认是使用JDK代理。
      • 对类代理是让遗留系统或无法实现接口的第三方类库同样可以得到通知,这种方式应该是备用方案。
      • 标记为final的方法不能够被通知。spring是为目标类产生子类。任何需要被通知的方法都被复写,将通知织入。final方法是不允许重写的
      • spring只支持方法连接点:不提供属性接入点,spring的观点是属性拦截破坏了封装。
      • spring只支持方法连接点:不提供属性接入点,spring的观点是属性拦截破坏了封装
      • 面向对象的概念是对象自己处理工作,其他对象只能通过方法调用的得到的结果。

    3.2 传统AOP编程

    3.2.1 Advice的5种类型

    Advice通知就是增强的方式方法

    • 前置通知 org.springframework.aop.MethodBeforeAdvice
      在目标方法执行前实施增强
    • 后置通知 org.springframework.aop.AfterReturningAdvice
      在目标方法执行后实施增强
    • 环绕通知 org.aopalliance.intercept.MethodInterceptor
      在目标方法执行前后实施增强
    • 异常抛出通知 org.springframework.aop.ThrowsAdvice
      在方法抛出异常后实施增强
    • 引介通知 org.springframework.aop.IntroductionInterceptor
      在目标类中添加一些新的方法和属性

    3.2.2 使用传统AOP编程实现环绕通知

    传统AOP编程配置过于麻烦,所以这里使用AspectJ的切入点语法(xml配置)来讲解

    1. 确定切入点
    2. 编写增强方法(通知)
    3. 配置切面(对哪些切入点,进行怎样的通知)

    一、引入导入依赖包

    <dependency>
      <groupId>org.springframeworkgroupId>
      <artifactId>spring-aopartifactId>
      <version>5.2.10.RELEASEversion>
    dependency>
    
    <dependency>
      <groupId>org.springframeworkgroupId>
      <artifactId>spring-aspectsartifactId>
      <version>5.2.10.RELEASEversion>
    dependency>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    二、编写环绕通知
    编写传统aop的Advice通知类。

    传统aop的通知,必须实现上面的这5类接口,例如我们编写一个环绕通知,就必须实现MethodInterceptor接口

    import org.aopalliance.intercept.MethodInterceptor;
    import org.aopalliance.intercept.MethodInvocation;
    import org.apache.log4j.Logger;
    //传统的aop的advice通知,增强类,必须实现org.aopalliance.intercept.MethodInterceptor接口(注意和cglib代理接口区分开)
    public class TimeLogInterceptor implements MethodInterceptor {
    	//log4j记录器
    	private static Logger LOG=Logger.getLogger(TimeLogInterceptor.class);
    
    	//回调方法
    	//参数:目标方法回调函数的包装类,获取调用方法的相关属性、方法名、调用该方法的对象(即封装方法、目标对象,方法的参数)
    	public Object invoke(MethodInvocation methodInvocation) throws Throwable {
    		//业务:记录目标的方法的运行时间
    		//方法调用之前记录时间
    		long beginTime = System.currentTimeMillis();
    		
    		//目标对象原来的方法的调用,返回目标对象方法的返回值。
    		Object object = methodInvocation.proceed();//类似于invoke
    		
    		//方法调用之后记录时间
    		long endTime = System.currentTimeMillis();
    		
    		//计算运行时间
    		long runTime=endTime-beginTime;
    		
    		//写日志:
    		/**
    		 * 1:记录日志到数据库(优势:便于查询;劣势:要占用数据库空间,日志一般都非常庞大)
    		 * 2:记录日志到log4j(优势:文件存储,可以记录非常大的日志数据,而且还有日志级别的特点;劣势:不便于查询)
    		 */
    		LOG.info("方法名为:"+methodInvocation.getMethod().getName()+"的运行时间为:"+runTime+"毫秒");
    		
    		return object;
    	}
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35

    三、确定切入点
    在确定切入点之前, 我们先要在applicationContext.xml中引入aop的文件约束
    在这里插入图片描述

    <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">
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    <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">
    	
    	
    	
    	<bean id="customerService" class="cn.itcast.spring.a_proxy.CustomerServiceImpl"/>
    	
    	<bean id="productService" class="cn.itcast.spring.a_proxy.ProductService"/>
    	
    	
    	<bean id="timeLogAdvice" class="cn.itcast.spring.b_oldaop.TimeLogInterceptor"/>
    	
    	
    	<aop:config>
    		
    		
    		
    		<aop:pointcut expression="bean(*Service)" id="myPointcut"/>
    		<aop:advisor advice-ref="timeLogAdvice" pointcut-ref="myPointcut"/>
    	aop:config>
    	
    		
    beans>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46

    3.3 使用AspectJ实现AOP编程

    3.3.1 AspectJ提供不同的通知类型

    • Before 前置通知,相当于BeforeAdvice
    • AfterReturning 后置通知,相当于AfterReturningAdvice
    • Around 环绕通知,相当于MethodInterceptor
    • AfterThrowing抛出通知,相当于ThrowAdvice
    • After 最终final通知,不管是否异常,该通知都会执行
    • DeclareParents 引介通知,相当于IntroductionInterceptor (不要求掌握)

    相比传统Spring AOP通知类型多了 After最终通知 (类似 finally )。

    3.3.2 使用AspectJ实现AOP编程

    一、确定切入点
    我们这把目标对象是name以Service结尾的bean

    1.我们创建两个bean, 一个基于接口的实现类, 另一个是一般类, 验证Jdk和Cglib的使用

    接口实现类(CustomerServiceImpl)

    public class CustomerServiceImpl implements ICustomerService {
    
        public void save() {
            System.out.println("客户保存了。。。。。");
        }
    
        public int find() {
            System.out.println("客户查询数量了。。。。。");
            return 100;
        }
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    一般类(ProductService)

    public class ProductService {
        public void save() {
            System.out.println("商品保存了。。。。。");
    
        }
    
        public int find() {
            System.out.println("商品查询数量了。。。。。");
            return 99;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    2.注册到Spring容器中, 取名时都以Service结尾

    
    
    
    <bean id="customerService" class="cn.itcast.spring.a_proxy.CustomerServiceImpl"/>
    
    <bean id="productService" class="cn.itcast.spring.a_proxy.ProductService"/>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    二、编写advice增强
    与传统AOP的不同在于, 不用去实现对应的接口(aspectj的advice通知增强类,无需实现任何接口)

    //aspectj的advice通知增强类,无需实现任何接口
    public class MyAspect {
    	
    	//前置通知
    	//普通的方法。方法名随便,但也不能太随便,一会要配置
    	public void firstbefore(){
    		System.out.println("------------第一个个前置通知执行了。。。");
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    同样将前置通知配置到spring的容器中

    
    <bean id="myAspectAdvice" class="cn.itcast.spring.c_aspectjaop.MyAspect"/>
    
    • 1
    • 2

    三、配置切面

    
    <aop:config>
    
    	
    	<aop:pointcut expression="bean(*Service)" id="myPointcut"/>
    	
    	
    	<aop:aspect ref="myAspectAdvice">
    		
    		
    	    <aop:before method="firstbefore" pointcut-ref="myPointcut"/>
    	aop:aspect>
    aop:config>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25

    3.4 AspectJ方式与传统AOP的不同

    • 传统AOP的advice需要实现对应接口(通知种类),而AspectJ的AOP不需要
    • 传统AOP是通过标签来配置切面(因为advice已经实现了对应的通知种类,所以不用在切面中配置),而AspectJ是通过等标签来标识通知种类

    3.5 使用AspectJ实现各类通知

    3.5.1 Before前置通知

    我们先来了解下xml方式, 是怎么实现的

    3.5.1.1 xml配置方式

    1.编写通知
    我们可以将所有的通知都写到同一个类中,
    配置MyAspect类(切面),配置before方法(通知)

    //aspectj的advice通知增强类,无需实现任何接口
    public class MyAspect {	
    
    	//前置通知的:方法运行之前增强
    	//应用: 权限控制 (权限不足,抛出异常)、 记录方法调用信息日志 
    	//参数:org.aspectj.lang.JoinPoint
    	//参数:连接点对象(方法的包装对象:方法,参数,目标对象)
    	public void before(JoinPoint joinPoint){
    		//分析:抛出异常拦截的
    		//当前登录用户
    		String loginName = "Rose";
    		System.out.println("方法名称:"+joinPoint.getSignature().getName());
    		System.out.println("目标对象:"+joinPoint.getTarget().getClass().getName());
    		System.out.println("代理对象:"+joinPoint.getThis().getClass().getName());
    		//判断当前用户有没有执行方法权限
    		if(joinPoint.getSignature().getName().equals("save")){
    			if(!loginName.equals("admin")){
    				//只有超级管理员admin有权限,其他人不能执行某个方法,比如查询方法
    				throw new RuntimeException("您没有权限执行方法:"+joinPoint.getSignature().getName()+",类型为:"+joinPoint.getTarget().getClass().getName());
    			}
    		}
    		
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    2.配置切面(关联切入点和通知)

    
    <bean id="customerService" class="com.itheima.jdk.CustomerServiceImpl"/>
    
    <bean id="productService" class="com.itheima.cglib.ProductService"/>
    
    
    <bean id="myAspectAdvice" class="com.itheima.aop.MyAspect"/>
    
    <aop:config>
        
        <aop:aspect ref="myAspectAdvice">
            
            <aop:pointcut expression="bean(*Service)" id="myPointcut"/>
            
            <aop:before method="before" pointcut-ref="myPointcut"/>
        aop:aspect>
    aop:config>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    简约写法

    <bean id="myAspectAdvice" class="cn.itcast.spring.c_aspectjaop.MyAspect"/>	
    <aop:config>
    	<aop:aspect ref="myAspectAdvice">
    		
    		<aop:before method="before" pointcut="bean(*Service)" />
    	aop:aspect>
    aop:config>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    我们测试一下

    //springjunit集成测试
    @RunWith(SpringJUnit4ClassRunner.class)
    @ContextConfiguration(locations="classpath:applicationContext-aspectj.xml")
    public class SpringTest {
    	//注入要测试bean
    	@Autowired
    	private ICustomerService customerService;
    	@Autowired
    	private ProductService productService;
    	
    	//测试
    	@Test
    	public void test(){
    		//基于接口
    		customerService.save();
    		customerService.find();
    		//基于类的
    		productService.save();
    		productService.find();
    	}
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    3.5.1.2 注解方式

    1.确定目标对象
    确定目标对象(目标对象需要被Spring管理),此处给目标对象设置了id标识

    //接口
    public interface CustomerService {
    	//保存
    	public void save();
    	
    	//查询
    	public int find();
    
    }
    
    //实现类
    /**
     * @Service("customerService")
     * 相当于spring容器中定义:
     * 
     */
    @Service("customerService")
    public class CustomerServiceImpl implements CustomerService{
    
    	public void save() {
    		System.out.println("客户保存了。。。。。");
    		
    	}
    
    	public int find() {
    		System.out.println("客户查询数量了。。。。。");
    		return 100;
    	}
    
    }
    
    //没有接口的类
    /**
     * @Service("productService")
     * 相当于spring容器中定义:
     * 
     */
    @Service("productService")
    public class ProductService {
    	public void save() {
    		System.out.println("商品保存了。。。。。");
    		
    	}
    
    	public int find() {
    		System.out.println("商品查询数量了。。。。。");
    		return 99;
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49

    2.开启aop的aspectj的自动代理
    在applicationContext.xml中开启aop的aspectj的自动代理

    1. 要引入aop的命名空间
    2. 配置aop的AspectJ注解自动代理
    
    <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
                               https://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.itheima"/>
    
        
        <aop:aspectj-autoproxy/>
    beans>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    3.编写通知,并加上注解
    需要保证通知类被Spring管理, 其次添加@Aspect注解, 当我们开启AspectJ注解自动代理机制后(), Sping能自动扫描带有@Aspect的bean,将其作为增强aop的配置,有点相当于:

    之后只需要在通知方法上添加对应的注解即可, 例如@Before(前置通知)

    // aspectj的advice通知增强类,无需实现任何接口
    @Component("myAspect")
    @Aspect
    public class MyAspect {
    
        //前置通知的:方法运行之前增强
        //应用: 权限控制 (权限不足,抛出异常)、 记录方法调用信息日志
        //参数:org.aspectj.lang.JoinPoint
        //参数:连接点对象(方法的包装对象:方法,参数,目标对象)
        @Before("bean(*Service)")
        public void before(JoinPoint joinPoint) {
            //分析:抛出异常拦截的
            //当前登录用户
            String loginName = "Rose";
            System.out.println("方法名称:" + joinPoint.getSignature().getName());
            System.out.println("目标对象:" + joinPoint.getTarget().getClass().getName());
            System.out.println("代理对象:" + joinPoint.getThis().getClass().getName());
            System.out.println("-----------------------------------------------------");
            //判断当前用户有没有执行方法权限
            if (joinPoint.getSignature().getName().equals("save")) {
                if (!loginName.equals("admin")) {
                    //只有超级管理员admin有权限,其他人不能执行某个方法,比如查询方法
                    throw new RuntimeException("您没有权限执行方法:" + joinPoint.getSignature().getName() + ",类型为:" + joinPoint.getTarget().getClass().getName());
                }
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27

    4.切入点表达式的两种写法

    • 方式一:可以直接将切入点的表达式写到@Before()中
    //前置通知
    //相当于:
       //@Before("bean(*Service)"):参数值:自动支持切入点表达式或切入点名字
    @Before("bean(*Service)")
    public void before(JoinPoint joinPoint){
    	System.out.println("=======前置通知。。。。。");
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 方式二:可以使用自定义方法,使用@Pointcut 定义切入点
      切入点方法的语法要求:private void 无参数、无方法体的方法,方法名为切入点的名称
    //自定义切入点
    //方法名就是切入点的名字
    //相当于
    @Pointcut("bean(*Service)")
    private void myPointcut(){}
    //自定义切入点
    //方法名就是切入点的名字
    //相当于
    @Pointcut("bean(*Service)")
    private void myPointcut2(){}
    
    
    //前置通知
    //相当于:
    //相当于:
    @Before("myPointcut()||myPointcut2()")
    public void before(JoinPoint joinPoint){
    	System.out.println("=======前置通知。。。。。");
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    3.5.2 AfterReturing后置通知

    特点:在目标方法运行后,返回值后执行通知增强代码逻辑。
    分析: 后置通知可以获取到目标方法返回值,如果想对返回值进行操作,使用后置通知(但不能修改目标方法返回 )

    @Component("myAspect")
    @Aspect
    public class MyAspect {
    
        @AfterReturning(value="bean(*Service)",returning="returnVal")
        public void afterReturing(JoinPoint joinPoint, Object returnVal) {
            //下发短信:调用运行商的接口,短信猫。。。
            System.out.println("-++++++++-后置通知-当前下发短信的方法" + "-尊敬的用户,您调用的方法返回余额为:" + returnVal);
    
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    应用场景

    • 网上营业厅查询余额后
    • 自动下发短信功能

    3.5.3 Around 环绕通知

    特点:目标执行前后,都进行增强(控制目标方法执行)

    @Component("myAspect")
    @Aspect
    public class MyAspect {
    
        @Around("bean(*Service)")
        public Object around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable{
            System.out.println("---环绕通知-----前");
            Object object = proceedingJoinPoint.proceed();
            System.out.println("---环绕通知-----后");
            return object;
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    应用场景

    • 日志
    • 缓存
    • 权限
    • 性能监控
    • 事务管理

    3.5.4 AfterThrowing 抛出通知

    作用:目标代码出现异常,通知执行。记录异常日志、通知管理员(短信、邮件)

    @Component("myAspect")
    @Aspect
    public class MyAspect {
    
        @AfterThrowing(value="bean(*Service)",throwing="ex")
        public void afterThrowing(JoinPoint joinPoint , Throwable ex){
            System.out.println("---抛出通知。。。。。。"+"抛出的异常信息:"+ex.getMessage());
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    应用场景

    • 处理异常(一般不可预知)
    • 记录日志

    3.5.5 After 最终通知

    作用:不管目标方法是否发生异常,最终通知都会执行(类似于finally代码功能)

    //最终通知
    //拦截所有以ice结尾的bean
    @After("bean(*ice)")
    public void after(JoinPoint joinPoint){
    	System.out.println("+++++++++最终通知。。。。。。。");
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    3.5.6 各种Advice方法可接收的参数和返回值小结

    在这里插入图片描述

    3.5.7 如果目标对象有接口,能否只对实现类代理,而不对接口进行代理呢

    第一步:在CustomerServiceImpl的子类中添加一个新的方法update(),而接口中不要定义update()的方法:

    @Service("customerService")
    public class CustomerServiceImpl implements CustomerService{
    
    	public void save() {
    		System.out.println("客户保存了。。。。。");
    		
    	}
    
    	public int find() {
    		System.out.println("客户查询数量了。。。。。");
    		return 100;
    	}
    	
    	//子类扩展方法
    	public void update(){
    		System.out.println("客户更新了。。。新增方法。。。");
    	}
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    第二步:在测试类中调用子类的扩展方法:

    //springjunit集成测试
    @RunWith(SpringJUnit4ClassRunner.class)
    @ContextConfiguration(locations="classpath:applicationContext.xml")
    public class SpringTest {
    	//注入要测试bean
    	@Autowired
    	private CustomerService customerService;
    	@Autowired
    	private ProductService productService;
    	
    	//测试
    	@Test
    	public void test(){
    		//基于接口
    		customerService.save();
    		customerService.find();
    		
    		//基于类的
    		productService.save();
    		productService.find();
    		
    		//扩展方法执行:customerService是一个动态代理对象,原因,该对象是接口的子类型的对象
    		((CustomerServiceImpl)customerService).update();
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25

    结果发现异常:
    在这里插入图片描述
    为什么会抛出异常呢?原因是代理的目标对象是接口,无法转换为子类。
    在这里插入图片描述
    在这里插入图片描述

    结局方式:设置 proxy-target-class = true

    1. 注解方式
    
    <aop:aspectj-autoproxy proxy-target-class="true"/>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    1. 配置文件XML的方式
    
    <aop:config proxy-target-class="true">
    aop:config>
    
    • 1
    • 2
    • 3

    3.6 AOP的纯注解开发(基于AspectJ)

    一、导入AOP相关坐标

    <dependencies>
        
        <dependency>
            <groupId>org.springframeworkgroupId>
            <artifactId>spring-contextartifactId>
            <version>5.2.10.RELEASEversion>
        dependency>
        
        
        <dependency>
            <groupId>org.aspectjgroupId>
            <artifactId>aspectjweaverartifactId>
            <version>1.9.4version>
        dependency>
    dependencies>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    在这里插入图片描述

    二、定义接口和实现类

    public interface BookDao {
        public void save();
        public void update();
    }
    
    @Repository
    public class BookDaoImpl implements BookDao {
    
        public void save() {
            System.out.println(System.currentTimeMillis());
            System.out.println("book dao save ...");
        }
        public void update(){
            System.out.println("book dao update ...");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    三、定义切入点表达式、配置切面(绑定切入点和通知关系)
    通知类需要添加@Aspect注解, 这样Spring才能把当前类标识为一个切面供容器读取, 当然我们还需要让Spring能识别这个注解

    //通知类必须配置成Spring管理的bean
    @Component
    //设置当前类为切面类
    @Aspect
    public class MyAdvice {
        //设置切入点,@Pointcut注解要求配置在方法上方
        @Pointcut("execution(void com.itheima.dao.BookDao.update())")
        private void pt(){}
    
        //设置在切入点pt()的前面运行当前操作(前置通知)
        @Before("pt()")
        public void method(){
            System.out.println(System.currentTimeMillis());
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    在这里插入图片描述

    四、在配置类中进行Spring注解包扫描和开启AOP功能
    只有添加了@EnableAspectJAutoProxy注解, 才能开启对@Aspect的识别

    @Configuration
    @ComponentScan("com.itheima")
    //开启注解开发AOP功能
    @EnableAspectJAutoProxy
    public class SpringConfig {
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    测试类和运行结果

    public class App {
        public static void main(String[] args) {
            ApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig.class);
            BookDao bookDao = ctx.getBean(BookDao.class);
            bookDao.update();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    3.7 AOP的工作流程

    1. Spring容器启动
    2. 扫描所有@Aspect注解的切面类, 读取其中切面配置的切入点
    3. 初始化bean, 判定bean对应的类中的方法是否匹配到任意切入点
      • 匹配失败,创建原始对象
      • 匹配成功,创建原始对象(目标对象)的代理对象
    4. 获取bean执行方法
      • 获取的bean是原始对象时,调用方法并执行,完成操作
      • 获取的bean是代理对象时,根据代理对象的运行模式运行原始方法与增强的内容,完成操作

    3.8 AOP核心概念

    • 目标对象(target): 被代理的对象,也叫原始对象,该对象中的方法没有任何功能增强
    • 代理对象(proxy):代理后生成的对象,由Spring帮我们创建代理对象

    在测试类中验证代理对象

    public class App {
        public static void main(String[] args) {
            ApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig.class);
            BookDao bookDao = ctx.getBean(BookDao.class);
            bookDao.update();
    		//打印对象的类名
            System.out.println(bookDao.getClass());
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    在这里插入图片描述

    4.Spring对bean的管理

    4.1 bean的获取方式

    1. 使用bean名称获取
    BookDao bookDao = (BookDao) ctx.getBean("bookDao");
    
    • 1
    1. 使用bean类型获取
    BookDao bookDao = ctx.getBean(BookDao.class);
    
    • 1
    1. 使用bean名称获取并指定类型
    BookDao bookDao = ctx.getBean("bookDao", BookDao.class);
    
    • 1

    4.2 bean的作用域

    在这里插入图片描述
    单例是默认值,如果需要单例对象,则不需要配置scope。

    1. xml方式
    <bean id="bookDao" class="com.itheima.ioc.BookDaoImpl" scope="prototype"/> 
    
    • 1
    1. 注解方式
    //测试生命周期过程中的初始化和销毁bean
    @Component("lifeCycleBean")
    //@Scope(value=ConfigurableBeanFactory.SCOPE_PROTOTYPE)
    @Scope("prototype")//默认是单例(singleton),更改为多例(prototype)
    public class LifeCycleBean {
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    4.3 bean的生命周期

    bean的生命周期有4个阶段

    实例化 > 属性赋值 > 初始化 > 销毁

    我们可以通过下图来了解这4个阶段分别在什么时候
    我们可以通过下图来了解

    4.3.1 掌控bean的生命周期

    我们如果想让bean在这四个阶段执行指定的代码, 该如何做呢?*

    • 实例化:我们可以在构造函数中(一般是无参构造)添加指定代码
    • 属性填充:在填充属性时(set方法), 添加指定代码
    • 初始化: 在init-method方法中执行
    • 销毁:在destroy-method方法中执行
    @Service
    public class BookService {
    
        private BookDao bookDao;
    
        public BookService(){
            System.out.println("实例化");
        }
    
        @Autowired
        public void setBookDao(BookDao bookDao) {
            System.out.println("属性填充");
            this.bookDao = bookDao;
        }
    
        //初始化后自动调用方法:方法名随意,但也不能太随便,一会要配置
        @PostConstruct
        public void init(){
            System.out.println("初始化");
        }
    
        //bean销毁时调用的方法
        @PreDestroy
        public void destroy(){
            System.out.println("销毁");
        }
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 使用 @PostConstruct 注解, 标明初始化方法(相当于 init-method 指定初始化方法)
    • 使用 @PreDestroy 注解, 标明销毁方法(相当于 destroy-method 指定对象销毁方法)
    <bean id="bookService" class="cn.itcast.spring.BookService" init-method="init" destroy-method="destroy" />
    
    • 1

    这两个注解在Java 9中已被弃用, 需要添加依赖才能使用

    执行结果如下:
    在这里插入图片描述

    为什么没有调用销毁方法?

    我们发现并没有执行销毁方法,这是因为当方法运行结束,jvm直接关了,Spring容器还来不及销毁对象
    解决方式:手动销毁spring容器,自动销毁单例的对象

    销毁方法的执行必须满足两个条件:

    • 单例(singleton)的bean才会可以手动销毁。
    • 必须手动关闭容器(调用close的方法)时,才会执行手动销毁的方法。
    public static void main(String[] args) {
    	AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig.class);
    	//为什么没有销毁方法调用。
    	//原因是:使用debug模式jvm直接就关了,spring容器还没有来得及销毁对象。
    	//解决:手动关闭销毁spring容器,自动销毁单例的对象
    	ctx.close();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    4.3.2 通过实现接口的方式来控制bean的初始化和销毁

    实现InitializingBean, DisposableBean接口, 跟添加@PostConstruct、@PreDestroy一样的效果

    public class BookServiceImpl implements BookService, InitializingBean, DisposableBean {
        private BookDao bookDao;
        public void setBookDao(BookDao bookDao) {
            System.out.println("set .....");
            this.bookDao = bookDao;
        }
        public void save() {
            System.out.println("book service save ...");
            bookDao.save();
        }
        public void destroy() throws Exception {
            System.out.println("service destroy");
        }
        public void afterPropertiesSet() throws Exception {
            System.out.println("service init");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    4.4 实例化bean的四种方式

    4.4.1 无参构造方法

    默认使用无参构造方法, 无参构造方法如果不存在,将抛出异常BeanCreationException

    <bean id="bookDao" class="com.itheima.dao.impl.BookDaoImpl"/>
    
    • 1

    4.4.2 静态工厂方法

    //静态工厂创建对象
    public class OrderDaoFactory {
        public static OrderDao getOrderDao(){
            System.out.println("factory setup....");
            return new OrderDaoImpl();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    
    <bean id="orderDao" class="com.itheima.factory.OrderDaoFactory" factory-method="getOrderDao"/>
    
    • 1
    • 2

    在这里插入图片描述
    在执行new ClassPathXmlApplicationContext(“applicationContext.xml”)时,就将所有的对象都生成了,无参构造就调用无参构造函数生成,如果是静态工厂方式就直接通过反射执行工厂类的静态方法,这个过程中是不需要创建工厂类的,所以Spring是单例的,之后获取的bean1都是同一个

    4.4.3 实例工厂方式

    //实例工厂创建对象
    public class UserDaoFactory {
        public UserDao getUserDao(){
            return new UserDaoImpl();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    <!--方式三:使用实例工厂实例化bean-->
    <bean id="userFactory" class="com.itheima.factory.UserDaoFactory"/>
    <bean id="userDao" factory-method="getUserDao" factory-bean="userFactory"/>
    
    • 1
    • 2
    • 3

    在这里插入图片描述

    纯注解实现静态工厂方式
    @Bean是一个方法级别上的注解,主要用在@Configuration注解的类里,也可以用在@Component注解的类里

    Spring的@Bean注解用于告诉方法,产生一个Bean对象,然后这个Bean对象交给Spring管理。产生这个Bean对象的方法Spring只会调用一次,随后这个Spring将会将这个Bean对象放在自己的IOC容器中。在一个方法上使用@Bean注解就表明这个方法需要交给Spring进行管理。

    @Component
    public class OrderDaoFactory {
        @Bean
        public OrderDao getOrderDao(){
            System.out.println("factory setup....");
            return new OrderDaoImpl();
        }
    }
    
    public static void main(String[] args) {
        AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig.class);
        OrderDao book = (OrderDao) ctx.getBean("orderDao");
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    4.4.4 FactoryBean方式

    之前讲过BeanFactory(工厂类的顶层接口),而这里是FactoryBean,是两个完全不同的概念

    public class Bean1 {
    	
    }
    
    //实现FactoryBean的方式  
    //泛型:你要返回什么类型的对象,泛型就是什么
    public class Bean1Factory implements FactoryBean<Bean1> {
    	public Bean1Factory() {
    		System.out.println("sss");
    	}
    	
    	//用来获取bean的实例对象
    	public Bean1 getObject() throws Exception {
    		//写一写初始化数据库连接等代码
    		System.out.println("ggg");
    		return new Bean1();
    	}
    	public Class<?> getObjectType() {
    		return null;
    	}
    	public boolean isSingleton() {
    		return false;
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    
    
    <bean id="bean1" class="cn.itcast.spring.a_quickstart.Bean1Factory"/>
    
    • 1
    • 2
    • 3

    4.4.5 总结

    • 无参数构造器:Spring工厂通过反射调用class对象的newInstance方法来创建实例对象,最常用
    • 静态工厂方式:Spring工厂通过反射执行工厂类的静态方法,获取实例对象(这个过程中不用创建静态工厂类)
    • 实例工厂方式:Spring工厂通过反射创建实例工厂对象,之后引用这个工厂类获取实例的方法来创建实例对象
    • FactoryBean方式:工厂类需要实现Bean1Factory接口,Spring在构建工厂类时执行getObject方法获取实例

    4.5 后处理bean

    在Bean初始化的前后,对Bean对象进行增强; 它既可以增强一个指定的Bean,也可以增强所有的Bean

    底层很多功能(如AOP等)的实现都是基于它的,Spring可以在容器中直接识别调用。
    在这里插入图片描述

    创建后处理类, 需要实现BeanPostProcessor(后置处理器)

    @Configuration
    public class MyBeanPostProcessor implements BeanPostProcessor{
    
    	/**
    	 * 实例化、依赖注入完毕,在调用显示的初始化之前完成一些定制的初始化任务
    	 * 注意:方法返回值不能为null
    	 * 如果返回null那么在后续初始化方法将报空指针异常或者通过getBean()方法获取不到bena实例对象
    	 * 因为后置处理器从Spring IoC容器中取出bean实例对象没有再次放回IoC容器中
    	 */
    	@Override
    	public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
    		System.out.println("初始化 before--实例化的bean对象:"+bean+"\t"+beanName);
    		// 可以根据beanName不同执行不同的处理操作
    		return bean;
    	}
    
    	/**
    	 * 实例化、依赖注入、初始化完毕时执行 
    	 * 注意:方法返回值不能为null
    	 * 如果返回null那么在后续初始化方法将报空指针异常或者通过getBean()方法获取不到bena实例对象
    	 * 因为后置处理器从Spring IoC容器中取出bean实例对象没有再次放回IoC容器中
    	 */
    	@Override
    	public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
    		System.out.println("初始化 after...实例化的bean对象:"+bean+"\t"+beanName);
    		// 可以根据beanName不同执行不同的处理操作
    		return bean;
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29

    如果要增强一个指定的bean,则只要在方法中根据bean的名称进行处理

    spring在初始化MyBeanPostProcessor的时候,判断是否实现了BeanPostProcessor,如果实现了,就采用动态代理的方式,对所有的bean对象增强

    4.5.1 后处理bean的执行时机

    方法说明
    postProcessBeforeInitialization实例化、依赖注入完毕,在调用显示的初始化之前完成一些定制的初始化任务
    postProcessAfterInitialization实例化、依赖注入、初始化完毕时执行

    4.5.2 使用场景

    1、处理自定义注解。bean可以添加我们自定义的注解,自定义的注解处理方式在该类中实现,如通过注解识别一组或几组bean,在后续的业务处理中根据组bean进行逻辑。
    2、打印日志,将每个bean的初始化情况打印出来;打印初始化时间等。

    参考内容: Spring之BeanPostProcessor(后置处理器)介绍

    4.6 bean属性的依赖注入

    4.6.1 构造器参数注入

    <bean id="car" class="cn.itcast.spring.Car">
    	
    	
    	<constructor-arg index="0" name="id" value="1"/>
    	
    	<constructor-arg name="name" >
    		<value>宝马2代value>
    	constructor-arg>
    	<constructor-arg type="java.lang.Double" value="99999d"/>
    	
    bean>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    注入引用类型的值(参数第一组不变, 用于定位属性, 参数第二组改为ref来指向对象)
    在这里插入图片描述

    4.6.2 setter方法属性注入

    /**
     * 定义人类
     * setter方法属性注入
     * 相当于new Person();
     */
    public class Person {
    	private Integer id;
    	private String name;
    	private Car car;
    	//必须提供setter属性方法
    	public void setId(Integer id) {
    		this.id = id;
    	}
    	public void setName(String name) {
    		this.name = name;
    	}
    	public void setCar(Car car) {
    		this.car = car;
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    
    <bean id="person" class="cn.itcast.spring.Person">
    	
    	<property name="id" value="1001"/>
    	<property name="name" value="Tom"/>
    	
    	<property name="car">
    		<ref bean="car"/>
    	property>
    bean>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    4.6.3 p名称空间

    Spring2.5版本开始引入了一个新的p名称空间。简单的说,它的作用是为了简化setter方法属性依赖注入配置的,它不是真正的名称空间。

    xmlns:p="http://www.springframework.org/schema/p"
    
    • 1
    
    
    <bean id="person" class="cn.itcast.spring.Person" p:id="1002" p:name="关羽" p:car-ref="car"/>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    4.6.4 spEL表达式

    • EL:操作servlet相关的一些对象和相关的值
    • OGNL:主要操作struts2值栈
    • spEL:操作bean相关的

    语法: #{…} , 引用另一个Bean 、属性、 方法 , 运算

    SpEL表达式的使用功能比较多,Bean操作相关的通常有:

    • #{beanid} 引用Bean(具体对象)
    • #{beanId.属性} 引用Bean的属性
    • #{beanId.方法(参数)} 调用Bean的方法
    
    
    <bean id="person3" class="cn.itcast.spring.e_xmlpropertydi.Person" 
    p:id="#{1+1}" p:name="#{person.name.toUpperCase()}" p:car="#{car}">bean>
    
    • 1
    • 2
    • 3
    • 4

    4.6.5 @Value

    简单数据类型, 我们可以使用@Value来注入值
    在这里插入图片描述

    4.6.6 @Value + SpEL

    在属性声明上面注入,底层自动还是生成set方法, 其中customerDao表示节点id的属性值

    //@Component(value="customer")
    @Service(value="customer")
    public class CustomerService {
    	//在属性声明上面注入,底层自动还是生成setCustomerDao()
    	//第一种: 使用@Value 结合SpEL  ---- spring3.0 后用
        //其中customerDao表示节点id的属性值
    	@Value("#{customerDao}")
    	private CustomerDao customerDao;
    	
    	//保存业务方法
    	public void save(){
    		System.out.println("CustomerService业务层被调用了。。。");
    		System.out.println("name:"+name);
    		customerDao.save();
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    4.6.7 @Autowired + @Qualifier

    单独使用@Autowired

    单独使用@Autowired ,表示按照类型注入,会到spring容器中查找CustomerDao的类型,对应,class的属性值,如果找到,可以匹配。

    //使用spring的@Autowired
    @Autowired//默认按照类型注入
    private CustomerDao customerDao;
    
    • 1
    • 2
    • 3

    按照类型注入会遇到的问题:如果IOC容器中同类的Bean有多个,那么默认按照变量名和Bean的名称匹配,建议使用@Qualifier注解指定要装配的bean名称
    在这里插入图片描述
    这时候会发现注入的地方报错 不清楚要注入哪个bean 错误如下图
    在这里插入图片描述
    这时候我们就可以使用@Qualifier来配合@Autowire进行注入了
    首先在实例化bean的时候指定名字 注入时使用对应的名字注入 如下图
    在这里插入图片描述
    指定名称为user1的bean
    在这里插入图片描述
    注意事项:
    在使用@Autowired时,首先在容器中查询对应类型的bean。如果查询结果刚好为一个,就将该bean装配给@Autowired指定的数据如果查询的结果不止一个,那么@Autowired会根据名称来查找。如果查询的结果为空,那么会抛出异常。

    参考: @Autowired用法详解

    使用@Autowired + @Qualifier
    使用@Autowired + @Qualifier表示按照名称注入,回到spring容器中查找customerDao的名称,对应,id的属性值,如果找到,可以匹配。

    @Autowired//默认按照类型注入的
    @Qualifier("customerDao") //必须配合@Autowired注解使用,根据名字注入
    private CustomerDao customerDao;
    
    • 1
    • 2
    • 3

    4.6.8 @Resource

    使用@Resource注解,表示先按照名称注入,会到spring容器中查找customerDao的名称,对应,id的属性值,如果找到,可以匹配。

    如果没有找到,则会按照类型注入,会到spring容器中查找CustomerDao的类型,对应,class的属性值,如果找到,可以匹配,如果没有找到会抛出异常。

    @Resource//默认先按照名称进行匹配,再按照类型进行匹配
    private CustomerDao customerDao;
    
    • 1
    • 2

    如果@Resource注解上添加name名称,只能按照customerDao名称进行匹配

    //第三种: JSR-250标准(jdk) 提供@Resource 
    @Resource(name="customerDao")//只能按照customerDao名称进行匹配
    private CustomerDao customerDao;
    
    • 1
    • 2
    • 3

    5.Spring整合JDBC

    5.1 复习JDBC

    之前我们如果要使用JDBC来连接数据库, 要进行六个步骤的操作

    1. 注册驱动:DriverManager.registerDriver(new com.mysql.jdbc.Driver());
    2. 获取连接:Connection conn = DriverManager.getConnection(url, user,password);
    3. 获取数据库操作对象:Statement stmt = conn.createStatement();
    4. 执行sql语句:int affectLine = stmt.executeUpdate(sql);
    5. 处理查询结果集
    6. 释放资源:stmt.close(), conn.close();

    而Spring提供了一个更快捷的jdbc操作目标:JdbcTemplate, 用来简化jdbc的操作

    5.2 JdbcTemplate

    Spring 框架对 JDBC 进行封装,使用 JdbcTemplate 方便实现对数据库操作

    一、引入依赖包

    
    <dependency>
      <groupId>org.springframeworkgroupId>
      <artifactId>spring-jdbcartifactId>
      <version>5.2.10.RELEASEversion>
    dependency>
    
    
    <dependency>
      <groupId>mysqlgroupId>
      <artifactId>mysql-connector-javaartifactId>
      <version>8.0.29version>
    dependency>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    二、代码实现

    @Test
    public void test(){
        //目标:使用jdbctemplate执行一段sql
    
        //1.构建数据源 DriverManagerDataSource
        //spring内置了一个数据源
        DriverManagerDataSource dataSource = new DriverManagerDataSource();
        dataSource.setDriverClassName("com.mysql.jdbc.Driver");
        dataSource.setUrl("jdbc:mysql://127.0.0.1:3306/my_database?useSSL=false");
        dataSource.setUsername("root");
        dataSource.setPassword("root");
    
        //2.创建jdbctemplate实例
        JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
    
        //3.执行sql,创建表test001
        jdbcTemplate.execute("create table test001(id int,name varchar(20))");
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    三、存在的问题
    上述代码中的dataSource以及jdbcTemplate都需要我们手动创建 ,能不能将这两个对象个交给Spring管理?

    5.3 Spring管理DataSource和JdbcTemplate

    
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
        <property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/>
        <property name="url" value="jdbc:mysql://127.0.0.1:3306/my_database"/>
        <property name="username" value="root"/>
        <property name="password" value="root"/>
    bean>
    
    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        
        <property name="dataSource" ref="dataSource"/>
    bean>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    参考内容: JdbcTemplate_注解使用方式

    5.3.1 管理DataSource连接池对象

    
    <dependency>
        <groupId>mysqlgroupId>
        <artifactId>mysql-connector-javaartifactId>
        <version>5.1.47version>
    dependency>
    
    <dependency>
        <groupId>org.springframeworkgroupId>
        <artifactId>spring-contextartifactId>
        <version>5.2.0.RELEASEversion>
    dependency>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    5.3.1.1 管理Druid连接池
    <dependency>
        <groupId>com.alibabagroupId>
        <artifactId>druidartifactId>
        <version>1.1.16version>
    dependency>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    5.3.1.2 管理c3p0连接池
    <dependency>
        <groupId>c3p0groupId>
        <artifactId>c3p0artifactId>
        <version>0.9.1.2version>
    dependency>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    @Configuration
    public class SpringConfig {
    
        @Bean
        public DruidDataSource dataSource() {
        	// ComboPooledDataSource comboPooledDataSource = new ComboPooledDataSource();
            DruidDataSource dataSource = new DruidDataSource();
            dataSource.setDriverClassName("com.mysql.jdbc.Driver");
            dataSource.setUrl("jdbc:mysql://localhost:3306/spring_db");
            dataSource.setUsername("root");
            dataSource.setPassword("root");
            return dataSource;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    public class App {
        public static void main(String[] args) {
            ApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig.class);
            DataSource dataSource = (DataSource) ctx.getBean("dataSource");
            System.out.println(dataSource);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    5.3.2 加载properties属性文件

    • 不加载系统属性
    <context:property-placeholder location="jdbc.properties" system-properties-mode="NEVER"/>
    
    • 1
    • 加载多个properties文件
    <context:property-placeholder location="jdbc.properties,msg.properties"/>
    
    • 1
    • 加载所有properties文件
    <context:property-placeholder location="*.properties"/>
    
    • 1
    • 加载properties文件
    <context:property-placeholder location="classpath:*.properties"/>
    
    • 1
    • 加载properties文件标准格式
    <context:property-placeholder location="classpath*:*.properties"/>
    
    • 1

    6.Spring纯注解开发

    Spring3.0开启了纯注解开发模式,使用Java类替代配置文件,开启了Spring快速开发赛道

    6.1 配置类代替配置文件

    传统的开发, 是要手动编写Spring核心配置文件(applicationContext.xml), 而Spring 3.0开启了纯注解的开发模式, 使用java类替代配置文件
    在这里插入图片描述

    1. 使用@Configuration用于定义配置类,用于替换xml配置文件applicationContext.xml
    2. 使用@ComponentScan用于扫描被@Component、@Controller、@Service、@Repository等注解的类, 这个类就会被作为bean注册到spring容器中, 用于替代标签, 此注解只能添加一次,多个数据请用数组格式
    //声明当前类为Spring配置类
    @Configuration
    //Spring注解扫描,相当于
    @ComponentScan("com.itheima")
    //设置bean扫描路径,多个路径书写为字符串数组格式
    //@ComponentScan({"com.itheima.service","com.itheima.dao"})
    public class SpringConfig {
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    6.2 读取配置类

    使用AnnotationConfigApplicationContext读取配置类, 初始化容器

    //加载配置文件初始化容器
    ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
    //加载配置类初始化容器
    ApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig.class);
    
    • 1
    • 2
    • 3
    • 4

    注意:使用注解开发初始化Spring容器时, 使用的是AnnotationConfigApplicationContext, 它是ApplicationContext的子类, 可以通过Ctrl+Alt+Shift+U查看继承关系图
    在这里插入图片描述

    6.3 定义bean

    在类上使用@Component注解定义Bean。

    @Component("bookDao")
    public class BookDaoImpl implements BookDao {
        public void save() {
            System.out.println("book dao save ...");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    如果@Component注解没有使用参数指定Bean的名称,那么类名首字母小写就是Bean在IOC容器中的默认名称。例如:BookServiceImpl对象在IOC容器中的名称是bookServiceImpl。

    @Component三个衍生注解

    1. @Controller:用于表现层bean定义
    2. @Service:用于业务层bean定义
    3. @Repository:用于数据层bean定义

    6.3.1 bean作用范围

    使用@Scope定义bean作用范围

    @Repository
    @Scope("singleton")
    public class BookDaoImpl implements BookDao {
    }
    
    • 1
    • 2
    • 3
    • 4

    6.3.2 bean生命周期

    使用@PostConstruct、@PreDestroy定义bean生命周期

    @Repository
    @Scope("singleton")
    public class BookDaoImpl implements BookDao {
        public BookDaoImpl() {
            System.out.println("book dao constructor ...");
        }
        @PostConstruct
        public void init(){
            System.out.println("book init ...");
        }
        @PreDestroy
        public void destroy(){
            System.out.println("book destory ...");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    注意:@PostConstruct和@PreDestroy注解是jdk中提供的注解,从jdk9开始,jdk中的javax.annotation包被移除了,也就是说这两个注解就用不了了,可以额外导入一下依赖解决这个问题。

    <dependency>
      <groupId>javax.annotationgroupId>
      <artifactId>javax.annotation-apiartifactId>
      <version>1.3.2version>
    dependency>
    
    • 1
    • 2
    • 3
    • 4
    • 5

    6.4 注解开发总结

    在这里插入图片描述

    7.Spring的事务管理机制

    Spring事务管理高层抽象主要包括3个接口,Spring的事务主要是由他们共同完成的:

    • PlatformTransactionManager 事务管理器
      主要用于平台相关事务的管理
    • TransactionDefinition 事务定义信息
      通过配置如何进行事务管理。(隔离、传播、超时、只读)
    • TransactionStatus 事务具体运行状态
      事务管理过程中,每个时间点事务的状态信息。

    7.1 事务管理器

    Spring为不同的持久化框架提供了不同PlatformTransactionManager接口实现:

    事务说明
    org.springframework.jdbc.datasource.DataSourceTransactionManager使用Spring JDBCiBatis进行持久化数据时使用
    org.springframework.orm.hibernate5.HibernateTransactionManager使用Hibernate5.0版本进行持久化数据时使用
    org.springframework.orm.jpa.JpaTransactionManager使用JPA进行持久化时使用
    org.springframework.jdo.JdoTransactionManager当持久化机制是Jdo时使用
    org.springframework.transaction.jta.JtaTransactionManager使用一个JTA实现来管理事务,在一个事务跨越多个资源时必须使用

    我们需要根据项目使用的持久化框架来配置不同的事务管理器(如果是SpringBoot, 则不需要手动定义事务管理器)

    7.1.1 配置事务管理器

    1. 配置事务管理器
    2. 往事务管理器中注入数据源

    一、xml方式

    • DataSourceTransactionManager
    
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        
        <property name="dataSource" ref="dataSource"/>
    bean>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • HibernateTransactionManager
    <bean id="transactionManager" class="org.springframework.orm.hibernate5.HibernateTransactionManager">
        
        <property name="dataSource" ref="dataSource"/>
    bean>
    
    • 1
    • 2
    • 3
    • 4

    二、配置方式
    在Spring配置类中,注册事务管理器

    //配置事务管理器,mybatis使用的是jdbc事务
    @Bean
    public PlatformTransactionManager transactionManager(DataSource dataSource){
    	// HibernateTransactionManager dtm = new HibernateTransactionManager();
        DataSourceTransactionManager dtm = new DataSourceTransactionManager();
        transactionManager.setDataSource(dataSource);
        return transactionManager;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    用户根据选择和使用的持久层技术,来选择对应的事务管理器

    7.2 事务定义信息

    • 定义事务隔离级别
    • 定义事务传播行为

    7.2.1 事务的隔离级别

    我们要了解事务的隔离级别, 首先要搞懂脏读、幻读、不可重复读

    7.2.1.1 脏读、幻读、不可重复读

    一、脏读 (读取未提交数据)
    一个事务读取了另一个事务改写但还未提交的数据,如果这些数据被回滚,则读到的数据是无效的。

    简单理解:事务A读取到事务B修改了但未提交的数据
    一个事务读取了另一个事务改写但还未提交的数据,如果这些数据被回滚,则读到的数据是无效的

    二、不可重复读 (前后多次读取,数据内容不一致)
    事务A在执行读取操作,由整个事务A比较大,前后读取同一条数据需要经历很长的时间 。而在事务A第一次读取数据,比如此时读取了小明的年龄为20岁,事务B执行更改操作,将小明的年龄更改为30岁,此时事务A第二次读取到小明的年龄时,发现其年龄是30岁,和之前的数据不一样了,也就是数据不重复了,系统不可以读取到重复的数据,成为不可重复读。

    简单理解:事务A前后两次读取同一条数据,数据内容不一致, 原因是其他事务对这条数据的内容进行了修改并提交
    在这里插入图片描述

    三、幻读 (前后多次读取,数据总量不一致)
    事务A在执行读取操作,需要两次统计数据的总量,前一次查询数据总量后,此时事务B执行了新增数据的操作并提交后,这个时候事务A读取的数据总量和之前统计的不一样,就像产生了幻觉一样,平白无故的多了几条数据,成为幻读。

    简单理解:事务A前后两次读取到的数据总量不一致, 原因是其他事务新增、删除了数据

    在这里插入图片描述

    7.2.1.2 四种隔离级别

    一、读未提交(Read uncommitted)
    在这种隔离级别下,所有事务能够读取其他事务未提交的数据。读取其他事务未提交的数据,会造成脏读。因此在该种隔离级别下,不能解决脏读、不可重复读和幻读。

    读未提交可能会产生脏读的现象,那么怎么解决脏读呢?那就是使用读已提交。

    二、读已提交(Read committed)
    在这种隔离级别下,所有事务只能读取其他事务已经提交的内容。能够彻底解决脏读的现象。但在这种隔离级别下,会出现一个事务的前后多次的查询中却返回了不同内容的数据的现象,也就是出现了不可重复读。

    注意:这是大多数数据库系统默认的隔离级别,例如Oracle和SQL Server,但mysql不是。
    已提交可能会产生不可重复读的现象,我们可以使用可重复读。

    三、可重复读(Repeatable read)
    在这种隔离级别下,所有事务前后多次的读取到的数据内容是不变的。也就是某个事务在执行的过程中,不允许其他事务进行update操作,但允许其他事务进行add操作,造成某个事务前后多次读取到的数据总量不一致的现象,从而产生幻读。

    注意:这才是mysql的默认事务隔离级别
    可重复读依然会产生幻读的现象,此时我们可以使用串行化来解决。

    四、可串行化(Serializable)
    在这种隔离级别下,所有的事务顺序执行,所以他们之间不存在冲突,从而能有效地解决脏读、不可重复读和幻读的现象。但是安全和效率不能兼得,这样事务隔离级别,会导致大量的操作超时和锁竞争,从而大大降低数据库的性能,一般不使用这样事务隔离级别。

    下面用一张表格来表示他们能够解决的问题

    隔离级别脏读不可重复读幻读
    读未提交(Read uncommitted)×(未解决)××
    读已提交(Read committed)√(解决)××
    可重复读(Repeatable read)×
    可串行化(Serializable)

    连接一
    连接二

  • 相关阅读:
    阿里云第一次面试记录
    qiankun使用Actions实现通信
    导入到idea里的springboot/maven项目不显示为maven项目
    基于风险容忍度的网络安全和风险管理战略和方法
    异常处理(try,catch,finally)
    Manacher算法
    P4027 [NOI2007] 货币兑换
    Azure Synapse Analytics 性能优化指南(3)——使用具体化视图优化性能(下)
    遗传算法求解多旅行商问题的新方法
    vscode setting.json 全局设置 工作区设置 位置 优先级
  • 原文地址:https://blog.csdn.net/qq_24099547/article/details/133027484