Spring AOP 案例分析

一、SpringBoot添加AOP

我们先为SpringBoot项目添加一个切面功能。
在这里,笔者的SpringBoot的版本为2.1.5.RELEASE,对应的Spring版本为5.1.7.RELEASE。
我们必须要先添加AOP的依赖:

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

然后来定义一个切面,来拦截Controller中的所有方法:

package com.example.demo.aop;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;

/**
 * @program: demo
 * @description:
 * @author: lee
 * @create: 2021-01-18 23:30
 **/
@Component
@Aspect
public class ControllerAspect {
    
    

    @Pointcut(value = "execution(* com.example.demo.controller..*.*(..))")
    public void pointcut(){
    
    }

    @Before("pointcut()")
    public void before(JoinPoint joinPoint){
    
    
        System.out.println("前置通知");
    }
    @After("pointcut()")
    public void after(JoinPoint joinPoint){
    
    
        System.out.println("后置通知");
    }
    @AfterReturning(pointcut="pointcut()",returning = "result")
    public void result(JoinPoint joinPoint,Object result){
    
    
        System.out.println(joinPoint.getSignature() + ":返回通知:"+result);
    }
}

好了,现在访问com.example.demo.controller包下的任何一个类中的方法,AOP就已经正常工作了。

前置通知
后置通知
Object com.example.demo.controller.SongController.songOfId(HttpServletRequest):返回通知:[com.example.demo.domain.Song@402deb15[id=126,singerId=5,name=G.E.M.邓紫棋-喜欢你,introduction=情歌,createTime=Sun Jan 17 04:38:21 CST 2021,updateTime=Sun Jan 17 04:38:21 CST 2021,pic=/img/songPic/tubiao.jpg,lyric=细雨带风湿透黄昏的街道

二、CGLIB原理

首先,我们要知道的是,在SpringBoot中,默认使用的就是CGLIB方式来创建代理,spring.aop.proxy-target-class默认是true。
在这里插入图片描述

package org.springframework.boot.autoconfigure.aop;

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.Advice;
import org.aspectj.weaver.AnnotatedElement;

import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;

/**
 * {@link org.springframework.boot.autoconfigure.EnableAutoConfiguration
 * Auto-configuration} for Spring's AOP support. Equivalent to enabling
 * {@link org.springframework.context.annotation.EnableAspectJAutoProxy} in your
 * configuration.
 * <p>
 * The configuration will not be activated if {@literal spring.aop.auto=false}. The
 * {@literal proxyTargetClass} attribute will be {@literal true}, by default, but can be
 * overridden by specifying {@literal spring.aop.proxy-target-class=false}.
 *
 * @author Dave Syer
 * @author Josh Long
 * @see EnableAspectJAutoProxy
 */
@Configuration
@ConditionalOnClass({
    
     EnableAspectJAutoProxy.class, Aspect.class, Advice.class,
		AnnotatedElement.class })
@ConditionalOnProperty(prefix = "spring.aop", name = "auto", havingValue = "true", matchIfMissing = true)
public class AopAutoConfiguration {
    
    

	@Configuration
	@EnableAspectJAutoProxy(proxyTargetClass = false)
	@ConditionalOnProperty(prefix = "spring.aop", name = "proxy-target-class", havingValue = "false", matchIfMissing = false)
	public static class JdkDynamicAutoProxyConfiguration {
    
    

	}

	@Configuration
	@EnableAspectJAutoProxy(proxyTargetClass = true)
	@ConditionalOnProperty(prefix = "spring.aop", name = "proxy-target-class", havingValue = "true", matchIfMissing = true)
	public static class CglibAutoProxyConfiguration {
    
    

	}

}

然后再回顾下CGLIB的原理:

动态生成一个要代理类的子类,子类重写要代理的类的所有不是final的方法。在子类中采用方法拦截的技术拦截所有父类方法的调用,顺势织入横切逻辑。它比使用java反射的JDK动态代理要快。

我们看到,CGLIB代理的重要条件是生成一个子类,然后重写要代理类的方法。

下面我们看看CGLIB最基础的应用。
假如我们有一个Student类,它有一个eat()方法。

public class Student {
    
    
    public void eat(String name) {
    
    
        System.out.println(name+"正在吃饭...");
    }
}

然后,创建一个拦截器,在CGLIB中,它是一个回调函数。

import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;

public class TargetInterceptor implements MethodInterceptor {
    
    
    @Override
    public Object intercept(Object obj, Method method,
                            Object[] params, MethodProxy proxy) throws Throwable {
    
    
        System.out.println("调用前");
        Object result = proxy.invokeSuper(obj, params);
        System.out.println("调用后");
        return result;
    }
}

然后我们测试它:

import org.springframework.cglib.proxy.Enhancer;

public class Main {
    
    
    public static void main(String[] args) {
    
    
        //创建字节码增强器
        Enhancer enhancer =new Enhancer();
        //设置父类
        enhancer.setSuperclass(Student.class);
        //设置回调函数
        enhancer.setCallback(new TargetInterceptor());
        //创建代理类
        Student student=(Student)enhancer.create();
        student.eat("王二杆子");
        System.out.println(student.getClass());

    }
}

输出为:

调用前
王二杆子正在吃饭...
调用后
class Student$$EnhancerByCGLIB$$51cf315c

这样就完成了通过CGLIB对Student类的代理。
上面代码中的student就是通过CGLIB创建的代理类,它的Class对象如下:

class Student$$EnhancerByCGLIB$$51cf315c

既然CGLIB是通过生成子类的方式来创建代理,那么它生成的子类就要继承父类咯。
关于Java中的继承,有一条很重要的特性就是:
子类拥有父类非 private 的属性、方法。
看到这里,也许你已经明白了一大半,不过咱们继续看。照这样说法,如果父类中有private方法,生成的代理类中是看不到的。
上面的Student类中,学生不仅要吃饭,也许还会偷偷睡觉,那我们给它加一个私有方法:

public class Student {
    
    

    public void eat(String name) {
    
    
        System.out.println(name+"正在吃饭...");
    }
    private void sleep(String name){
    
    
        System.out.println(name+"正在偷偷睡觉...");
    }
}

不过,怎么测试呢?这私有方法在外面也调用不到呀。没关系,我们用反射来试验:

//创建代理类
Student student=(Student)enhancer.create();

Method eat = student.getClass().getMethod("eat", String.class);
eat.invoke(student,"王二杆子");

Method sleep = student.getClass().getMethod("sleep", String.class);
sleep.invoke(student,"王二杆子");

输出结果如下:

调用前
王二杆子正在吃饭...
调用后
Exception in thread "main" java.lang.NoSuchMethodException: Student$$EnhancerByCGLIB$$51cf315c.sleep(java.lang.String)
	at java.lang.Class.getMethod(Class.java:1786)
	at Main.main(Main.java:20)

很明显,在调用sleep方法的时候,抛出了java.lang.NoSuchMethodException异常。
至此,我们更加确定了一件事:
由CGLIB创建的代理类,不会包含父类中的私有方法。

猜你喜欢

转载自blog.csdn.net/m0_46864744/article/details/112797959