Java AOP (2) runtime weaving 【Java 切面编程 (2) 运行时织入】

时间:2021-04-15 18:12:33

接上一篇 Java AOP (1) compile time weaving 【Java 切面编程 (1) 编译期织入】

Dynamic proxy   动态代理

Befor talking about runtime weaving, let's take a look at Java dynamic proxy.

在说运行时织入之间,我们先看看java动态代理

public class DynamicProxyTest {
    public interface Vehicle
    {
        void whistle();
    }

    public static class Boat implements Vehicle
    {
        @Override
        public void whistle()
        {
            System.out.println( "Boat whistle!" );
        }
    }

    public static class VehicleHandler implements InvocationHandler
    {
        private Object proxied;

        public VehicleHandler(Object proxied )
        {
            this.proxied = proxied;
        }

        public Object invoke(Object proxy, Method method, Object[] args ) throws Throwable
        {
            checkVehicle();
            return method.invoke( proxied, args);
        }

        private void checkVehicle() {
            System.out.println("Check vehicle status ...");
        }
    }

    public static void main(String[] args) throws Exception {
        Boat boat = new Boat();
        boat.whistle();
        System.out.println("--------------------------");
        Vehicle proxyBoat = (Vehicle) Proxy.newProxyInstance(DynamicProxyTest.class.getClassLoader(), new Class[]{Vehicle.class}, new VehicleHandler(boat));
        proxyBoat.whistle();
    }
}

Output:

Boat whistle!
--------------------------
Check vehicle status ...
Boat whistle!

Note the marked codes, we add an InvocationHandler class and then create a new proxy instance based on that. After the proxy object is generated, every method in vehicle will be invoked with an addition checkVehicle() call.

注意标红的代码,我们增加了一个请求处理类,然后基于它创建了一个代理实例。代理对象被创建之后,针对它的每一个方法调用都会伴随着一次额外的checkVehicle函数调用。

What's the magic behind dynamic proxy? Let's trace the JDK source code.

动态代理的底层魔法是什么呢? 来看看JDK源码一探究竟。

    public static Object newProxyInstance(ClassLoader loader,
                                          Class<?>[] interfaces,
                                          InvocationHandler h)
        throws IllegalArgumentException
    {
       .......
        /*
         * Look up or generate the designated proxy class.
         */
        Class<?> cl = getProxyClass0(loader, intfs);

        /*
         * Invoke its constructor with the designated invocation handler.
         */
       ........
        return cons.newInstance(new Object[]{h});
    }

The getProxyClass0 method will generate a proxy class using ProxyClassFactory

newProxyInstance 会首先用getProxyClass0方法,然后继续调用 ProxyClassFactory 工厂类来生成一个代理类,继而产生代理实例。

    /**
     * A factory function that generates, defines and returns the proxy class given
     * the ClassLoader and array of interfaces.
     */
    private static final class ProxyClassFactory
        implements BiFunction<ClassLoader, Class<?>[], Class<?>>
    {
     ..........
    }

After the proxy class is finally generated, every interface method will looks like

当实例类生成之后,每一个方法的实现大致如下

   public final int customizedMethod()
    {
        try
        {
            return ((Integer)super.h.invoke(this, m0, null)).intValue();
        }
        catch(Exception e) {
            .......
        }
    }

The marked part tells why the implementation of invocation handler becomes so useful, it becomes the entrance of every interface method.

标红部分会调用之前 InvocationHandler 方法中的自定义的 invoke 方法,它成为了每一个接口方法的入口函数。

Spring AOP

Official document https://docs.spring.io/spring/docs/current/spring-framework-reference/html/aop.html

11.1.3 AOP Proxies

Spring AOP defaults to using standard JDK dynamic proxies for AOP proxies. This enables any interface (or set of interfaces) to be proxied.

Spring AOP can also use CGLIB proxies. This is necessary to proxy classes rather than interfaces. CGLIB is used by default if a business object does not implement an interface. As it is good practice to program to interfaces rather than classes; business classes normally will implement one or more business interfaces. It is possible toforce the use of CGLIB, in those (hopefully rare) cases where you need to advise a method that is not declared on an interface, or where you need to pass a proxied object to a method as a concrete type.

It is important to grasp the fact that Spring AOP is proxy-based. See Section 11.6.1, “Understanding AOP proxies” for a thorough examination of exactly what this implementation detail actually means.

JDK proxy  --> proxy interface, CFLIB proxy --> proxy any class

根据Spring的官方文档,Spring AOP有两种实现。 对于接口,默认使用JDK动态代理,而对于无接口实现的普通类,默认使用CGLIB进行代理。

Next, a demo will show how to use Spring AOP based on maven.

接下来将会演示如何使用Spring AOP

1) Add dependency

增加maven依赖

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
<version>1.4.0.RELEASE</version>
</dependency>

2) Create a simple spring boot app

创建一个简单的spring boot应用

@SpringBootApplication
public class App {

    public static void main(String[] args) {
        SpringApplication.run(AppConfig.class, args);
    }
}

@Configuration
@ComponentScan
public class AppConfig extends WebMvcConfigurerAdapter {
    @Autowired
    HandlerInterceptor apiInterceptor;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(apiInterceptor);
    }
}

@RestController
public class SimpleController {

    @RequestMapping("/greeting")
    public String greeting(@RequestParam(value="name", defaultValue="World") String name) {
        ExpressionParser parser = new SpelExpressionParser();
        Expression exp = parser.parseExpression("'root.methodName'");
        String message = exp.getValue(String.class);

        return message;
    }

    @RequestMapping("/hello")
    public ResponseEntity hello(@RequestParam(value="name", defaultValue = "SYSTEM") String name) {
        return ResponseEntity.ok().body("Hello from " + name);
    }
}

3) Define an aspect class

定义一个切面类

@Aspect
@Component
public class ExecutionLogger {

    private Logger logger;

    public ExecutionLogger() {
        logger = LoggerFactory.getLogger(getClass());
        logger.info("Execution time logger created.");
    }

    @Pointcut("execution(* spring.SimpleController.*(*))")
    public void methodPointcut() {
    }

    @Around("methodPointcut()")
    public Object proceed(ProceedingJoinPoint pjp) throws Throwable {
        String name = pjp.getSignature().getName();
        logger.info("This is aop join point before [{}]", name);
        try {
            return pjp.proceed();
        } finally {
        }
    }
}

@Aspect, @Pointcut and @Around annotations makes it very convinient to use Spring AOP.

We can add point cut at any method around which cutomized process like log, time evaluation, etc can be added.

Aspect, Pointcut, Around注解使得使用Spring AOP变得很简单

我们可以在想要切入的方法上加入切点(通过Pointcut注解完成),然后在通过Around注解实现具体的切面逻辑。

4) Open browser, visit greeting controller

打开浏览器,访问刚刚定义的greeting

Java AOP (2) runtime weaving 【Java 切面编程 (2) 运行时织入】

Below is console output

下面是控制台输出,可以看到每次greeting页面被打开时会有一条调用记录

2017-05-09 14:04:38.814  INFO 7580 --- [  XNIO-2 task-1] spring.ExecutionLogger                   : This is aop join point before [greeting]