乐趣区

关于java:手写Spring框架是时候撸个AOP与Bean生命周期融合了

作者:小傅哥
博客:https://bugstack.cn

积淀、分享、成长,让本人和别人都能有所播种!😄

一、前言

嘎小子,这片代码水太深你把握不住!

在电视剧《楚汉传奇》中有这么一段刘邦与韩信的饮酒对话,刘邦问韩信我那个 曹参 读过书见过世面能带多少兵,韩信说能带一万五,又补充说一万五都吃力。刘邦又一一说出 樊哙 卢绾 周勃,韩信笑着说有余 2 万,脑子不行。这时候刘邦有点挂不住脸了,问:那我呢,我能带多少兵。韩信说,你能带十万。刘邦一看比他们都多,啊,还行。转头一想就问韩信那你呢,你能带多少兵。韩信喝多了,说啊,我,我多多益善。这时候刘邦恼了领导劲上来了,问:那我为什么能管着你,你给我说,说呀!


这像不像你领导问你,你能写多少代码、搭多少框架、接多少我的项目。可能很大一部分没经验太多的新人码农,仅仅是能实现一些简略的功能模块开发,而没有方法驾驭整个我的项目的波及到的所有工程,也不能为我的项目提炼出一些可复用的通用性组件模块。在高级码农的心里,接一点需要还好,但没有人带的时候齐全接一个较大型我的项目就会比拟慌了,不晓得这里有没有坑,本人也把握住不。这些代码一块块的带着能写,然而都弄到一块,就太难了!

在代码开发成长的这条路上,要经验 CRUD、ERP 查数据、接口包装、性能开发、服务整合、零碎建设等,始终到独立带人承当较大型我的项目的搭建。这一过程须要你能有大量的编写代码教训积攒和简单问题的解决伎俩,之后能力一段段的把看似独立的模块后者代码片段组装成一个较大型能跑起来的我的项目。就像 Spring 的开发过程一样,咱们总是一直在增加新的性能片段,最初又把技术实现与 Spring 容器整合,让应用方能够更简略的使用 Spring 提供的能力。

二、指标

在上一章节咱们通过基于 Proxy.newProxyInstance 代理操作中解决办法匹配和办法拦挡,对匹配的对象进行自定义的解决操作。并把这样的技术核心内容拆解到 Spring 中,用于实现 AOP 局部,通过拆分后根本能够明确各个类的职责,包含你的代理指标对象属性、拦截器属性、办法匹配属性,以及两种不同的代理操作 JDK 和 CGlib 的形式。

再有了一个 AOP 外围性能的实现后,咱们能够通过单元测试的形式进行验证切面性能对办法进行拦挡,但如果这是一个面向用户应用的性能,就不太可能让用户这么简单且没有与 Spring 联合的形式独自应用 AOP,尽管能够满足需要,但应用上还是过来扩散。

因而咱们须要在本章节实现 AOP 外围性能与 Spring 框架的整合,最终能通过在 Spring 配置的形式实现切面的操作。

三、计划

  1. 从 BeanPostProcessor 开始,让 xml 中的配置加载到 DefaultAdvisorAutoProxyCreator 实现,
  2. 其实再有了外围性能的开发后,这事也不难。只不过咱们要解决几个问题,包含:怎么串联到 Bean 的生命周期中、怎么组装各项性能、怎么适配代理,
  3. 借着 BeanPostProcessor,把动静代理融入到 Bean 的生命周期

其实在有了 AOP 的外围性能实现后,把这部分性能服务融入到 Spring 其实也不难,只不过要解决几个问题,包含:怎么借着 BeanPostProcessor 把动静代理融入到 Bean 的生命周期中,以及如何组装各项切点、拦挡、前置的性能和适配对应的代理器。整体设计构造如下图:

  • 为了能够让对象创立过程中,能把 xml 中配置的代理对象也就是切面的一些类对象实例化,就须要用到 BeanPostProcessor 提供的办法,因为这个类的中的办法能够别离作用与 Bean 对象执行初始化前后批改 Bean 的对象的扩大信息。但这里须要汇合于 BeanPostProcessor 实现新的接口和实现类,这样能力定向获取对应的类信息。
  • 但因为创立的是代理对象不是之前流程里的一般对象,所以咱们须要前置于其余对象的创立,所以在理论开发的过程中,须要在 AbstractAutowireCapableBeanFactory#createBean 优先实现 Bean 对象的判断,是否须要代理,有则间接返回代理对象。在 Spring 的源码中会有 createBean 和 doCreateBean 的办法拆分
  • 这里还包含要解决办法拦截器的具体性能,提供一些 BeforeAdvice、AfterAdvice 的实现,让用户能够更简化的应用切面性能。除此之外还包含须要包装切面表达式以及拦挡办法的整合,以及提供不同类型的代理形式的代理工厂,来包装咱们的切面服务。

四、实现

1. 工程构造

small-spring-step-12
└── src
    ├── main
    │   └── java
    │       └── cn.bugstack.springframework
    │           ├── aop
    │           │   ├── aspectj
    │           │   │   └── AspectJExpressionPointcut.java
    │           │   │   └── AspectJExpressionPointcutAdvisor.java
    │           │   ├── framework 
    │           │   │   ├── adapter
    │           │   │   │   └── MethodBeforeAdviceInterceptor.java
    │           │   │   ├── autoproxy
    │           │   │   │   └── MethodBeforeAdviceInterceptor.java
    │           │   │   ├── AopProxy.java
    │           │   │   ├── Cglib2AopProxy.java
    │           │   │   ├── JdkDynamicAopProxy.java
    │           │   │   ├── ProxyFactory.java
    │           │   │   └── ReflectiveMethodInvocation.java
    │           │   ├── AdvisedSupport.java
    │           │   ├── Advisor.java
    │           │   ├── BeforeAdvice.java
    │           │   ├── ClassFilter.java
    │           │   ├── MethodBeforeAdvice.java
    │           │   ├── MethodMatcher.java
    │           │   ├── Pointcut.java
    │           │   ├── PointcutAdvisor.java
    │           │   └── TargetSource.java
    │           ├── beans
    │           │   ├── factory
    │           │   │   ├── config
    │           │   │   │   ├── AutowireCapableBeanFactory.java
    │           │   │   │   ├── BeanDefinition.java
    │           │   │   │   ├── BeanFactoryPostProcessor.java
    │           │   │   │   ├── BeanPostProcessor.java
    │           │   │   │   ├── BeanReference.java
    │           │   │   │   ├── ConfigurableBeanFactory.java
    │           │   │   │   ├── InstantiationAwareBeanPostProcessor.java
    │           │   │   │   └── SingletonBeanRegistry.java
    │           │   │   ├── support
    │           │   │   │   ├── AbstractAutowireCapableBeanFactory.java
    │           │   │   │   ├── AbstractBeanDefinitionReader.java
    │           │   │   │   ├── AbstractBeanFactory.java
    │           │   │   │   ├── BeanDefinitionReader.java
    │           │   │   │   ├── BeanDefinitionRegistry.java
    │           │   │   │   ├── CglibSubclassingInstantiationStrategy.java
    │           │   │   │   ├── DefaultListableBeanFactory.java
    │           │   │   │   ├── DefaultSingletonBeanRegistry.java
    │           │   │   │   ├── DisposableBeanAdapter.java
    │           │   │   │   ├── FactoryBeanRegistrySupport.java
    │           │   │   │   ├── InstantiationStrategy.java
    │           │   │   │   └── SimpleInstantiationStrategy.java  
    │           │   │   ├── support
    │           │   │   │   └── XmlBeanDefinitionReader.java
    │           │   │   ├── Aware.java
    │           │   │   ├── BeanClassLoaderAware.java
    │           │   │   ├── BeanFactory.java
    │           │   │   ├── BeanFactoryAware.java
    │           │   │   ├── BeanNameAware.java
    │           │   │   ├── ConfigurableListableBeanFactory.java
    │           │   │   ├── DisposableBean.java
    │           │   │   ├── FactoryBean.java
    │           │   │   ├── HierarchicalBeanFactory.java
    │           │   │   ├── InitializingBean.java
    │           │   │   └── ListableBeanFactory.java
    │           │   ├── BeansException.java
    │           │   ├── PropertyValue.java
    │           │   └── PropertyValues.java 
    │           ├── context
    │           │   ├── event
    │           │   │   ├── AbstractApplicationEventMulticaster.java 
    │           │   │   ├── ApplicationContextEvent.java 
    │           │   │   ├── ApplicationEventMulticaster.java 
    │           │   │   ├── ContextClosedEvent.java 
    │           │   │   ├── ContextRefreshedEvent.java 
    │           │   │   └── SimpleApplicationEventMulticaster.java 
    │           │   ├── support
    │           │   │   ├── AbstractApplicationContext.java 
    │           │   │   ├── AbstractRefreshableApplicationContext.java 
    │           │   │   ├── AbstractXmlApplicationContext.java 
    │           │   │   ├── ApplicationContextAwareProcessor.java 
    │           │   │   └── ClassPathXmlApplicationContext.java 
    │           │   ├── ApplicationContext.java 
    │           │   ├── ApplicationContextAware.java 
    │           │   ├── ApplicationEvent.java 
    │           │   ├── ApplicationEventPublisher.java 
    │           │   ├── ApplicationListener.java 
    │           │   └── ConfigurableApplicationContext.java
    │           ├── core.io
    │           │   ├── ClassPathResource.java 
    │           │   ├── DefaultResourceLoader.java 
    │           │   ├── FileSystemResource.java 
    │           │   ├── Resource.java 
    │           │   ├── ResourceLoader.java 
    │           │   └── UrlResource.java
    │           └── utils
    │               └── ClassUtils.java
    └── test
        └── java
            └── cn.bugstack.springframework.test
                ├── bean
                │   ├── IUserService.java
                │   ├── UserService.java
                │   └── UserServiceInterceptor.java
                └── ApiTest.java

工程源码 公众号「bugstack 虫洞栈」,回复:Spring 专栏,获取残缺源码

AOP 动静代理融入到 Bean 的生命周期中类关系,如图 13-2

  • 整个类关系图中能够看到,在以 BeanPostProcessor 接口实现继承的 InstantiationAwareBeanPostProcessor 接口后,做了一个主动代理创立的类 DefaultAdvisorAutoProxyCreator,这个类的就是用于解决整个 AOP 代理融入到 Bean 生命周期中的外围类。
  • DefaultAdvisorAutoProxyCreator 会依赖于拦截器、代理工厂和 Pointcut 与 Advisor 的包装服务 AspectJExpressionPointcutAdvisor,由它提供切面、拦挡办法和表达式。
  • Spring 的 AOP 把 Advice 细化了 BeforeAdvice、AfterAdvice、AfterReturningAdvice、ThrowsAdvice,目前咱们做的测试案例中只用到了 BeforeAdvice,这部分能够对照 Spring 的源码进行补充测试。

2. 定义 Advice 拦截器链

cn.bugstack.springframework.aop.BeforeAdvice

public interface BeforeAdvice extends Advice {}

cn.bugstack.springframework.aop.MethodBeforeAdvice

public interface MethodBeforeAdvice extends BeforeAdvice {

    /**
     * Callback before a given method is invoked.
     *
     * @param method method being invoked
     * @param args   arguments to the method
     * @param target target of the method invocation. May be <code>null</code>.
     * @throws Throwable if this object wishes to abort the call.
     *                   Any exception thrown will be returned to the caller if it's
     *                   allowed by the method signature. Otherwise the exception
     *                   will be wrapped as a runtime exception.
     */
    void before(Method method, Object[] args, Object target) throws Throwable;

}
  • 在 Spring 框架中,Advice 都是通过办法拦截器 MethodInterceptor 实现的。盘绕 Advice 相似一个拦截器的链路,Before Advice、After advice 等,不过临时咱们须要那么多就只定义了一个 MethodBeforeAdvice 的接口定义。

3. 定义 Advisor 访问者

cn.bugstack.springframework.aop.Advisor

public interface Advisor {

    /**
     * Return the advice part of this aspect. An advice may be an
     * interceptor, a before advice, a throws advice, etc.
     * @return the advice that should apply if the pointcut matches
     * @see org.aopalliance.intercept.MethodInterceptor
     * @see BeforeAdvice
     */
    Advice getAdvice();}

cn.bugstack.springframework.aop.PointcutAdvisor

public interface PointcutAdvisor extends Advisor {

    /**
     * Get the Pointcut that drives this advisor.
     */
    Pointcut getPointcut();}
  • Advisor 承当了 Pointcut 和 Advice 的组合,Pointcut 用于获取 JoinPoint,而 Advice 决定于 JoinPoint 执行什么操作。

cn.bugstack.springframework.aop.aspectj.AspectJExpressionPointcutAdvisor

public class AspectJExpressionPointcutAdvisor implements PointcutAdvisor {

    // 切面
    private AspectJExpressionPointcut pointcut;
    // 具体的拦挡办法
    private Advice advice;
    // 表达式
    private String expression;

    public void setExpression(String expression){this.expression = expression;}

    @Override
    public Pointcut getPointcut() {if (null == pointcut) {pointcut = new AspectJExpressionPointcut(expression);
        }
        return pointcut;
    }

    @Override
    public Advice getAdvice() {return advice;}

    public void setAdvice(Advice advice){this.advice = advice;}

}
  • AspectJExpressionPointcutAdvisor 实现了 PointcutAdvisor 接口,把切面 pointcut、拦挡办法 advice 和具体的拦挡表达式包装在一起。这样就能够在 xml 的配置中定义一个 pointcutAdvisor 切面拦截器了。

4. 办法拦截器

cn.bugstack.springframework.aop.framework.adapter.MethodBeforeAdviceInterceptor

public class MethodBeforeAdviceInterceptor implements MethodInterceptor {

    private MethodBeforeAdvice advice;

    public MethodBeforeAdviceInterceptor(MethodBeforeAdvice advice) {this.advice = advice;}

    @Override
    public Object invoke(MethodInvocation methodInvocation) throws Throwable {this.advice.before(methodInvocation.getMethod(), methodInvocation.getArguments(), methodInvocation.getThis());
        return methodInvocation.proceed();}

}
  • MethodBeforeAdviceInterceptor 实现了 MethodInterceptor 接口,在 invoke 办法中调用 advice 中的 before 办法,传入对应的参数信息。
  • 而这个 advice.before 则是用于本人实现 MethodBeforeAdvice 接口后做的相应解决。其实能够看到具体的 MethodInterceptor 实现类,其实和咱们之前做的测试是一样的,只不过当初交给了 Spring 来解决

5. 代理工厂

cn.bugstack.springframework.aop.framework.ProxyFactory

public class ProxyFactory {

    private AdvisedSupport advisedSupport;

    public ProxyFactory(AdvisedSupport advisedSupport) {this.advisedSupport = advisedSupport;}

    public Object getProxy() {return createAopProxy().getProxy();}

    private AopProxy createAopProxy() {if (advisedSupport.isProxyTargetClass()) {return new Cglib2AopProxy(advisedSupport);
        }

        return new JdkDynamicAopProxy(advisedSupport);
    }

}
  • 其实这个代理工厂次要解决的是对于 JDK 和 Cglib 两种代理的抉择问题,有了代理工厂就能够依照不同的创立需要进行管制。

6. 融入 Bean 生命周期的主动代理创建者

cn.bugstack.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator

public class DefaultAdvisorAutoProxyCreator implements InstantiationAwareBeanPostProcessor, BeanFactoryAware {

    private DefaultListableBeanFactory beanFactory;

    @Override
    public void setBeanFactory(BeanFactory beanFactory) throws BeansException {this.beanFactory = (DefaultListableBeanFactory) beanFactory;
    }

    @Override
    public Object postProcessBeforeInstantiation(Class<?> beanClass, String beanName) throws BeansException {if (isInfrastructureClass(beanClass)) return null;

        Collection<AspectJExpressionPointcutAdvisor> advisors = beanFactory.getBeansOfType(AspectJExpressionPointcutAdvisor.class).values();

        for (AspectJExpressionPointcutAdvisor advisor : advisors) {ClassFilter classFilter = advisor.getPointcut().getClassFilter();
            if (!classFilter.matches(beanClass)) continue;

            AdvisedSupport advisedSupport = new AdvisedSupport();

            TargetSource targetSource = null;
            try {targetSource = new TargetSource(beanClass.getDeclaredConstructor().newInstance());
            } catch (Exception e) {e.printStackTrace();
            }
            advisedSupport.setTargetSource(targetSource);
            advisedSupport.setMethodInterceptor((MethodInterceptor) advisor.getAdvice());
            advisedSupport.setMethodMatcher(advisor.getPointcut().getMethodMatcher());
            advisedSupport.setProxyTargetClass(false);

            return new ProxyFactory(advisedSupport).getProxy();}

        return null;
    }
    
}
  • 这个 DefaultAdvisorAutoProxyCreator 类的次要外围实现在于 postProcessBeforeInstantiation 办法中,从通过 beanFactory.getBeansOfType 获取 AspectJExpressionPointcutAdvisor 开始。
  • 获取了 advisors 当前就能够遍历相应的 AspectJExpressionPointcutAdvisor 填充对应的属性信息,包含:指标对象、拦挡办法、匹配器,之后返回代理对象即可。
  • 那么当初调用方获取到的这个 Bean 对象就是一个曾经被切面注入的对象了,当调用办法的时候,则会被按需拦挡,解决用户须要的信息。

五、测试

1. 当时筹备

public class UserService implements IUserService {public String queryUserInfo() {
        try {Thread.sleep(new Random(1).nextInt(100));
        } catch (InterruptedException e) {e.printStackTrace();
        }
        return "小傅哥,100001,深圳";
    }

    public String register(String userName) {
        try {Thread.sleep(new Random(1).nextInt(100));
        } catch (InterruptedException e) {e.printStackTrace();
        }
        return "注册用户:" + userName + "success!";
    }

}
  • 在 UserService 中提供了 2 个不同办法,另外你还能够减少新的类来退出测试。前面咱们的测试过程,会给这个两个办法增加咱们的拦挡解决,打印办法执行耗时。

2. 自定义拦挡办法

public class UserServiceBeforeAdvice implements MethodBeforeAdvice {

    @Override
    public void before(Method method, Object[] args, Object target) throws Throwable {System.out.println("拦挡办法:" + method.getName());
    }

}
  • 与上一章节的拦挡办法相比,咱们不在是实现 MethodInterceptor 接口,而是实现 MethodBeforeAdvice 盘绕拦挡。在这个办法中咱们能够获取到办法的一些信息,如果还开发了它的 MethodAfterAdvice 则能够两个接口一起实现。

3. spring.xml 配置 AOP

<beans>

    <bean id="userService" class="cn.bugstack.springframework.test.bean.UserService"/>

    <bean class="cn.bugstack.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"/>

    <bean id="beforeAdvice" class="cn.bugstack.springframework.test.bean.UserServiceBeforeAdvice"/>

    <bean id="methodInterceptor" class="cn.bugstack.springframework.aop.framework.adapter.MethodBeforeAdviceInterceptor">
        <property name="advice" ref="beforeAdvice"/>
    </bean>

    <bean id="pointcutAdvisor" class="cn.bugstack.springframework.aop.aspectj.AspectJExpressionPointcutAdvisor">
        <property name="expression" value="execution(* cn.bugstack.springframework.test.bean.IUserService.*(..))"/>
        <property name="advice" ref="methodInterceptor"/>
    </bean>

</beans>
  • 这回再应用 AOP 就能够像 Spring 中一样,通过在 xml 中配置即可。因为咱们曾经把 AOP 的性能交融到 Bean 的生命周期里去了,你的新增拦挡办法都会被主动解决。

4. 单元测试

@Test
public void test_aop() {ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("classpath:spring.xml");
    IUserService userService = applicationContext.getBean("userService", IUserService.class);
    System.out.println("测试后果:" + userService.queryUserInfo());
}
  • 在单元测试中你只须要依照失常获取和应用 Bean 对象即可,不过这个时候如果被切面拦挡了,那么其实你获取到的就是对应的代理对象外面的解决操作了。

测试后果

拦挡办法:queryUserInfo
测试后果:小傅哥,100001,深圳

Process finished with exit code 0
  • 通过测试后果能够看到,咱们曾经让拦挡办法失效了,也不须要本人手动解决切面、拦挡办法等内容。截图上能够看到,这个时候的 IUserService 就是一个代理对象

六、总结

  • 本章节实现 AOP 性能的外在体现次要是把以前本人在单元测试中的切面拦挡,交给 Spring 的 xml 配置了,也就不须要本人手动解决了。那么这里有一个十分重要的知识点,就是把相应的性能如何与 Spring 的 Bean 生命周期联合起来,本章节用到的 BeanPostProcessor,因为它能够解决在 Bean 对象执行初始化办法之前,用于批改新实例化 Bean 对象的扩大点,所以咱们也就能够解决本人的 AOP 代理对象逻辑了。
  • 一个性能的实现往往包含外围局部、组装局部、链接局部,为了这些各自职责的分工,则须要创立接口和类,由不同关系的继承、实现进行组装。只有明确了各个职责分工,才好灵便的扩大相应的性能逻辑,否则很难驾驭大型零碎的开发和建设,也就是那种不好把握的感觉。
  • 目前咱们实现的 AOP 与 Spring 源码中的外围逻辑是相似的,但更会偏简略一些,也不会思考更多的简单场景遇到的问题,包含是否有构造函数、是否为代理中的切面等。其实也能够看出只有是 Java 中的一些个性,都须要在实在应用的 Spring 中进行残缺的实现,否则在应用这些性能的时候就会遇到各种问题。

七、系列举荐

  • 对于 Spring 中 getBean 的全流程源码解析
  • 你说,怎么把 Bean 塞到 Spring 容器?
  • SpringBoot 中间件设计和开发
  • 数学,离一个程序员有多近?
  • 半年招聘筛选了 400+ 份简历,通知你怎么写容易被撩!
退出移动版