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日志并进行多维度分析。
相关文章
|
8天前
|
XML Java 数据安全/隐私保护
Spring Aop该如何使用
本文介绍了AOP(面向切面编程)的基本概念和术语,并通过具体业务场景演示了如何在Spring框架中使用Spring AOP。文章详细解释了切面、连接点、通知、切点等关键术语,并提供了完整的示例代码,帮助读者轻松理解和应用Spring AOP。
Spring Aop该如何使用
|
28天前
|
存储 缓存 Java
Spring高手之路23——AOP触发机制与代理逻辑的执行
本篇文章深入解析了Spring AOP代理的触发机制和执行流程,从源码角度详细讲解了Bean如何被AOP代理,包括代理对象的创建、配置与执行逻辑,帮助读者全面掌握Spring AOP的核心技术。
34 3
Spring高手之路23——AOP触发机制与代理逻辑的执行
|
13天前
|
Java Spring
[Spring]aop的配置与使用
本文介绍了AOP(面向切面编程)的基本概念和核心思想。AOP是Spring框架的核心功能之一,通过动态代理在不修改原代码的情况下注入新功能。文章详细解释了连接点、切入点、通知、切面等关键概念,并列举了前置通知、后置通知、最终通知、异常通知和环绕通知五种通知类型。
26 1
|
28天前
|
Java 数据安全/隐私保护 Spring
Spring进阶:初识动态代理
本文介绍了Spring框架中AOP切面编程的基础——动态代理。通过定义Vehicle接口及其实现类Car和Ship,展示了如何使用动态代理在不修改原代码的基础上增强功能。文章详细解释了动态代理的工作原理,包括通过`Proxy.newProxyInstance()`方法创建代理对象,以及`InvocationHandler`接口中的`invoke()`方法如何处理代理对象的方法调用。最后,通过一个测试类`TestVehicle`演示了动态代理的具体应用。
|
9天前
|
安全 Java 测试技术
Java开发必读,谈谈对Spring IOC与AOP的理解
Spring的IOC和AOP机制通过依赖注入和横切关注点的分离,大大提高了代码的模块化和可维护性。IOC使得对象的创建和管理变得灵活可控,降低了对象之间的耦合度;AOP则通过动态代理机制实现了横切关注点的集中管理,减少了重复代码。理解和掌握这两个核心概念,是高效使用Spring框架的关键。希望本文对你深入理解Spring的IOC和AOP有所帮助。
21 0
|
1月前
|
Java 编译器 Spring
Spring AOP 和 AspectJ 的区别
Spring AOP和AspectJ AOP都是面向切面编程(AOP)的实现,但它们在实现方式、灵活性、依赖性、性能和使用场景等方面存在显著区别。‌
65 2
|
1月前
|
XML Java 数据格式
Spring的IOC和AOP
Spring的IOC和AOP
45 0
|
2月前
Micronaut AOP与代理机制:实现应用功能增强,无需侵入式编程的秘诀
AOP(面向切面编程)能够帮助我们在不修改现有代码的前提下,为应用程序添加新的功能或行为。Micronaut框架中的AOP模块通过动态代理机制实现了这一目标。AOP将横切关注点(如日志记录、事务管理等)从业务逻辑中分离出来,提高模块化程度。在Micronaut中,带有特定注解的类会在启动时生成代理对象,在运行时拦截方法调用并执行额外逻辑。例如,可以通过创建切面类并在目标类上添加注解来记录方法调用信息,从而在不侵入原有代码的情况下增强应用功能,提高代码的可维护性和可扩展性。
61 1
|
15天前
|
安全 Java 编译器
什么是AOP面向切面编程?怎么简单理解?
本文介绍了面向切面编程(AOP)的基本概念和原理,解释了如何通过分离横切关注点(如日志、事务管理等)来增强代码的模块化和可维护性。AOP的核心概念包括切面、连接点、切入点、通知和织入。文章还提供了一个使用Spring AOP的简单示例,展示了如何定义和应用切面。
49 1
什么是AOP面向切面编程?怎么简单理解?
|
19天前
|
XML Java 开发者
论面向方面的编程技术及其应用(AOP)
【11月更文挑战第2天】随着软件系统的规模和复杂度不断增加,传统的面向过程编程和面向对象编程(OOP)在应对横切关注点(如日志记录、事务管理、安全性检查等)时显得力不从心。面向方面的编程(Aspect-Oriented Programming,简称AOP)作为一种新的编程范式,通过将横切关注点与业务逻辑分离,提高了代码的可维护性、可重用性和可读性。本文首先概述了AOP的基本概念和技术原理,然后结合一个实际项目,详细阐述了在项目实践中使用AOP技术开发的具体步骤,最后分析了使用AOP的原因、开发过程中存在的问题及所使用的技术带来的实际应用效果。
47 5