细说Spring Aop

什么是aop ?

aop(Aspect-Oriented Programming) ,即面向切面编程,面向切面的目的就是,抽象重复代码,复用代码,提高编码效率

aop 实现原理?

Spring aop 底层原理很简单,即为动态代理模式,包含jdk 动态代理和cglb 动态代理

什么是动态代理?

要搞清楚什么是动态代理,那就要搞清楚什么是静态代理模式 ?
代理模式,属于23种设计模式中的结构型模式,copy 网上一句话来简单描述一下:

由于某些原因需要给某对象提供一个代理以控制对该对象的访问。这时,访问对象不适合或者不能直接引用目标对象,代理对象作为访问对象和目标对象之间的中介

、、、一脸懵逼

talk is cheap show me the code

静态代理

声明一个 User 类,

public class User {

    private String name;

    private int age;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }
}

声明一个Talk 接口,有一个 hello 方法

public interface Talk {

    void hello(User user);
}

声明一个UserService 类,实现Talk接口

public class UserService implements Talk{

    @Override
    public void hello(User user) {
        System.out.println("你好,我是: "+user.getName());
    }
}

假如现在要实现一个功能,hello 方法执行前后打印日志,静态代理类就这样实现

public class StaticProxy implements Talk{

    private UserService userService = new UserService();

    @Override
    public void hello(User user) {
        System.out.println("before");
        userService.hello(user);
        System.out.println("after");
    }
}

写个main 方法来看看结果

   public static void main(String[] args) {
        User user = new User();
        user.setName("静态代理");
        StaticProxy staticProxy = new StaticProxy();
        staticProxy.hello(user);
    }

结果

before
你好,我是: 静态代理
after

分别在hello 方法执行前后打印日志,实现了我们的需求
实现这个需求后,再加入,现在有100个方法,需要在执行前后打印日志,那么这个时候怎么办呢,上面代码写100遍嘛?显然这是不科学的,科学的方法就是使用我们的动态代理,重复的事情交给代码做,由代码来生成代理类,动态代理就是这么回事儿,动态代理实现方式spring aop 采用的主要两种,jdk 动态代理和 cglib 动态代理

jdk 动态代理

下面我们使用jdk 动态代理来实现上述需求
声明一个 MyHandler 类,实现InvocationHandler接口

public class MyHandler implements InvocationHandler {
    private Object target;

    public MyHandler(Object target) {
        this.target = target;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("before");
        Object result = method.invoke(target, args);
        System.out.println("after");
        return result;
    }

    public  T getProxyInstance(){
        return (T) Proxy.newProxyInstance(target.getClass().getClassLoader(),target.getClass().getInterfaces(),this);
    }

}

执行代理类

public static void main(String[] args) {
        System.getProperties().setProperty("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
        User user = new User();
        user.setName("jdk动态代理");

        MyHandler handler = new MyHandler(new UserService());
        Talk proxyInstance = handler.getProxyInstance();
        proxyInstance.hello(user);
    }

结果

before
你好,我是: jdk动态代理
after

结果符合预期

System.getProperties().setProperty("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");

这一句的作用是,生成代理类字节码到本地,下面我们来看看生成的代理类

public final class $Proxy0 extends Proxy implements Talk {
    private static Method m1;
    private static Method m3;
    private static Method m2;
    private static Method m0;

    public $Proxy0(InvocationHandler var1) throws  {
        super(var1);
    }

    public final boolean equals(Object var1) throws  {
        try {
            return (Boolean)super.h.invoke(this, m1, new Object[]{var1});
        } catch (RuntimeException | Error var3) {
            throw var3;
        } catch (Throwable var4) {
            throw new UndeclaredThrowableException(var4);
        }
    }

    public final void hello(User var1) throws  {
        try {
            super.h.invoke(this, m3, new Object[]{var1});
        } catch (RuntimeException | Error var3) {
            throw var3;
        } catch (Throwable var4) {
            throw new UndeclaredThrowableException(var4);
        }
    }

    public final String toString() throws  {
        try {
            return (String)super.h.invoke(this, m2, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    public final int hashCode() throws  {
        try {
            return (Integer)super.h.invoke(this, m0, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    static {
        try {
            m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
            m3 = Class.forName("com.hzins.springboot.admin.pattern.proxy.Talk").getMethod("hello", Class.forName("com.hzins.springboot.admin.pattern.proxy.User"));
            m2 = Class.forName("java.lang.Object").getMethod("toString");
            m0 = Class.forName("java.lang.Object").getMethod("hashCode");
        } catch (NoSuchMethodException var2) {
            throw new NoSuchMethodError(var2.getMessage());
        } catch (ClassNotFoundException var3) {
            throw new NoClassDefFoundError(var3.getMessage());
        }
    }
}

我们看到$Proxy0这个代理类,继承了Proxy 类,实现了我们定义的Talk接口,看到这儿就想起了一个问题,jdk 动态代理的目标类,有一个必要条件,必须实现一个接口,不然jdk 动态代理机制没有办法为我们生成代理类,这也是为什么spring aop 需要采用jdk 和 cglib 2种方案来实现,因为cglib 是采用继承的方式,而不需要实现接口,(我们可以通配置参数???强制spring 采用cglib)
观察代理类发现,重写了hello方法,m3 通过反射获取目标类的Method实例

 super.h.invoke(this, m3, new Object[]{var1})

执行的是MyHandler 中实现的invoke 方法,最终实现了代理

接下来再看看cglib动态代理 是如何实现的

声明一个拦截器,实现MethodInterceptor

public class MyInterceptor implements MethodInterceptor {
    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        System.out.println("before");
        Object result = methodProxy.invokeSuper(o, objects);
        System.out.println("after");
        return result;
    }
}

执行cglib

 public static void main(String[] args) {
        System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "D:\\code");
        User user = new User();
        user.setName("Cglib动态代理");

        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(UserService.class);
        enhancer.setCallback(new MyInterceptor());
        Talk proxyInstance = (Talk) enhancer.create();
        proxyInstance.hello(user);
    }

结果

before
你好,我是: Cglib动态代理
after

cglib原理通过ASM框架生成代理类继承于目标类,使用FastClass机制访问方法,相较于反射,性能更好,通过设置下面参数,将代理类生产到D盘

System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "D:\\code");
企业微信截图_16134566042291.png

可以看到,不同于jdk动态代理,这里生产了3个字节码文件,分别是代理类与两个FastClass类

FastClass机制
FastClass机制就是对一个类的方法建立索引,调用方法时根据方法的签名来计算索引,通过索引来直接调用相应的方法。2个FastClass类分别为目标类和代理类的FastClass

搞清楚了什么是动态代理后,想想我们的100个类方法执行前后打印日志这个需求,是不是就不用写100遍了,只需要通过代码生成100个代理即可,但是想一下我们生成代理类的代码长什么样

Talk proxyInstance = handler.getProxyInstance();

把这个代码写100遍,好像也很痛苦,这可怎么办呀 >.<,并且我们只想为某一些方法进行增强,而不是整个类。那么使用 Spring aop就行 ,它的出现就是为了让程序猿更方便的使用动态代理这一特性,让我们专注于业务代码,curd curd curd ...

Spring aop 的基本概念

  • Aspect 切面是Pointcut和Advice的集合,一般单独作为一个类。Pointcut和Advice共同定义了关于切面的全部内容,在什么时候,何处完成何种功能
  • Advice 这是在方法执行之前或之后采取的实际操作。 这是在Spring AOP框架的程序执行期间调用的实际代码片段
  • JoinPoint 连接点,一般是被拦截的方法
  • Pointcut 这是一组一个或多个切入点,在切点应该执行Advice。 可以使用表达式或模式指定切入点
  • Introduction 引用允许我们向现有的类添加新的方法或者属性
  • Weaving 创建一个被增强对象的过程。这可以在编译时完成(例如使用AspectJ编译器),也可以在运行时完成。Spring和其他纯Java AOP框架一样,在运行时完成织入

Spring aop 的2种使用方式

  • 使用 XML 的方式来配置,使用 命名空间
  • @AspectJ 配置:注解方式。

Spring aop 注解方式实现日志打印需求

开启AOP

@EnableAspectJAutoProxy

定义切面 Aspect

@Component
@Aspect
public class UserServiceAspect {

}

定义切点 Pointcut ,拦截签名为hello方法

@Pointcut("execution(* hello(..))")
public void pointcut() {}

定义通知(增强)Advice

    @Before("com.hzins.springboot.admin.pattern.proxy.UserServiceAspect.pointcut()")
    public void doBefore(JoinPoint joinPoint) {
        // 前置增强
        System.out.println("before");
    }

    @After("com.hzins.springboot.admin.pattern.proxy.UserServiceAspect.pointcut()")
    public void doAfter(JoinPoint joinPoint){
        // 后置增强
        System.out.println("after");
    }

执行单元测试

@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest(classes = AdminServerApplication.class)
public class AopTest {
    @Autowired
    private UserService userService;

    @Test
    public void test(){
        User user = new User();
        user.setName("spring aop");
        userService.hello(user);
    }
}

结果

before
你好,我是: spring aop
after

通过Pointcut,可以灵活指定需要被代理的方法,Pointcut表达式有多种方式
切点表达式,主要分为匹配方法(execution),匹配注解(@within,@target,@args,@annotation),匹配包/类型(within()),匹配对象(target,this),匹配参数(args)

  • execution()
    execution(modifiers-pattern? ret-type-pattern declaring-type-pattern?name-pattern(param-pattern) throws-pattern?)

  • within() 用于匹配指定的类及其子类中的所有方法
    within(declaring-type-pattern)

  • this() 匹配可以向上转型为this指定的类型的代理对象中的所有方法

  • target() 匹配可以向上转型为target指定的类型的目标对象中的所有方法

  • args() 用于匹配当前执行的方法传入的参数为指定类型的执行方法
    args(param-pattern)

  • @annotation() 匹配带指定注解方法的连接点
    @annotation(annotation-type)

  • @within() 匹配指定注解标注的类内的方法都执行
    @within(annotation-type)

  • @target 用于匹配当前目标对象类型的执行方法,其中目标对象持有指定的注解

  • @args() 用于匹配运行时 传入的参数列表的类型持有 注解列表对应的注解的方法
    @args(annotation-type)
    通配符
    * 匹配任何数量字符
    .. 匹配任何数量字符的重复,如在类型模式中匹配任何数量子包;而在方法参数模式中匹配任何数量参数
    + 匹配指定类型的子类型
    操作符
    &&或者 and 与操作符
    || 或者 or 或操作符
    ! 或者 not 非操作符

Advice
执行顺序

image.png

多个Aspect切面,advice执行顺序

Spring AOP通过指定aspect的优先级,来控制不同aspect,advice的执行顺序,有两种方式:

Aspect 类添加注解:org.springframework.core.annotation.Order,使用注解value属性指定优先级。
Aspect 类实现接口:org.springframework.core.Ordered,实现 Ordered 接口的 getOrder() 方法。
数值越低,表明优先级越高,@Order 默认为最低优先级,即最大数值


image.png

先入后出,后入先出

Weaving
Spring和其他纯Java AOP框架一样,在运行时完成织入,那么我们通过源码来看看这个织入过程
AspectJAwareAdvisorAutoProxyCreator是aop的入口类,它的父类AbstractAutoProxyCreator继承了BeanPostProcessor,所以在目标类注入时后置回调,生成代理类

public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
    if (bean != null) {
        Object cacheKey = getCacheKey(bean.getClass(), beanName);
        if (!this.earlyProxyReferences.contains(cacheKey)) {
                        // 生成代理类
            return wrapIfNecessary(bean, beanName, cacheKey);
        }
    }
    return bean;
}

protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) {
    // Create proxy if we have advice.
    //先看它的注释,大意说:如果有通知,就创建代理。
    //其实就是在bean.getClass()找到所有的通知和advisor
    //这里面其实又分为两个步骤:
    //第一,在Bean工厂找到所有的Advisor 第二,根据beanClass和Pointcut去做匹配
    Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, null);
    if (specificInterceptors != DO_NOT_PROXY) {
        this.advisedBeans.put(cacheKey, Boolean.TRUE);
        //真正创建代理并返回,这时候返回的就是代理类了,把真实的bean替换掉
        Object proxy = createProxy(bean.getClass(), beanName, specificInterceptors, new SingletonTargetSource(bean));
        this.proxyTypes.put(cacheKey, proxy.getClass());
        return proxy;
    }
    this.advisedBeans.put(cacheKey, Boolean.FALSE);
    return bean;
}

createProxy 创建代理的过程,大概就是默认使用jdk为有接口的类创建代理,无接口的类使用cglib创建代理,然后返回注入;

仔细阅读源码发现,还有调用wrapIfNecessary 的地方

    @Override
    public Object getEarlyBeanReference(Object bean, String beanName) throws BeansException {
        Object cacheKey = getCacheKey(bean.getClass(), beanName);
        if (!this.earlyProxyReferences.contains(cacheKey)) {
            this.earlyProxyReferences.add(cacheKey);
        }
        return wrapIfNecessary(bean, beanName, cacheKey);
    }

getEarlyBeanReference 是SmartInstantiationAwareBeanPostProcessor 接口的方法,这个方法是获取bean提前引用的意思,Spring中解决循环引用的时候有调用这个方法

代理模式,可以说是无处不在,很多框架中都有使用到
比如在catfish中,也使用到了cglib代理来为我们的rpc接口生成代理类 (公司框架)

public class CglibClientProxy implements ClientProxy {


    @SuppressWarnings("unchecked")
    @Override
    public  T buildProxy(Class serviceInterface, ClientConfig clientConfig, RemoteClientProcessor remoteClientProcessor) {
        Enhancer enhancer = new Enhancer();
        enhancer.setInterfaces(new Class[]{serviceInterface});
        enhancer.setNamingPolicy(CatfishNamingPolicy.INSTANCE);
        enhancer.setCallback(new ClientMethodInterceptor(clientConfig, remoteClientProcessor));
        return (T) enhancer.create();
    }

}

ScanClientAnnotationPostProcessor 实现 BeanPostProcessor 接口进行代理织入(创建)

    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        registerClientBean(beanName, bean);
        return bean;
    }
... 省略
    setTargetProxyBean(bean, field, convertClientConfig(AnnotationUtils.findAnnotation(field, CatfishClient.class)));
... 省略
    field.set(bean, clientProxy.buildProxy(field.getType(), clientConfig, remoteClientProcessor));
...

你可能感兴趣的:(细说Spring Aop)