2
头图

Author: Xiao Fu Ge
Blog: https://bugstack.cn

Precipitate, share, and grow, so that you and others can gain something! 😄

I. Introduction

Ga kid, this piece of code is too deep for you to grasp!

In the TV series "Legend of Chu Han", there is a drinking dialogue between Liu Bang and Han Xin. Liu Bang asked Han Xin. My Cao has read the book and has seen how many soldiers can be brought by the world. Han Xin said that he could bring 15 thousand, and added 15 thousand. It's all strenuous. Liu Bang Fan Kui, Lu Wan, Zhou Bo, and Han Xin smiled and said that he was less than 20,000, and his mind was not good. At this time, Liu Bang was a little embarrassed, and asked: What about me, how many soldiers can I bring. Han Xin said, you can bring one hundred thousand. Liu Bang saw that there were more than them, ah, it was okay. Turning his head and thinking about it, I asked Han Xin what about you, how many soldiers can you bring. Han Xin drank too much, and said, me, the more I am, the better. At this time, Liu Bang was annoyed by his leadership and asked: Then why can I control you? Tell me, tell me!


It is not like your leader asking you how much code you can write, how many frameworks you can build, and how many projects you can accept. Perhaps a large part of the newcomers who have not experienced too many programmers can only complete the development of some simple functional modules, but cannot control all the projects involved in the entire project, nor can they extract some reusable versatility for the project. Component module. In the minds of junior coders, it's okay to pick up a bit of demand, but when no one takes it, it will be more panic to pick up a larger project. I don't know if there are any pits here, and I can't grasp it. These codes can be written piece by piece, but it is too difficult to get one piece!

On the road of code development and growth, one has to go through CRUD, ERP data checking, interface packaging, function development, service integration, system construction, etc., until it independently leads people to undertake the construction of larger projects. This process requires you to have a lot of experience in writing code and the means to deal with complex problems, and then you can assemble the seemingly independent modules and the code fragments into a larger project that can be run. Just like the Spring development process, we are always adding new feature fragments, and finally integrate the technology implementation with the Spring container, so that users can more easily use the capabilities provided by Spring.

2. Goal

In the previous chapter, we used the processing method matching and method interception in the proxy operation based on Proxy.newProxyInstance to perform custom processing operations on the matched objects. And disassemble the core content of this technology into Spring to implement the AOP part. After splitting, you can basically clarify the responsibilities of each class, including your proxy target object attributes, interceptor attributes, method matching attributes, and two Different agents operate the way JDK and CGlib.

After the realization of an AOP core function, we can verify the aspect function to intercept the method through unit testing, but if this is a user-oriented function, it is unlikely that the user will be so complicated and not related to Spring The combined method uses AOP alone, although it can meet the demand, but the use is still scattered in the past.

Therefore, we need to complete the integration of the AOP core functions and the Spring framework in this chapter, and finally we can complete the aspect operations through the Spring configuration.

Three, the plan

  1. Starting from BeanPostProcessor, let the configuration in xml be loaded into the DefaultAdvisorAutoProxyCreator implementation,
  2. In fact, with the development of core functions, this is not difficult. We just have to solve a few problems, including: how to connect to the life cycle of Bean, how to assemble various functions, how to adapt the agent,
  3. With BeanPostProcessor, dynamic proxy is integrated into the life cycle of Bean

In fact, after the realization of the core functions of AOP, it is not difficult to integrate this part of the functional services into Spring. It just has to solve a few problems, including: how to integrate dynamic proxy into the life cycle of Bean through BeanPostProcessor, and How to assemble various pointcuts, intercepts, pre-functions and adapt the corresponding agents. The overall design structure is as follows:

  • In order to be able to instantiate the proxy objects configured in xml, that is, some class objects of the aspect, during the object creation process, you need to use the methods provided by BeanPostProcessor, because the methods in this class can be used to initialize the Bean object separately Modify the extended information of the Bean object before and after. But here needs to be assembled in BeanPostProcessor to implement new interfaces and implementation classes, so that the corresponding class information can be obtained directionally.
  • But because the proxy object created is not an ordinary object in the previous process, we need to precede the creation of other objects, so in the actual development process, it is necessary to complete the judgment of the Bean object first in AbstractAutowireCapableBeanFactory#createBean, whether it needs a proxy, If there is, the proxy object is returned directly. will have createBean and doCreateBean methods in the Spring source code to split
  • It also includes the specific functions of the method interceptor to be solved, and provides some implementations of BeforeAdvice and AfterAdvice, so that users can more simplify the use of aspect functions. In addition, it also includes the need to package aspect expressions and the integration of interception methods, as well as agency factories that provide different types of agency methods to package our aspect services.

Fourth, realize

1. Engineering structure

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

project source code : public account "bugstack wormhole stack", reply: Spring column, get the complete source code

The AOP dynamic proxy is integrated into the class relationship in the Bean's life cycle, as shown in Figure 13-2

图 13-2

  • As can be seen in the entire class relationship diagram, after implementing the inherited InstantiationAwareBeanPostProcessor interface with the BeanPostProcessor interface, an automatic proxy creation class DefaultAdvisorAutoProxyCreator is made. This class is the core class used to process the entire AOP proxy into the Bean life cycle.
  • DefaultAdvisorAutoProxyCreator will rely on the interceptor, proxy factory and Pointcut and Advisor packaging service AspectJExpressionPointcutAdvisor, which provides aspects, interception methods and expressions.
  • Spring's AOP refines Advice into BeforeAdvice, AfterAdvice, AfterReturningAdvice, and ThrowsAdvice. At present, only BeforeAdvice is used in the test cases we are doing. This part can be supplemented with Spring's source code.

2. Define the Advice interceptor chain

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;

}
  • In the Spring framework, advice is implemented through the method interceptor MethodInterceptor. Surrounding Advice is similar to the link of an interceptor, Before Advice, After advice, etc., but for the time being, we only need to define an interface definition of MethodBeforeAdvice.

3. Define Advisor Visitors

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 is responsible for the combination of Pointcut and Advice, Pointcut is used to obtain JoinPoint, and Advice determines what operation JoinPoint performs.

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 implements the PointcutAdvisor interface, which wraps the aspect pointcut, interception method advice and specific interception expressions together. In this way, a pointcutAdvisor aspect interceptor can be defined in the xml configuration.

4. Method Interceptor

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 implements the MethodInterceptor interface. In the invoke method, the before method in the advice is called and the corresponding parameter information is passed in.
  • And this advice.before is for the corresponding processing after implementing the MethodBeforeAdvice interface. can actually see the specific MethodInterceptor implementation class, which is actually the same as the test we did before, but now it is handed over to Spring to handle

5. Agent Factory

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);
    }

}
  • In fact, this agent factory mainly solves the problem of choosing between JDK and Cglib agents. With an agent factory, it can be controlled according to different creation requirements.

6. Automatic proxy creator integrated into the Bean life cycle

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;
    }
    
}
  • The main core implementation of the DefaultAdvisorAutoProxyCreator class is in the postProcessBeforeInstantiation method, starting from getting the AspectJExpressionPointcutAdvisor through beanFactory.getBeansOfType.
  • After obtaining the advisors, you can traverse the corresponding AspectJExpressionPointcutAdvisor to fill in the corresponding attribute information, including: target object, interception method, matcher, and then return the proxy object.
  • Now the Bean object obtained by the caller is an object that has been injected by the aspect. When the method is called, it will be intercepted on demand to process the information that the user needs.

Five, test

1. Prepare in advance

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!";
    }

}
  • Two different methods are provided in UserService, and you can also add new classes to join the test. Later in our testing process, we will add our interception processing to these two methods, and the printing method takes time to execute.

2. Custom interception method

public class UserServiceBeforeAdvice implements MethodBeforeAdvice {

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

}
  • Compared with the interception method in the previous chapter, we no longer implement the MethodInterceptor interface, but implement MethodBeforeAdvice to surround interception. In this method, we can get some information about the method. If the MethodAfterAdvice is also developed, the two interfaces can be implemented together.

3. Spring.xml configuration 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>
  • This time you can use AOP again, just like in Spring, by configuring it in xml. Because we have integrated the functions of AOP into the life cycle of the Bean, your new interception methods will be automatically processed.

4. Unit Testing

@Test
public void test_aop() {
    ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("classpath:spring.xml");
    IUserService userService = applicationContext.getBean("userService", IUserService.class);
    System.out.println("测试结果:" + userService.queryUserInfo());
}
  • In the unit test, you only need to obtain and use the Bean object as normal, but if it is intercepted by the aspect at this time, then what you get is actually the processing operation in the corresponding proxy object.

test result

拦截方法:queryUserInfo
测试结果:小傅哥,100001,深圳

Process finished with exit code 0
  • Through the test results, we can see that we have made the interception method take effect, and there is no need to manually handle aspects, interception methods and other content. As you can see from the screenshot of

Six, summary

  • The external manifestation of the realization of the AOP function in this chapter is mainly to intercept the previous aspects in the unit test and hand it over to Spring's xml configuration, so there is no need to handle it manually. So there is a very important point of knowledge here, that is, how to combine the corresponding functions with Spring's Bean life cycle. The BeanPostProcessor used in this chapter can be used to modify the new instantiation before the Bean object executes the initialization method. The extension point of the Bean object, so we can deal with our own AOP proxy object logic.
  • The realization of a function often includes the core part, the assembly part, and the link part. For the division of these respective responsibilities, it is necessary to create interfaces and classes, which are assembled by inheritance and realization of different relationships. Only by clarifying the division of responsibilities can we flexibly expand the corresponding functional logic. Otherwise, it will be difficult to control the development and construction of large-scale systems, which is the kind of feeling of unsure.
  • At present, the core logic of the AOP we have implemented is similar to the core logic in the Spring source code, but it will be simpler, and will not consider the problems encountered in more complex scenarios, including whether there is a constructor, whether it is an aspect in the proxy, etc. . In fact, it can be seen that as long as some features in Java, they need to be fully implemented in the real Spring, otherwise various problems will be encountered when using these features.

Seven, series recommendation


小傅哥
4.7k 声望28.4k 粉丝

CodeGuide | 程序员编码指南 - 原创文章、案例源码、资料书籍、简历模版等下载。