1
头图

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

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

I. Introduction

Why, your code always sticks to the pigpen?

🎙What to do, know that you are on the Internet, but don't know which big factory you are in. Knowing that you are working overtime, not knowing which product you are arguing with. Know you are lazy, don't know when you want to fish. Know that you are moving bricks, but don't know which pigsty you are building.


When you have worked hard day and night to complete the repetitive work every day, week, and month, the growth you can get is the least, and the return you get is little. keep the most sweat and hold the least money

Maybe you start to look at the source code when you get excited, but don't know where you can use the source code after reading it. Look at the design pattern and understand when you look at it, but you can't change your own code. In fact, on the one hand, the knowledge of its own technology stack is insufficient, and on the other hand, the code reserved by itself is not enough. In the end, it is impossible to connect a series of knowledge at all, just like you , but you can't think that the data hash in the sub-database and sub-table components will also use the disturbance function idea and Poisson distribution in HashMap. Verification, looked at the Spring source code, and I can’t read how Mybatis solves the problem of using configuration or annotation to perform CRUD operations on the database only by defining the Dao interface. seems to be the dynamic proxy of the JDK, and I can’t think of how AOP is designed. Therefore, learning in a systematic manner and strengthening the integrity of the technology stack knowledge can make better use of these learned coding capabilities.

2. Goal

In this chapter, we will shift from the implementation of IOC to the development Aspect Oriented Programming In the software industry, AOP means: aspect-oriented programming, which implements unified maintenance of program functions through pre-compilation and dynamic agents during runtime. In fact, AOP is also a continuation of OOP. It is a very important content in the Spring framework. Using AOP can isolate various parts of business logic, thereby reducing the coupling of business logic between modules and improving code reusability. It can also improve development efficiency.

The core technology implementation of AOP is mainly the use of dynamic proxy, just like you can give an interface implementation class, use proxy to replace this implementation class, and use proxy class to process the logic you need. such as:

@Test
public void test_proxy_class() {
    IUserService userService = (IUserService) Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(), new Class[]{IUserService.class}, (proxy, method, args) -> "你被代理了!");
    String result = userService.queryUserInfo();
    System.out.println("测试结果:" + result);
}

Everyone has basically seen the implementation of proxy classes, so after having a basic idea, then you need to consider how to proxy methods instead of proxy classes. In addition, how to delegate all methods in all classes that meet certain rules. If you can proxy the methods of all classes, you can make a method interceptor and add some custom processing to all proxy methods, such as printing logs, recording time-consuming, and monitoring exceptions.

Three, the plan

Before integrating the entire aspect design of AOP into Spring, we need to solve two problems, including: how to proxy the method that meets the rules, and the case of the proxy method, split the responsibilities of the class. The realization of these two function points is designed and developed with a cross-cutting idea. as cutting leeks with a knife. It’s always a bit slow to cut one by one. Then use your hand (160ef96b30ae3b proxy) to squeeze the leeks into a handful, and use a kitchen knife or an axe to intercept different kinds of leeks. Operation to deal with. The same is true in the program, except that leek has become a method, and the chopper has become an interception method. The overall design structure is as follows:

  • Just like you are using Spring's AOP, only handle some methods that need to be intercepted. After intercepting the method, perform your extension operations on the method.
  • Then we need to implement a proxy method that can proxy method first. In fact, the proxy method mainly uses the method interceptor class to process the method call MethodInterceptor#invoke method.invoke(targetObj, args) parameter Method method in the invoke method to perform the 060ef96b30aefe block. Time difference.
  • In addition to the realization of the above core functions, we also need to use org.aspectj.weaver.tools.PointcutParser process the interception expression "execution(* cn.bugstack.springframework.test.bean.IUserService.*(..))" . With the method proxy and the processing interception, we can complete the design of an AOP prototype.

Fourth, realize

1. Engineering structure

![spring-12-02](https://bugstack.cn/assets/images/spring/spring-12-02.png)![spring-12-02](https://bugstack.cn/assets/images/spring/spring-12-02.png)small-spring-step-11
└── src
    ├── main
    │   └── java
    │       └── cn.bugstack.springframework
    │           ├── aop
    │           │   ├── aspectj
    │           │   │   └── AspectJExpressionPointcut.java
    │           │   ├── framework 
    │           │   │   ├── AopProxy.java
    │           │   │   ├── Cglib2AopProxy.java
    │           │   │   ├── JdkDynamicAopProxy.java
    │           │   │   └── ReflectiveMethodInvocation.java
    │           │   ├── AdvisedSupport.java
    │           │   ├── ClassFilter.java
    │           │   ├── MethodMatcher.java
    │           │   ├── Pointcut.java
    │           │   └── TargetSource.java
    │           ├── beans
    │           │   ├── factory
    │           │   │   ├── config
    │           │   │   │   ├── AutowireCapableBeanFactory.java
    │           │   │   │   ├── BeanDefinition.java
    │           │   │   │   ├── BeanFactoryPostProcessor.java
    │           │   │   │   ├── BeanPostProcessor.java
    │           │   │   │   ├── BeanReference.java
    │           │   │   │   ├── ConfigurableBeanFactory.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

AOP pointcut expression and use, and dynamic proxy class relationship based on JDK and CGLIB, as shown in Figure 12-2

图 12-2

  • The entire class diagram is where AOP implements the core logic. The upper part is about the matching implementation of methods, and the following is about the proxy operation of methods starting from AopProxy.
  • The core function of AspectJExpressionPointcut mainly relies on the aspectj component and handles the implementation of Pointcut, ClassFilter, and MethodMatcher interfaces, specifically for processing the matching filtering operations of classes and methods.
  • AopProxy is an abstract object of proxy, and its implementation is mainly based on JDK proxy and Cglib proxy. In the previous chapter about object instantiation CglibSubclassingInstantiationStrategy, we have also used the functions provided by Cglib.

2. Agency method case

Before realizing the core functions of AOP, let's make a case of the proxy method. Through such a core overview of the proxy method, we can better understand the subsequent disassembly methods and design the AOP implementation process of decoupling functions. .

unit test

@Test
public void test_proxy_method() {
    // 目标对象(可以替换成任何的目标对象)
    Object targetObj = new UserService();
    // AOP 代理
    IUserService proxy = (IUserService) Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(), targetObj.getClass().getInterfaces(), new InvocationHandler() {
        // 方法匹配器
        MethodMatcher methodMatcher = new AspectJExpressionPointcut("execution(* cn.bugstack.springframework.test.bean.IUserService.*(..))");
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            if (methodMatcher.matches(method, targetObj.getClass())) {
                // 方法拦截器
                MethodInterceptor methodInterceptor = invocation -> {
                    long start = System.currentTimeMillis();
                    try {
                        return invocation.proceed();
                    } finally {
                        System.out.println("监控 - Begin By AOP");
                        System.out.println("方法名称:" + invocation.getMethod().getName());
                        System.out.println("方法耗时:" + (System.currentTimeMillis() - start) + "ms");
                        System.out.println("监控 - End\r\n");
                    }
                };
                // 反射调用
                return methodInterceptor.invoke(new ReflectiveMethodInvocation(targetObj, method, args));
            }
            return method.invoke(targetObj, args);
        }
    });
    String result = proxy.queryUserInfo();
    System.out.println("测试结果:" + result);
}
  • First of all, the goal of the whole case is to treat a UserService as the target object, intercept all methods in the class and add monitoring information printing processing.
  • From the case, you can see that there is a proxy implementation Proxy.newProxyInstance, a method matching MethodMatcher, a reflection call invoke(Object proxy, Method method, Object[] args), and the user's own interception of the operation of the method. In this way, it is actually very similar to the AOP we use, but when you use AOP, the framework has already provided better functions. Here is to show you all the core processes.

test result

监控 - Begin By AOP
方法名称:queryUserInfo
方法耗时:86ms
监控 - End

测试结果:小傅哥,100001,深圳

Process finished with exit code 0
  • From the test results, we can see that we have intercepted and monitored the UserService#queryUserInfo method. In fact, the AOP we implemented later is the result now reflected, but we need to decouple this part of the test case into a more extensible Implementation of each module.

disassembly case

图 12-3

  • For the disassembly process, please refer to screenshot 12-3. We need to disassemble the proxy object because it can be implemented by JDK or processed by Cglib.
  • The method matcher operation is actually a separate implementation class, but we also need to package the incoming target object, method matching, and interception methods in a unified way to facilitate an incoming parameter transparent transmission when external calls are made.
  • Finally, it is ReflectiveMethodInvocation the use of 060ef96b30b431. It is currently MethodInvocation interface. The parameter information includes: the called object, the called method, and the called input parameters.

3. Cut Point Expression

defines interface

cn.bugstack.springframework.aop.Pointcut

public interface Pointcut {

    /**
     * Return the ClassFilter for this pointcut.
     * @return the ClassFilter (never <code>null</code>)
     */
    ClassFilter getClassFilter();

    /**
     * Return the MethodMatcher for this pointcut.
     * @return the MethodMatcher (never <code>null</code>)
     */
    MethodMatcher getMethodMatcher();

}
  • The pointcut interface defines two classes for obtaining ClassFilter and MethodMatcher. The acquisition of these two interfaces is the content provided by the pointcut expression.

cn.bugstack.springframework.aop.ClassFilter

public interface ClassFilter {

    /**
     * Should the pointcut apply to the given interface or target class?
     * @param clazz the candidate target class
     * @return whether the advice should apply to the given target class
     */
    boolean matches(Class<?> clazz);

}
  • Define the class matching class to find the given interface and target class for the pointcut.

cn.bugstack.springframework.aop.MethodMatcher

public interface MethodMatcher {

    /**
     * Perform static checking whether the given method matches. If this
     * @return whether or not this method matches statically
     */
    boolean matches(Method method, Class<?> targetClass);
    
}
  • Method matching, find the target class and method under the match in the expression range. This is reflected in the above case: methodMatcher.matches(method, targetObj.getClass())

implements the tangent expression class

public class AspectJExpressionPointcut implements Pointcut, ClassFilter, MethodMatcher {

    private static final Set<PointcutPrimitive> SUPPORTED_PRIMITIVES = new HashSet<PointcutPrimitive>();

    static {
        SUPPORTED_PRIMITIVES.add(PointcutPrimitive.EXECUTION);
    }

    private final PointcutExpression pointcutExpression;

    public AspectJExpressionPointcut(String expression) {
        PointcutParser pointcutParser = PointcutParser.getPointcutParserSupportingSpecifiedPrimitivesAndUsingSpecifiedClassLoaderForResolution(SUPPORTED_PRIMITIVES, this.getClass().getClassLoader());
        pointcutExpression = pointcutParser.parsePointcutExpression(expression);
    }

    @Override
    public boolean matches(Class<?> clazz) {
        return pointcutExpression.couldMatchJoinPointsInType(clazz);
    }

    @Override
    public boolean matches(Method method, Class<?> targetClass) {
        return pointcutExpression.matchesMethodExecution(method).alwaysMatches();
    }

    @Override
    public ClassFilter getClassFilter() {
        return this;
    }

    @Override
    public MethodMatcher getMethodMatcher() {
        return this;
    }

}
  • The pointcut expression implements three interface definition methods, Pointcut, ClassFilter, and MethodMatcher. At the same time, this class is mainly used for the expression verification methods provided by the aspectj package.
  • Matches: pointcutExpression.couldMatchJoinPointsInType(clazz) , pointcutExpression.matchesMethodExecution(method).alwaysMatches() , this part of the content can be tested and verified separately.

match verification

@Test
public void test_aop() throws NoSuchMethodException {
    AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut("execution(* cn.bugstack.springframework.test.bean.UserService.*(..))");
    Class<UserService> clazz = UserService.class;
    Method method = clazz.getDeclaredMethod("queryUserInfo");   

    System.out.println(pointcut.matches(clazz));
    System.out.println(pointcut.matches(method, clazz));          
    
    // true、true
}
  • Here is a separate verification test of the matching method to see if the method you intercept matches the corresponding object.

4. Packaging section notification information

cn.bugstack.springframework.aop.AdvisedSupport

public class AdvisedSupport {

    // 被代理的目标对象
    private TargetSource targetSource;
    // 方法拦截器
    private MethodInterceptor methodInterceptor;
    // 方法匹配器(检查目标方法是否符合通知条件)
    private MethodMatcher methodMatcher;
    
    // ...get/set
}
  • AdvisedSupport is mainly used to pack the various attributes of proxy, interception, and matching into a class, which is convenient for use in the Proxy implementation class. This is the same as packaging in your business development
  • TargetSource, a target object, provides Object input parameter attributes in the target object class and obtains the TargetClass information of the target class.
  • MethodInterceptor is a specific interception method implementation class. The user implements the MethodInterceptor#invoke method for specific processing. like our case in this article is to do method monitoring processing
  • MethodMatcher is an operation of matching methods. This object is served by AspectJExpressionPointcut.

5. Proxy abstract implementation (JDK&Cglib)

defines the interface

cn.bugstack.springframework.aop.framework

public interface AopProxy {

    Object getProxy();

}
  • Define a standard interface for obtaining proxy classes. Because the specific way to implement the proxy can be either JDK or Cglib, it will be more convenient to manage implementation classes by defining interfaces.

cn.bugstack.springframework.aop.framework.JdkDynamicAopProxy

public class JdkDynamicAopProxy implements AopProxy, InvocationHandler {

    private final AdvisedSupport advised;

    public JdkDynamicAopProxy(AdvisedSupport advised) {
        this.advised = advised;
    }

    @Override
    public Object getProxy() {
        return Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(), advised.getTargetSource().getTargetClass(), this);
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        if (advised.getMethodMatcher().matches(method, advised.getTargetSource().getTarget().getClass())) {
            MethodInterceptor methodInterceptor = advised.getMethodInterceptor();
            return methodInterceptor.invoke(new ReflectiveMethodInvocation(advised.getTargetSource().getTarget(), method, args));
        }
        return method.invoke(advised.getTargetSource().getTarget(), args);
    }

}
  • The proxy class implemented based on JDK needs to implement the interfaces AopProxy and InvocationHandler, so that the proxy object getProxy and the reflection invocation method invoke can be handled separately.
  • The getProxy method is to proxy the operation of an object, and you need to provide the input parameters ClassLoader, AdvisedSupport, and the current class this, because this class provides the invoke method.
  • After the matching method is mainly processed in the invoke method, the method provided by the user is used to intercept the implementation, and the methodInterceptor.invoke is called by reflection.
  • There is also a ReflectiveMethodInvocation here, and the other is the package information of the input parameter, which provides the input object: target object, method, and input parameter.

cn.bugstack.springframework.aop.framework.Cglib2AopProxy

public class Cglib2AopProxy implements AopProxy {

    private final AdvisedSupport advised;

    public Cglib2AopProxy(AdvisedSupport advised) {
        this.advised = advised;
    }

    @Override
    public Object getProxy() {
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(advised.getTargetSource().getTarget().getClass());
        enhancer.setInterfaces(advised.getTargetSource().getTargetClass());
        enhancer.setCallback(new DynamicAdvisedInterceptor(advised));
        return enhancer.create();
    }

    private static class DynamicAdvisedInterceptor implements MethodInterceptor {

        @Override
        public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
            CglibMethodInvocation methodInvocation = new CglibMethodInvocation(advised.getTargetSource().getTarget(), method, objects, methodProxy);
            if (advised.getMethodMatcher().matches(method, advised.getTargetSource().getTarget().getClass())) {
                return advised.getMethodInterceptor().invoke(methodInvocation);
            }
            return methodInvocation.proceed();
        }
    }

    private static class CglibMethodInvocation extends ReflectiveMethodInvocation {

        @Override
        public Object proceed() throws Throwable {
            return this.methodProxy.invoke(this.target, this.arguments);
        }

    }

}
  • Cglib-based classes that use the Enhancer proxy can use the underlying ASM bytecode enhancement technology to process the proxy object generation for the interface during runtime, so the proxy class does not need to implement any interface.
  • Regarding the extended user interception method, it is mainly handled in Enhancer#setCallback, the user's own new interception processing. Here you can see that DynamicAdvisedInterceptor#intercept has done the corresponding reflection operation after matching the method.

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 UserServiceInterceptor implements MethodInterceptor {

    @Override
    public Object invoke(MethodInvocation invocation) throws Throwable {
        long start = System.currentTimeMillis();
        try {
            return invocation.proceed();
        } finally {
            System.out.println("监控 - Begin By AOP");
            System.out.println("方法名称:" + invocation.getMethod());
            System.out.println("方法耗时:" + (System.currentTimeMillis() - start) + "ms");
            System.out.println("监控 - End\r\n");
        }
    }

}
  • The user-defined interception method needs to implement the invoke method of the MethodInterceptor interface. The usage is very similar to Spring AOP. It also wraps invocation.proceed() for release, and adds monitoring information to finally.

3. Unit Testing

@Test
public void test_dynamic() {
    // 目标对象
    IUserService userService = new UserService();     

    // 组装代理信息
    AdvisedSupport advisedSupport = new AdvisedSupport();
    advisedSupport.setTargetSource(new TargetSource(userService));
    advisedSupport.setMethodInterceptor(new UserServiceInterceptor());
    advisedSupport.setMethodMatcher(new AspectJExpressionPointcut("execution(* cn.bugstack.springframework.test.bean.IUserService.*(..))"));
    
    // 代理对象(JdkDynamicAopProxy)
    IUserService proxy_jdk = (IUserService) new JdkDynamicAopProxy(advisedSupport).getProxy();
    // 测试调用
    System.out.println("测试结果:" + proxy_jdk.queryUserInfo());
    
    // 代理对象(Cglib2AopProxy)
    IUserService proxy_cglib = (IUserService) new Cglib2AopProxy(advisedSupport).getProxy();
    // 测试调用
    System.out.println("测试结果:" + proxy_cglib.register("花花"));
}
  • The whole case tests the core code of AOP before Spring is combined, including what is the target object, how to assemble the proxy information, and how to call the proxy object.
  • AdvisedSupport, which encapsulates the target object, user-implemented interception methods, and method matching expressions.
  • After that, call JdkDynamicAopProxy, Cglib2AopProxy, two proxy classes implemented in different ways, to see if the method can be successfully intercepted

test result

监控 - Begin By AOP
方法名称:public abstract java.lang.String cn.bugstack.springframework.test.bean.IUserService.queryUserInfo()
方法耗时:86ms
监控 - End

测试结果:小傅哥,100001,深圳
监控 - Begin By AOP
方法名称:public java.lang.String cn.bugstack.springframework.test.bean.UserService.register(java.lang.String)
方法耗时:97ms
监控 - End

测试结果:注册用户:花花 success!

Process finished with exit code 0
  • Like the AOP function definition, we can perform interception operations to print monitoring information under the corresponding target method after such proxy mode, method matching and interception.

Six, summary

  • From this article’s use of Proxy#newProxyInstance, MethodInterceptor#invoke, to verify the core principles of the aspect and to disassemble the functions into the implementation of the Spring framework, we can see that a seemingly complex technology does not have much core content, but because it needs to be satisfied More subsequent extensions require decoupling and packaging of responsibilities. Through the use of this design pattern, the caller can be more simplified, and the caller can continue to expand as needed.
  • The functional realization of AOP has not been combined with Spring yet, it is just a concrete realization of the aspect technology. You can first learn how to deal with proxy objects, filtering methods, intercepting methods, and the difference between using Cglib and JDK proxy. In fact, this and the technology It is not only reflected in the Spring framework, but also used in various other scenarios where manual hard coding needs to be reduced. such as RPC, Mybatis, MQ, distributed tasks
  • The use of some core technologies is strongly related, and they do not exist in isolation. And this process of connecting the entire technology stack requires a lot of learning, accumulation, and paving from point to surface in order to expand from a knowledge point to the construction of a knowledge area and knowledge system.

Seven, series recommendation


小傅哥
4.7k 声望28.4k 粉丝

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