转载

Spring 核心思想笔记

本文对Spring相关知识点做了归纳整理,包括 Spring 优势、其框架结构、核心思想,并对IoC思想及AOP思想进行手动实现,增强对Spring 核心思想的理解。之后对Spring IoC、AOP 的实现方式和特性进行介绍,并对照源码理解其实现思路。

Spring 优势

  • 方便解耦,简化开发

    [注:IoC(降低组件耦合性)、DI(降低业务对象替换的复杂性)]

  • AOP编程的思想

    [注:通用任务集中管理,使得代码可更好的复用]

  • 声明式事务的支持

    [注:@Transaction]

  • 轻量级框架,代码侵入性低,可自由选择框架部分组件

  • 方便集成各种优秀框架

Spring 核心结构

Spring 核心思想笔记
  • Spring核心容器(Core Container):容器是Spring框架最核心的部分,管理Bean的创建(IoC)、配置(DI)和管理(Context),所有的Spring模块都构建于核心容器之上.
  • 面向切面编程(AOP):Spring应用系统中开发切面的基础,代码解耦,增强复用。
  • 数据访问与集成(Data Access):Spring的JDBC和DAO模块封装大量样板代码,使得数据库代码变得简洁,也可避免数据库资源释放失败引发的问题。由JDBC、Transactions(AOP模块支持)、ORM、OXM 和 JMS等模块组成。
  • Web:提供了SpringMVC框架给Web应⽤,还提供了多种构建和其它应⽤交互的远程调⽤⽅ 案。 SpringMVC框架在Web层提升了应⽤的松耦合⽔平。
  • Test 为了使得开发者能够很⽅便的进⾏测试,Spring提供了测试模块以致⼒于Spring应⽤的测试。 通过该模块,Spring为使⽤Servlet、JNDI等编写单元测试提供了⼀系列的mock对象实现。

核心思想

IoC(Inversion of Control)控制反转

IoC 是一个技术思想,而Spring对其进行了实现

控制反转:对象创建(实例化、管理)的权利交给外部环境(Spring框架、IoC容器)

解决问题:解决对象间的耦合问题

Spring 核心思想笔记

IoC 和 DI 的区别

DI(Dependancy Injection)依赖注⼊

IoC和DI描述的是同一件事情(对象实例化及依赖关系维护),只不过角度不一样罢了
IoC:站在对象的角度,对象实例化及其管理交给容器
DI:站在容器角度,容器会把对象依赖的其他对象注入

AOP(Aspect oriented Programming)面向切面编程

OOP与AOP

OOP是一种垂直继承体系(三大特征:封装、继承和多态)

AOP为解决纵向重复问题,抽取横向逻辑代码,分离业务逻辑和横切逻辑,解耦合

AOP解决问题

解决纵向重复问题,抽取横向逻辑代码,分离业务逻辑和横切逻辑,解耦合

IoC和AOP的手动实现

IoC与DI实现

  1. 定义beans.xml,其中定义:
    • 需要创建的对象的id和全限定类名 ⇒ 反射创建对象,id为标识
    • 属性名及依赖对象id ⇒ 通过对应类中的 Set方法 注入依赖属性
  2. 定义BeanFactory,读取解析xml,通过反射实例化对象,统一管理,并提供外部获取对象的接口方法

AOP 事务管理

  1. 将 Connection 与当前线程绑定,手动控制 JDBC 的 Connection 事务(关闭自动提交事务)
  2. 创建代理工厂类,对代理类进行事务增强
  3. 使用代理类对象替代对象进行方法的调用

Spring IoC 应用

IoC 实现方式及其对应启动方法

  1. 纯 xml 实现(bean 信息定义全部配置在 xml 中)

    // JavaSE 应用启动
    ApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml");
    // 或者读取绝对路径下的配置文件(不推荐)
    ApplicationContext applicationContext = new FileSystemXmlApplicationContext("c:/beans.xml");
    
    // JavaWeb 应用
    /**
    	配置 ContextLoaderListener 类(web.xml)
    	监听器机制加载 xml
    */
    复制代码
    <!DOCTYPE web-app PUBLIC
    "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN" "http://java.sun.com/dtd/web-app_2_3.dtd" >
    <web-app>
        <display-name>Archetype Created Web Application</display-name>
        <!--配置Spring ioc容器的配置⽂件-->
        <context-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>classpath:applicationContext.xml</param-value>
        </context-param>
        <!--使⽤监听器启动Spring的IOC容器-->
        <listener>
            <listenerclass>org.springframework.web.context.ContextLoaderListener</listenerclass>
        </listener>
    </web-app>
    复制代码
  2. xml + 注解(部分 bean 使用 xml 定义,部分 bean 使用注解定义)

    和纯 xml 实现一致

  3. 纯注解 (所有 bean 都是用注解来定义)

    // JavaSE 应用启动
    ApplicationContext applicationContext = new AnnotationConfigApplicationContext(Spring.class);
    
    // JavaWeb 应用
    /**
    	配置 ContextLoaderListener 类(web.xml)
    	监听器机制加载 注解配置类
    */
    复制代码
    <!DOCTYPE web-app PUBLIC
    "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN" "http://java.sun.com/dtd/web-app_2_3.dtd" >
    <web-app>
        <display-name>Archetype Created Web Application</display-name>
        <!--告诉ContextloaderListener知道我们使⽤注解的⽅式启动ioc容器-->
        <context-param>
            <param-name>contextClass</param-name>
            <paramvalue>org.springframework.web.context.support.AnnotationConfigWebAppli cationContext</param-value>
        </context-param>
        <!--配置启动类的全限定类名-->
        <context-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>com.lagou.edu.SpringConfig</param-value>
        </context-param>
        <!--使⽤监听器启动Spring的IOC容器-->
        <listener>
            <listenerclass>org.springframework.web.context.ContextLoaderListener</listenerclass>
        </listener>
    </web-app>
    复制代码

BeanFactory 与 ApplicationContext 区别

BeanFactory是Spring框架中IoC容器的顶层接⼝,它只是⽤来定义⼀些基础功能,定义⼀些基础规范,⽽ApplicationContext是它的⼀个⼦接⼝,所以ApplicationContext是具备BeanFactory提供的全部功能的。
ApplicationContext是容器的⾼级接⼝,⽐ BeanFactory要拥有更多的功能,⽐如说国际化⽀持和资源访问(xml,java配置类)等等

实例化Bean的三种方式

  1. 使用无参构造器

    <!--配置service对象-->
    <bean id="userService" class="com.lagou.service.impl.TransferServiceImpl">
    </bean>
    复制代码
  2. 使用静态方法创建

    <!--使⽤静态⽅法创建对象的配置⽅式-->
    <bean id="userService" class="com.lagou.factory.BeanFactory" factory-method="getTransferService"></bean>
    复制代码
  3. 使用实例化方法创建

    <!--使⽤实例⽅法创建对象的配置⽅式-->
    <bean id="beanFactory" class="com.lagou.factory.instancemethod.BeanFactory"></bean>
    <bean id="transferService" factory-bean="beanFactory" factory-method="getTransferService"></bean>
    复制代码

Bean 的作用范围及生命周期

  • singleton(单例模式)

    单例模式的 bean 对象生命周期与容器相同

  • prototype(多例模式)

    多例模式的 bean 对象,Spring 框架只负责创建,不负责销毁

Xml 模式下 Bean 标签的属性

  • id:bean 的唯一标识
  • class:创建 Bean 对象的全限定类名
  • name:给 bean 提供一个或多个名称
  • factory-bean:指定创建当前 bean 对象的工厂bean 的唯一标识(当指定此属性,class 属性失效)
  • factory-method:指定创建当前对象的工厂方法
    • 注:若配合 factory-bean 使用,class 属性失效,若配合 class 属性使用,方法必须是 static 修饰的
  • scope:bean 作用域,默认 singleton
  • init-method:指定 bean 对象初始化方法,在 bean 装配后调用,必须是无参方法。
  • destory-method:指定 bean 对象销毁方法,在 bean 销毁前执行,只在 scope 为 singleton 时起作用(因为多例模式下Spring框架只负责创建)

DI 依赖注入的 xml 配置

  • 依赖注入分类

    • 按注入的方式分类

      1. 构造函数注入
        • 注:当没有无参构造时,必须提供与构造参数个数及数据类型匹配的配置参数
        • 使用标签 <constructor-arg> 为构造函数赋值,其属性如下:
          • name:对应参数名
          • index:对应构造函数中指定索引
          • value:指定基本类型或 String 类型数据
          • ref:指定其他 Bean 类型的数据,值为 bean 的 id
      2. set 方法注入
        • 使用标签 <property> 对应 set 方法,标签属性如下:
          • name:注入时调用的 set 方法名去掉 set 后的词缀
          • value:指定基本类型或 String 类型数据
          • ref:指定其他 Bean 类型的数据,值为 bean 的 id
    • 按注入的数据类型分类

      1. 基本类型、String

      2. 复杂类型 [Array,List,Set,Map,Properties]

        示例( 以set方法注入为例,构造函数注入同样 ):

        <!-- Array -->
        <property name="myArray">
        	<array>
                <value>array1</value>
                <value>array2</value>
            </array>
        </property>
        
        <!-- List -->
        <property name="myArray">
        	<list>
                <value>list1</value>
                <value>list2</value>
            </list>
        </property>
        
        <!-- Set -->
        <property name="mySet">
        	<set>
                <value>set1</value>
                <value>set2</value>
            </set>
        </property>
        
        <!-- Map -->
        <property name="myMap">
        	<map>
                <entry key="key1" value="value1"/>
                <entry key="key2" value="value2"/>
            </map>
        </property>
        
        <!-- Properties -->
        <property name="myProperties">
        	<map>
                <prop key="key1" value="value1"/>
                <prop key="key2" value="value2"/>
            </map>
        </property>
        复制代码

        在List结构的集合数据注⼊时,array , list , set这三个标签通⽤,另外注值的value标签内部 可以直接写值,也可以使⽤bean标签配置⼀个对象,或者⽤ref标签引⽤⼀个已经配合的bean 的唯⼀标识。 在Map结构的集合数据注⼊时,map标签使⽤entry⼦标签实现数据注⼊,entry标签可以使 ⽤key和value属性指定存⼊map中的数据。使⽤value-ref属性指定已经配置好的bean的引⽤。 同时entry标签中也可以使⽤ref标签,但是不能使⽤bean标签。⽽property标签中不能使 ⽤ref或者bean标签引⽤对象

      3. 其他 Bean 类型

注解与 xml 标签对应关系

xml 与注解相结合模式下,通常第三方jar 中的bean 定义在 xml 中,自己开发的 bean 定义使用注解

  1. 配置方面

    • @Configuration 注解,对应 beans.xml 文件
    • @ComponentScan 注解,替代 <context:component-scan>
    • @PropertySource,引⼊外部属性配置⽂件
    • @Import 引⼊其他配置类
    • @Value 对变量赋值,可以直接赋值,也可以使⽤ ${} 读取资源配置⽂件中的信息
    • @Bean 将⽅法返回对象加⼊ SpringIoC 容器
  2. IoC

    xml形式 对应的注解形式
    <bean> 标签 @Component("accountDao"),注解加在类上 bean的id属性内容直接配置在注解后⾯如果不配置,默认定义个这个bean的id为类 的类名⾸字⺟⼩写;
    另外,针对分层代码开发提供了@Componenet的三种别名@Controller、 @Service、@Repository分别⽤于控制层类、服务层类、dao层类的bean定义,这 四个注解的⽤法完全⼀样,只是为了更清晰的区分⽽已
    scope属性 @Scope("prototype"),默认单例,注解加在类上
    标签的 init-method 属性 @PostConstruct,注解加在⽅法上,该⽅法就是初始化后调⽤的⽅法
    destory-method 属性 @PreDestory,注解加在⽅法上,该⽅法就是销毁前调⽤的⽅法
  3. DI

    • @Autowired
      • 按类型注入,如果有多个 bean 值,需配合 @Qualifier(name="") 使用
    • @Resource
      • J2EE 提供,@Resource 在 Jdk 11中已经移除,使用需导入包 javax.annotation.Resource
      • 注入规则:
        • 如果同时指定了 name 和 type,则从Spring上下⽂中找到唯⼀匹配的bean进⾏装配,找不到则抛出异常。
        • 如果指定了 name,则从上下⽂中查找名称(id)匹配的bean进⾏装配,找不到则抛出异常。
        • 如果指定了 type,则从上下⽂中找到类似匹配的唯⼀bean进⾏装配,找不到或是找到多个, 都会抛出异常。
        • 如果既没有指定name,⼜没有指定type,则⾃动按照byName⽅式进⾏装配;

Spring IoC 常用特性

lazy-Init 延迟加载

  • 关闭延迟加载(默认状态) :ApplicationContext 容器的默认⾏为是在启动服务器时将所有 singleton bean 提前进⾏实例化
  • 开启延迟加载:bean 将不会在 ApplicationContext 启动时提前被实例化,⽽是第⼀次向容器通过 getBean 索取 bean 时实例化的。
    1. 在 <bean> 设置 lazy-init="true",控制单个 bean
    2. 在 <beans> 设置 default-lazy-init="true" ,控制该容器下所有 bean

作用:

  1. 开启延迟加载⼀定程度提⾼容器启动和运转性能
  2. 对于不常使⽤的 Bean 设置延迟加载,这样偶尔使⽤的时候再加载,不必要从⼀开始该 Bean 就占⽤资源

FactoryBean 和 BeanFactory

  • BeanFactory接口:容器顶级接口,定义容器的一些基础行为

  • FactoryBean接口:借助他自定义Bean,作用类似于 Bean 创建方式的静态方法和实例化方法

    使用方法

    1. 创建 Bean 对应的 Factory 类,实现 FactoryBean 接口,重写 getObject() 、getObjectType() 和 isSingleton()

    2. 在 xml 中配置该 Factory 类

    3. 容器初始化过程中 会自动实例化 Factory 对象并调用其 getObject() 并管理

      注:如果需要 Factory 对象,可获取容器中的 &${id} 对象

      <bean id="testBean" class="com.test.TestBeanFactory"></bean>
      
      <!--
      	getBean("testBean") 获取的是 TestBean 对象
      	getBean("&testBean") 获取的是 TestBeanFactory 对象
      -->
      复制代码

后置处理器

Spring 提供两种后置处理 bean 的扩展接口

  • BeanPostProcessor:Bean 对象实例化且装配后调用

    该接口提供两个方法:

    1. postProcessBeforeInitialization:Bean 初始化方法前
    2. postProcessAfterInitialization:Bean 初始化方法后
  • BeanFactoryPostProcessor:BeanFactory 初始化后(bean 还未实例化,此时 bean 刚被解析成 BeanDefinition对象)调用

    此接口提供了⼀个⽅法,⽅法参数为ConfigurableListableBeanFactory,该参数类型定义了⼀些⽅法其中有个⽅法名为getBeanDefinition的⽅法,我们可以根据此⽅法,找到我们定义bean 的 BeanDefinition对象。然后我们可以对定义的属性进⾏修改。

    **BeanDefinition对象:**我们在 XML 中定义的 bean标签,Spring 解析 bean 标签成为⼀个 JavaBean, 这个JavaBean 就是 BeanDefinition

Spring IoC 源码剖析

Spring IoC 容器继承体系

![](img/Spring IoC容器继承体系.jpg)

Bean 生命周期关键时机点

Bean对象创建的⼏个关键时机点代码层级的调⽤都在 AbstractApplicationContext 类 的 refresh ⽅法中

关键点 触发代码
构造器 refresh#finishBeanFactoryInitialization(beanFactory)(beanFactory)
BeanFactoryPostProcessor 初始化 refresh#invokeBeanFactoryPostProcessors(beanFactory)
BeanFactoryPostProcessor ⽅法调⽤ refresh#invokeBeanFactoryPostProcessors(beanFactory)
BeanPostProcessor 初始化 registerBeanPostProcessors(beanFactory)
BeanPostProcessor ⽅法调⽤ refresh#finishBeanFactoryInitialization(beanFactory)

源码如下:

public void refresh() throws BeansException, IllegalStateException {
	// 对象锁加锁
	synchronized (this.startupShutdownMonitor) {
		/*
			Prepare this context for refreshing.
			刷新前的预处理
			表示在真正做refresh操作之前需要准备做的事情:
				设置Spring容器的启动时间,
				开启活跃状态,撤销关闭状态
				验证环境信息里一些必须存在的属性等
		*/
		prepareRefresh();

		/*
			Tell the subclass to refresh the internal bean factory.
			获取BeanFactory;默认实现是DefaultListableBeanFactory
			加载BeanDefition 并注册到 BeanDefitionRegistry
		*/
		ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

		/*
			Prepare the bean factory for use in this context.
			BeanFactory的预准备工作(BeanFactory进行一些设置,比如context的类加载器等)
		*/
		prepareBeanFactory(beanFactory);

		try {
            /*
            	Allows post-processing of the bean factory in context subclasses.
            	BeanFactory准备工作完成后进行的后置处理工作
            */
            postProcessBeanFactory(beanFactory);

            /*
            	Invoke factory processors registered as beans in the context.
            	实例化实现了BeanFactoryPostProcessor接口的Bean,并调用接口方法
            */
            invokeBeanFactoryPostProcessors(beanFactory);

            /*
            	Register bean processors that intercept bean creation.
            	注册BeanPostProcessor(Bean的后置处理器),在创建bean的前后等执行
            */
            registerBeanPostProcessors(beanFactory);
            
            /*
            	Initialize message source for this context.
            	初始化MessageSource组件(做国际化功能;消息绑定,消息解析);
            */
            initMessageSource();

            /*
            	Initialize event multicaster for this context.
            	初始化事件派发器
            */
            initApplicationEventMulticaster();

            /*
            	Initialize other special beans in specific context subclasses.
            	子类重写这个方法,在容器刷新的时候可以自定义逻辑;如创建Tomcat,Jetty等WEB服务器
            */
            onRefresh();

            /*
            	Check for listener beans and register them.
            	注册应用的监听器。就是注册实现了ApplicationListener接口的监听器bean
            */
            registerListeners();

            /*
            	Instantiate all remaining (non-lazy-init) singletons.
            	初始化所有剩下的非懒加载的单例bean
            	初始化创建非懒加载方式的单例Bean实例(未设置属性)
            	填充属性
            	初始化方法调用(比如调用afterPropertiesSet方法、init-method方法)
            	调用BeanPostProcessor(后置处理器)对实例bean进行后置处理
            */
            finishBeanFactoryInitialization(beanFactory);

            /*
            	Last step: publish corresponding event.
            	完成context的刷新。主要是调用LifecycleProcessor的onRefresh()方法,并且发布事件(ContextRefreshedEvent)
            */
            finishRefresh();
		}catch (BeansException ex) {
            if (logger.isWarnEnabled()) {
                logger.warn("Exception encountered during context initialization - " +
							"cancelling refresh attempt: " + ex);}
            
				// Destroy already created singletons to avoid dangling resources.
				destroyBeans();

				// Reset 'active' flag.
				cancelRefresh(ex);

				// Propagate exception to caller.
				throw ex;
			}finally {
				// Reset common introspection caches in Spring's core, since we
				// might not ever need metadata for singleton beans anymore...
				resetCommonCaches();
			}
		}
复制代码

BeanFactory 创建流程

获取 BeanFactory 子流程

Spring 核心思想笔记

BeanDefinition加载解析及注册⼦流程

  1. Resource 定位:把 XML 文件封装成 Resource 对象的过程
  2. BeanDefinition 载入:把⽤户定义好的Javabean表示为IoC容器内部的数据结构,这个容器内部的数 据结构就是BeanDefinition。
  3. 注册BeanDefinition到 IoC 容器
Spring 核心思想笔记

Bean 创建流程

注:参考IoC 循环依赖时序图

lazy-inti 延迟加载机制原理

public void preInstantiateSingletons() throws BeansException {
    if (logger.isTraceEnabled()) {
        logger.trace("Pre-instantiating singletons in " + this);
    }

	// Iterate over a copy to allow for init methods which in turn register new bean definitions.
    // While this may not be part of the regular factory bootstrap, it does otherwise work fine.
    // 所有bean的名字
    List<String> beanNames = new ArrayList<>(this.beanDefinitionNames);

    // Trigger initialization of all non-lazy singleton beans...
    // 触发所有非延迟加载单例bean的初始化,主要步骤为getBean
    for (String beanName : beanNames) {
        // 合并父BeanDefinition对象
        // map.get(beanName)
        RootBeanDefinition bd = getMergedLocalBeanDefinition(beanName);
        
        if (!bd.isAbstract() && bd.isSingleton() && !bd.isLazyInit()) {
            if (isFactoryBean(beanName)) {
                Object bean = getBean(FACTORY_BEAN_PREFIX + beanName);
                // 如果是FactoryBean则加&
                if (bean instanceof FactoryBean) {
                    final FactoryBean<?> factory = (FactoryBean<?>) bean;
                    boolean isEagerInit;
                    if (System.getSecurityManager() != null && factory instanceof SmartFactoryBean) {
                        isEagerInit = AccessController
                            .doPrivileged((PrivilegedAction<Boolean>)((SmartFactoryBean<?>) factory)::isEagerInit, getAccessControlContext());
                    }else {
                        isEagerInit = (factory instanceof SmartFactoryBean 
                                       && ((SmartFactoryBean<?>) factory).isEagerInit());
                    }
                    if (isEagerInit) {
                        getBean(beanName);
                    }
                }
            }else {
                // 实例化当前bean
                getBean(beanName);
            }
        }
    }

    // Trigger post-initialization callback for all applicable beans...
    for (String beanName : beanNames) {
        Object singletonInstance = getSingleton(beanName);
        if (singletonInstance instanceof SmartInitializingSingleton) {
            final SmartInitializingSingleton smartSingleton = (SmartInitializingSingleton) singletonInstance;
            if (System.getSecurityManager() != null) {
                AccessController.doPrivileged((PrivilegedAction<Object>) () -> {
                    smartSingleton.afterSingletonsInstantiated();
                    return null;
                }, getAccessControlContext());
            }else {
                smartSingleton.afterSingletonsInstantiated();
            }
        }
    }
}
复制代码

如源码:

  • 对于被修饰为lazy-init的bean Spring 容器初始化阶段不会进⾏ init 并且依赖注⼊,当第⼀次 进⾏getBean时候才进⾏初始化并依赖注⼊
  • 对于⾮懒加载的bean,getBean的时候会从缓存⾥头获取,因为容器初始化阶段 Bean 已经 初始化完成并缓存了起来

Spring IoC 循环依赖问题

  • 单例 bean 构造器参数循环依赖(⽆法解决)

  • prototype 原型 bean循环依赖(⽆法解决)

    对于原型bean的初始化过程中不论是通过构造器参数循环依赖还是通过setXxx⽅法产⽣循环依 赖,Spring都 会直接报错处理。

  • 单例bean通过setXxx或者@Autowired进⾏循环依赖

    Spring 的循环依赖的理论依据基于 Java 的引⽤传递,当获得对象的引⽤时,对象的属性是可以延后设置的,但是构造器必须是在获取引⽤之前

Spring 核心思想笔记

Spring AOP 应用

相关概念

  • joinPoint(连接点):方法开始时、结束时、正常运行完毕时、方法异常时等这些特殊的时机点,连接点时一种候选点
  • pointCut(切入点):指定 AOP 影响的具体方法,描述感兴趣的方法
  • Advice(通知/增强):切⾯类中⽤于提供增强功能的⽅法,也表示通知的类型,其分类有: 前置通知、后置通知、异常通知、最终通知、环绕通知
    • 横切逻辑
    • 方位点
  • Target(⽬标对象):被代理对象
  • Proxy(代理):代理对象
  • Weaving(织⼊):把增强应⽤到⽬标对象来创建新的代理对象的过程。spring采⽤动态代 理织⼊,⽽AspectJ采⽤编译期织⼊和类装载期织⼊。
  • Aspect(切⾯):增强的代码所关注的方面。[例如:TransactionManage类]
    • Aspect切⾯:切⾯概念是对上述概念的⼀个综合
    • Aspect切⾯= 切⼊点+增强 = 切⼊点(锁定⽅法) + ⽅位点(锁定⽅法中的特殊时机)+ 横切逻辑

Spring中 AOP 的代理选择

  • Spring 实现AOP 思想使用的是动态代理技术

  • 默认情况下

    如果 被代理类 实现接口 ⇒ 选择 JDK 动态代理

    否则 ⇒ 选择 CGLIB 动态代理

  • 可通过配置方式设置强制使用 CGLIB 动态代理

Spring AOP 实现

1. XML 模式

  1. 引入依赖
  2. 把 Bean 交给 Spring 管理
  3. 配置 <aop:config>
    • 配置切面 <aop:aspect>
    • 在切面中 配置 通知增强,一是方位,二是切点

需要注意的点:

  1. 切入点表达式

    指的是遵循特定语法结构的字符串,其 作⽤是⽤于对符合语法格式的连接点进⾏增强。

// 示例
// 全限定⽅法名 访问修饰符 返回值 包名.包名.包名.类名.⽅法名(参数列表)
// 全匹配⽅式:
public void com.lagou.service.impl.TransferServiceImpl.updateAccountByCardNo(com.lagou.pojo.Account) // 访问修饰符可以省略
void com.lagou.service.impl.TransferServiceImpl.updateAccountByCardNo(com.lagou.pojo.Account) 
// 返回值可以使⽤*,表示任意返回值
* com.lagou.service.impl.TransferServiceImpl.updateAccountByCardNo(com.lagou.pojo.Account)
// 包名可以使⽤.表示任意包,但是有⼏级包,必须写⼏个
* ....TransferServiceImpl.updateAccountByCardNo(com.lagou.pojo.Account)
// 包名可以使⽤..表示当前包及其⼦包
* ..TransferServiceImpl.updateAccountByCardNo(com.lagou.pojo.Account)
// 类名和⽅法名,都可以使⽤.表示任意类,任意⽅法
* ...(com.lagou.pojo.Account)
// 参数列表,可以使⽤具体类型 基本类型直接写类型名称 : int
// 引⽤类型必须写全限定类名:java.lang.String
// 参数列表可以使⽤*,表示任意参数类型,但是必须有参数
* *..*.*(*)
// 参数列表可以使⽤..,表示有⽆参数均可。有参数可以是任意类型
* *..*.*(..)
// 全通配⽅式:
* *..*.*(..)
复制代码
  1. 改变代理方式的配置

    • 使用<aop:config> 标签配置

      <aop:config proxy-target-class="true">
      复制代码
    • 使⽤<aop:aspectj-autoproxy>标签配置

      <!--此标签是基于XML和注解组合配置AOP时的必备标签,表示Spring开启注解配置AOP 的⽀持-->
      <aop:aspectj-autoproxy proxy-target-class="true"></aop:aspectjautoproxy>
      复制代码
  2. 五种通知类型

    1. 前置通知:切入点方法执行前执行

      <aop:before>

    2. 后置通知:切入点方法正常执行后执行

      <aop:after-returning>

    3. 异常通知:切⼊点⽅法执⾏产⽣异常之后执⾏

      <aop:after-throwing>

    4. 最终通知:切⼊点⽅法执⾏完成之后,切⼊点⽅法返回之前执⾏(无论是否异常都会执行,finally)

      <aop:after>

    5. 环绕通知:较特殊,借助的ProceedingJoinPoint接⼝及其实现类, 实现⼿动触发切⼊点⽅法的调⽤

2. XML + 注解模式

  1. XML 中开启 Spring 对注解 AOP 的⽀持

    <!--开启spring对注解aop的⽀持-->
    <aop:aspectj-autoproxy/>
    复制代码
  2. 示例

    /**
    * 模拟记录⽇志
    */
    @Component
    @Aspect
    public class LogUtil {
        /**
        * 我们在xml中已经使⽤了通⽤切⼊点表达式,供多个切⾯使⽤,那么在注解中如何使⽤呢?* 第⼀步:编写⼀个⽅法
        * 第⼆步:在⽅法使⽤@Pointcut注解
        * 第三步:给注解的value属性提供切⼊点表达式
        * 细节:
        *	1.在引⽤切⼊点表达式时,必须是⽅法名+(),例如"pointcut()"。
        *	2.在当前切⾯中使⽤,可以直接写⽅法名。在其他切⾯中使⽤必须是全限定⽅法名。
        */
        @Pointcut("execution(* com.lagou.service.impl.*.*(..))")
        public void pointcut(){}
        
        @Before("pointcut()")
        public void beforePrintLog(JoinPoint jp){
            Object[] args = jp.getArgs();
            System.out.println("前置通知:beforePrintLog,参数是:"+Arrays.toString(args));
        }
        
        @AfterReturning(value = "pointcut()",returning = "rtValue")
        public void afterReturningPrintLog(Object rtValue){
            System.out.println("后置通知:afterReturningPrintLog,返回值是:"+rtValue);
        }
    
        @AfterThrowing(value = "pointcut()",throwing = "e")
        public void afterThrowingPrintLog(Throwable e){
            System.out.println("异常通知:afterThrowingPrintLog,异常是:"+e);
        }
    
        @After("pointcut()")
        public void afterPrintLog(){
            System.out.println("最终通知:afterPrintLog");
        }
    
        /**
        * 环绕通知
        * @param pjp
        * @return
        */
        @Around("pointcut()")
        public Object aroundPrintLog(ProceedingJoinPoint pjp){
            //定义返回值
            Object rtValue = null;
            try{
                //前置通知
                System.out.println("前置通知");
                //1.获取参数
                Object[] args = pjp.getArgs();
                //2.执⾏切⼊点⽅法
                rtValue = pjp.proceed(args);
                //后置通知
                System.out.println("后置通知");
            }catch (Throwable t){
                //异常通知
                System.out.println("异常通知");
                t.printStackTrace();
            }finally {
                //最终通知
                System.out.println("最终通知");
            }
            return rtValue;
        }
    }
    复制代码

3. 注解模式

  1. 在配置类上加 @EnableAspectJAutoProxy 开启spring对注解AOP的⽀持

Spring 声明式事务的⽀持

**编程式事务:**在业务代码中添加事务控制代码,这样的事务控制机制就叫做编程式事务
**声明式事务:**通过xml或者注解配置的⽅式达到事务控制的⽬的,叫做声明式事务

事务四大特性

  1. **原⼦性(Atomicity):**原⼦性是指事务是⼀个不可分割的⼯作单位,事务中的操作要么都发⽣,要么都不发⽣。

    是从 操作 的角度描述的

  2. ⼀致性(Consistency):事务必须使数据库从⼀个⼀致性状态变换到另外⼀个⼀致性状态。

    数据 的角度描述

  3. **隔离性(Isolation):**事务的隔离性是多个⽤户并发访问数据库时,数据库为每⼀个⽤户开启的事务, 每个事务不能被其他事务的操作数据所⼲扰,多个并发事务之间要相互隔离。

  4. **持久性(Durability):**持久性是指⼀个事务⼀旦被提交,它对数据库中数据的改变就是永久性的,接下来即使数据库发⽣故障 也不应该对其有任何影响。

事务隔离级别

问题:

  • 脏读:⼀个线程中的事务读到了另外⼀个线程中 未提交 的数据。
  • 不可重复读:⼀个线程中的事务读到了另外⼀个线程中 已经提交update 的数据(前后内容不⼀样)
  • 虚读(幻读):⼀个线程中的事务读到了另外⼀个线程中 已经提交 的insert或者delete的数据(前后条数不⼀样)

数据库四种隔离级别:

  • Serializable(串⾏化):可避免脏读、不可重复读、虚读情况的发⽣。(串⾏化) 最⾼

  • Repeatable read(可重复读):可避免脏读、不可重复读情况的发⽣。(幻读有可能发⽣) 第⼆

    该机制下会对要update的⾏进⾏加锁

  • Read committed(读已提交):可避免脏读情况发⽣。不可重复读和幻读⼀定会发⽣。 第三

  • Read uncommitted(读未提交):最低级别,以上情况均⽆法保证。(读未提交) 最低

注意:级别依次升⾼,效率依次降低

MySQL的默认隔离级别是:REPEATABLE READ

事务传播行为

常用前两条(加粗)

PROPAGATION_REQUIRED 如果当前没有事务,就新建⼀个事务,如果已经存在⼀个事务中, 加⼊到这个事务中。这是最常⻅的选择。
PROPAGATION_SUPPORTS ⽀持当前事务,如果当前没有事务,就以⾮事务⽅式执⾏。
PROPAGATION_MANDATORY 使⽤当前的事务,如果当前没有事务,就抛出异常。
PROPAGATION_REQUIRES_NEW 新建事务,如果当前存在事务,把当前事务挂起。
PROPAGATION_NOT_SUPPORTED 以⾮事务⽅式执⾏操作,如果当前存在事务,就把当前事务挂起。
PROPAGATION_NEVER 以⾮事务⽅式执⾏,如果当前存在事务,则抛出异常。
PROPAGATION_NESTED 如果当前存在事务,则在嵌套事务内执⾏。如果当前没有事务,则 执⾏与PROPAGATION_REQUIRED类似的操作。

Spring 声明式事务配置

注:与AOP配置类似,配置<tx:advice>

开启spring对注解事务的⽀持

<tx:annotation-driven transactionmanager="transactionManager"/>@EnableTransactionManagement

Spring AOP 源码剖析

AOP源码分析类⽅法调⽤关系

org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#initializeBean
调⽤
org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#applyBeanPostProcessorsAfterInitialization
调⽤
org.springframework.aop.framework.autoproxy.AbstractAutoProxyCreator#postProcessAfterInitialization(后置处理器AbstractAutoProxyCreator完成bean代理对象创建)
调⽤
org.springframework.aop.framework.autoproxy.AbstractAutoProxyCreator#wrapIfNecessary
调⽤
org.springframework.aop.framework.autoproxy.AbstractAutoProxyCreator#createProxy(在这⼀步把委托对象的aop增强和通⽤拦截进⾏合并,最终给代理对象)
调⽤
org.springframework.aop.framework.DefaultAopProxyFactory#createAopProxy
调⽤
org.springframework.aop.framework.CglibAopProxy#getProxy(java.lang.ClassLoader )
复制代码

Spring声明式事务控制

@EnableTransactionManagement 注解
1)通过@import引⼊了TransactionManagementConfigurationSelector类
    它的selectImports⽅法导⼊了另外两个类:AutoProxyRegistrar和ProxyTransactionManagementConfiguration 
2)AutoProxyRegistrar类分析
    ⽅法registerBeanDefinitions中,引⼊了其他类,通过AopConfigUtils.registerAutoProxyCreatorIfNecessary(registry)引⼊
InfrastructureAdvisorAutoProxyCreator,它继承了AbstractAutoProxyCreator,是⼀个后置处理器类
3)ProxyTransactionManagementConfiguration 是⼀个添加了@Configuration注解的配置类 (注册bean)
    注册事务增强器(注⼊属性解析器、事务拦截器)
    属性解析器:AnnotationTransactionAttributeSource,内部持有了⼀个解析器集合Set<TransactionAnnotationParser> annotationParsers;具体使⽤的是SpringTransactionAnnotationParser解析器,⽤来解析@Transactional的事务属性
    事务拦截器:TransactionInterceptor实现了MethodInterceptor接⼝,该通⽤拦截会在产⽣代理对象之前和aop增强合并,最终⼀起影响到代理对象
        TransactionInterceptor的invoke⽅法中invokeWithinTransaction会触发原有业
务逻辑调⽤(增强事务)
复制代码
原文  https://juejin.im/post/5f055cd06fb9a07ebb224a0a
正文到此结束
Loading...