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

简介: 这篇文章讲解了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的相关术语大家理解即可,不需要死究定义(根据例子慢慢感悟)。
在这里插入图片描述

相关文章
|
10天前
|
弹性计算 人工智能 架构师
阿里云携手Altair共拓云上工业仿真新机遇
2024年9月12日,「2024 Altair 技术大会杭州站」成功召开,阿里云弹性计算产品运营与生态负责人何川,与Altair中国技术总监赵阳在会上联合发布了最新的“云上CAE一体机”。
阿里云携手Altair共拓云上工业仿真新机遇
|
6天前
|
机器学习/深度学习 算法 大数据
【BetterBench博士】2024 “华为杯”第二十一届中国研究生数学建模竞赛 选题分析
2024“华为杯”数学建模竞赛,对ABCDEF每个题进行详细的分析,涵盖风电场功率优化、WLAN网络吞吐量、磁性元件损耗建模、地理环境问题、高速公路应急车道启用和X射线脉冲星建模等多领域问题,解析了问题类型、专业和技能的需要。
2506 14
【BetterBench博士】2024 “华为杯”第二十一届中国研究生数学建模竞赛 选题分析
|
6天前
|
机器学习/深度学习 算法 数据可视化
【BetterBench博士】2024年中国研究生数学建模竞赛 C题:数据驱动下磁性元件的磁芯损耗建模 问题分析、数学模型、python 代码
2024年中国研究生数学建模竞赛C题聚焦磁性元件磁芯损耗建模。题目背景介绍了电能变换技术的发展与应用,强调磁性元件在功率变换器中的重要性。磁芯损耗受多种因素影响,现有模型难以精确预测。题目要求通过数据分析建立高精度磁芯损耗模型。具体任务包括励磁波形分类、修正斯坦麦茨方程、分析影响因素、构建预测模型及优化设计条件。涉及数据预处理、特征提取、机器学习及优化算法等技术。适合电气、材料、计算机等多个专业学生参与。
1519 14
【BetterBench博士】2024年中国研究生数学建模竞赛 C题:数据驱动下磁性元件的磁芯损耗建模 问题分析、数学模型、python 代码
|
8天前
|
编解码 JSON 自然语言处理
通义千问重磅开源Qwen2.5,性能超越Llama
击败Meta,阿里Qwen2.5再登全球开源大模型王座
531 13
|
1月前
|
运维 Cloud Native Devops
一线实战:运维人少,我们从 0 到 1 实践 DevOps 和云原生
上海经证科技有限公司为有效推进软件项目管理和开发工作,选择了阿里云云效作为 DevOps 解决方案。通过云效,实现了从 0 开始,到现在近百个微服务、数百条流水线与应用交付的全面覆盖,有效支撑了敏捷开发流程。
19282 30
|
1月前
|
人工智能 自然语言处理 搜索推荐
阿里云Elasticsearch AI搜索实践
本文介绍了阿里云 Elasticsearch 在AI 搜索方面的技术实践与探索。
18836 20
|
1月前
|
Rust Apache 对象存储
Apache Paimon V0.9最新进展
Apache Paimon V0.9 版本即将发布,此版本带来了多项新特性并解决了关键挑战。Paimon自2022年从Flink社区诞生以来迅速成长,已成为Apache顶级项目,并广泛应用于阿里集团内外的多家企业。
17524 13
Apache Paimon V0.9最新进展
|
8天前
|
人工智能 自动驾驶 机器人
吴泳铭:AI最大的想象力不在手机屏幕,而是改变物理世界
过去22个月,AI发展速度超过任何历史时期,但我们依然还处于AGI变革的早期。生成式AI最大的想象力,绝不是在手机屏幕上做一两个新的超级app,而是接管数字世界,改变物理世界。
458 48
吴泳铭:AI最大的想象力不在手机屏幕,而是改变物理世界
|
1天前
|
云安全 存储 运维
叮咚!您有一份六大必做安全操作清单,请查收
云安全态势管理(CSPM)开启免费试用
355 4
叮咚!您有一份六大必做安全操作清单,请查收
|
2天前
|
存储 关系型数据库 分布式数据库
GraphRAG:基于PolarDB+通义千问+LangChain的知识图谱+大模型最佳实践
本文介绍了如何使用PolarDB、通义千问和LangChain搭建GraphRAG系统,结合知识图谱和向量检索提升问答质量。通过实例展示了单独使用向量检索和图检索的局限性,并通过图+向量联合搜索增强了问答准确性。PolarDB支持AGE图引擎和pgvector插件,实现图数据和向量数据的统一存储与检索,提升了RAG系统的性能和效果。