Spring 基础框架二:深入理解Spring AOP

什么是Spring Aop

AOP:面向切面编程技术,被定义为促使软件系统实现关注点分离的技术,分为:核心关注点和横切关注点,业务处理的主要流程是核心关注点,与之关系不大的部分是横切关注点。剖解开封装的对象内部,并将那些影响了多个类的公共行为封装到一个可重用模块,并将其名为“Aspect”,即切面。所谓“切面”,简单地说,就是将那些与业务无关,却为业务模块所共同调用的逻辑或责任封装起来,便于减少系统的重复代码,降低模块间的耦合度,并有利于未来的可操作性和可维护性。

Spring 基础框架二:深入理解Spring AOP_第1张图片

在代理类中包裹切面,Spring在运行期把切面织入到Spring管理的bean中。如图所示,代理类封装了目标类,并拦截被通知方法的调用,再把调用转发给真正的目标bean。当代理拦截到方法调用时,在调用目标bean方法之前,会执行切面逻辑。

AOP相关术语

1、切面(Aspect):横切关注点的模块化(跨越应用程序多个模块的功能,比如 日志功能),这个关注点实现可能横切多个对象。

2、通知(Advice):在AOP中,描述切面要完成的工作被称为通知。

  • 前置通知(Before):在目标方法被调用之前调用通知功能
  • 后置通知(After):在目标方法完成之后调用通知,此时不会关心方法的输出是什么
  • 返回通知(After-returning):在目标方法成功执行之后调用通知
  • 异常通知(After-throwing):在目标方法抛出异常后调用通知
  • 环绕通知(Around):通知包裹了被通知的方法,在被通知的方法调用之前和调用之后执行自定义的行为

3、目标(Target):包含连接点的对象。也被称作被通知或被代理对象。

4、代理(Proxy):向目标对象应用通知之后创建的对象。在Spring中,AOP代理可以是JDK动态代理或者CGLIB代理。

5、连接点(Join point):连接点是在应用执行过程中能够插入切面的一个点。这个点可以是类的某个方法调用前、调用后、方法抛出异常后等。切面代码可以利用这些点插入到应用的正常流程之中,并添加行为。

6、切点(Pointcut):

指定一个通知将被引发的一系列连接点的集合。AOP 通过切点定位到特定的连接点。切点和连接点不是一对一的关系,一个切点匹配多个连接点,切点通过 org.springframework.aop.Pointcut 接口进行描述,它使用类和方法作为连接点的查询条件。每个类都拥有多个连接点,例如 ArithmethicCalculator类的所有方法实际上都是连接点。

7、织入(Weaving):织入描述的是把切面应用到目标对象来创建新的代理对象的过程。

Spring AOP 的切面是在运行时被织入,原理是使用了动态代理技术。Spring支持两种方式生成代理对象:JDK动态代理和CGLib,默认的策略是如果目标类是接口,则使用JDK动态代理技术,否则使用Cglib来生成代理。

Spring AOP使用

基于注解的方式,以用户注册、登录为例,UserService负责处理用户注册、登录等相关逻辑。

Spring 基础框架二:深入理解Spring AOP_第2张图片

Spring 基础框架二:深入理解Spring AOP_第3张图片

假设我们需要在UserService 的login方法执行前后增加日志服务模块,接下来我们要定义个切面,也就是所谓的日志功能的类。

Spring 基础框架二:深入理解Spring AOP_第4张图片

Spring 基础框架二:深入理解Spring AOP_第5张图片

Spring AOP 实现原理

Spring 的 AOP 是基于动态代理实现的,谈到动态代理就不得不提下静态代理。静态代理只能代理一个具体的类,如果要代理一个接口的多个实现的话需要定义不同的代理类。

需要解决这个问题就可以用到 动态代理,动态代理分为:JDK动态代理和Cglib动态代理。

1、JDK动态代理

JDK动态代理在1.3的时候引入,其底层需要依赖一个工具类java.lang.reflect.Proxy和一个接口java.lang.reflect.InvocationHandler ,Proxy 类是用于创建代理对象,而 InvocationHandler 接口主要你是来处理执行逻辑。JDK 动态代理 必须基于 接口进行代理

JDK动态代理解析:jdk代理最主要的就是三个类:目标类,代理类(实现了目标接口)和扩展处理器InvocationHandler类。

Spring 基础框架二:深入理解Spring AOP_第6张图片

【1】jdk代理代码样例: 

//目标接口,对应ITraget
public interface Subject {
    void hello(String str);
}

//目标类,对应Target
public class RealSubject implements Subject{
    public void hello(String str) {
        System.out.println("hello" + str);
    }
}

//InvocationHandler增强处理器接口实现类
//方法调用句柄invoke方法内部就是代理类的扩展点
public class DynamicHandler implements InvocationHandler{

    private Object target;//反射代理目标类(被代理,解耦的目标类)
    
    //可以通过构造器动态设置被代理目标类,以便于调用指定方法
    public DynamicHandler(Object subject){
        this.target = subject;
    }
    
    //代理过程中的扩展点
    public Object invoke(Object proxy, Method method, Object[] args)
            throws Throwable {
        System.out.println("brfore call specific method >>" + method.getName());
        Object result = method.invoke(target, args);//MethodAccessor.invoke()
        System.out.println("after call specific method>>" + method.getName());
        return result;
    }
}

【2】客户端调用:

@Test
    public void t() throws Exception{
        Subject realSubject = new RealSubject();
        System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles","true"); 
        //1.0 获取代理类的类对象,主要设置相同的ClassLoader去加载目标类实现的接口Subject类
        Class proxyClass = Proxy.getProxyClass(Client.class.getClassLoader(), new Class[]{Subject.class});
        //2.0 得到代理类后,就可以通过代理类的处理器句柄来得到构造器
        final Constructor con = proxyClass.getConstructor(InvocationHandler.class);
        //3.0 获取具体执行方法的句柄处理器,目的通过构造器传入被代理目标类对象,注入到代理类处理器句柄中进行代理调用
        final InvocationHandler handler = new DynamicHandler(realSubject);
        //4.0 通过构造器创建代理类对象
        Subject subject = (Subject)con.newInstance(handler);
        
        //5.0 最后调用方法
        subject.hello("proxy");

说明:InvocationHandler接口是 调用处理程序接口,每一个proxy代理实例都有一个关联的调用处理程序,每一个动态代理类的调用处理程序都必须实现InvocationHandler接口,当我们通过动态代理对象调用一个方法时候,这个方法的调用就会被转发到实现InvocationHandler接口类的invoke方法来调用。通过Java反射执行原始的目标类方法代码。

    /**
    * proxy:代理类代理的真实代理对象com.sun.proxy.$Proxy0
    * method:我们所要调用某个对象真实的方法的Method对象
    * args:指代代理对象方法传递的参数
    */
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable;

 步骤总结:目标接口、目标实现类、InvocationHandler调用处理程序实现类

  1. 通过实现InvocationHandler接口,来定义自己的InvocationHandler
  2. 通过Proxy.getProxyClass(Client.class.getClassLoader(), new Class[]{Subject.class})获得动态代理类。
  3. 通过动态代理类proxyClass.getConstructor(InvocationHandler.class)来获得代理类的构造函数。
  4. 通过构造函数来创建动态动态代理对象 con.newInstance(dlHandler)
  5. 通过代理对象调用目标对象方法。

2、Cglib动态代理

CGLIB(Code Generation Library)是一个开源项目,是一个强大的,高性能,高质量的Code生成类库,它可以在运行期扩展Java类与实现Java接口,通俗说cglib可以在运行时动态生成字节码

CGLIB实现原理:运行时,动态的生成一个被代理类的子类(其底层实现是通过 ASM字节码处理框架来转换字节码,并生成新的类),子类重写了被代理类中所有非Final的方法,在子类中采用方法拦截的技术拦截所有父类方法的调用,顺势织入横切逻辑。

优点:cglib代理要比JDK代理快。缺点:被代理类中的final方法无法被代理,因为子类无法重写final函数。

【1】cglib代理代码样例: 

// 被代理类
public class HelloServiceImpl {
    public void sayHello(){
        System.out.println("Hello Zhanghao");
    }

    public void sayBey(){
        System.out.println("Bye Zhanghao");
    }
}
// 2.实现MethodInterceptor接口生成方法拦截器:
public class HelloMethodInterceptor  implements MethodInterceptor{
    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        System.out.println("Before: "  + method.getName());
        Object object = methodProxy.invokeSuper(o, objects);
        System.out.println("After: " + method.getName());
        return object;
    }
}

【2】客户端调用:生成代理类对象并打印在代理类对象调用方法之后的执行结果

public class Client {
    public static void main(String[] args) {
        // 代理类class文件存入本地磁盘方便我们反编译查看源码
        System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "/Users/zhanghao/Documents/toy/spring-framework-source-study/");
        Enhancer enhancer = new Enhancer();
        //继承被代理类
        enhancer.setSuperclass(HelloServiceImpl.class);
        //设置enhancer的回调对象,在调用中拦截对目标方法的调用
        enhancer.setCallback(new HelloMethodInterceptor());
        //创建代理类对象
        HelloServiceImpl helloService = (HelloServiceImpl) enhancer.create();
        //在调用代理类中方法时会被我们实现的方法拦截器进行拦截
        helloService.sayBey();
    }
}
result:
Before: sayBey
Bye Zhanghao
After: sayBey

说明:Enhancer是一个非常重要的类,它允许为非接口类型创建一个JAVA代理,Enhancer动态的创建给定类的子类并且拦截代理类的所有的方法,和JDK动态代理不一样的是不管是接口还是类它都能正常工作。

步骤总结:被代理类、自定义MethodInterceptor方法拦截器

  1. 通过实现MethodInterceptor接口生成方法拦截器
  2. 使用Enhancer类动态的创建目标类的子类
  3. 设置enhancer的回调对象,在调用中拦截对目标方法的调用
  4. 通过enhancer.create() 创建代理类对象
  5. 通过代理类对象调用目标对象方法。

 

文章参考:

https://www.toutiao.com/a6811489014982377992/?timestamp=1586043155&app=news_article&group_id=6811489014982377992&req_id=2020040507323501012903209413A83F5D

https://www.toutiao.com/a6706764980106035719/?timestamp=1586047559&app=news_article&group_id=6706764980106035719&req_id=2020040508455801012904822311B51369

你可能感兴趣的:(Spring架构,spring)