美文网首页
Spring | 2.0.0

Spring | 2.0.0

作者: 不一样的卡梅利多 | 来源:发表于2020-05-19 17:09 被阅读0次

本文主要内容包含 2.0 IoC实现 ,AOP实现 , aspectj原理与应用。

简介

2.0.0 版本发布时间为2006-10-03 ,JDK 为 1.5,同时期阿里将EJB 替换为Spring 。2.0 新增两个重要特性JavaBeans-based configuration,集成aspects。

IoC 部分

主要分析功能点:
  1. 新增bean 的自动装配,包含BY_NAME,BY_TYPE,CONSTRUCTOR
  2. 新增bean scope 实现。
  3. context 功能增强。
  4. 新增扩展接口
IoC 容器核心类图

0.9 实现 对比2.0 如下图,

0.9 容器.png
2.0 容器.png

2.0主要类职责
1、XMLBeanFactory : 负责读取配置文件以及注册bean配置信息。 这部分功能的实现委派给了 new XmlBeanDefinitionReader(BeanDefinitionRegistry),继承父类功能(强耦合),获取bean,bean 信息查询

2、BeanDefinitionRegistry: 负责bean 定义的注册,该功能从0.9 的ListableBeanFactory 独立出来,功能拆分。让类的职责更加单一。单一职责需要依据具体的业务场景与功能来看,没有统一的标准。

3、DefaultListableBeanFactory : 依赖BeanDefinitionRegistry 实现 查询bean 注册信息功能,复用父类功能。

4、bean 创建与连接的配置信息类

1、HierarchicalBeanFactory :继承beanFactory,比如一个文件文件对应一个BeanFactory,将所有配置文件merge到一起
2、SingletonBeanRegistry :单例配置
3、ConfigurableBeanFactory :添加的BeanPostProcessor 前后值处理,bean 的属性处理,bean 的作用范围,bean 销毁,与一个bean 的声明周期相关

5、AutowireCapableBeanFactory :bean 依据配置的自动装配类型进行装陪

Object autowire(Class beanClass, int autowireMode, boolean dependencyCheck) throws BeansException;

 void autowireBeanProperties(Object existingBean, int autowireMode, boolean dependencyCheck) throws BeansException;

Object createBean(Class beanClass, int autowireMode, boolean dependencyCheck) throws BeansException;

6、核心实现类 AbstractBeanFactory:bean 的生成,AbstractAutowireCapableBeanFactory: bean 自动依赖注入。

Scope 实现
1、单例:缓存创建对象,一次createBean

    if (mergedBeanDefinition.isSingleton()) {
                sharedInstance = getSingleton(beanName, new ObjectFactory() {
                    public Object getObject() throws BeansException {
                      return createBean(beanName, mergedBeanDefinition, args);
                });
                bean = getObjectForBeanInstance(sharedInstance, name, mergedBeanDefinition);
            }

2、原型:每次getBean ,每次createBean。实现自动注入后,要实现原型。可以用代理模式,实现每次方法调用绑定的对象为BeanFactory.getBean() 详见 ScopedObject 对象的实现

 if (mergedBeanDefinition.isPrototype()) {
                // It's a prototype -> create a new instance.
                Object prototypeInstance = null;
                try {
                    beforePrototypeCreation(beanName);
                    prototypeInstance = createBean(beanName, mergedBeanDefinition, args);
                }
                finally {
                    afterPrototypeCreation(beanName);
                }
                bean = getObjectForBeanInstance(prototypeInstance, name, mergedBeanDefinition);
            }

原型代理对象生成:

    public Object getTargetObject() {
        return this.beanFactory.getBean(this.targetBeanName);
    }

3、自定义 :自定义 createBean 次数和如何创建 bean。

final Scope scope = (Scope) this.scopes.get(scopeName);
Object scopedInstance = scope.get(beanName, new ObjectFactory() {
                        public Object getObject() throws BeansException {
                            beforePrototypeCreation(beanName);
Object bean = createBean(beanName, mergedBeanDefinition, args);
afterPrototypeCreation(beanName);
}
bean = getObjectForBeanInstance(scopedInstance, name, mergedBeanDefinition);

beans 组件的新增扩展点
1、xml 命名空间支持与自定义解析增强相关接口:

NamespaceHandler、NamespaceHandlerResolver

META-INF/spring.schemas

http\://www.springframework.org/schema/tx/spring-tx-2.0.xsd=org/springframework/transaction/config/spring-tx-2.0.xsd

META-INF/spring.handlers

http\://www.springframework.org/schema/tx=org.springframework.transaction.config.TxNamespaceHandler

2、bean 实例化的前后置处理接口BeanPostProcessor,postProcessBeforeInitialization,postProcessAfterInitialization

3、生成bean 对象后,执行bean 初始化操AbstractAutowireCapableBeanFactory#initializeBean作,BeanNameAware,BeanClassLoaderAware,BeanPostProcessor

    BeanNameAware.setBeanName
    BeanClassLoaderAware.setBeanClassLoader
    BeanFactoryAware.setBeanFactory
    applyBeanPostProcessorsBeforeInitialization  (BeanPostProcessor 接口)
    invokeInitMethods
    applyBeanPostProcessorsAfterInitialization   (BeanPostProcessor 接口)

Context
核心类图

Context.png

类职责:
ClassPathXmlApplicationContext :定义xml 文件位置
AbstractXmlApplicationContext :读取xml 文件,解析注册
AbstractRefreshableApplicationContext #refreshBeanFactory:创建beanfactory ,调用AbstractXmlApplicationContext 进行bean 注册
AbstractApplicationContext :bean 流程管理,并且与context 集成

客户端使用:
New ClassPathXmlApplicationContext(file) 执行流程

 this.configLocations = configLocations;
 refresh();

主要流程还是在refresh 方法中,
1、创建BeanFactory,refreshBeanFactory,getBeanFactory
2、设置BeanFactory配置

    addPropertyEditorRegistrar
    addBeanPostProcessor(ApplicationContextAwareProcessor) ,与context 接口集成
    ignoreDependencyInterface
    postProcessBeanFactory(beanFactory)对BeanFactory 进行自定义设置

3、BeanFactoryPostProcessor,对bean 注册完成后进行修改的扩展点。比如属性文件读取的 PropertyPlaceholderConfigurer 就是一个BeanFactoryPostProcessor。解析bean 定义后,读取项目属性文件。

//扩展 ,context add BeanFactoryPostProcessor
 BeanFactoryPostProcessor.postProcessBeanFactory (beanFactory)
//调用所有 bean 实现了 BeanFactoryPostProcessor 接口,来自于BeanDefine
invokeBeanFactoryPostProcessors :

4、registerBeanPostProcessors,将 所有实现 BeanPostProcessor 接口的bean 注册到 BeanFactory 中,供实例化bean 时候调用

5、其他步骤
initMessageSource :国际化资源文件
initApplicationEventMulticaster
onRefresh 扩展
registerListeners
beanFactory.preInstantiateSingletons(); 实例化所有单例bean
publishEvent(new ContextRefreshedEvent(this));

小结
IoC 部分整体框架与流程与0.9 变化不大,添加了很多新的扩展点供客户端使用。对项目中使用的类进行了更细粒度的拆分,更高维度的抽象,比如 Resource 来定义资源。0.9 版本中只有File。2.0 版本 实现了 更多的扩展,更细的拆分,更高的抽象。框架随着业务进行演进。

AOP 部分

简介:
AOP 实现的目标就是讲两部分的代码集成到一起来执行。比如业务代码bussinessMethod+通用代码(切面代码)commonMethod 。为了到达 执行bussinessMethod 的时候,也将 commonMethod 执行了。并且需要对客户端是透明的。例如:
call bussinessMethod() :
bussinessMethod();commonMethod() 或者commonMethod() ;bussinessMethod()

Spring 的主要抽象
Advice :代表 commonMethod(切面代码块)。有before ,after ,表示添加到bussinessMethod的什么地方。
Advisor :对Advice的一次封装
TargetSource:对代理目标的描述
MethodMatcher :方法匹配 ,bussinessMethod 与commonMethod 是否匹配
Interceptor :是一个特殊的Advice。
Pointcut :切点,将bussinessMethod 与commonMethod 连接到一起。

Aspectj 的抽象
Advice : before ,after,afterreturning ,xxxx , 将业务代码与通用代码关联。切面代码以及切点在通用代码什么位置。与spring 一致
Join point: 业务代码method 定义,commonMethod
Pointcut : Advice(commonMethod)+Join point (bussinessMethod)连接关系, 与spring MethodMatcher 类似
Aspect : 一系列 Pointcut 的组合。

Spring AOP 实现
Spring 依据JdkDynamicAopProxy,Cglib2AopProxy 来实现AOP 功能,并且适配Aspectj 的注解配置形式,将Aspectj 的配置解析到Spring 的抽象上面来。配置的注解和接触注解依赖Aspectj 内置提供的功能。

核心类图:


ProxyFactory.png

抽象工厂模式:
ProxyConfig —>AopProxyFactory#createAopProxy —>AopProxy(Fatory)#getObject()

AopProxyFactory :依据ProxyConfig 配置是接口代理还是类代理,接口代理使用创建JdkDynamicAopProxy ,类代理创建Cglib2AopProxy。
AopProxy :依据配置的advice ,TargetSource 创建代理对象。

客户端使用

ProxyFactory pf = new ProxyFactory();
pf.getProxy()

实现:

    public static Object getProxy(Class proxyInterface, Interceptor interceptor) {
        ProxyFactory proxyFactory = new ProxyFactory();
        proxyFactory.addInterface(proxyInterface);
        proxyFactory.addAdvice(interceptor);
        return proxyFactory.getProxy();
    }

手动配置创建Spring 容器的代理,ProxyFactoryBean,Interceptor 来源于xml 配置文件

ProxyFactoryBean.png
   public Object getObject() throws BeansException {
        //  initializeAdvisorChain();
        addAdvisorOnChainCreation(advice, this.interceptorNames[i]);
       
        //getSingletonInstance 使用 ProxyFactory 创建代理对
        this.singletonInstance = getProxy(createAopProxy());
    
    }

自动创建代理对象,使用aspectj ,将aspectj 配置的切面适配到spring 的内容上面来,AnnotationAwareAspectJAutoProxyCreator 是一个BeanPostProcessor,Spring 实例化后,会进行2次修改创建的对象。查询系统中所有配置的@aspect 注解的类,对创建的对象生成代理对象。


AnnotationAwareAspectJAutoProxyCreator.png

AdvisorFactory :Advisor 生产工厂,将aspectj 的advice 转换成spring 的advice。ReflectiveAspectJAdvisorFactory 将解析 aspectj 定义的注解 before ,after 等


AdvisorFactory.png

ReflectiveAspectJAdvisorFactory 部分代码


ReflectiveAspectJAdvisorFactory.png

小结:
由于添加了支持aspectj 语法的支持,做了大量工作将aspectj 与spring 之间进行适配。创建代理对象的核心是找到定义的advice,ProxyFactory 依据advice 创建代理对象。

Aspectj 部分

简介
Aspectj 是一个对java 语言就行切面编程的框架,依赖对java 自己码的修改,来实现业务代码与切面代码的连接。项目由以下jar 包组成:

aspectjrt - the AspectJ runtime
aspectjweaver - the AspectJ weaver ,包含了aspectjrt ,java agent 等
aspectjtools - the AspectJ compiler

Aspectj 编写切面的方式
1、使用.aj 源文件,这种语法需要用ajc 编译成字节码。无论是依赖那种植入方式,都需要先使用ajc 编译。

public aspect AccountAspect {
    final int MIN_BALANCE = 10;

    pointcut callWithDraw(int amount, Account acc) :
            call(boolean Account.withdraw(int)) && args(amount) && target(acc);

    before(int amount, Account acc) : callWithDraw(amount, acc) {
    }

    boolean around(int amount, Account acc) :
            callWithDraw(amount, acc) {
        System.out.println("aspect ..");
        if (acc.balance < amount) {
            return false;
        }
        return proceed(amount, acc);
    }

    after(int amount, Account balance) : callWithDraw(amount, balance) {
    }
}

2、使用注解方式。这种可以使用javac 编译,也可以使用ajc 编译。

@Aspect
public class SecuredMethodAspect {

    @Pointcut("@annotation(secured)")
    public void callAt(Secured secured) {
    }
 
    @Around("callAt(secured)")
    public Object around(ProceedingJoinPoint pjp,
                         Secured secured) throws Throwable {
        return secured.isLocked() ? null : pjp.proceed();
    }
}

AspectJ 对java 字节码修改时机
1、编译时期植入:使用ajc 将 java 和aj 文件编译成字节码。
2、编译后植入 : 使用ajc ,依据aspect 配置重新编译已经存在的字节码。效果和编译时植入一样
3、加载时植入(LTW) :类加载的时候进行植入,依赖java agent 实现。

编译时植入测试
AccountAspect.aj 文件

public aspect AccountAspect {
  final int MIN_BALANCE = 10;

  pointcut callWithDraw(int amount, Account acc) :
          call(boolean Account.withdraw(int)) && args(amount) && target(acc);

  before(int amount, Account acc) : callWithDraw(amount, acc) {
  }

  boolean around(int amount, Account acc) :
          callWithDraw(amount, acc) {
      System.out.println("aspect ..");
      if (acc.balance < amount) {
          return false;
      }
      return proceed(amount, acc);
  }

  after(int amount, Account balance) : callWithDraw(amount, balance) {
  }
}

ajc 编译后 使用IDEA 反编译(有问题的)

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

package com.github.yulechen.aspectj.model;

import org.aspectj.lang.NoAspectBoundException;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.runtime.internal.AroundClosure;

@Aspect
public class AccountAspect {
    final int MIN_BALANCE = 10;

    static {
        try {
            ajc$postClinit();
        } catch (Throwable var1) {
            ajc$initFailureCause = var1;
        }

    }

    public AccountAspect() {
    }

    @Before(
        value = "callWithDraw(amount, acc)",
        argNames = "amount,acc"
    )
    public void ajc$before$com_github_yulechen_aspectj_model_AccountAspect$1$14220714(int amount, Account acc) {
    }

    @Around(
        value = "callWithDraw(amount, acc)",
        argNames = "amount,acc,ajc$aroundClosure"
    )
    public boolean ajc$around$com_github_yulechen_aspectj_model_AccountAspect$2$14220714(int amount, Account acc, AroundClosure ajc$aroundClosure) {
        System.out.println("aspect ..");
        return ajc$inlineAccessFieldGet$com_github_yulechen_aspectj_model_AccountAspect$com_github_yulechen_aspectj_model_Account$balance(acc) < amount ? false : ajc$around$com_github_yulechen_aspectj_model_AccountAspect$2$14220714proceed(amount, acc, ajc$aroundClosure);
    }

    @After(
        value = "callWithDraw(amount, balance)",
        argNames = "amount,balance"
    )
    public void ajc$after$com_github_yulechen_aspectj_model_AccountAspect$3$a7995619(int amount, Account balance) {
    }

    public static AccountAspect aspectOf() {
        if (ajc$perSingletonInstance == null) {
            throw new NoAspectBoundException("com_github_yulechen_aspectj_model_AccountAspect", ajc$initFailureCause);
        } else {
            return ajc$perSingletonInstance;
        }
    }

    public static boolean hasAspect() {
        return ajc$perSingletonInstance != null;
    }
}

调用的地方的字节码被修改了:


image.png

编译时期植入:
1、编译时期,需要使用aspectjtools 进行编译。
2、运行,需要依赖 aspectjrt。

加载时植入测试:
1、配置jvm 启动参数
-javaagent:path/aspectjweaver.jar
2、配置classpath 下面 META-INF/aop.xml 文件,aspectjweaver 会依据默认的路径读取aop.xml 文件来判断加载类的时候是否进行植入。
3、如果使用aj 源文件,则还需要使用ajc 进行编译后才能使用,如果使用注解配置方式,则只需要使用javac 编译就可以。
4、自己码是在加载的时候进行修改。不是在磁盘上面,类似于groovy 的classloader 。

IDEA反编译坑:
AnnotationBeanConfigurerAspect.class idea 反编译后

@Aspect
public class AnnotationBeanConfigurerAspect extends AbstractBeanConfigurerAspect {
    static {
        try {
            ajc$postClinit();
        } catch (Throwable var1) {
            ajc$initFailureCause = var1;
        }

    }

    public AnnotationBeanConfigurerAspect() {
        this.setBeanWiringInfoResolver(new AnnotationBeanWiringInfoResolver());
    }

    public static AnnotationBeanConfigurerAspect aspectOf() {
        if (ajc$perSingletonInstance == null) {
            throw new NoAspectBoundException("org_springframework_beans_factory_aspectj_AnnotationBeanConfigurerAspect", ajc$initFailureCause);
        } else {
            return ajc$perSingletonInstance;
        }
    }

    public static boolean hasAspect() {
        return ajc$perSingletonInstance != null;
    }
}

show 字节码,字节码上面的内容和idea 反编译有不一致的地方。


AnnotationBeanConfigurerAspect.bytecode
Spring 集成aspectj

1、@Configurable 注解

 @Configurable("beanOne")
 private static class ShouldBeConfiguredBySpring  {

     private String name;

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

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

 }

Spring bean beanConfigurerTests.xml配置

<bean  class="org.springframework.beans.factory.aspectj.AnnotationBeanConfigurerAspect"
            factory-method="aspectOf"/>

    <bean id="beanOne" 
          class="com.github.yulechen.spring.test.Main$ShouldBeConfiguredBySpring"
          lazy-init="true">
        <property name="name" value="Rod"/>
    </bean>

myObject.getName==Rod

 new ClassPathXmlApplicationContext("beanConfigurerTests.xml");
 ShouldBeConfiguredBySpring myObject = new ShouldBeConfiguredBySpring();

怎么实现的?

public aspect AnnotationBeanConfigurerAspect extends AbstractBeanConfigurerAspect {
    
    public AnnotationBeanConfigurerAspect() {
        setBeanWiringInfoResolver(new AnnotationBeanWiringInfoResolver());
    }

    /**
     * The creation of a new bean (an object with the @Configurable annotation)
     */
    protected pointcut beanCreation(Object beanInstance) :
        initialization((@Configurable *).new(..)) && this(beanInstance);

}

切面匹配:
1、只有匹配到有@Configurable 注解的类,new 创建对象的之后,会调用AbstractBeanConfigurerAspect#configureBean(beanInstance);

2、AnnotationBeanConfigurerAspect 是一个单例,在整个系统中只有一份。Spring 读取bean定义的时候,把BeanFactory 注入到了AnnotationBeanConfigurerAspect 中,并且Spring 创建 AnnotationBeanConfigurerAspect 使用了factory-method="aspectOf" ,这个源码中没有该方法,使用ajc 编译后,所有的切面都会添加该方法,见前面编译时期植入代码。

3、LTW 植入
1、 添加jvm 启动参数 -javaagent:path/aspectjweaver.jar
2、aop 文件存在于spring-aspectj.jar/META-INF/aop.xml 中。

Spring LTW 实现

LTW 实现方式有两种
1、使用java agent,hook 所有类加载器。
2、使用自定义classloader
Spring classloader 方式实现
AbstractOverridingClassLoader,重写类加载过程,


image.png

Spring java agent 方式实现,只实现了获取Instrumentation 对象。需要启动的时候添加 -javaagent 参数


InstrumentationSavingAgent.png

后续扩展操作:

public void addTransformer(ClassFileTransformer transformer) {
        Assert.notNull(transformer, "Transformer must not be null");
        Instrumentation instrumentation = InstrumentationSavingAgent.getInstrumentation();
        if (instrumentation == null) {
            throw new IllegalStateException(
                    "Must start with Java agent to use InstrumentationLoadTimeWeaver. See Spring documentation.");
        }
        instrumentation.addTransformer(transformer);
    }

小结:
aspectj 整体在项目中使用不多,但是如果普通的切面编程没法满足需求时候。可以使用aspectj 现有的功能。java agent 在APM 项目中使用较多。可以通过java agent 获取jvm 运行时期的一些信息,没有代码侵入性。java agent 的instrumentation 是jvm 实现的留下的埋点,通过事件驱动触发。

Spring 专题

附录:
Java agent 实现原理
Java agent demo
JVMTI 规范
aspectj docs

相关文章

网友评论

      本文标题:Spring | 2.0.0

      本文链接:https://www.haomeiwen.com/subject/afrbohtx.html