spring复习04,静态代理动态代理,AOP

本文涉及的产品
日志服务 SLS,月写入数据量 50GB 1个月
简介: 这篇文章讲解了Java代理模式的相关知识,包括静态代理和动态代理(JDK动态代理和CGLIB),以及AOP(面向切面编程)的概念和在Spring框架中的应用。文章还提供了详细的示例代码,演示了如何使用Spring AOP进行方法增强和代理对象的创建。

概念引入

先用一个小例子,来引入我们为什么需要代理,以及代理是什么?
假设当前有一个业务需求,一个加减乘除的四则运算:
方法接口:

package com.gothic.sunset;

public interface Calculate {
   

    public int add(int a,int b);

    public int sub(int a,int b);

    public int mul(int a,int b);

    public int div(int a,int b);
}

方法实现类:

package com.gothic.sunset;

public class CalculateImpl implements Calculate{
   

    @Override
    public int add(int a, int b) {
   
        int result = a + b ;
        return result;
    }

    @Override
    public int sub(int a, int b) {
   
        int result = a - b ;
        return result;
    }

    @Override
    public int mul(int a, int b) {
   
        int result = a * b ;
        return result;
    }

    @Override
    public int div(int a, int b) {
   
        int result = a / b ;
        return result;
    }
}

测试类|用户端:

package com.gothic.sunset;

public class Client {
   
    public static void main(String[] args) {
   
        CalculateImpl calculate = new CalculateImpl();
        System.out.println(calculate.add(1,2));//3
        System.out.println(calculate.sub(2,1));//1
        System.out.println(calculate.mul(1,2));//2
        System.out.println(calculate.div(2,1));//2
    }
}

新的需求:需要看到每次执行的是什么方法,以及返回的结果是什么,和参与运算的数字分别是什么。
大家可以想一下,怎么修改,以及在原有的业务代码上面修改还是通过别的方法进行修改更好一点。答案当然是后者,因为当你原有的业务代码如果有很多呢,那你一个一个的修改,岂不是很浪费时间和拉低效率,而且假设你别的业务也需要添加类似的功能,怎么办也是在原有的业务代码上修改吗?


java代理模式

java代理模式,即Proxy Pattern,23种java常用设计模式之一。代理模式的定义:对其他对象提供一种代理以控制对这个对象的访问。

代理模式的主要作用是为其他对象提供一种代理以控制对这个对象的访问。在某些情况下,一个对象不想或者不能直接引用另一个对象,而代理对象可以在客户端和目标对象之间起到中介的作用。
代理模式的思想是为了提供额外的处理或者不同的操作而在实际对象与调用者之间插入一个代理对象。这些额外的操作通常需要与实际对象进行通信。

在这里插入图片描述


静态代理

需求:需要看到每次执行的是什么方法,以及返回的结果是什么,和参与运算的数字分别是什么。

解决上面的需求,需要抽象出对应的代理类,然后在代理类中添加新增的业务功能,并且调用其对应的实体类对象。
如下:
代理类的抽象,代理类也需要实现相同的接口,从而添加新增的业务功能。需要注意的是,它需要将真实角色注入,然后在原有的方法中去调用真实角色的方法即可。

package com.gothic.sunset;

public class CalculateProxy implements Calculate{
   
    private Calculate calculate;

    public void setCalculate(Calculate calculate) {
   
        this.calculate = calculate;
    }

    @Override
    public int add(int a, int b) {
   
        argPrint(a,b);
        logBack("add");
        int rs = calculate.add(a,b);
        rsPrint(rs);
        return rs;
    }

    @Override
    public int sub(int a, int b) {
   
        argPrint(a,b);
        logBack("sub");
        int rs = calculate.sub(a,b);
        rsPrint(rs);
        return rs;
    }

    @Override
    public int mul(int a, int b) {
   
        argPrint(a,b);
        logBack("mul");
        int rs = calculate.mul(a,b);
        rsPrint(rs);
        return rs;
    }

    @Override
    public int div(int a, int b) {
   
        argPrint(a,b);
        logBack("div");
        int rs = calculate.div(a,b);
        rsPrint(rs);
        return rs;
    }

    public void argPrint(int a,int b){
   
        System.out.println("参数A:"+a+"参数B:"+b);
    }

    public void logBack(String msg){
   
        System.out.println("执行了"+msg+"方法");
    }

    public void rsPrint(int rs){
   
        System.out.println("结果为--->"+rs);
    }
}

(用户端)测试输出:

package com.gothic.sunset;

public class Client {
   
    public static void main(String[] args) {
   
        CalculateImpl calculate = new CalculateImpl();
        CalculateProxy cp = new CalculateProxy();
        cp.setCalculate(calculate);
        cp.add(1,2);
        cp.sub(8,1);
        cp.mul(2,3);
        cp.div(8,4);
    }
}

在这里插入图片描述

静态代理模式的好处:

  • 可以使真实角色的操作更加纯粹,不用去关心一些公共的业务
  • 公共就是交给代理角色,实现了业务的分工
  • 公共业务发生扩展的时候,方便集中管理

静态代理模式的缺点:

静态代理确实实现了解耦,但是由于代码都写死了,完全不具备任何的灵活性。就拿日志功能来说,将来其他地方也需要附加日志,那还得再声明更多个静态代理类,那就产生了大量重复的代码,日志功能还是分散的,没有统一管理。


动态代理

动态代理:

  • 动态代理和静态代理角色一样
  • 动态代理的代理类是动态生成的,不是我们直接写好的
  • 动态代理分为两大类:基于接口的动态代理 ,基于类的动态代理
  • 基于接口的动态代理:jdk动态代理
  • 基于类的动态代理:cglib

jdk动态代理

JDK动态代理是指:代理类实例在程序运行时,由JVM根据反射机制动态的生成。也就是说代理类不是用户自己定义的,而是由JVM生成的。

1. InvocationHandler接口

代理实例的调用处理器需要实现InvocationHandler接口,并且每个代理实例都有一个关联的调用处理器。当一个方法在代理实例上被调用时,这个方法调用将被编码并分派到其调用处理器的invoke方法上。

也就是说,我们创建的每一个代理实例都要有一个关联的InvocationHandler,并且在调用代理实例的方法时,会被转到InvocationHandler的invoke方法上。
publicObject invoke(Object proxy, Method method, Object[] args) throws Throwable;
该invoke方法的作用是:处理代理实例上的方法调用并返回结果。

其有三个参数,分别为:

  1. proxy:是调用该方法的代理实例。
  2. method:是在代理实例上调用的接口方法对应的Method实例。
  3. args:一个Object数组,是在代理实例上的方法调用中传递的参数值。如果接口方法为无参,则该值为null。
    其返回值为:调用代理实例上的方法的返回值。

2. Proxy类

Proxy类提供了创建动态代理类及其实例的静态方法,该类也是动态代理类的超类。

代理类具有以下属性:

代理类的名称以 “$Proxy” 开头,后面跟着一个数字序号。代理类继承了Proxy类。代理类实现了创建时指定的接口(JDK动态代理是面向接口的)。
每个代理类都有一个公共构造函数,它接受一个参数,即接口InvocationHandler的实现,用于设置代理实例的调用处理器。
Proxy提供了两个静态方法,用于获取代理对象。

getProxyClass,用于获取代理类的Class对象,再通过调用构造函数创建代理实例。

    @CallerSensitive
    public static Class<?> getProxyClass(ClassLoader loader,Class<?>... interfaces)throws IllegalArgumentException

该方法有两个参数:

  1. loader:为类加载器。
  2. intefaces:为接口的Class对象数组。

方法返回值为动态代理类的Class对象。

newProxyInstance,用于创建一个代理实例。

public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces,InvocationHandler h)  throws IllegalArgumentException

该方法有三个参数:

  1. loader:为类加载器。
  2. interfaces:为接口的Class对象数组。
  3. h:指定的调用处理器。

返回值为指定接口的代理类的实例。

Proxy类主要用来获取动态代理对象,InvocationHandler接口主要用于方法调用的约束与增强。

jdk动态代理代码

创建一个代理实例的调用处理器,需要实现InvocationHandler接口

proxy:代理对象
method 表示要执行的方法,
args要执行的方法的参数列表。
通过method.getName可以获取对应方法名

package com.gothic.sunset;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Arrays;

public class ProxyInvocationHandler implements InvocationHandler {
   

    //被代理的接口
    private Object target;

    //set注入不同的接口
    public void setTarget(Object target) {
   
        this.target = target;
    }

    //得到生成的代理类
    public Object getProxy(){
   
        return Proxy.newProxyInstance(this.getClass().getClassLoader(), target.getClass().getInterfaces(),this);
    }

    //处理代理类实例,并且返回结果
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
   
        Object rs =null;
        try {
   
            System.out.println("日志,方法"+method.getName()+",参数"+ Arrays.toString(args));
            //proxy 表示代理对象 ,method 表示要执行的方法,args要执行的方法的参数列表
            rs = method.invoke(target, args);
            System.out.println("日志,方法"+method.getName()+",结果"+ rs);
        } catch (Exception e) {
   
            e.printStackTrace();
            System.out.println("日志,方法"+method.getName()+",异常"+ e);
        }  finally {
   
            System.out.println("日志,方法"+method.getName()+",执行完毕");
        }
        return rs;
    }
}

方法测试:

package com.gothic.sunset;

public class Client {
   
    public static void main(String[] args) {
   
        //实际角色
        CalculateImpl calculate = new CalculateImpl();
        //代理角色 不存在
        ProxyInvocationHandler pih = new ProxyInvocationHandler();
        //设置要代理的对象
        pih.setTarget(calculate);
        //动态生成代理类
        Calculate proxy = (Calculate) pih.getProxy();
        proxy.add(1, 2);
    }
}

输出:
在这里插入图片描述


AOP

AOP的概念

AOP为Aspect Oriented Programming的缩写,意为:面向切面编程通过预编译方式和运行期间动态代理实现程序功能的统一维护的一种技术。AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率

相关术语:

  1. 连接点(join Point):程序执行的某个特定位置,比如(类开始初始化前,初始化后,方法前,方法后,异常后) 一个类或者一段程序代码拥有一些具有边界的特定点,这些特定点称为连接点
  2. 切点(pointcut):每个程序都拥有多个连接点,比如一个类有2个方法,这2个方法就是连接点,连接点就是程序中具体的事物,AOP 通过切点来定位特定的连接点,连接点相当于数据库中的记录,而切点相当于是查询记录的条件。在程序中,切点是连接点位置的集合
  3. 增强(advice):就是我们要具体做的事情,也就在原有的方法之上添加新的能力
  4. 目标对象:就是我们要增强的类;
  5. 引入:特殊的增强,为类添加属性和方法。哪怕这个类没有任何的属性和方法我们也可以通过aop去添加方法和属性实现逻辑
  6. 织入(weaving):就是把增强添加到目标类具体的连接点上的过程
  7. 代理:一个类被AOP织入增强后产生的结果类,他是原类和增强后的代理类,更具代理s不同的方式,代理类可能是和原类具有相同接口的类,也可能是原类的子类
  8. 切面(Aspect):切面由切点和增强组成,他既包含横切的定义,也包括了连接点的定义,spring aop就是负责实施切面的框架,他将切面定义为横切逻辑织入到切面所指定的连接点

spirng中aop的实现

首先在pom.xml中导入依赖:

    <dependencies>
        <!-- 基于Maven依赖传递性,导入spring-context依赖即可导入当前所需所有jar包 -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.3.1</version>
        </dependency>
        <!-- junit测试 -->
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
            <scope>test</scope>
        </dependency>
        <!-- spring-aspects会帮我们传递过来aspectjweaver -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-aspects</artifactId>
            <version>5.3.1</version>
        </dependency>
    </dependencies>

方式一:spring原生的接口

然后观察其导入的jar包
在这里插入图片描述

只要实现下面对应的spring接口即可完成原生的springAop
在这里插入图片描述
新建一个SpringProxyTest 类,然后实现spring中的aop相关的一些接口:

package com.gothic.sunset;

import org.springframework.aop.AfterReturningAdvice;
import org.springframework.aop.MethodBeforeAdvice;

import java.lang.reflect.Method;
import java.util.Arrays;

public class SpringProxyTest implements MethodBeforeAdvice, AfterReturningAdvice {
   

    /**
     *
     * @param method 要执行的目标对象的方法名
     * @param objects 参数数组
     * @param o 要执行的目标对象
     * @throws Throwable
     */
    @Override
    public void before(Method method, Object[] objects, Object o) throws Throwable {
   
        System.out.println("执行开始--->"+o.getClass().getName()+"中的方法"+method.getName()+"----参数为:"+ Arrays.toString(objects));
    }

    @Override
    public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
   
        System.out.println("执行完毕--->"+target.getClass().getName()+"中的方法"+method.getName()+"----参数为:"+ Arrays.toString(args)+"结果为:"+returnValue.toString());
    }
}

我这里只实现类前置通知和后置通知,其余的大家可自行补充,和jdk动态代理也差不多(其实还是主要用注解去设置,这里只是另一种方法,注解会在下面使用)。
编写spring配置文件:

  1. 在spring中向ioc容器注册bean:
     <!--注册bean,也可以使用注解,这里再回忆一下-->
    <bean id="calculate" class="com.gothic.sunset.CalculateImpl"/>
    <bean id="springProxyTest" class="com.gothic.sunset.SpringProxyTest"/>
  1. 在spring配置文件中的头文件导入aop约束:
xmlns:aop="http://www.springframework.org/schema/aop"

3.配置aop:

    <!--aop配置,需要导入aop的约束-->
    <aop:config>
        <!--切入点  expression表达式 execution要执行的位置-->
        <aop:pointcut id="pt" expression="execution(* com.gothic.sunset.CalculateImpl.*(..))"/>
        <!--aop环绕通知-->
        <aop:advisor advice-ref="springProxyTest" pointcut-ref="pt"/>
    </aop:config>

junit测试用例:

import com.gothic.sunset.Calculate;
import com.gothic.sunset.CalculateImpl;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class Mytest {
    @Test
    public void testAop(){
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
        Calculate calculate = (Calculate) context.getBean("calculate");
        calculate.add(1,2);
    }

}

输出:
在这里插入图片描述

方式二:spring中注解配置aop

注解解释:

  1. 通过注解 @Aspect标识一个类作为切面类
  2. 前置通知:使用@Before注解标识,在被代理的目标方法前执行
  3. 返回通知:使用@AfterReturning注解标识,在被代理的目标方法成功结束后执行
  4. 异常通知:使用@AfterThrowing注解标识,在被代理的目标方法异常结束后执行
  5. 后置通知:使用@After注解标识,在被代理的目标方法最终结束后执行
  6. 环绕通知:使用@Around注解标识,使用try…catch…finally结构围绕整个被代理的目标方法,包括上面四种通知对应的所有位置

JointPoint对象:
JoinPoint对象封装了SpringAop中切面方法的信息,在切面方法中添加JoinPoint参数,就可以获取到封装了该方法信息的JoinPoint对象.
常用API:

方法名 功能
Signature getSignature(); 获取封装了署名信息的对象,在该对象中可以获取到目标方法名,所属类的Class等信息
Object[] getArgs(); 获取传入目标方法的参数对象
Object getTarget(); 获取被代理的对象
Object getThis(); 获取代理对象

新建一个AnnoatationPointCut 类,用来实现切面(将切面类通过注解@Aspect标识):

package com.gothic.sunset;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.*;

import java.util.Arrays;

@Aspect //标注这个类是一个切面类
public class AnnoatationPointCut {
   

    //切入点的重用,通过 @PointCut注解的value属性值来设置,然后其他的各种通知注解的value属性值,即可简写
    @Pointcut("execution(* com.gothic.sunset.CalculateImpl.*(..))")
    public void pointCut(){
   };


    //获取连接点信息可以在通知方法的参数位置设置JoinPoint类型的形参
    //前置通知
    @Before("pointCut()")
    public void beforeAdviceMethod(JoinPoint joinPoint){
   
        //获取目标对象签名信息
        Signature signature = joinPoint.getSignature();
        //获取目标对象的参数列表
        Object[] args = joinPoint.getArgs();
        System.out.println("LoggerAspect,前置通知,方法:"+signature.getName()+",参数:"+ Arrays.toString(args));
    }

    //后置通知
    @After("pointCut()")
    public void afterAdviceMethod(JoinPoint joinPoint){
   
        Signature signature = joinPoint.getSignature();
        System.out.println("LoggerAspect,后置通知,方法:"+signature.getName());
    }


    //回溯通知
    @AfterReturning(value = "pointCut()",returning = "result")
    public void afterReturningAdviceMethod(JoinPoint joinPoint,Object result){
   
        Signature signature = joinPoint.getSignature();
        System.out.println("LoggerAspect,回溯通知,方法:"+signature.getName()+",结果:"+result.toString());
    }


    //异常通知
    @AfterThrowing(value = "pointCut()",throwing = "ex")
    public void afterThrowException(JoinPoint joinPoint,Throwable ex){
   
        Signature signature = joinPoint.getSignature();
        System.out.println("LoggerAspect,回溯通知,方法:"+signature.getName()+",异常:"+ex);
    }


    //环绕通知   --- 切面类型的返回值必须与目标对象类型一致
    @Around("pointCut()")
    public Object aroundMethod(ProceedingJoinPoint joinPoint){
   
        String methodName = joinPoint.getSignature().getName();
        String args = Arrays.toString(joinPoint.getArgs());
        Object result = null;
        try {
   
            System.out.println("环绕通知-->目标对象方法执行之前");
            //目标方法的执行,目标方法的返回值一定要返回给外界调用者
            result = joinPoint.proceed();
            System.out.println("环绕通知-->目标对象方法返回值之后");
        } catch (Throwable throwable) {
   
            throwable.printStackTrace();
            System.out.println("环绕通知-->目标对象方法出现异常时");
        } finally {
   
            System.out.println("环绕通知-->目标对象方法执行完毕");
        }
        return result;
    }
}

修改spring配置文件:

基于注解的aop需要使用<aop:aspectj-autoproxy/>,开启AspectJ的自动代理,为目标对象自动生成代理

测试输出:

import com.gothic.sunset.Calculate;
import com.gothic.sunset.CalculateImpl;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class Mytest {
   
    @Test
    public void testAop(){
   
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
        Calculate calculate = (Calculate) context.getBean("calculate");
        calculate.add(1,2);
    }

}

在这里插入图片描述


好嘞,AOP就暂时到这里啦!!!!AOP的相关术语大家理解即可,不需要死究定义(根据例子慢慢感悟)。
在这里插入图片描述

相关实践学习
日志服务之使用Nginx模式采集日志
本文介绍如何通过日志服务控制台创建Nginx模式的Logtail配置快速采集Nginx日志并进行多维度分析。
相关文章
|
1月前
|
XML Java 数据安全/隐私保护
Spring Aop该如何使用
本文介绍了AOP(面向切面编程)的基本概念和术语,并通过具体业务场景演示了如何在Spring框架中使用Spring AOP。文章详细解释了切面、连接点、通知、切点等关键术语,并提供了完整的示例代码,帮助读者轻松理解和应用Spring AOP。
Spring Aop该如何使用
|
22天前
|
监控 安全 Java
什么是AOP?如何与Spring Boot一起使用?
什么是AOP?如何与Spring Boot一起使用?
46 5
|
26天前
|
Java 开发者 Spring
深入解析:Spring AOP的底层实现机制
在现代软件开发中,Spring框架的AOP(面向切面编程)功能因其能够有效分离横切关注点(如日志记录、事务管理等)而备受青睐。本文将深入探讨Spring AOP的底层原理,揭示其如何通过动态代理技术实现方法的增强。
53 8
|
26天前
|
Java 开发者 Spring
Spring AOP 底层原理技术分享
Spring AOP(面向切面编程)是Spring框架中一个强大的功能,它允许开发者在不修改业务逻辑代码的情况下,增加额外的功能,如日志记录、事务管理等。本文将深入探讨Spring AOP的底层原理,包括其核心概念、实现方式以及如何与Spring框架协同工作。
|
26天前
|
XML 监控 安全
深入调查研究Spring AOP
【11月更文挑战第15天】
37 5
|
26天前
|
Java 开发者 Spring
Spring AOP深度解析:探秘动态代理与增强逻辑
Spring框架中的AOP(Aspect-Oriented Programming,面向切面编程)功能为开发者提供了一种强大的工具,用以将横切关注点(如日志、事务管理等)与业务逻辑分离。本文将深入探讨Spring AOP的底层原理,包括动态代理机制和增强逻辑的实现。
39 4
|
1月前
|
安全 Java 开发者
AOP中的JDK动态代理与CGLIB动态代理:深度解析与实战模拟
【11月更文挑战第21天】面向切面编程(AOP,Aspect-Oriented Programming)是一种编程范式,它通过将横切关注点(cross-cutting concerns)与业务逻辑分离,以提高代码的可维护性和可重用性。在Java开发中,AOP的实现离不开动态代理技术,其中JDK动态代理和CGLIB动态代理是两种常用的方式。本文将从背景、历史、功能点、业务场景、底层逻辑等多个维度,深度解析这两种代理方式的区别,并通过Java示例进行模拟和比较。
45 4
|
7月前
|
安全 Java Spring
Spring之Aop的底层原理
Spring之Aop的底层原理
|
7月前
|
设计模式 Java uml
Spring AOP 原理
Spring AOP 原理
41 0
|
4月前
|
Java
Spring5入门到实战------9、AOP基本概念、底层原理、JDK动态代理实现
这篇文章是Spring5框架的实战教程,深入讲解了AOP的基本概念、如何利用动态代理实现AOP,特别是通过JDK动态代理机制在不修改源代码的情况下为业务逻辑添加新功能,降低代码耦合度,并通过具体代码示例演示了JDK动态代理的实现过程。
Spring5入门到实战------9、AOP基本概念、底层原理、JDK动态代理实现
下一篇
DataWorks