preface

In the last section, we realized the function of AOP, and we can generate the corresponding proxy class, but the choice of proxy object can only be through the specified class, which is really inconvenient and unreasonable. In this section, we’ll use AspectJ to implement more powerful pointcuts.

AOP functionality was also cumbersome to use in the early days of Spring, but it was later integrated with AspectJ that the AOP functionality now comes in handy, such as the following code, which defines pointcuts easily and intuitively.

@Component
@Aspect
public class LogAspect {
	@Pointcut("execution(* com.zbw.*.service.. *Impl.*(..) ) && @annotation(Log)")
	public void logPointcut(a) {}@Before("logPointcut()")
    public void before(a)
    {System.out.println("Before");}
}
Copy the code

Now let’s also introduce AspectJ to implement AOP pointcut functionality

Introduces AspectJ and implements AspectJ’s pointcut class

Start by adding an AspectJ dependency to POM.xml

<properties>.<aspectj.version>1.8.13</aspectj.version>
</properties>
<dependencies>.<! -- aspectj -->
    <dependency>
        <groupId>org.aspectj</groupId>
        <artifactId>aspectjweaver</artifactId>
        <version>${aspectj.version}</version>
    </dependency>
</dependencies>

Copy the code

Next, you can start implementing a pointcut class that uses AspectJ to determine whether an AspectJ expression matches a specified class or method.

Create a class under the ZBW. Aop package called ProxyPointcut

package com.zbw.aop;

import./** * proxy pointcut class */
public class ProxyPointcut {
    /**
     * 切点解析器
     */
    private PointcutParser pointcutParser;

    /** * (AspectJ) expression */
    private String expression;

    /** * expression parser */
    private PointcutExpression pointcutExpression;

    /** * AspectJ syntax set */
    private static final Set<PointcutPrimitive> DEFAULT_SUPPORTED_PRIMITIVES = new HashSet<>();

    static {
        DEFAULT_SUPPORTED_PRIMITIVES.add(PointcutPrimitive.EXECUTION);
        DEFAULT_SUPPORTED_PRIMITIVES.add(PointcutPrimitive.ARGS);
        DEFAULT_SUPPORTED_PRIMITIVES.add(PointcutPrimitive.REFERENCE);
        DEFAULT_SUPPORTED_PRIMITIVES.add(PointcutPrimitive.THIS);
        DEFAULT_SUPPORTED_PRIMITIVES.add(PointcutPrimitive.TARGET);
        DEFAULT_SUPPORTED_PRIMITIVES.add(PointcutPrimitive.WITHIN);
        DEFAULT_SUPPORTED_PRIMITIVES.add(PointcutPrimitive.AT_ANNOTATION);
        DEFAULT_SUPPORTED_PRIMITIVES.add(PointcutPrimitive.AT_WITHIN);
        DEFAULT_SUPPORTED_PRIMITIVES.add(PointcutPrimitive.AT_ARGS);
        DEFAULT_SUPPORTED_PRIMITIVES.add(PointcutPrimitive.AT_TARGET);
    }

    public ProxyPointcut(a) {
        this(DEFAULT_SUPPORTED_PRIMITIVES);
    }

    public ProxyPointcut(Set<PointcutPrimitive> supportedPrimitives) {
        pointcutParser = PointcutParser
                .getPointcutParserSupportingSpecifiedPrimitivesAndUsingContextClassloaderForResolution(supportedPrimitives);
    }

    /** * Class matches the pointcut expression */
    public boolean matches(Class
        targetClass) {
        checkReadyToMatch();
        return pointcutExpression.couldMatchJoinPointsInType(targetClass);
    }

    /** * Method matches the pointcut expression */
    public boolean matches(Method method) {
        checkReadyToMatch();
        ShadowMatch shadowMatch = pointcutExpression.matchesMethodExecution(method);
        if (shadowMatch.alwaysMatches()) {
            return true;
        } else if (shadowMatch.neverMatches()) {
            return false;
        }
        return false;
    }

    /** * initializes the pointcut parser */
    private void checkReadyToMatch(a) {
        if (null== pointcutExpression) { pointcutExpression = pointcutParser.parsePointcutExpression(expression); }}public void setExpression(String expression) {
        this.expression = expression;
    }

    public String getExpression(a) {
        return expression;
    }
Copy the code

This class has three variables: pointcutParser, expression, pointcutExpression.

Expression is a String that holds the AspectJ expression we want to set, such as execution(* com.zbw.*.service.. *Impl.*(..) ) Yes.

PointcutParser and pointcutExpression are classes in AspectJ. PointcutParser is used to create a pointcutExpression expression parser based on expressions in expression. PointcutExpression can be used to determine whether a method or class matches an expression.

The two main methods in this Class are matches(Class
targetClass) and matches(Method Method), which determine whether the targetClass and Method match the aspectj expression in expression, respectively.

Now you can add the pointcut class ProxyPointcut to the AOP functionality we implemented earlier.

Implement AOP’s pointcut function

First modify the Aspect annotations, changing the previous target() to pointcut() to store aspectJ expressions.

package com.zbw.aop.annotation;
import. ;@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Aspect {
    /** * pointcut expression */
    String pointcut(a) default "";
}
	
Copy the code

Then modify the ProxyAdvisor class to put a pointcut expression matcher in it and use the matcher to determine whether the target class should be enhanced.

.public class ProxyAdvisor {.../** * AspectJ expression pointcut matcher */
    private ProxyPointcut pointcut;

    /** * Executes the proxy method */
    public Object doProxy(Object target, Class
        targetClass, Method method, Object[] args, MethodProxy proxy) throws Throwable {
        if(! pointcut.matches(method)) {returnproxy.invokeSuper(target, args); }... }}Copy the code

Pointcut.matches () is used at the top of the doProxy() method to determine whether the target method matches the expression. If so, it executes the previously written notifications, and if not, it executes the target method directly. In this way, AspectJ expressions control enhancements to target classes.

The next step is to modify the Aop class, overwriting the previous doAop() method because you have changed the rules for matching the target class.

.public class Aop {...public void doAop(a) {
        beanContainer.getClassesBySuper(Advice.class)
                .stream()
                .filter(clz -> clz.isAnnotationPresent(Aspect.class))
                .map(this::createProxyAdvisor) .forEach(proxyAdvisor -> beanContainer.getClasses() .stream() .filter(target -> ! Advice.class.isAssignableFrom(target)) .filter(target -> ! target.isAnnotationPresent(Aspect.class)) .forEach(target -> {if(proxyAdvisor.getPointcut().matches(target)) { Object proxyBean = ProxyCreator.createProxy(target, proxyAdvisor); beanContainer.addBean(target, proxyBean); }})); }/** * Create the proxy notification class */ through the Aspect section class
    private ProxyAdvisor createProxyAdvisor(Class
        aspectClass) {
        String expression = aspectClass.getAnnotation(Aspect.class).pointcut();
        ProxyPointcut proxyPointcut = new ProxyPointcut();
        proxyPointcut.setExpression(expression);
        Advice advice = (Advice) beanContainer.getBean(aspectClass);
        return newProxyAdvisor(advice, proxyPointcut); }}Copy the code

Although the doAop() method has been rewritten, the implementation principle remains the same. CreateProxyAdvisor (), then iterates through all the beans in the Bean container except for the aspect class. If the Bean matches the pointcut expression in the ProxyAdvisor, The corresponding proxy class is generated.

With aspectJ’s AOP implementation pointcut complete, it’s time to test the functionality with test cases.

The test case

In the previous article, we implemented a simple Java MVC framework from scratch (part 4), modifying test cases from implementing test cases in AOP.

Start by modifying the Aspect annotation on the Aspect class DoodleAspect

package com.zbw.bean;
import.@Slf4j
@Aspect(pointcut = "execution(* com.zbw.bean.DoodleController.helloForAspect(..) )")
public class DoodleAspect implements AroundAdvice {... }Copy the code

The value in Aspect@pointcut() makes it match only the helloForAspect() method in DoodleController.

Next add the helloForAspect() method to the DoodleController

.public class DoodleController {...public void helloForAspect(a) {
        log.info("Hello Aspectj"); }}Copy the code

Finally, rewrite the test case of AopTest.

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(); controller.helloForAspect(); }}Copy the code

As you can see from the resulting diagram, no extra logs are printed before and after hello() in the DoodleController, while the contents of the notification method in the DoodleAspect are printed before and after the helloForAspect() method. This shows that our AOP has matched exactly the desired target.


  • 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

Implementing a simple Java MVC framework from scratch (5)– Introducing AspectJ to implement AOP pointcuts