preface

AOP full name is Aspect Oriented Programming, called Aspect Oriented Programming, and object Oriented Programming (OOP) is also a kind of Programming ideas, is also an important part of Spring.

Its implementation is based on the proxy mode, which enhances the original business. For example, the original function is to add, delete, change and check, want to enhance the original function without modifying the source code, so you can generate a proxy object for the original business class, in the proxy object to achieve the method of the original business enhancement.

There are static proxies and dynamic proxies, and we usually use dynamic proxies because static proxies are hard coded and not suitable for implementation frameworks. There are usually two kinds of proxies in Java, one is the JDK’s own proxy, the other is the cglib implementation of the proxy, these two agents have their own characteristics, if not familiar with the information you can look up.

Both are supported at the bottom of Spring. By default, spring uses JDK proxies if the bean implements an interface, and cglib proxies otherwise.

The Cglib proxy is used in the Doodle framework because the proxy classes do not implement interfaces and are more flexible

prepared

Before implementing AOP’s capabilities in detail, let’s do some preparation.

Since the Cglib proxy is not shipped with the JDK, cglib is first introduced in POM.xml.

<properties>.<cglib.version>3.2.6</cglib.version>
</properties>
<dependencies>.<! -- cglib -->
    <dependency>
        <groupId>cglib</groupId>
        <artifactId>cglib</artifactId>
        <version>${cglib.version}</version>
    </dependency>
</dependencies>
Copy the code

Then create an Annotation package under the ZBW. Aop package, and then create an Aspect annotation. This annotation is used to mark the class in the “section” that implements the proxy function.

package com.zbw.aop.annotation;
import. ;@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Aspect {
    /** * The scope of the target proxy class */
    Class<? extends Annotation> target();
}

Copy the code

Then create an advice package under the ZBW. Aop package, which lays down a set of advice interfaces. These include:

  • Basic notification interfaceAdviceAll notification interfaces inherit from this interface
  • Pre-notification interfaceMethodBeforeAdviceBy inheriting the notification interface and implementing its pre-method, the target class can be pre-enhanced, i.e. the pre-method is executed before the target method is executed.
  • Rear notification interfaceAfterReturningAdviceThe target class can be postenhanced by inheriting the notification interface and implementing its return method, that is, the return method is executed after the target method is executed and the result is put back.
  • Exception notification interfaceThrowsAdvice, inheriting the notification interface and implementing its exception method enhances the exception of the target class, which is executed when an exception occurs in the target method.
  • Surround notification interfaceAroundAdviceThis interface inheritsMethodBeforeAdvice.AfterReturningAdvice.ThrowsAdviceThese three interfaces are the sum of these three interfaces.

There are several other notifications available in Spring, and while we won’t implement them here, we’ll implement the relatively common ones.

/** * notification interface */
public interface Advice {}/** ** pre-notification interface */
public interface MethodBeforeAdvice extends Advice {
    /** ** method */
    void before(Class
        clz, Method method, Object[] args) throws Throwable;
}


/** * returns notification interface */
public interface AfterReturningAdvice extends Advice {
    /** * return method */
    void afterReturning(Class
        clz, Object returnValue, Method method, Object[] args) throws Throwable;
}

/** * Exception notification interface */
public interface ThrowsAdvice extends Advice {
    /** * exception method */
    void afterThrowing(Class
        clz, Method method, Object[] args, Throwable e);
}



/** * wrap around the notification interface */
public interface AroundAdvice extends MethodBeforeAdvice.AfterReturningAdvice.ThrowsAdvice {}Copy the code

Implement AOP

We’ve just implemented several notification interfaces. Let’s use these notification interfaces to implement the proxy class.

package com.zbw.aop;
import./** * Proxy notification class */
@Slf4j
@AllArgsConstructor
@NoArgsConstructor
@Data
public class ProxyAdvisor {

    /** * notify */
    private Advice advice;

    /** * Executes the proxy method */
    public Object doProxy(Object target, Class
        targetClass, Method method, Object[] args, MethodProxy proxy) throws Throwable {
        Object result = null;

        if (advice instanceof MethodBeforeAdvice) {
            ((MethodBeforeAdvice) advice).before(targetClass, method, args);
        }
        try {
            // Execute the target class's methods
            result = proxy.invokeSuper(target, args);
            if (advice instanceofAfterReturningAdvice) { ((AfterReturningAdvice) advice).afterReturning(targetClass, result, method, args); }}catch (Exception e) {
            if (advice instanceof ThrowsAdvice) {
                ((ThrowsAdvice) advice).afterThrowing(targetClass, method, args, e);
            } else {
                throw newThrowable(e); }}returnresult; }}Copy the code

This class is the proxy Class ProxyAdvisor, which actually executes our proxy class when our target class executes. The Advice attribute in the ProxyAdvisor is the Advice interface just written, and then when the target method executes, the doProxy() method executes the method implemented in the interface by determining the type of the Advice interface.

The order of execution is MethodBeforeAdvice@before() -> MethodProxy@invokeSuper() -> AfterReturningAdvice@afterReturning(), If the target method is abnormal, the ThrowsAdvice@afterThrowing() method is executed.

Next comes the executor that implements AOP

package com.zbw.aop;
import./** * Aop executor */
@Slf4j
public class Aop {

    /** * Bean container */
    private BeanContainer beanContainer;

    public Aop(a) {
        beanContainer = BeanContainer.getInstance();
    }

    public void doAop(a) {
        beanContainer.getClassesBySuper(Advice.class)
                .stream()
                .filter(clz -> clz.isAnnotationPresent(Aspect.class))
                .forEach(clz -> {
                    finalAdvice advice = (Advice) beanContainer.getBean(clz); Aspect aspect = clz.getAnnotation(Aspect.class); beanContainer.getClassesByAnnotation(aspect.target()) .stream() .filter(target -> ! Advice.class.isAssignableFrom(target)) .filter(target -> ! target.isAnnotationPresent(Aspect.class)) .forEach(target -> { ProxyAdvisor advisor =newProxyAdvisor(advice); Object proxyBean = ProxyCreator.createProxy(target, advisor); beanContainer.addBean(target, proxyBean); }); }); }}Copy the code

As in the previous section, when implementing IOC’s executor, we first get the singleton BeanContainer from the AOP executor constructor.

You then implement the AOP functionality in the doAop() method.

  • Traverse the BeanContainer container byAspectAnnotate the Bean and find the implementationAdviceInterface classes, which are aspects
  • Gets annotations on the cut surfaceAspectthetarget()The value is the annotation of the class to be proxied. Let’s say I have a tangent annotation@Aspect(target = Controller.class), then the section will act on theControllerAnnotation class.
  • Traverses the BeanContainer byAspects. The target value ()Annotated Bean to find the target proxy class
  • createProxyAdvisorCglib creates an instance of the proxy class and puts it back into the BeanContainer.

There is a proxy class creator in the method, ProxyCreator, which uses cglib to create the proxy class, and finally implements this creator.

package com.zbw.aop;

import./** * Proxy class creator */
public final class ProxyCreator {

    /** * Create the proxy class */
    public static Object createProxy(Class
        targetClass, ProxyAdvisor proxyAdvisor) {
        returnEnhancer.create(targetClass, (MethodInterceptor) (target, method, args, proxy) -> proxyAdvisor.doProxy(target, targetClass, method, args, proxy)); }}Copy the code

Our Advice implementation class will not be loaded by BeanContainer, so add @aspect to the BEAN_ANNOTATION property of the Bean container.

//BeanContainer./** * Load the bean's list of annotations */
private static finalList<Class<? extends Annotation>> BEAN_ANNOTATION = Arrays.asList(Component.class, Controller.class, Service.class, Repository.class,Aspect.class); .Copy the code

The test case

In the previous article, we implemented a simple Java MVC framework from scratch (3), which implements test cases in IOC, and then implemented a DoodleAspect aspect. This aspect implements the AroundAdvice notification interface and implements three of its methods.

package com.zbw.bean;
import.@Slf4j
@Aspect(target = Controller.class)
public class DoodleAspect implements AroundAdvice {

    @Override
    public void before(Class
        clz, Method method, Object[] args) throws Throwable {
        log.info("Before DoodleAspect ----> class: {}, method: {}", clz.getName(), method.getName());
    }

    @Override
    public void afterReturning(Class
        clz, Object returnValue, Method method, Object[] args) throws Throwable {
        log.info("After DoodleAspect ----> class: {}, method: {}", clz, method.getName());
    }

    @Override
    public void afterThrowing(Class
        clz, Method method, Object[] args, Throwable e) {
        log.error("Error DoodleAspect ----> class: {}, method: {}, exception: {}", clz, method.getName(), e.getMessage()); }}Copy the code

Then write the AopTest test case, noting that the Aop executor must be executed before the Ioc executor, or the instance injected into the Bean may not be a proxy class.

package com.zbw.aop;
import.@Slf4j
public class AopTest {
    @Test
    public void doAop(a) {
        BeanContainer beanContainer = BeanContainer.getInstance();
        beanContainer.loadBeans("com.zbw");
        new Aop().doAop();
        newIoc().doIoc(); DoodleController controller = (DoodleController) beanContainer.getBean(DoodleController.class); controller.hello(); }}Copy the code

You can see that the DoodleAspect@before() and DoodleAspect@afterReturning() methods are executed before and after the DoodleController@hello() method. Demonstrate that AOP functionality is complete.

The current defect

While AOP functionality is complete, there are several serious drawbacks

  • Filtering the target class is not very convenientAspect.target()To filter out classes annotated by this value, which is too general. ifAspect.target()=Controller.class, then all byControllerThe left and right methods in the annotated controller will be propped. We want to be like Springexecution(* com.zbw.*.service.. *Impl.*(..) ), using expressions to filter the target class.
  • A target class can only be used by one aspect. For now, for example, yesDoodleAspect1andDoodleAspect2Both of these sections operate on phiDoodleControllerIt is also unreasonable that only one slice works.

So later chapters will refine the implementation of these two issues.


  • Implement a simple Java MVC framework from scratch (I)– Preface
  • Implement a simple Java MVC framework from scratch (two)- implement Bean container
  • Implement a simple Java MVC framework from scratch (iii)– implement IOC
  • Implement a simple Java MVC framework from scratch (four)– implement AOP
  • Implementing a simple Java MVC framework from scratch (5)– Introducing AspectJ to implement AOP pointcuts
  • Implement a simple Java MVC framework from scratch (6)– enhance AOP functionality
  • Implement a simple Java MVC framework from scratch (7)– Implement MVC
  • Implement a simple Java MVC framework from scratch (8)- Starter
  • Implement a simple Java MVC framework from scratch (9)– optimize THE MVC code

Source address :doodle

Implement a simple Java MVC framework from scratch (4)– implement AOP