《Spring源码深度解析》读书笔记

2021-02-10 fishedee 后端

0 概述

在以前看了Spring实战第四版以后,最近再次看了《Spring Boot实战》,觉得Spring很神奇,怎么加个注解能实现这么多功能。

最后翻到了这本好书,《Spring源码深度解析(第2版)》,算是彻底解开了这部分的谜题了。

1 XmlBeanFactory

1.1 功能

XmlBeanFactory的基础功能是,读取xml构造bean容器,然后可以随意在容器中获取bean。它的主要功能有:

  • 多构造器构造bean,自定义bean的构造参数
  • 多scope构造支持(单例构造,原型构造,session和请求级别的构造),factory方法构造,factory类构造,懒加载构造,dependsOn依赖构造
  • 构造后的init-method回调,销毁时的destory-method回调
  • 多种方式的依赖注入,构造器注入,setter注入。
  • 支持循环依赖,这点确实很屌!
  • 支持以不同beanId(即使同类型)来构造bean,或者仅支持类型传入来构造bean。
  • 支持丰富的插件机制,beanPostProcessor,各种aware,各种生命周期回调
  • 支持属性注解解析,属性表达式求解,属性编辑器(字符串转目标类型)的插件支持。
  • 支持多线程

1.2 例子

1.2.1 xml描述bean

代码在这里

<?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="service1" class="spring_test.ServiceA">
            <constructor-arg index="0">
                <value>fish</value>
            </constructor-arg>
    </bean>
    <bean id="service2" class="spring_test.ServiceA">
        <constructor-arg index="0">
            <list>
                <value>cat</value>
                <value>dog</value>
            </list>
        </constructor-arg>
    </bean>
    <!--不能直接通过私有变量来注入-->
    <bean id="service3" class="spring_test.ServiceA">
        <property name="gg" value="kk">
        </property>
    </bean>
    <!--setter注入-->
    <bean id="service4" class="spring_test.ServiceB">
        <property name="animal">
            <value>shell</value>
        </property>
    </bean>
    <!--factory模式-->
    <bean id="service5" class="spring_test.ServiceCFactory">
        <property name="animal">
            <value>Weasel</value>
        </property>
    </bean>
</beans>

完全以xml的方式来描述bean,支持构造器和setter来注入依赖。注意,可以指定bean的工厂来生成bean。但是注意不能以私有变量的方式注入依赖。

package spring_test;

import org.springframework.beans.factory.BeanFactory ;
import org.springframework.beans.factory.xml.XmlBeanFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.core.io.ClassPathResource;

/**
 * Hello world!
 *
 */
public class App 
{
    public static void main( String[] args )
    {

        BeanFactory beanFactory = new XmlBeanFactory(new ClassPathResource("beanFactory.xml"));

        //通过id来获取bean
        ServiceA service1 = (ServiceA)beanFactory.getBean("service1");
        service1.showMessage();

        ServiceA service2 = (ServiceA)beanFactory.getBean("service2");
        service2.showMessage();

        //<!--不能直接通过私有变量来注入-->
        //ServiceA service3 = (ServiceA)beanFactory.getBean("service3");
        //service3.showMessage();

        //通过类型来获取bean
        ServiceB service4 = (ServiceB)beanFactory.getBean(ServiceB.class);
        service4.showAnimal();

        //获取factory的bean
        //IServiceC service5 = (IServiceC) beanFactory.getBean(IServiceC.class);
        IServiceC service5 = (IServiceC) beanFactory.getBean("service5");
        service5.swim();
    }
}

支持以id或者类型的方式来获取bean,非常方便。

package spring_test;

import org.springframework.beans.factory.FactoryBean;

/**
 * Created by fish on 2021/2/20.
 */
public class ServiceCFactory implements FactoryBean<ServiceC> {
    private String animal;

    public void setAnimal(String animal){
        this.animal = animal;
    }

    public ServiceC getObject(){
        System.out.println("Factory getBean serviceC");
        return new ServiceC(this.animal);
    }

    public Class<ServiceC> getObjectType(){
        return ServiceC.class;
    }

    public boolean isSingleton(){
        return true;
    }

}

特别注意的是,当bean的类型实现了FactoryBean接口的时候,Spring不是创建这个ServiceCFactory类型,而是创建getObject方法返回的实例作为bean。

1.2.2 循环依赖

代码在这里

<?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="serviceA" class="spring_test.ServiceA">
        <property name="serviceB" ref="serviceB"></property>
    </bean>
    <bean id="serviceB" class="spring_test.ServiceB">
        <property name="serviceC" ref="serviceC">
        </property>
    </bean>
    <bean id="serviceC" class="spring_test.ServiceC">
        <property name="serviceA">
            <ref bean="serviceA"></ref>
        </property>
    </bean>
</beans>

定义三个服务,这三个服务互相引用对方,注意,只能是属性注入的方式。如果是构造器注入依赖的方式,循环依赖会失败。

package spring_test;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

/**
 * Created by fish on 2021/2/19.
 */
public class ServiceA {
    private ServiceB serviceB;

    public void setServiceB(ServiceB serviceB){
        this.serviceB = serviceB;
    }
    public ServiceB getServiceB(){
        return this.serviceB;
    }
}

ServiceA代码

package spring_test;

/**
 * Created by fish on 2021/2/19.
 */
public class ServiceB {
    private ServiceC serviceC;

    public void setServiceC(ServiceC serviceC){
        this.serviceC = serviceC;
    }

    public ServiceC getServiceC(){
        return this.serviceC;
    }
}

ServiceB代码

package spring_test;

/**
 * Created by fish on 2021/2/20.
 */
public class ServiceC {
    private ServiceA serviceA;

    public void setServiceA(ServiceA serviceA){
        this.serviceA = serviceA;
    }

    public ServiceA getServiceA(){
        return this.serviceA;
    }
}

ServiceC代码

package spring_test;

import org.springframework.beans.factory.BeanFactory ;
import org.springframework.beans.factory.xml.XmlBeanFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.core.io.ClassPathResource;
import org.springframework.util.Assert;

/**
 * Hello world!
 *
 */
public class App 
{
    public static void main( String[] args )
    {

        BeanFactory beanFactory = new XmlBeanFactory(new ClassPathResource("beanFactory.xml"));

        //获取bean
        ServiceA serviceA = (ServiceA)beanFactory.getBean(ServiceA.class);
        ServiceB serviceB = (ServiceB)beanFactory.getBean(ServiceB.class);
        ServiceC serviceC = (ServiceC)beanFactory.getBean(ServiceC.class);

        //检查对应的ref
        System.out.println(serviceA.getServiceB()==serviceB);
        System.out.println(serviceB.getServiceC()==serviceC);
        System.out.println(serviceC.getServiceA()==serviceA);
    }
}

获取bean,并且检查它们互相的引用都是正确的

1.2.3 扩展机制

代码在这里

package spring_test;

import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanClassLoaderAware;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.BeanNameAware;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

/**
 * Created by fish on 2021/2/19.
 */
public class ServiceA implements BeanNameAware,BeanClassLoaderAware,BeanFactoryAware{
    private String animal;

    public void setAnimal(String animal){
        this.animal = animal;
    }

    //BeanNameAware接口
    private String beanName;
    public void setBeanName(String var1){
        this.beanName = var1;
    }

    //BeanClassLoaderAware接口
    private ClassLoader classLoader;
    public void setBeanClassLoader(ClassLoader var1){
        this.classLoader = var1;
    }

    //BeanFactoryAware接口
    private BeanFactory beanFactory;
    public void setBeanFactory(BeanFactory var1) throws BeansException{
        this.beanFactory = var1;
    }

    public void showAnimal(){
        System.out.println("beanFactory:"+this.beanFactory);
        System.out.println("classLoader:"+this.classLoader);
        System.out.println("beanName:"+this.beanName);
        System.out.println("animal:"+this.animal);
    }
}

Spring可以指定各个Aware接口,在创建bean以后会检查是否满足Aware接口,满足的话回调数据给他。Aware扩展提供了bean获取bean容器自身相关信息的工具。

package spring_test;

import org.springframework.beans.factory.InitializingBean;

/**
 * Created by fish on 2021/2/19.
 */
public class ServiceB implements InitializingBean{
    public ServiceB(){
        System.out.println("serviceB constructor");
    }

    private String message;
    public void setMessage(String message){
        System.out.println("serviceB property inject");
        this.message = message;
    }

    //InitializingBean接口
    public void afterPropertiesSet() throws Exception{
        System.out.println("serviceB afterPropertiesSet");
    }

    //在xml指定的initMethod
    public void myInitMethod(){
        System.out.println("serviceB myInit Method");
    }

    public void showMessage(){
        System.out.println("serviceB showMessage:"+this.message);
    }
}
<bean id="serviceB" class="spring_test.ServiceB" init-method="myInitMethod">
    <property name="message"><value>fish</value></property>
</bean>

定义一个ServiceB,实现InitializingBean接口,并且在xml中指定了init-method。那么,Spring的调用顺序是:

  • serviceB constructor
  • serviceB property inject
  • serviceB afterPropertiesSet
  • serviceB myInit Method

InitializingBean扩展提供了bean获取初始化完成回调的通知

package spring_test;

import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.lang.Nullable;

/**
 * Created by fish on 2021/2/20.
 */
public class MyBeanPostProcessor implements BeanPostProcessor {
    @Nullable
    public  Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("before init:"+beanName);
        return bean;
    }

    @Nullable
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("after init:"+beanName);
        //对于符合条件的bean,我们可以返回新的bean给他
        //注意,原始的bean,只能以参数的方式传入
        if( beanName.equals("serviceD")){
            return new ServiceCMock((ServiceC)bean);
        }
        return bean;
    }
}
package spring_test;

import org.springframework.beans.factory.BeanFactory ;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.beans.factory.xml.XmlBeanFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.core.io.ClassPathResource;
import org.springframework.util.Assert;

/**
 * Hello world!
 *
 */
public class App 
{
    public static void main( String[] args )
    {

        DefaultListableBeanFactory beanFactory = new XmlBeanFactory(new ClassPathResource("beanFactory.xml"));

        //手动添加BeanPostProcessor扩展
        MyBeanPostProcessor myBeanPostProcessor = beanFactory.getBean(MyBeanPostProcessor.class);
        beanFactory.addBeanPostProcessor(myBeanPostProcessor);

        //serviceC没有被mock掉
        ServiceC serviceC = (ServiceC)beanFactory.getBean("serviceC");
        serviceC.work();

        //serviceD被mock掉了,mock的逻辑在MyBeanPostProcessor里面
        ServiceC serviceD = (ServiceC)beanFactory.getBean("serviceD");
        serviceD.work();
    }
}

BeanPostProcessor是最为重要的扩展,它提供了通知第三方bean初始化完成前,初始化完成后的通知。这种通知并且给与了BeanPostProcessor修改bean实例的机会,甚至提供了修改bean指向的能力。

1.3 原理

1.3.1 循环依赖

具体原理看这里。它的主要思路是,建立三级缓存:

  • singletonObjects缓存,已经完整建立bean以后的缓存
  • earlySingletonObjects缓存,已经执行构造器创建bean,但是仍没有进行属性注入和初始化的缓存
  • singletonsCurrentlyInCreation缓存,在构造器开始前添加缓存,构造器完成,并且注入属性依赖后删除缓存。
T doGenBean(final String name){//P86
    //查找singletonObjects缓存,或者earlySingletonObjects缓存有没有该bean
    Object sharedInstance = getSingleton(beanName);
    if( sharedInstance != null ){
        //有的话直接返回
        return sharedInstance
    }else{
        ...
        sharedInstance = getSingleton(beanName,new ObjectFactory<Object>{
            public Object getObject() throws BeansException{
                try{
                    //创建bean本身,这一行最终会调用到doCreateBean
                    return createBean(beanName,mbd,args);
                }
            }
        });
    }
}

我们执行getBean的时候,先尝试在singletonObjects缓存,或者earlySingletonObjects缓存查看一下有没有这个对象,有的话就返回

//P98,当没有缓存的时候,尝试创建对象
public Object getSingleton(String beanName, ObjectFactory singletonFactory){
    synchronized(this.singletonObjects){
        Object singletonObject = this.singletonObjects.get(beanName);

        //添加到singletonsCurrentlyInCreation缓存
        beforeSingletonCreation(beanName);
        ...

        //创建对象本身
        singletonObject = singletonFactory.getObject();

        //删除到singletonsCurrentlyInCreation缓存
        afterSingletonCreation(beanName);

        //添加到singletonObjects缓存
        addSingleton(beanName,singletonObject);
    }
}

public void beforeSingletonCreation(String beanName){
    //检查是否在循环构造中
    if( !this.singletonsCurrentlyInCreation.add(beanName)){
        throw new BeanCurrentlyInCreationException(beanName);
    }
}

public void afterSingletonCreation(String beanName){
    //检查是否在循环依赖中
    if( !this.singletonsCurrentlyInCreation.remove(beanName)){
        throw new IllegalStateException('....');
    }
}

没有缓存的时候,使用getSingleton来创建bean本身。注意,这里用了singletonsCurrentlyInCreation来检查循环的构造依赖。完成所有bean创建以后,会添加singletonObjects缓存。

Object doCreateBean(String beanName){//P108
    ...
    bean = createBeanInstance(....);//108,调用构造器创建bean
    ...
    addSingletonFactory(beanName,new ObjectFactory(){})//P109,提供添加到earlySingletonObjects缓存的工厂方法
    ...
    poplulateBean(beanName,mbd,bean)//P109,属性依赖注入
    ...
    initializeBean(beanName,...)//P109,调用aware扩展,InitializingBean扩展,和BeanPostProcessor扩展
    ...
    return bean;
}

ObjectFactory最终会执行getObject来创建bean,而getObject会调用createBean,createBean会调用doCreateBean。doCreateBean会先用构造创建bean,然后就马上添加到earlySingletonObjects缓存。

这个就是创建bean的关键流程了,我们试一下分析具体的例子

1.3.1.1 普通流程

创建A的bean,并且A的构造器依赖于B。B的构造器是空的。

-> 检查singletonObjects和earlySingletonObjects是否有A的bean,发现没有,需要创建A的bean。
-> singletonsCurrentlyInCreation缓存添加A
-> new A()
    ->检查singletonObjects和earlySingletonObjects是否有B的bean,发现没有,需要创建B的bean。
    ->singletonsCurrentlyInCreation缓存添加B
    ->new B()
    ->earlySingletonObjects缓存添加B
    ->singletonsCurrentlyInCreation缓存删除B
    ->singletonObjects缓存添加B
->earlySingletonObjects缓存添加A
->singletonsCurrentlyInCreation缓存删除A
->singletonObjects缓存添加A

这是我们尝试构造一个的过程。

1.3.1.2 构造器循环依赖

创建A的bean,并且A的构造器依赖于B。B的构造器是也是依赖于A的。

-> 检查singletonObjects和earlySingletonObjects是否有A的bean,发现没有,需要创建A的bean。
-> singletonsCurrentlyInCreation缓存添加A
-> new A()
    ->检查singletonObjects和earlySingletonObjects是否有B的bean,发现没有,需要创建B的bean。
    ->singletonsCurrentlyInCreation缓存添加B
    ->new B()
        ->singletonsCurrentlyInCreation缓存添加A,失败,这个时候报错,因为singletonsCurrentlyInCreation缓存已经有A了,有循环构造依赖的问题

这就是Spring中构造器循环依赖失败的过程。

1.3.1.3 属性循环依赖

创建A的bean,并且A的属性注入依赖于B。B的属性注入也是依赖于A的。

-> 检查singletonObjects和earlySingletonObjects是否有A的bean,发现没有,需要创建A的bean。
-> singletonsCurrentlyInCreation缓存添加A
-> new A(),空构造器
-> earlySingletonObjects缓存添加A
    ->检查singletonObjects和earlySingletonObjects是否有B的bean,发现没有,需要创建B的bean。
    ->singletonsCurrentlyInCreation缓存添加B
    ->new B()
    ->earlySingletonObjects缓存添加B
        ->检查singletonObjects和earlySingletonObjects是否有A的bean,发现在earlySingletonObjects已经有了,直接返回A的bean。
    ->singletonsCurrentlyInCreation缓存删除B
    ->singletonObjects缓存添加B
->singletonsCurrentlyInCreation缓存删除A
->singletonObjects缓存添加A

由于构造器执行之后,就会马上添加earlySingletonObjects缓存,所以,B的bean在属性注入的时候可以复用这个只创建,但是仍没有执行属性依赖的A的bean。因此,Spring实现了属性循环依赖

1.3.2 属性流程

//P143 设置@Qualifer和@Autowired的自动注入解析器
beanFactory.setAutowireCandicateResolver(new QulifierAnnotationAutowireCandidateResolver);

//P145 设置属性SpEL表达式处理器,运行时解析字符串为#{xxx}形式时的值,来注入属性
beanFactory.setExpressionResolver(new StandardBeanExpressionResolver);

//P145 设置属性编辑器,设置字符串到属性目标类型的转换,来注入属性
beanFactory.addPropertyEditorRegistrar(new ResourceEditorRegistrar(this,getEnvironment()));

在populateBean中主要实现的上面的三个方法。

//P123
protected void populdateBean(String beanName,AbstractBeanDefinition mbd,BeanWrapper bw){
    ...
    if( mbd.getResolvedAutowireMode() == RootBeanDefinition.AUTOWIRE_BY_NAME ){
        autowireByName(beanName,mbd,bw,pvs)
    }
    if( mbd.getResolvedAutowireMode() == RootBeanDefinition.AUTOWIRE_BY_TYPEs ){
        autowireByType(beanName,mbd,bw,pvs)
    }
    ...
    applyPropertyValues(beanName,mbd,bw,pvs);
};

//P126
protected void autowireByTye(String beanName,AbstractBeanDefinition mbd,BeanWrapper bw, MutablePropertyValues pvs){
    ...
    for( String propertyName: propertyNames ){
        ...
        Object autowireArgument = resolveDependency(...);

        pvs.add(propertyName,autowireArgument);
        ...
    }
}

//P128
protected void doResolveDependency(...){
    //在这里使用了beanFactory的setAutowireCandicateResolver的值,用了自动注入注解的解析
    Object value = getAutowireCandidateResolver().getSuggestedValue(descripteor);
    if( value != null ){
        if( value instanceof String){
            //读取配置文件中的值
            String strVal = resolveEmbeddedValue((String)value);
        }
        ...
    }
    ...
}

//P131
protected void applyPropertyValues(String beanName, BeanDefinition mbd , BeanWrapper bw, PropertyValue pvs){
    ...
    List<PropertyValue> original = pvs.getPropertyValueList();
    ...
    BeanDefinitionValueResolver valueResolver = new BeanDefinitionValueResolver(this,beanName,mbd,converter);
    ...
    for( PropertyValue pv:original){
        ...
        //在这里使用了expressionResolver来处理SpEL表达式处理器
        Object resolvedValue = valueResolver.resoveValueIfNecessary(pv,originalValue);
        ...
    }

    ...
    //BeanWrapper内部使用属性编辑器来做字符串到目标类型的转换
    bw.setPropertyValues(...);
}

以上就是概要的属性注入流程了

2 ClassPathXmlApplicationContext

2.1 功能

ClassPathXmlApplicationContext相对于XmlBeanFactory提供了的功能是:

  • 自动添加BeanFactoryPostProcessor和BeanPostProcessor
  • 预先加载bean,这样就能避免首次使用时才加载的延迟时间,但是会增加初始化启动程序的时间
  • 消息广播器,国际化支持

2.2 例子

2.2.1 BeanFactoryPostProcessor和其他aware

代码在这里

package spring_test;

import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.EmbeddedValueResolverAware;
import org.springframework.context.EnvironmentAware;
import org.springframework.core.env.Environment;
import org.springframework.util.StringValueResolver;

public class ServiceB implements EnvironmentAware,EmbeddedValueResolverAware, ApplicationContextAware{
    public ServiceB(){
        System.out.println("serviceB constructor");
    }

    //获取,环境变量
    private Environment environment;
    public void setEnvironment(Environment var1){
        this.environment = var1;
    }

    //获取,配置文件的字符串解析器
    private StringValueResolver stringValueResolver;
    public void setEmbeddedValueResolver(StringValueResolver var1){
        this.stringValueResolver = var1;
    }

    //获取,应用程序上下文
    private ApplicationContext applicationContext;
    public void setApplicationContext(ApplicationContext var1) throws BeansException{
        this.applicationContext = var1;
    }

    public void show(){
        System.out.println("environment:"+this.environment);
        System.out.println("stringValueResolver:"+this.stringValueResolver);
        System.out.println("applicationContext:"+this.applicationContext);
    }
}

加入了新的aware机制

package spring_test;

import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;

/**
 * Created by fish on 2021/2/24.
 */
public class MyBeanFactoryPostProcessor implements BeanFactoryPostProcessor {
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException{
        System.out.println("beanFactoryPostProcesssor postProcessBeanFactory :"+beanFactory);
    }
}

加了新的扩展机制,BeanFactoryPostProcessor,它会在ApplicationContext在执行前回调。一般给予了第三方注册自己的bean的机会。

package spring_test;

import org.springframework.beans.factory.BeanFactory ;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.beans.factory.xml.XmlBeanFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.core.io.ClassPathResource;
import org.springframework.util.Assert;

/**
 * Hello world!
 *
 */
public class App 
{
    public static void main( String[] args )
    {
        System.out.println("before init application context ");
        ApplicationContext bf = new ClassPathXmlApplicationContext("beanFactory.xml");

        System.out.println("after init application context");

        ServiceA serviceA = (ServiceA) bf.getBean("serviceA");
        serviceA.showAnimal();

        ServiceB serviceB = (ServiceB) bf.getBean("serviceB");
        serviceB.show();
    }
}

值得注意的是,ClassPathXmlApplicationContext不再需要手动注册BeanPostProcessor和BeanFactoryPostProcessor。它会在启动的时候自动寻找所有实现了BeanPostProcessor和BeanFactoryPostProcessor的bean,并把它们注册到ApplicationContext里面

2.2.2 消息广播器

代码在这里

package spring_test;

import org.springframework.context.ApplicationEvent;

/**
 * Created by fish on 2021/2/25.
 */
public class MyEvent extends ApplicationEvent {
    private String msg ;

    public MyEvent(Object source,String msg){
        super(source);
        this.msg = msg;
    }

    public void print(){
        System.out.println(this.msg);
    }
}

先定义一个消息类型MyEvent

package spring_test;

import org.springframework.context.ApplicationEvent;

/**
 * Created by fish on 2021/2/25.
 */
public class MyEvent2 extends ApplicationEvent {
    private String msg ;

    public MyEvent2(Object source,String msg){
        super(source);
        this.msg = msg;
    }

    public void print(){
        System.out.println(this.msg);
    }
}

再定义另外一个消息类型MyEvent2

package spring_test;

import org.springframework.context.ApplicationListener;

/**
 * Created by fish on 2021/2/19.
 */
public class MyListener implements ApplicationListener<MyEvent>{
    public void onApplicationEvent(MyEvent var1){
        System.out.println("receive MyEvent: "+var1);
    }
}

注册一个MyListener,只响应MyEvent的消息

package spring_test;

import org.springframework.beans.BeansException;
import org.springframework.context.*;
import org.springframework.core.env.Environment;
import org.springframework.util.StringValueResolver;

public class ServiceB implements ApplicationEventPublisherAware{
    public ServiceB(){
        System.out.println("serviceB constructor");
    }

    private ApplicationEventPublisher applicationEventPublisher;
    public void setApplicationEventPublisher(ApplicationEventPublisher var1){
        this.applicationEventPublisher = var1;
    }

    public void sendMsg(){
        this.applicationEventPublisher.publishEvent(new MyEvent("hello","msg"));
    }

    public void sendMsg2(){
        this.applicationEventPublisher.publishEvent(new MyEvent2("hello2","msg2"));
    }
}

对于任意一个bean,如果它想发布消息,只需要实现ApplicationEventPublisherAware接口就可以了,取出publisher来直接发布消息。

<?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="serviceB" class="spring_test.ServiceB"></bean>
    <bean class="spring_test.MyListener"></bean>
</beans>

xml中的定义也不需要对消息倾听器和发布器进行区别对待,把它们都放进bean容器就可以了。

package spring_test;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

/**
 * Hello world!
 *
 */
public class App 
{
    public static void main( String[] args )
    {
        ApplicationContext bf = new ClassPathXmlApplicationContext("beanFactory.xml");

        ServiceB serviceB = (ServiceB) bf.getBean("serviceB");

        //这个消息,MyListener是接收的
        serviceB.sendMsg();

        //这个消息,MyListener是不接收的,因为类型不匹配
        serviceB.sendMsg2();
    }
}

最后是启动程序

2.3 原理

public void refresh(){

    ...

    //对beanFactory进行功能填充
    prepareBeanFactory();

    ...

    //手动查找BeanFactoryPostProcessor,并调用它们。
    invokeBeanFactoryPostProcessor();

    //手动查找BeanPostProcessor,并注册它们
    registerBeanPostProcessors();

    //初始化注册国际化的消息源
    initMessageSource();

    //初始化注册应用消息广播器
    initApplicationEventMulticaster();

    ...

    //手动查找消息倾听器,并注册它们
    registerListerns();

    //初始化剩下的单实例,预热bean
    finishBeanFactoryInitializtion();
}

在默认的ApplicationContext的构造函数中,会自动调用refresh函数来执行固定化的初始化流程。

3 aop

3.1 功能

Spring的Aop提供了面向切面编程,以aspectJ的方式指定切面,并提供了外部接口,让第三方以注解的方式实现切面。

3.2 例子

3.2.1 AspectJ的AOP使用

代码在这里

<?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:aop="http://www.springframework.org/schema/aop"
       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/aop
       http://www.springframework.org/schema/aop/spring-aop-3.0.xsd
       http://www.springframework.org/schema/context
       http://www.springframework.org/schema/context/spring-context-3.0.xsd">
    <aop:aspectj-autoproxy/>
    <bean id="serviceA" class="spring_test.ServiceA">
        <property name="animal">
            <value>dog</value>
        </property>
    </bean>
    <bean class="spring_test.MyAspect"></bean>
</beans>

在xml上,首先引入aspectj-autoproxy标签,它实际上是注册了一个BeanPostProcessor。然后直接将某个Aspect切面加入到bean工厂就可以了。这样,AspectJ的AOP指定就实现了。

package spring_test;


import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;

/**
 * Created by fish on 2021/2/27.
 */
@Aspect
public class MyAspect {
    @Pointcut("execution( * *.showAnimal(..))")
    public void test(){

    }

    @Before("test()")
    public void beforeShowAnimal(){
        System.out.println("before test...");
    }

    @After("test()")
    public void afterShowAnimal(){
        System.out.println("after test...");
    }

    @Around("test()")
    public void aroundShowAnimal(ProceedingJoinPoint p) throws Throwable{
        System.out.println("before around");
        p.proceed();
        System.out.println("after around");
    }

}

对于一个POJO类,加Aspect注解就能指定了它为AspectJ类了。然后用Pointcut来定义对什么接口进行切面,用@Before,@After和@Around来定义这些接口编程是什么

package spring_test;

/**
 * Created by fish on 2021/2/19.
 */
public class ServiceA {
    private String animal;

    public void setAnimal(String animal){
        this.animal = animal;
    }

    public void showAnimal(){
        System.out.println("animal:"+this.animal);
    }
}

一个普通的ServiceA类

package spring_test;

import org.springframework.beans.factory.BeanFactory ;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.beans.factory.xml.XmlBeanFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.core.io.ClassPathResource;
import org.springframework.util.Assert;

/**
 * Hello world!
 *
 */
public class App 
{
    public static void main( String[] args )
    {
        ApplicationContext bf = new ClassPathXmlApplicationContext("beanFactory.xml");

        ServiceA serviceA = (ServiceA) bf.getBean("serviceA");
        serviceA.showAnimal();
    }
}

最后,启动程序即可。

before around
before test...
animal:dog
after around
after test...

这个是程序输出的结果

3.2.2 Proxy和Cglib的实现

代码在这里

<?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:aop="http://www.springframework.org/schema/aop"
       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/aop
       http://www.springframework.org/schema/aop/spring-aop-3.0.xsd
       http://www.springframework.org/schema/context
       http://www.springframework.org/schema/context/spring-context-3.0.xsd">
    <aop:aspectj-autoproxy/>
    <aop:config expose-proxy="true"/>
    <bean id="serviceA" class="spring_test.ServiceA">
        <property name="animal">
            <value>dog</value>
        </property>
    </bean>
    <bean id="serviceB" class="spring_test.ServiceB">
        <property name="place">
            <value>beijing</value>
        </property>
    </bean>
    <bean id="serviceC" class="spring_test.ServiceC"/>
    <bean class="spring_test.MyAspect"></bean>
</beans>

打开aop开关,并且设置aop的expose-proxy属性为true。

package spring_test;


import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;

/**
 * Created by fish on 2021/2/27.
 */
@Aspect
public class MyAspect {
    @Pointcut("execution( * ServiceA.*(..))|| execution(* ServiceB.*(..)) || execution(* ServiceC.*(..))")
    public void test(){

    }

    @Around("test()")
    public Object aroundShowAnimal(ProceedingJoinPoint p) throws Throwable{
        System.out.println("---- before around ---- ");
        Object result = p.proceed();
        System.out.println("---- after around  ---- ");
        return result;
    }

}

设置一个简单的AspectJ定位的aop。

package spring_test;

/**
 * Created by fish on 2021/2/19.
 */
public class ServiceA {
    private String animal;

    public ServiceA(){
        System.out.println("serviceA ref "+this);
    }

    public void setAnimal(String animal){
        this.animal = animal;
    }

    public String getAnimal(){
        return this.animal;
    }

    public void showAnimal(){
        System.out.println("ServiceA showAnimal ref : "+ System.identityHashCode(this));
        System.out.println("showAnimal : "+this.animal);
    }

    public final void finalShowAnimal(){
        System.out.println("ServiceA finalShowAnimal ref : "+ System.identityHashCode(this));
        //this指针被替换为cglib的派生类,cglib派生类无法捕捉final函数,其会自动引用到空基类的final函数
        // 而空基类的animal为null的,所以这样做会失败,this.animal为空
        System.out.println("finalShowAnimal : "+this.animal);
    }

    public final void finalShowAnimal2(){
        //this指针被替换为cglib的派生类,派生类是可以捕捉getAnimal方法,所以这样会成功
        System.out.println("finalShowAnimal2 : "+this.getAnimal());
    }

    private void privateShowAnimal(){
        System.out.println("privateShowAnimal : "+this.getAnimal());
    }
}

然后建立一个普通的ServiceA类,由于这个类没有实现任何接口,Spring的AOP会采用Cglib库来实现。

//spring的serviceA
ServiceA serviceA = (ServiceA) bf.getBean("serviceA");
System.out.println("serviceA ref: "+System.identityHashCode(serviceA) );
serviceA.showAnimal();
serviceA.finalShowAnimal();
serviceA.finalShowAnimal2();

所以,对于这样的一个运行而言,会输出以下的结果:

---- before around ---- 
ServiceA showAnimal ref : 211968962
showAnimal : dog
---- after around  ---- 
ServiceA finalShowAnimal ref : 396883763
finalShowAnimal : null
---- before around ---- 
---- after around  ---- 
finalShowAnimal2 : dog

注意,finalShowAnimal和finalShowAnimal2都没有被AOP捕捉到,而showAnimal和getAnimal方法就会被AOP捕捉到。并且,finalShowAnimal输出的animal成员变量为null,而finalShowAnimal2调用getAnimal方法获取的变量就不是null了。

package spring_test;

import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.InitializingBean;

/**
 * Created by fish on 2021/2/28.
 */
public class ServiceAExtends extends ServiceA {

    private ServiceA serviceA;
    public ServiceAExtends() {
        this.serviceA = new ServiceA();
        this.serviceA.setAnimal("cat");
    }

    public String getAnimal(){
        System.out.println("---- before around2 ---- ");
        Object result = this.serviceA.getAnimal();
        System.out.println("---- after around2  ---- ");
        return (String)result;
    }

    public void showAnimal(){
        System.out.println("---- before around2 ---- ");
        this.serviceA.showAnimal();
        System.out.println("---- after around2  ---- ");
    }

    //finalShowAnimal的无法被覆写
    //finalShowAnimal的无法被覆写
    //privateShowAnimal的无法被覆写
}

其实CgLib的实现就是集成一个ServiceA类生成一个新的ServiceAExtends类,来返回出来的代理对象,因此无法覆写final方法,最终导致final方法会引导到一个空成员变量的基类上面。

package spring_test;

/**
 * Created by fish on 2021/2/28.
 */
public class ServiceB implements  IServiceB {

    private String place;

    public void setPlace(String place){
        this.place = place;
    }

    public void showPlace(){
        System.out.println("serviceB showPlace ref: "+ System.identityHashCode(this));
        System.out.println("serviceB place : "+this.place);
    }
}

然后我们尝试用实现了接口的ServiceB类作为测试。由于这个类实现了接口,Spring的AOP会采用Proxy标准库来实现。

//这样会失败,因为ServiceB被aop包绕后,用接口的情况下用Proxy实现,返回的是IServiceB类型,不是ServiceB类型
//ServiceB serviceB = (ServiceB) bf.getBean("serviceB");

//成功,返回的是Proxy的aop实现,IServiceB类型
IServiceB serviceB = (IServiceB) bf.getBean("serviceB");
System.out.println("serviceB showPlace ref: "+ System.identityHashCode(serviceB));
serviceB.showPlace();

注意,对于实现了接口的,返回的bean不再是ServiceB类型,而是IServiceB的接口类型。

serviceB showPlace ref: 1810899357
---- before around ---- 
serviceB showPlace ref: 954702563
serviceB place : beijing
---- after around  ---- 

执行结果如上,简单且容易理解。

package spring_test;

/**
 * Created by fish on 2021/2/28.
 */
public class ServiceBInterface implements IServiceB {
    private ServiceB serviceB;
    public ServiceBInterface(){
        this.serviceB = new ServiceB();
        this.serviceB.setPlace("shanghai");
    }

    public void showPlace(){
        System.out.println("---- before around3 ---- ");
        this.serviceB.showPlace();
        System.out.println("---- after around3  ---- ");
    }
}

Proxy标准库实现的AOP也是相当直观,建立一个新的ServiceBInterface类型,实现了ServiceB的所有接口,然后将代理的实现全部引导到ServiceB实际类型上。注意,这个方法的局限是,只能代理实现了接口的类型,并且只能代理接口的方法。

package spring_test;

import org.springframework.aop.framework.AopContext;

/**
 * Created by fish on 2021/3/1.
 */
public class ServiceC {

    public void go1(){
        System.out.println("I am go1 begin ... ");
        go1_inner();
        System.out.println("I am go1 end ...");
    }

    public void go1_inner(){
        System.out.println("I am go1_inner ");
    }

    public void go2(){
        System.out.println("I am go2 begin ... ");
        ((ServiceC)AopContext.currentProxy()).go2_inner();
        System.out.println("I am go2 end ...");
    }

    public void go2_inner(){
        System.out.println("I am go2_inner ");
    }
}

最后,讨论一个ServiceC的代码自身调用的问题。go1直接调用自身的go1_inner方法,go2通过AopContext来调用go2_inner方法。

ServiceC serviceC = (ServiceC) bf.getBean("serviceC");
serviceC.go1();
serviceC.go2();

测试代码如上

---- before around ---- 
I am go1 begin ... 
I am go1_inner 
I am go1 end ...
---- after around  ---- 


---- before around ---- 
I am go2 begin ... 
---- before around ---- 
I am go2_inner 
---- after around  ---- 
I am go2 end ...
---- after around  ---- 

测试结果为,go1_inner中失去了代理能力,而使用AopContext的go2_inner就依然有代理能力。这是因为this指针,不是外部的代理类型的指针,原因是:

  • 在Proxy实现里面,this是serviceB类型的地址,而不是ServiceBInterface类型的地址。
  • 在Cglib实现里面,this是serviceB类型的地址,不是ServiceBExtends类型的地址,也不是ServiceBExtends基类的地址

这事情提醒我们,需要特别注意,对自身实例的其他方法的调用会失去AOP支持。并且,特别注意以上测试的两点:

  • Cglib实现,调用final方法会造成空实例成员的问题,无法代理private方法。
  • Proxy实现,只能代理所有接口展示的方法

3.2.3 第三方的AOP扩展

代码在这里

Spring的AOP允许以AspectJ作为切面编程的接口,也可以以其约定类型作为切面编程的接口。

<?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:aop="http://www.springframework.org/schema/aop"
       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/aop
       http://www.springframework.org/schema/aop/spring-aop-3.0.xsd
       http://www.springframework.org/schema/context
       http://www.springframework.org/schema/context/spring-context-3.0.xsd">
    <aop:aspectj-autoproxy/>
    <aop:config expose-proxy="true"/>
    <bean id="serviceA" class="spring_test.ServiceA">
    </bean>
    <bean class="spring_test.MyAdvisor"></bean>
</beans>

添加一个beanFactory,注意,添加了MyAdvisor类型

package spring_test;

import org.aopalliance.aop.Advice;
import org.springframework.aop.Advisor;
import org.springframework.aop.Pointcut;
import org.springframework.aop.PointcutAdvisor;

/**
 * Created by fish on 2021/3/1.
 */
public class MyAdvisor implements PointcutAdvisor {


    public Pointcut getPointcut(){
        return new MyPointcut();
    }

    public Advice getAdvice(){
        return new MyAdvise();
    }

    public boolean isPerInstance(){
        return true;
    }
}

一个切面包括两部分,Pointcut指定了对哪些方法进行切面,Advice指定了对切面执行什么操作。

package spring_test;

import org.springframework.aop.ClassFilter;
import org.springframework.aop.MethodMatcher;
import org.springframework.aop.Pointcut;
import org.springframework.lang.Nullable;

import java.lang.reflect.Method;

/**
 * Created by fish on 2021/3/1.
 */
public class MyPointcut implements Pointcut,ClassFilter,MethodMatcher{
    public ClassFilter getClassFilter(){
        return this;
    }

    public MethodMatcher getMethodMatcher(){
        return this;
    }

    public boolean matches(Class<?> var1){
        //我们不采用类级别的检验
        return true;
    }

    public boolean matches(Method var1, @Nullable Class<?> var2){
        //我们检查方法上是否有@MyShow的注解
        MyShow showAnnotation = var1.getAnnotation(MyShow.class);
        return showAnnotation != null;
    }

    public boolean isRuntime(){
        //不采用动态的需要传入Object的检查
        return false;
    }

    public boolean matches(Method var1, @Nullable Class<?> var2, Object... var3){
        //不采用动态的需要传入Object的检查
        return false;
    }
}

Pointcut的实现,可以在三级级别上的指定:

  • 类级别上是否执行切面ClassFilter和boolean matches(Class<?> var1)方法
  • 方法级别上是否执行切面getMethodMatcher和boolean matches(Method var1, @Nullable Class<?> var2)
  • 运行级别的特定实例上的方法是否执行切面,isRuntime和boolean matches(Method var1, @Nullable Class<?> var2, Object… var3)。
package spring_test;


import org.aopalliance.intercept.MethodInvocation;
import org.springframework.aop.AfterReturningAdvice;
import org.springframework.aop.MethodBeforeAdvice;
import org.aopalliance.intercept.MethodInterceptor;
import java.lang.reflect.Method;
import org.springframework.aop.BeforeAdvice;
import org.springframework.lang.Nullable;


/**
 * Created by fish on 2021/3/1.
 */
public class MyAdvise implements MethodInterceptor,MethodBeforeAdvice,AfterReturningAdvice {

    public Object invoke(MethodInvocation var1) throws Throwable{
        System.out.println("--- myShow intercept begin ---");
        Object result = var1.proceed();
        System.out.println("--- myShow intercept end  ---");
        return result;
    }

    public void before(Method var1, Object[] var2, @Nullable Object var3) throws Throwable{
        System.out.println("--- myShow before  ---");
    }

    public void afterReturning(@Nullable Object var1, Method var2, Object[] var3, @Nullable Object var4) throws Throwable{
        System.out.println("--- myShow afterReturning ---");
    }
}

我们可以用MethodInterceptor来实现Around增强,MethodBeforeAdvice来实现Before增强,AfterReturningAdvice来实现AfterReturning增强。还有其他类型的增强,可以试试。

package spring_test;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * Created by fish on 2021/3/1.
 */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyShow {
    String value() default "";
}

定义一个特定的注解

package spring_test;

import org.springframework.aop.framework.AopContext;

/**
 * Created by fish on 2021/3/1.
 */
public class ServiceA {

    @MyShow("mmk")
    public void go1(){
        System.out.println("I am go1 ... ");
    }

    public void go2(){
        System.out.println("I am go2 ... ");
    }
}

然后在执行go1方法上的时候,就会执行我们定义在AOP切面的代码了。

3.3 原理

//P179
AopNamespaceUtils.registerAspectJAnnotationAutoProxyCreateorIfNecessary(parserContext,element);

对于aop:aspectj-autoproxy的标签,会自动注册AnnotationAwareAspectJAutoProxyCreator.class类型。该类型会继承BeanPostProcessor。显然,当任意一个bean定义的时候,就会调用它的postProcesssAfterInitialization方法。

//P182
public Object postProcessAfterInitialization(Object bean,String beanName){
    ...
    return wrapIfNecessary(bean,beanName,cacheKey);
}

public Object warpIfNecessary(Object bean,String beanName,Object cacheKey){
    ...
    Object[] interceptors = getAdvicesAndAdvisorsForBean(bean.getClass(),beanName,null);

    ...

    Object proxy = createProxy(bean.getClass(),beanName,interceptors,new SingletonTargetSource(bean));

    ...
    return proxy;
}

创建AOP的方法为,首先检查bean的类型,来确定需要执行多少种增强。如果增强器不为空,则用Cglib或者Proxy来创建新的bean实例。

//P184
public Object[] getAdvicesAndAdvisorsForBean(Class beanClass,String beanName,TargetSource targetSource){
    ...
    //查找所有的advisor
    List<Advisor> candidateAdvisors = findCandidateAdvisors();

    //对每个advisor,检查它的类或者方法是否需要满足advisor
    List<Advisor> eligibleAdvisors = findAdvisorsThatCanApply(candidateAdvisors,beanClass,beanName);

    ...

    return eligibleAdvisors;
}

增强器的查找分两步。首先,获取所有增强器,然后检查能满足当前类的有哪些增强器。

//P184
protected List<Advisor> findCandidateAdvisors(){

    //P270,获取当前bean容器里面所有继承了Advisor.class类型的bean
    List<Advisor> advisors = super.findCandidateAdvisors();

    //P185,获取当前bean容器中,遍历所有的bean类型,检查它是否有@AspectJ注解,有的话,用AspectJAdvisors来包装它,生成一个新的advisors
    advisors.addAll(this.aspectJAdvisorBuilder.buildAspectJAdvisors());
}

增强器分两种,要么是在bean容器的Advisor.class类型,要么是普通的POJO类型,但是有@AspectJ注解。

//P194
public static List<Advisor> findAdvisorsThatCanApply(List<Advisor> candidateAdvisors,Class beanClass,String beanName){
    //检查方法和类自身是否有满足advisor要求的,主要就是调用advisor的pointcut的matches来检查。
    xxxx.matches(....)
}

检查方法和类自身是否有满足advisor要求的

//P195
protected Object createProxy(){
    ...
    ProxyFactory proxyFactory = new ProxyFactory();

    ...

    return proxyFactory.getProxy(this.proxyClassLoader);
}

//P198
public Object getProxy(ClassLoader classLoader){
    return createAoProxy().getProxy(classLoader);
}

创建代理的过程了,这里具体看P198的代码,这里倒是不太难。

4 SpringBootApplication

4.1 功能

SpringBootApplication在ClassPathXmlApplicationContext上新增了以下功能:

  • 以注解的方式定义和注入bean,并且提供条件自动注入bean的能力
  • 提供统一的属性配置文件,和获取属性的方式
  • 依赖的以starter的方式嵌入,自动引入依赖

4.2 例子

4.2.1 自动注入bean

代码在这里

package spring_test;

import org.springframework.stereotype.Component;

/**
 * Created by fish on 2021/3/15.
 */
@Component
public class ServiceADepend {

    public String getAnimal(){
        return "fish";
    }
}

以@Component注意来定义一个bean,默认这个bean的ID为类型名称

package spring_test;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

/**
 * Created by fish on 2021/3/15.
 */
@Component
public class ServiceA {

    @Autowired
    private ServiceADepend serviceADepend;

    public void showAnimal(){
        System.out.println("showAnimal : "+this.serviceADepend.getAnimal());
    }
}

然后我们以@Autowired的方式就能自动找到这个依赖,并以私有变量的方式注入进去,不需要写setter方法。注意,@Autowired的查找依赖的方式默认是以byType的方式,就是根据查找满足该ServiceADepend的类型来尝试注入进去。

package spring_test;

/**
 * Created by fish on 2021/3/15.
 */
public interface ServiceBDepend {
    String getPlace();
}

当依赖是一个接口类型

package spring_test;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

/**
 * Created by fish on 2021/3/15.
 */
@Component
public class ServiceBDepend1 implements ServiceBDepend{
    public String getPlace(){
        return "home";
    }
}

然后我们提供了实现该接口的类型ServiceBDepend1

package spring_test;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

/**
 * Created by fish on 2021/3/15.
 */
@Component
public class ServiceBDepend2 implements ServiceBDepend{

    public String getPlace(){
        return "garden";
    }
}

以及实现了该接口的另外一个类型ServiceBDepend2

package spring_test;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Component;

/**
 * Created by fish on 2021/3/15.
 */
@Component
public class ServiceB {
    @Autowired
    @Qualifier("serviceBDepend1")
    private ServiceBDepend serviceBDepend;

    public void play(){
        System.out.println("we are playing in : "+this.serviceBDepend.getPlace());
    }
}

最后,我们如果尝试直接注入ServiceBDepend类型就会失败,因为满足ServiceBDepend类型要求的有两个具体类型,Spring就会抱怨有歧义,无法选择。这个时候,我们可以用Qualifier来指定bean的名称,注意,是名称,不是类型。这个时候,Spring就会使用根据名称来查找bean的方式来注入。

package spring_test;

/**
 * Created by fish on 2021/3/15.
 */
public interface ServiceCDepend {
    String getInstrument();
}

同理,我们再次定义一个接口类型为ServiceCDepend

package spring_test;

import org.springframework.stereotype.Component;

/**
 * Created by fish on 2021/3/15.
 */
@Component("guitar")
public class ServiceCDepend1 implements  ServiceCDepend {
    public String getInstrument(){
        return "guitar";
    }
}

然后定义一个ServiceCDepend1类型,满足ServiceCDepend接口,注意,Component指定了该bean的名称为guitar,而不是默认的serviceCDepend1。

package spring_test;

import org.springframework.stereotype.Component;

/**
 * Created by fish on 2021/3/15.
 */
@Component("piano")
public class ServiceCDepend2 implements ServiceCDepend {
    public String getInstrument(){
        return "piano";
    }
}

然后也定义了一个ServiceCDepend2类型,并指定了它的名称为piano。

package spring_test;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;


/**
 * Created by fish on 2021/3/15.
 */
@Component
public class ServiceC {
    @Resource(name="piano")
    private ServiceCDepend serviceCDepend;

    @Autowired
    @Qualifier("guitar")
    private ServiceCDepend serviceCDepend2;

    public void showMusic(){
        System.out.println("He is playing : "+this.serviceCDepend.getInstrument());

        System.out.println("She is playing : "+this.serviceCDepend2.getInstrument());
    }
}

这个时候,我们既可以用之前的@Autowired与@Qualifier的方式结合来查找bean,也可以直接用java的标准注解@Resource来查找需要注入的bean。

4.2.2 配置类,组件扫描和条件配置

代码在这里

├── spring_test
│   ├── App.java
│   ├── ServiceA.java
│   └── package1
│       ├── ServiceB.java
│       └── inner_package1
│           └── ServiceC.java
├── spring_test2
│   └── ServiceD.java
├── spring_test3
│   ├── ConfigureTest3.java
│   ├── ServiceE.java
│   ├── ServiceF.java
│   ├── ServiceG.java
│   ├── ServiceGImpl1.java
│   └── inner_package3
│       ├── ConfigureTestInner3.java
│       └── ServiceGImpl3.java
└── spring_test4
    ├── ConfigureTest4.java
    └── ServiceGImpl2.java

7 directories, 14 files

这是源代码的包结构

package spring_test;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Import;
import spring_test.package1.ServiceB;
import spring_test.package1.inner_package1.ServiceC;
import spring_test2.ServiceD;
import spring_test3.ConfigureTest3;
import spring_test3.ServiceE;
import spring_test3.ServiceF;
import spring_test3.ServiceG;

/**
 * Hello world!
 *
 */
@SpringBootApplication
@ComponentScan({"spring_test2","spring_test"})
@Import(ConfigureTest3.class)
public class App implements ApplicationRunner
{
    public static void main( String[] args )
    {
        SpringApplication.run(App.class,args);
    }

    @Autowired
    private ServiceA serviceA;

    @Autowired
    private ServiceB serviceB;

    @Autowired
    private ServiceC serviceC;

    @Autowired
    private ServiceD serviceD;

    @Autowired
    private ServiceE serviceE;

    @Autowired
    private ServiceF serviceF;

    @Autowired
    private ServiceG serviceG;

    public void run(ApplicationArguments arguments) throws Exception{
        serviceA.go();
        serviceB.go();
        serviceC.go();
        serviceD.go();
        serviceE.go();
        serviceF.go();
        serviceG.go();
    }
}

首先是,ServiceA在spring_test包下面,ServiceB在spring_test.package1下面,ServiceC在spring_test.package1.inner_package1下面。但是,他们都在入口类App的所在包spring_test下面,所以他们都不需要任何的操作,只需要写个@Component注解,就能自动被添加到bean工厂里面。

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(
    excludeFilters = {        @Filter(
            type = FilterType.CUSTOM,
            classes = {TypeExcludeFilter.class}
        ),         @Filter(
            type = FilterType.CUSTOM,
            classes = {AutoConfigurationExcludeFilter.class}
        )}
)
public @interface SpringBootApplication {
    @AliasFor(
        annotation = EnableAutoConfiguration.class
    )
    Class<?>[] exclude() default {};

    @AliasFor(
        annotation = EnableAutoConfiguration.class
    )
    String[] excludeName() default {};

    @AliasFor(
        annotation = ComponentScan.class,
        attribute = "basePackages"
    )
    String[] scanBasePackages() default {};

    @AliasFor(
        annotation = ComponentScan.class,
        attribute = "basePackageClasses"
    )
    Class<?>[] scanBasePackageClasses() default {};
}

这是因为@SpringBootApplication包含了@ComponentScan子注解,该注解会默认扫描当前类所在的包,将包里面所有的@Component的类都添加到bean工厂里面。

package spring_test2;

import org.springframework.stereotype.Component;

/**
 * Created by fish on 2021/3/15.
 */
@Component
public class ServiceD {
    public void go(){
        System.out.println("serviceD go");
    }
}

对于serviceD类,因为它所在的包为spring_test2与入口类的包spring_test,不一样,也不是嵌套关系。所以,无法自动添加到bean工厂里面。我们可以通过在App类,加入@ComponentScan({“spring_test2”,“spring_test”})来扫描这两个包。

package spring_test3;

import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import spring_test4.ConfigureTest4;

/**
 * Created by fish on 2021/3/15.
 */
@Configuration
@Import(ConfigureTest4.class)
@ComponentScan
public class ConfigureTest3 {
    @Bean
    public ServiceF getServiceF(){
        return new ServiceF("[I am tip]");
    }

    @Bean
    @ConditionalOnMissingBean(ServiceG.class)
    public ServiceG getServiceG(){
        return new ServiceGImpl1();
    }
}

对于spring_test3的类型,我们是通过在入口类,使用@Import(ConfigureTest3.class)注解来添加进来的。注意,添加的仅仅只是ConfigureTest3这个配置类,spring_test3包的其他类型是无法自动添加进来的。因此,我们在ConfigureTest3这个配置类使用了@ComponentScan注解,它会自动扫描spring_test3所在的以及嵌套的所有类。

另外,注意,ConfigureTest3还以方法加@Bean注解的方式来添加Bean类型。这样,我们可以在不使用@Component的方式来注册bean,同时@Bean类型可以允许注册bean的构造函数的参数是什么,具体看getServiceF函数。

最后,这个ConfigureTest3还使用了@Import(ConfigureTest4.class)来进一步导入spring_test4包的配置类。

package spring_test4;

import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import spring_test3.ServiceG;

/**
 * Created by fish on 2021/3/15.
 */
@Configuration
@ComponentScan
public class ConfigureTest4 {

    @Bean
    @ConditionalOnMissingBean(ServiceG.class)
    public ServiceG getServiceG(){
        return new ServiceGImpl2();
    }
}

这是ConfigureTest4的配置类

package spring_test4;

import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;
import spring_test3.ServiceG;

/**
 * Created by fish on 2021/3/15.
 */
public class ServiceGImpl2 implements ServiceG {

    public void go(){
        System.out.println("serviceG go impl2");
    }
}

这是ServiceGImpl2的实现,注意,没有@Component的注解。

最终,我们在三个地方定义了ServiceG的实现类:

  • spring_test3的包的ConfigureTest3的@Bean注解定义,有@ConditionalOnMissingBean注解
  • spring_test3的嵌套包inner_package3的ConfigureTestInner3的@Bean注解定义,同样用@ConditionalOnMissingBean注解
  • spring_test4的包的ConfigureTest4的@Bean注解定义,有@ConditionalOnMissingBean注解

那么,如果我自动注入@ServiceG接口的话,会用哪个实现类呢?答案是使用inner_package3的ConfigureTestInner3的@Bean注解定义。Bean定义的执行顺序为:

  • 首先运行@ComponentScan扫描当前包的或者嵌套包的bean定义
  • 然后运行@Import定义其他包里面的bean定义
  • 最后运行自身配置类的@Bean定义

然后,Spring定义@ConditionalOnMissingBean定义的bean是一旦存在,后面的就不会重复定义这个bean。但是,如果这三个地方都是有@Bean定义,但是没有@ConditionalOnMissingBean注解时,Spring就会认为后面定义的bean就会覆盖前面的bean,最终造成:

  • 三个地方都有@Bean注解和@ConditionalOnMissingBean注解时,优先级最高的是嵌套包inner_package3的bean定义。因为最先声明bean的,后面就会忽略。
  • 三个地方都有@Bean注解,但都没有@ConditionalOnMissingBean注解时,优先级最高的是自身配置类spring_test3的bean定义,因为最后声明bean的,就会覆盖前面声明的bean。

4.2.3 配置文件

代码在这里

/**
 * Created by fish on 2021/3/15.
 */
spring.profiles.active = production

study.testStr = Hello world

myapp.mail.host = smtp.163.com
myapp.mail.port = 8080
myapp.mail.user = fish
myapp.mail.password = 123

myapp.mail2.name = MMK

首先在resources文件夹加入以上的application.properties文件。

/**
 * Created by fish on 2021/3/16.
 */
study.testStr = Hello world development

然后在resources文件夹加入以上的application-development.properties文件。

package spring_test;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

/**
 * Created by fish on 2021/3/15.
 */
@Component
public class ServiceA {
    @Value("${study.testStr}")
    private String testStr;

    @Value("#{serviceB.host}")
    private String emailHost;

    public void go(){
        System.out.println("servierA go : "+this.testStr);

        System.out.println("serviceA get host : "+this.emailHost);
    }
}

那么,我们可以用@Value注解来获取配置文件的值,@Value注解使用$符号时,使用的是配置文件项的值。而@Value直接使用#符号时,使用的是SpEL表达式计算的值,它可以用来获取其他bean,和类的属性和方法的值。

package spring_test;

import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;

/**
 * Created by fish on 2021/3/15.
 */
//缺少Component注解的时候,无法注册BeanFactory,更无法注入属性到该类实例中
@Component
@ConfigurationProperties(prefix="myapp.mail")
public class ServiceB {
    private String host;

    public void setHost(String host){
        this.host = host;
    }

    public String getHost(){
        return this.host;
    }

    private int port;

    public void setPort(int port){
        this.port = port;
    }

    public int getPort(){
        return this.port;
    }

    private String user;

    public void setUser(String user){
        this.user = user;
    }

    public String getUser(){
        return this.user;
    }

    private String password;

    public void setPassword(String passowrd){
        this.password = passowrd;
    }

    public String getPassword(){
        return this.password;
    }

    public void sendEmail(){
        System.out.printf("host:%s,port:%d,user:%s,password:%s\n",host,port,user,password);
    }
}

另外一种使用ConfigurationProperties加入prefix来将整个部分的属性配置文件项都写入到bean的依赖上面。注意,这种方法,必须要写setter方法才能实现。而SpEL表达式依赖getter方法来获取bean的属性值。

package spring_test;

import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;

/**
 * Created by fish on 2021/4/12.
 */
@ConfigurationProperties(prefix="myapp.mail2")
public class ServiceC {
    private String name;

    public void setName(String name){
        this.name = name;
    }

    public String getName(){
        return this.name;
    }
}

我们也可以对ServiceC不使用@Component注解,同时使用ConfigurationProperties来配置属性。

@SpringBootApplication
@EnableConfigurationProperties(ServiceC.class)
public class App implements ApplicationRunner
{
    public static void main( String[] args )
    {
        SpringApplication.run(App.class,args);
    }

    @Autowired
    private ServiceA serviceA;

    @Autowired
    private ServiceB serviceB;

    @Autowired
    private ServiceC serviceC;

    public   void run(ApplicationArguments arguments) throws Exception{
        this.serviceA.go();

        this.serviceB.sendEmail();

        System.out.printf("serviceC name: %s\n",serviceC.getName());
    }
}

但是,要在入口或者配置类中,设置EnableConfigurationProperties,将ServiceC注入到beanFactory里面。

最后,profile可以在命令行参数中指定。–spring.profiles.active=development,你可以用这种方法来覆盖属性文件文件的其他项。

4.2.4 Import的使用

代码在这里

普通的Import使用,就是指定导入一个固定的Configuration文件

4.2.4.1 ImportSelector

ImportSelector的意义在于,可以可以根据注解的不同来选择导入一个不同的Configuration文件,

package spring_test;

import org.springframework.context.annotation.Import;

import java.lang.annotation.*;

/**
 * Created by fish on 2021/4/14.
 */
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(ShowMsgSelector.class)
public @interface ShowMsg {
    String value() default "";
}

定义一个ShowMsg注解,并且该注解定义导入一个ShowMsgSelector

package spring_test;

import org.springframework.context.annotation.ImportSelector;
import org.springframework.core.annotation.AnnotationAttributes;
import org.springframework.core.type.AnnotationMetadata;

/**
 * Created by fish on 2021/4/14.
 */
public class ShowMsgSelector implements ImportSelector {

    public String[] selectImports(AnnotationMetadata importingClassMetadata){
        AnnotationAttributes attributes = AnnotationAttributes
                .fromMap(importingClassMetadata.getAnnotationAttributes(
                        ShowMsg.class.getName(), false));

        String name = attributes.getString("value");
        String[] result = {name+".MyConfiguration"};
        return result;
    }
}

ShowMsgSelector继承于ImportSelector,导入的时候,Spring会执行它的selectImports方法,importingClassMetadata就是使用@Import(ShowMsgSelector.class)注解所在类的元信息。从代码中我们可以看出,我们根据@ShowMsg的value值来导入指定的MyConfiguration配置文件。

//尝试将ShowMsg的value从spring_test2改为spring_test3
@ShowMsg("spring_test2")
public class App implements ApplicationRunner{

}

因此使用@ShowMsg就是导入spring_test2.MyConfiguration的配置文件,或者你可以改为导入spring_test3.MyConfiguration的配置文件

4.2.4.2 DeferredImportSelector

package spring_test;

import org.springframework.context.annotation.Import;

import java.lang.annotation.*;

/**
 * Created by fish on 2021/4/14.
 */
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(ShowHelloSelector.class)
public @interface ShowHello {
    String value() default "";
}

定义一个@ShowHello注解,依然导入ShowHelloSelector

package spring_test;

import org.springframework.context.annotation.DeferredImportSelector;
import org.springframework.context.annotation.ImportSelector;
import org.springframework.core.annotation.AnnotationAttributes;
import org.springframework.core.type.AnnotationMetadata;

/**
 * Created by fish on 2021/4/14.
 */
//尝试将接口DeferredImportSelector改为ImportSelector
public class ShowHelloSelector implements DeferredImportSelector {
    public String[] selectImports(AnnotationMetadata importingClassMetadata){
        String[] result = {"spring_test4.MyConfiguration"};
        return result;
    }
}

ShowHelloSelector改为继承于DeferredImportSelector,而不是ImportSelector。它的区别在于,该selector的执行在配置类之后,而不是配置类之前。

@ShowHello
public class App implements ApplicationRunner{

}

因为@ShowHello定义在入口配置类App中,正常情况是先执行Import的ShowHelloSelector,再执行配置类App。但是因为ShowHelloSelector继承于DeferredImportSelector,所以会先执行入口配置类App,才执行ShowHelloSelector。

4.2.4.3 ImportBeanDefinitionRegistrar

package spring_test5;

import java.lang.annotation.*;

/**
 * Created by fish on 2021/4/14.
 */
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MyComponent {
}

定义一个@MyComponent注解

package spring_test5;

import org.springframework.context.annotation.Import;
import org.springframework.core.annotation.AliasFor;

import java.lang.annotation.*;

/**
 * Created by fish on 2021/4/14.
 */
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(ImportMyComponentRegistrar.class)
public @interface EnableMyComponent {
    @AliasFor("basePackages")
    String[] value() default {};

    @AliasFor("value")
    String[] basePackages() default {};
}

然后定义一个@EnableMyComponent开关的注解,它使用了@Import的ImportMyComponentRegistrar。

package spring_test5;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.context.ResourceLoaderAware;
import org.springframework.context.annotation.ClassPathBeanDefinitionScanner;
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
import org.springframework.core.annotation.AnnotationAttributes;
import org.springframework.core.io.ResourceLoader;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.core.type.filter.AnnotationTypeFilter;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

/**
 * Created by fish on 2021/4/14.
 */
public class ImportMyComponentRegistrar implements
        ImportBeanDefinitionRegistrar, ResourceLoaderAware {
    private ResourceLoader resourceLoader;

    private Logger  logger = LoggerFactory.getLogger(getClass());

    @Override
    public void setResourceLoader(ResourceLoader resourceLoader) {
        this.resourceLoader = resourceLoader;
    }

    @Override
    public void registerBeanDefinitions(
            AnnotationMetadata importingClassMetadata,
            BeanDefinitionRegistry registry) {
        AnnotationAttributes annotationAttributes = AnnotationAttributes
                .fromMap(importingClassMetadata.getAnnotationAttributes(
                        EnableMyComponent.class.getName(), false));

        String[] basePackages = annotationAttributes.getStringArray("basePackages");
        List<String> basePackageList = new ArrayList<String>(Arrays.asList(basePackages));

        if( basePackages.length == 0){
            //当没有输入包名的时候,就用注解所在类的包
            try {
                String annotationClass = importingClassMetadata.getClassName();
                String importPackage = Class.forName(annotationClass).getPackage().getName();
                basePackageList.add(importPackage);
            }catch(Exception e){
                e.printStackTrace();
            }
        }
        System.out.println(basePackageList);

        //开始扫描包并添加到工厂
        ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(registry,false);
        scanner.setResourceLoader(resourceLoader);
        scanner.addIncludeFilter(new AnnotationTypeFilter(MyComponent.class));
        scanner.scan(basePackageList.toArray(new String[]{}));
    }
}

ImportMyComponentRegistrar实现了ImportBeanDefinitionRegistrar,然后Spring在Import的时候,就会执行它的registerBeanDefinitions方法。我们在里面使用自己的方法来做最彻底的动态bean注册。例如,我们在这里进行包扫描,将所有含有@MyComponent的类都加入到beanFactory里面。

@EnableMyComponent
public class App implements ApplicationRunner{

}

我们在入口类中使用EnableMyComponent就能打开开关,默认无传入basePackages参数的时候,就会扫描注解所在类App的所在的包

4.2.4.4 根据注解生成bean

package spring_test6;

import java.lang.annotation.*;

/**
 * Created by fish on 2021/4/14.
 */
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MyRepository {
}

依然是先定义@MyRepository注解

package spring_test6;

import org.springframework.context.annotation.Import;
import org.springframework.core.annotation.AliasFor;

import java.lang.annotation.*;

/**
 * Created by fish on 2021/4/14.
 */
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(ImportMyRepositoryRegistrar.class)
public @interface EnableMyRepository {
    @AliasFor("basePackages")
    String[] value() default {};

    @AliasFor("value")
    String[] basePackages() default {};
}

设置一个EnableMyRepository注解的开关,使用ImportMyRepositoryRegistrar的Import

package spring_test6;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.config.BeanDefinitionHolder;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.boot.autoconfigure.AutoConfigurationPackages;
import org.springframework.context.ResourceLoaderAware;
import org.springframework.context.annotation.ClassPathBeanDefinitionScanner;
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
import org.springframework.core.annotation.AnnotationAttributes;
import org.springframework.core.io.ResourceLoader;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.core.type.filter.AnnotationTypeFilter;
import org.springframework.core.type.filter.AssignableTypeFilter;
import org.springframework.util.StringUtils;
import spring_test6.MyRepository;
import spring_test6.MyRepositoryFactory;
import spring_test6.MyRepositoryScanner;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Set;

/**
 * Created by fish on 2021/4/14.
 */
public class ImportMyRepositoryRegistrar implements
        ImportBeanDefinitionRegistrar, ResourceLoaderAware {
    private ResourceLoader resourceLoader;

    private Logger  logger = LoggerFactory.getLogger(getClass());

    @Override
    public void setResourceLoader(ResourceLoader resourceLoader) {
        this.resourceLoader = resourceLoader;
    }

    @Override
    public void registerBeanDefinitions(
            AnnotationMetadata importingClassMetadata,
            BeanDefinitionRegistry registry) {
        AnnotationAttributes annotationAttributes = AnnotationAttributes
                .fromMap(importingClassMetadata.getAnnotationAttributes(
                        EnableMyRepository.class.getName(), false));

        String[] basePackages = annotationAttributes.getStringArray("basePackages");
        List<String> basePackageList = new ArrayList<String>(Arrays.asList(basePackages));

        if( basePackageList.size() == 0){
            //当没有输入包名的时候,就用注解所在类的包
            try {
                //以下这句用的是入口类的所在的包
                //basePackageList = AutoConfigurationPackages.get(this.beanFactory);
                String annotationClass = importingClassMetadata.getClassName();
                String importPackage = Class.forName(annotationClass).getPackage().getName();
                basePackageList.add(importPackage);
            }catch(Exception e){
                e.printStackTrace();
            }
        }
        //开始扫描包并添加到工厂
        logger.info("info ImportMyRepositoryRegistrar begin ... {}",basePackageList);
        MyRepositoryScanner scanner = new MyRepositoryScanner(registry);
        scanner.setResourceLoader(resourceLoader);
        scanner.addIncludeFilter(new AnnotationTypeFilter(MyRepository.class));
        scanner.scan(basePackageList.toArray(new String[]{}));
    }
}

ImportMyRepositoryRegistrar代码类似,但是使用了MyRepositoryScanner

package spring_test6;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.BeanDefinitionHolder;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.context.annotation.ClassPathBeanDefinitionScanner;

import java.util.Arrays;
import java.util.Set;

/**
 * Created by fish on 2021/4/14.
 */
public class MyRepositoryScanner extends ClassPathBeanDefinitionScanner {

    private Logger logger = LoggerFactory.getLogger(getClass());

    static final String FACTORY_BEAN_OBJECT_TYPE = "factoryBeanObjectType";

    public MyRepositoryScanner(BeanDefinitionRegistry registry) {
        super(registry, false);
    }

    @Override
    public Set<BeanDefinitionHolder> doScan(String... basePackages) {
        Set<BeanDefinitionHolder> beanDefinitions = super.doScan(basePackages);

        if (beanDefinitions.isEmpty()) {
            logger.warn("No MyRepository was found in {} package. Please check your configuration.",Arrays.toString(basePackages));
        } else {
            System.out.println(beanDefinitions);
            processBeanDefinitions(beanDefinitions);
        }

        return beanDefinitions;
    }

    private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) {
        AbstractBeanDefinition definition;
        BeanDefinitionRegistry registry = getRegistry();
        for (BeanDefinitionHolder holder : beanDefinitions) {
            definition = (AbstractBeanDefinition) holder.getBeanDefinition();
            String beanClassName = definition.getBeanClassName();
            logger.debug( "Creating MapperFactoryBean with name {} and {} mapperInterface",holder.getBeanName() ,beanClassName);

            definition.getConstructorArgumentValues().addGenericArgumentValue(beanClassName); // issue #59
            definition.setBeanClass(MyRepositoryFactory.class);
            //参考代码:https://github.com/mybatis/spring-boot-starter/issues/475
            definition.setAttribute(FACTORY_BEAN_OBJECT_TYPE, beanClassName);
        }
    }

    @Override
    protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
        return beanDefinition.getMetadata().isInterface() && beanDefinition.getMetadata().isIndependent();
    }

    @Override
    protected boolean checkCandidate(String beanName, BeanDefinition beanDefinition) {
        if (super.checkCandidate(beanName, beanDefinition)) {
            return true;
        } else {
            logger.warn( "Skipping MapperFactoryBean with name {} and mapperInterface {}. Bean already defined with the same name!" , beanName , beanDefinition.getBeanClassName() );
            return false;
        }
    }
}

这个类继承于ClassPathBeanDefinitionScanner,我们重写了doScan,使得在扫描类以后,我们有机会可以更改bean的beanClass。因为,我们目标是扫描接口,而不是具体的类,所以,还要重写isCandidateComponent,使得可以接受接口上面的注解作为扫描的目标。注意processBeanDefinitions的代码,它指定的模板的参数,和beanClass为MyRepositoryFactory.

package spring_test6;

import org.springframework.beans.factory.BeanClassLoaderAware;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.lang.Nullable;
import spring_test.ServiceA;

import java.lang.reflect.Proxy;

/**
 * Created by fish on 2021/4/14.
 */
public class MyRepositoryFactory<T> implements FactoryBean<T> ,BeanClassLoaderAware{

    //可以正常使用Autowired
    @Autowired
    private ServiceA serviceA;

    private ClassLoader classLoader;

    private Class<T> mapperInterface;

    public MyRepositoryFactory(Class<T> mapperInterface){
        System.out.println("factory create");
        this.mapperInterface = mapperInterface;
    }

    @Override
    public void setBeanClassLoader(ClassLoader var1){
        this.classLoader = var1;
    }

    @Nullable
    public  T getObject() throws Exception{
        System.out.println("factory getObject");
        serviceA.showMsg();

        MyRepositoryInvocationHandler handler = new MyRepositoryInvocationHandler();
        Class<?>[] classes = new Class<?>[]{mapperInterface};
        return (T)Proxy.newProxyInstance(this.classLoader,classes,handler);
    }

    @Nullable
    public Class<?> getObjectType(){
        return mapperInterface;
    }

    @Override
    public boolean isSingleton() {
        return true;
    }
}

MyRepositoryFactory就是一个模板,而且实现了FactoryBean。getObject的实现就是从接口生成的AOP类。

@EnableMyRepository
public class App implements ApplicationRunner{

}

我们在入口类使用一个注解@EnableMyRepository即可。

package spring_test;

import spring_test6.MyRepository;

/**
 * Created by fish on 2021/4/14.
 */
@MyRepository
public interface RepositoryA {
    void findById(int a);
}

那么这个包下,任意的含有@MyRepository注解的接口,都会有有一个默认的实现,相当方便。这个就是MyBatis和JPA,在只写接口的情况,Spring就会自动提供实现的原理了。

4.3 原理

4.3.1 自动注册ConfigurationClassPostProcessor

在SpringApplication里面,会固定注册一个ConfigurationClassPostProcessor。它是一个BeanFactoryPostProcessor,所以,在spring工厂启动以后,会回调它的postProcessBeanDefinitionRetistry方法。(详情看书本的P406页)

然后在postProcessBeanDefinitionRetistry里面,以入口函数SpringApplication.run(App.class,args)传入的App.class作为配置类,执行自动导入bean的流程。(详情看书本的P409页)

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//

package org.springframework.boot.autoconfigure;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.boot.SpringBootConfiguration;
import org.springframework.boot.autoconfigure.AutoConfigurationExcludeFilter;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.context.TypeExcludeFilter;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.FilterType;
import org.springframework.context.annotation.ComponentScan.Filter;
import org.springframework.core.annotation.AliasFor;

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(
    excludeFilters = {        @Filter(
            type = FilterType.CUSTOM,
            classes = {TypeExcludeFilter.class}
        ),         @Filter(
            type = FilterType.CUSTOM,
            classes = {AutoConfigurationExcludeFilter.class}
        )}
)
public @interface SpringBootApplication {
    @AliasFor(
        annotation = EnableAutoConfiguration.class
    )
    Class<?>[] exclude() default {};

    @AliasFor(
        annotation = EnableAutoConfiguration.class
    )
    String[] excludeName() default {};

    @AliasFor(
        annotation = ComponentScan.class,
        attribute = "basePackages"
    )
    String[] scanBasePackages() default {};

    @AliasFor(
        annotation = ComponentScan.class,
        attribute = "basePackageClasses"
    )
    Class<?>[] scanBasePackageClasses() default {};
}

对于入口类App.class,我们都会添加一个@SpringBootApplication的注解。首先,这个注解会继承@SpringBootConfiguration注解。

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//

package org.springframework.boot;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.context.annotation.Configuration;

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration
public @interface SpringBootConfiguration {
}

@SpringBootConfiguration注解也是包含有@Configuration注解的,因此,@SpringBootApplication的注解包含了入口类也是配置类的意思

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//

package org.springframework.boot.autoconfigure;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.boot.autoconfigure.AutoConfigurationImportSelector;
import org.springframework.boot.autoconfigure.AutoConfigurationPackage;
import org.springframework.context.annotation.Import;

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import({AutoConfigurationImportSelector.class})
public @interface EnableAutoConfiguration {
    String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";

    Class<?>[] exclude() default {};

    String[] excludeName() default {};
}

然后,@SpringBootApplication的注解包含了@EnableAutoConfiguration注解,该注解其实是一个@Import注解,它的类型AutoConfigurationImportSelector.class的作用是,自动导入所有依赖包里面,固定META-INF/spring.factories里面指定的bean。这是spring-starter在maven自动导入以后,会自动添加到Spring工厂的原因。

@ComponentScan(
    excludeFilters = {        @Filter(
            type = FilterType.CUSTOM,
            classes = {TypeExcludeFilter.class}
        ),         @Filter(
            type = FilterType.CUSTOM,
            classes = {AutoConfigurationExcludeFilter.class}
        )}
)

最后,@SpringBootApplication也包含了ComponentScan注解,因此,入口类所在的包,以及它嵌套的包的bean都会被自动扫描后,添加到Spring工厂里面。

4.3.2 配置类的解析

我们谈到了在postProcessBeanDefinitionRetistry里面,会解析以入口类(例如是App.class)作为配置类的配置。它最终会调用到一个关键的doProcessCofingurationClass函数里面。

//P410
protected final SourceClass doProcessConfigureationClass(ConfiguationClass configClass, SourceClass sourceClass){
    //Process Any @PropertySource annotations

    //Process any @ComponentScan annotations

    //Process any @Import annotations

    //Process any @ImportResource annotations

    //Process @Bean methods
}

从这个代码中就可以看出,在配置类的解析时,按照以下顺序执行

  • 首先,深度优先遍历该配置类的当前包和其他包,的其他配置类或者bean
  • 然后,使用@Import指定来导入其他包的bean
  • 最后,执行本配置类的@Bean方法来注册bean

显然,这个顺序,和我们之前在4.2.2讨论的顺序一致。

//P404
public String[] selectImports(AnnotationMetadata annotationMetadata){
    return ....
}

在使用Import导入其他配置类时,如果该类实现了org.springframework.context.annotation.ImportSelector接口。那么,导入的类就不看成是配置类,而是执行该类的selectImports方法来获取具体的bean的类名称作为导入。对于在@SpringBootApplication注解类中,包含的@Import({AutoConfigurationImportSelector.class})注解中,AutoConfigurationImportSelector就是这样的一个类。它实现了ImportSelector接口,然后它就能在selectImports里面扫描固定路径的资源文件,来获取各个starter的类添加到bean工厂里面。

Spring Boot的这个思路相当巧妙,使用约定的方法来导入bean,避免了繁琐的依赖包bean的添加工作。

//P419
void processConfigurationClass(ConfigationClass configClass){
    ///
}

最后,如果配置类自身是有@Conditional配置的,那么它就会在解析前,先执行一次@Conditional测试,只有条件配置测试通过以后,才能导入该配置类的信息。这就是Spring Boot的条件配置bean的实现原理了。

5 事务

5.1 事务

Spring提供了以注解的方式使用事务,实现了

  • 自动提交回滚
  • 方便指定数据库的隔离模式,回滚的异常模式
  • 指定事务嵌套的传递方式(这点重要)

5.2 例子

5.2.1 基础选项

代码在这里

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>

依赖里面要打开aop和jdbc选项

@SpringBootApplication
@EnableAspectJAutoProxy(exposeProxy = true)
public class App implements ApplicationRunner
{
    public static void main( String[] args )
    {
        SpringApplication.run(App.class,args);
    }
}

在启动类里面,打开AOP的exposeProxy选项,因为Spring的事务依赖于AOP的实现,不打开的话,会出现无法在当前bean中获取代理对象的问题。

//数据被回滚了
@Transactional
public void addOneAndHaveRuntimeException(){
    User user = new User();
    user.setName("cat");
    user.setAge(700);
    userService.save(user);

    throw new RuntimeException("Throw by me");
}

简单的用法,有RuntimeException,所以数据被回滚了

//数据没有被回滚,因为Transaction标签默认只捕捉RunTimeException
@Transactional
public void addOneAndHaveNormalException()throws Exception{
    User user = new User();
    user.setName("cat");
    user.setAge(700);
    userService.save(user);

    throw new Exception("Throw by me2");
}

数据没有被回滚,因为Spring事务默认只回滚RuntimeException异常。

//数据被回滚了,因为Transaction标签指定了遇到Exception的时候都要回滚
@Transactional(rollbackFor=Exception.class)
public void addOneAndHaveNormalExceptionAndHaveRollBackForLabel()throws Exception{
    User user = new User();
    user.setName("cat");
    user.setAge(700);
    userService.save(user);

    throw new Exception("Throw by me2");
}

我们可以用rollbackFor标签,来要求事务遇到Exception异常的时候也要回滚

//直接使用this来调用自身的其他方法,会绕过AOP实现,导致事务注解没有开启
public void addOneWithThis(){
    this.addOneAndHaveRuntimeException();
}

//应该AopContext来调用自身的其他方法,事务注解依然会开启
public void addOneWithAopContextThis()throws Exception{
    ((UserAo)AopContext.currentProxy()).addOneAndHaveRuntimeException();
}

注意,事务依赖于Spring的AOP的实现,在3.2.2节我们提到了,在当前bean类的方法中,要用AopContext.currentProxy()才能获取到真正的代理对象。

//对于readOnly为true,在应用层层面,会禁止修改操作,并且去掉脏数据检查.在数据库层面,会避免数据上锁.
@Transactional(readOnly = true)
public void addOneWithReadOnly(){
    User user = new User();
    user.setName("cat");
    user.setAge(700);
    userService.save(user);
}

我们可以设置readOnly为true,打开只读模式。这会产生优化,会禁止写入操作。

//可以指定数据库的隔离级别,这个需要在并发环境下才能测试到不同的地方
@Transactional(isolation = Isolation.READ_COMMITTED)
public void addOneWithIsolation(){
    this.addOneAndHaveRuntimeException();
}

可以方便地指定数据库的隔离级别。

5.2.2 事务同步管理器

代码在这里

事务同步管理器十分方便地设置了,在一个事务里面,当事务提交或者回滚前后的通知,以及当前事务的信息。

//数据正常提交
@Transactional()
public void addOne(){

    this.showTransactionInfo();
    User user = new User();
    user.setName("cat");
    user.setAge(700);
    userService.save(user);

    this.addTransactionNotify();
}

private void showTransactionInfo(){
    String transactionName = TransactionSynchronizationManager.getCurrentTransactionName();
    boolean isReadOnly = TransactionSynchronizationManager.isCurrentTransactionReadOnly();
    Integer isolationLevel = TransactionSynchronizationManager.getCurrentTransactionIsolationLevel();

    System.out.printf("transactionName:%s,isReadOnly:%s,isolationLevel:%s\n",transactionName,isReadOnly,isolationLevel);
}

private void addTransactionNotify(){
    TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronizationAdapter(){

        //只有可以commit才会回调,在提交前回调
        @Override
        public void beforeCommit(boolean readOnly) {
            System.out.printf("before Commit,isReadOnly :%s\n",readOnly);
        }

        //只有可以commit才会回调,在提交后回调
        @Override
        public void afterCommit() {
            System.out.println("afterCommit");
        }

        //总是回调,没有参数,在完成前回调
        @Override
        public void beforeCompletion() {
            System.out.println("beforeCompletion");
        }

        //总是回调,status为0是提交成功,status为1是提交失败,在完成后回调
        @Override
        public void afterCompletion(int status) {
            System.out.printf("afterCompletion status:%s\n",status);
        }
    });
}

一个简单的事务同步管理器的使用,它就是一个简单的线程级变量的实现。

[User[id:1,name:cat,age:6], User[id:2,name:fish,age:7]]
transactionName:spring_test.UserAo.addOne,isReadOnly:false,isolationLevel:null
before Commit,isReadOnly :false
beforeCompletion
afterCommit
afterCompletion status:0
[User[id:1,name:cat,age:6], User[id:2,name:fish,age:7], User[id:23,name:cat,age:700]]

这是输出结果

//数据有异常,被回滚
@Transactional(isolation = Isolation.REPEATABLE_READ)
public void addOneAndHaveRuntimeException(){

    this.showTransactionInfo();
    User user = new User();
    user.setName("cat");
    user.setAge(700);
    userService.save(user);

    this.addTransactionNotify();

    throw new RuntimeException("Throw by me");
}

我们看一下有异常需要回滚的时候,会产生什么输出。

[User[id:1,name:cat,age:6], User[id:2,name:fish,age:7], User[id:23,name:cat,age:700]]
transactionName:spring_test.UserAo.addOneAndHaveRuntimeException,isReadOnly:false,isolationLevel:4
beforeCompletion
afterCompletion status:1
[User[id:1,name:cat,age:6], User[id:2,name:fish,age:7], User[id:23,name:cat,age:700]]

没有了beforeCommit和afterCommit的通知了,而且afterCompletion的status变量也不同。

//没有使用同步管理器,可以看到不会有输出.同步管理器只在当前的事务里面是单次有效的.
@Transactional(isolation = Isolation.REPEATABLE_READ)
public void addOneAndNoSyncTransaction(){
    User user = new User();
    user.setName("cat");
    user.setAge(700);
    userService.save(user);
}

最后,如果在一个事务中没有调用事务同步管理器,就什么输出也没有。

[User[id:1,name:cat,age:6], User[id:2,name:fish,age:7], User[id:23,name:cat,age:700]]
[User[id:1,name:cat,age:6], User[id:2,name:fish,age:7], User[id:23,name:cat,age:700], User[id:25,name:cat,age:700]]

这是输出

5.2.3 事务嵌套

代码在这里

如果有一段过程中经过了多个有事务注解的方法,就称为事务嵌套。这个问题很容易就会踩坑,要十分注解。在实际开发中,尽可能避免使用事务嵌套。

//默认的事务传播为REQUIRED,执行成功
@Transactional(propagation = Propagation.REQUIRED)
public void mod1_AgeOne(){
    User user = new User();
    user.setId(1);
    user.setAge(1);
    userService.mod(user);
}

这是默认的没有嵌套的情况

 //默认的事务传播为REQUIRED,执行失败
@Transactional(propagation = Propagation.REQUIRED)
public void mod2_AgeTwo(){
    User user = new User();
    user.setId(1);
    user.setAge(2);
    ((UserAo)AopContext.currentProxy()).mod2_AgeTwo_inner();
}

//默认的事务传播为REQUIRED,当已经有事务的时候,就沿用原来的事务,没有的话就重新开事务
@Transactional(propagation = Propagation.REQUIRED)
public void mod2_AgeTwo_inner(){
    User user = new User();
    user.setId(1);
    user.setAge(3);
    userService.mod(user);

    throw new RuntimeException("throw by me");
}

这是mod2中嵌套了mod2_inner方法的情况,由于使用了Propagation.REQUIRED配置,所以,mod2_inner方法会沿用mod2的同一个事务。

//默认的事务传播为REQUIRED,
@Transactional(propagation = Propagation.REQUIRED)
public void mod3_AgeThree(){
    User user = new User();
    user.setId(1);
    user.setAge(3);
    //错误用法,因为mod3_AgeThree_inner沿用已经同一个事务,已经捕捉到了RunTimeException,该事务被标注了回滚状态,但是依然企图对外部事务执行commit操作
    try {
        ((UserAo) AopContext.currentProxy()).mod3_AgeThree_inner();
    }catch(Exception e){

    }
}

@Transactional(propagation = Propagation.REQUIRED)
public void mod3_AgeThree_inner(){
    User user = new User();
    user.setId(1);
    user.setAge(3);
    userService.mod(user);

    throw new RuntimeException("throw by me");
}

这是一个经常犯错的例子,因为mod3_inner使用了Propagation.REQUIRED,所以,它会沿用mod3的同一个事务。但是,在mod3_inner中已经抛出了RuntimeException异常,所以,被Spring事务标注了这个事务为回滚状态。但是,在mod3强行按住了异常不放出来,所以mod3就会发出警报,因为该事务已经标注了回滚状态,但是没有收到异常。

org.springframework.transaction.UnexpectedRollbackException: Transaction rolled back because it has been marked as rollback-only
    at org.springframework.transaction.support.AbstractPlatformTransactionManager.processRollback(AbstractPlatformTransactionManager.java:873)
    at org.springframework.transaction.support.AbstractPlatformTransactionManager.commit(AbstractPlatformTransactionManager.java:710)
    at org.springframework.transaction.interceptor.TransactionAspectSupport.commitTransactionAfterReturning(TransactionAspectSupport.java:532)
    at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:304)
    at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:98)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:185)
    at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:688)
    at spring_test.UserAo$$EnhancerBySpringCGLIB$$928f914d.mod3_AgeThree(&lt;generated&gt;)
    at spring_test.App.test3(App.java:85)

这个是上述错误的时候,产生的日志,网上一大堆这类犯错的。

@Transactional(propagation = Propagation.REQUIRED)
public void mod4_AgeFour(){
    User user = new User();
    user.setId(1);
    user.setAge(4);
    userService.mod(user);

    //正确用法,因为mod4_AgeThree_inner总是新建一个事务,所以它的事务是否回滚与外部事务没有关系
    try {
        ((UserAo) AopContext.currentProxy()).mod4_AgeFive_inner();
    }catch(Exception e){
    }

}

//使用REQUIRES_NEW的事务传播,表达总是使用新事务来执行代码,当异常发生的时候会触发回滚
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void mod4_AgeFive_inner(){
    User user = new User();
    //当这里的id改为1的时候就会产生死锁。
    user.setId(2);
    user.setAge(5);
    userService.mod(user);

    throw new RuntimeException("throw by me");
}

正确的做法是,让mod4_inner使用REQUIRES_NEW的事务传播模式,而不是REQUIRED的事务传播模式。它的含义是,mod4总是自己新建一个事务来执行代码,并挂起原来的事务。所以,当mod4的事务收到异常要回滚的时候,它并不影响外部事务是否回滚。

但是,这个方法会产生新的问题!因为,mod4是处于事务执行状态的,同时它又在等待mod4_inner方法的执行完成。所以,如果mod4_inner在修改mod4已有的数据行的时候,它就会在数据库层面等待mod4数据的释放。这显然,产生了死锁。而且这种死锁十分隐蔽,mod4对mod4_inner的依赖是应用层面的,而mod4_inner对mod4的依赖是数据库层面的,因此这类死锁数据库是不会报错的!它只会在等待锁超时后发出警告而已。

//默认的事务传播为REQUIRED,
@Transactional(propagation = Propagation.REQUIRED)
public void mod5_AgeSix(){
    User user = new User();
    user.setId(1);
    user.setAge(6);
    userService.mod(user);

    //正确用法,因为mod4_AgeThree_inner总是新建一个事务,所以它的事务是否回滚与外部事务没有关系
    try {
        ((UserAo) AopContext.currentProxy()).mod5_AgeSeven_inner();
    }catch(Exception e){

    }
}

//使用NOT_SUPPORTED的事务传播,总是用无事务状态来执行操作,当异常发生的时候没有回滚
@Transactional(propagation = Propagation.NOT_SUPPORTED)
public void mod5_AgeSeven_inner(){
    User user = new User();
    user.setId(2);
    user.setAge(7);
    userService.mod(user);

    throw new RuntimeException("throw by me");
}

最后,我们看一个有用的NOT_SUPPORTED的事务传播模式,它的意思是,mod5_inner总是使用无事务的状态来执行代码,并将原来的事务挂起。

5.3 原理

在AOP原理的一节中,我们看到,Spring的AOP会自动添加所有满足Advisor接口的bean作为增强器,所以依赖于AOP的事务实现,也会添加自己的Advisor。

这个就是TransactionAttributeSourceAdvisor(P272页),然后该Advisor会使用TransactionAttributeSourcePointcut(P273页)来查找该类是否有@Transactional注解来决定是否开启事务增强代理。最后,对于有@Transactional注解的方法,会使用TransactionInterceptor(P277页)来执行代理。

所以,原理关键在TransactionInterceptor的实现,具体可以看一下书本,难度不高。主要是事务嵌套那里的实现要特别注意各个之间有什么不同。

6 总结

第一次这么系统地看一个大框架的代码,收获良多,简直是Java代码的典范。在开发上,我收获到了:

  • 输入对象的接口抽象,像Spring对于不同资源的抽象,P27页。
  • 实现过程中,使用接口来解耦对不同具体对象的实现,像AOP中对不同Advisor的抽象,P190页
  • 使用接口作为需要不同功能的开关。像Aware接口,实现了不同的Aware接口相当于指定对当前bean打开不同的功能开关。
  • 逐文件作为类隔离局部变量
  • 并发控制,两次检查缓存。在获取单例上,首先有一次无锁地检查一次缓存(P94页)。当检查到没有数据以后,依然需要在有锁的状态下检查一次缓存(P98页)。这样是为了考虑并发环境下,单例可能造成创建多次的问题,这个知识点值得注意。
  • 在循环状态下捕捉异常,并重试的逻辑。如果在循环中不需要重试的话,就直接抛出异常的就好了。但是在循环中需要重试的话,你还需要将之前的异常都保存起来,形成异常链,这样返回给调用端的错误提示才是完整的。具体看P115页。
  • 缓存中其实空有两个意思,这个key还没有开始查缓存,和,这个key查了缓存以后依然永久为空。这点也很漂亮,Spring中使用null和NULL_OBJECT来区分。P94页。
  • 面向对象是自底向上的思维,接口让不同差异的实现归入到同一个接口上。

参考资料:

相关文章