代理详解之静态代理、动态代理、SpringAOP实现

news/2024/9/11 2:55:32/ 标签: 代理模式

1、代理介绍

代理是指一个对象A通过持有另一个对象B,可以具有B同样的行为的模式。为了对外开放协议,B往往实现了一个接口,A也会去实现接口。但是B是“真正”实现类,A则比较“虚”,他借用了B的方法去实现接口的方法。A虽然是“伪军”,但它可以增强B,在调用B的方法前后都做些其他的事情。Spring AOP就是使用了动态代理完成了代码的动态“织入”。

使用代理好处还不止这些,一个工程如果依赖另一个工程给的接口,但是另一个工程的接口不稳定,经常变更协议,就可以使用一个代理,接口变更时,只需要修改代理,不需要一一修改业务代码。从这个意义上说,所有调外界的接口,我们都可以这么做,不让外界的代码对我们的代码有侵入,这叫防御式编程。代理其他的应用可能还有很多。

上述例子中,类A写死持有B,就是B的静态代理。如果A代理的对象是不确定的,就是动态代理。动态代理目前有两种常见的实现,jdk动态代理和cglib动态代理。

2、静态代理

静态代理是一种设计模式,属于代理模式的一种。在静态代理中,代理类在程序运行前就已经被定义,并且在编译时就确定了代理类和被代理类的关系。这意味着代理类和被代理类都实现了相同的接口或继承了相同的父类,代理类内部持有一个被代理类的实例,并在自己的方法中调用被代理类的方法,同时可以在调用前后添加一些自己的操作,例如日志记录、权限检查、事务处理等。

静态代理有三个组成部分:抽象接口、代理类、被代理类,其实现例子如下:

1)定义抽象接口

public interface TargetInteface {void method1();void method2();int method3(Integer i);
}

2)定义代理类

public class TargetProxy implements TargetInteface {private Target target =new Target();@Overridepublic void method1() {System.out.println("执行方法前...");target.method1();System.out.println("执行方法后...");}@Overridepublic void method2() {System.out.println("执行方法前...");target.method2();System.out.println("执行方法后...");}@Overridepublic int method3(Integer i) {System.out.println("执行方法前...");int method3 = target.method3(i);System.out.println("执行方法后...");return method3;}
}

3)定义被代理类

public class Target implements TargetInteface {@Overridepublic void method1() {System.out.println(" Target method1 running ...");}@Overridepublic void method2() {System.out.println("Target method2 running ...");}@Overridepublic int method3(Integer i) {System.out.println("Target  method3 running ...");return i;}
}

4)定义客户端,查看执行结果

public class TargetUser {public static void main(String[] args) {TargetInteface target = new TargetProxy();target.method1();System.out.println("-----------------------------");target.method2();System.out.println("-----------------------------");System.out.println(target.method3(3));}
}

结果输出:

执行方法前...
 Target method1 running ...
执行方法后...
-----------------------------
执行方法前...
Target method2 running ...
执行方法后...
-----------------------------
执行方法前...
Target  method3 running ...
执行方法后...
3

从静态代理的实现不难看出,静态代理的优点是实现简单,易于理解。但其缺点也很明显,即每当需要为一个新的类添加代理功能时,都需要手动创建一个新的代理类,这会导致类的数量急剧增加,维护成本也随之提高。同时,代理类与被代理类之间的耦合程度太高 ,当被代理类中增加、删除、修改方法后,那么代理类中的也必须增加、删除、修改相应的方法,提高了代码的维护成本。另一个问题就是当代理对象代理多个target接口的实现类时,多个实现类中必然出在不同的方法,由于代理对象要实现与目标对象一致的接口(其实是包含关系),必然需要编写众多的方法,极其容易造成臃肿且难以维护的代码。

3、动态代理

动态代理的核心思想是在不修改原始对象代码的情况下,通过代理对象来间接访问原始对象,并在访问前后执行额外的操作。

动态代理的实现原理主要基于Java的反射机制。当使用动态代理时,需要定义一个接口或者一组接口,这些接口定义了被代理类(被代理对象)的行为。然后,需要编写一个实现了InvocationHandler接口的类,这个类中包含了在代理对象的方法调用前后执行的逻辑。当调用Proxy.newProxyInstance()方法时,传入接口的类加载器、接口数组和InvocationHandler对象,Java将会在运行时动态地生成一个实现了指定接口的代理类,并将方法调用委托给InvocationHandler对象来处理。当调用代理对象的方法时,实际上是调用了InvocationHandler接口的invoke()方法,在该方法中可以根据方法的名称、参数等信息执行一些预处理逻辑,然后再通过反射调用被代理对象的对应方法。

接下来介绍两种动态代理:JDK Proxy和CGLib

1)JDK Proxy

① JDK Proxy的内部机制

JDK Proxy通过Java的反射机制来动态生成代理类。具体来说,Proxy类会利用ProxyGenerator类(虽然这个类不是公开的API,但它是JDK内部实现动态代理的关键)来生成代理类的字节码,并将其加载到JVM中。生成的代理类会继承自java.lang.reflect.Proxy类,并实现指定的接口。在代理类的方法中,会调用InvocationHandlerinvoke方法,将方法调用转发给处理器处理。

此外,为了提高性能,JDK Proxy还提供了一个缓存机制,用于缓存已经生成的代理类的Class对象。这样,当需要创建相同类型的代理对象时,可以直接从缓存中获取代理类的Class对象,而无需重新生成。缓存是通过WeakCache类实现的,它利用弱引用来缓存对象,以便在JVM进行垃圾回收时能够自动清理不再使用的缓存项。

② JDK Proxy的实现步骤

  • 定义接口和被代理类:首先定义一个或多个接口,这些接口将被代理类实现。
public interface TargetInteface {void method1();void method2();int method3(Integer i);
}
public class Target implements TargetInteface {@Overridepublic void method1() {System.out.println("method1 running ...");}@Overridepublic void method2() {System.out.println("method2 running ...");}@Overridepublic int method3(Integer i) {System.out.println("method3 running ...");return i;}
}
  • 创建InvocationHandler:实现InvocationHandler接口,并重写invoke方法。在invoke方法中,可以添加自定义的逻辑,如日志记录、权限检查等,并通过反射调用原始类的方法。
  • 生成代理对象:调用Proxy.newProxyInstance方法,传入类加载器、接口数组以及InvocationHandler实例,来动态生成代理对象。该方法会返回一个实现了指定接口的代理类实例。
public class TargetProxy {public static  <T> Object getTarget(T t) {//新构建了一个 新的 代理类的对象return Proxy.newProxyInstance(t.getClass().getClassLoader(), t.getClass().getInterfaces(), new InvocationHandler() {@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {// proxy就是目标对象t,method就是调用目标对象中方法,args就是调用目标对象中方法的参数。//比如说:代理对象.method1(),这时proxy就是目标类,method1就是method,args就是method1方法参数。System.out.println("执行方法前...");Object invoke = method.invoke(t, args);System.out.println("执行方法后...");return invoke;}});}
}
  • 使用代理对象:通过代理对象调用方法时,实际上是调用了InvocationHandlerinvoke方法,在该方法中执行自定义逻辑后,再调用原始类的方法。
public class TargetUser {public static void main(String[] args) {TargetInteface target = (TargetInteface) TargetProxy.getTarget(new Target());target.method1();System.out.println("-----------------------------");target.method2();System.out.println("-----------------------------");System.out.println(target.method3(3));}
}

结果输出:
 执行方法前...
method1 running ...
执行方法后...
-----------------------------
执行方法前...
method2 running ...
执行方法后...
-----------------------------
执行方法前...
method3 running ...
执行方法后...
3

③ JDK Proxy的特点

  1. 接口代理:JDK Proxy只能代理实现了接口的类,不能代理没有实现接口的普通类。
  2. 动态生成:代理类是在运行时动态生成的,开发者无需手动编写代理类的代码。
  3. 灵活性强:可以在不修改原始类代码的情况下,为原始类添加额外的功能或逻辑。
  4. 性能考虑:由于涉及到反射和动态类的生成,JDK Proxy的性能可能略低于静态代理或直接调用原始类的方法。

2)CGLib

① CGLib动态代理的核心原理

  1. 字节码操作:CGLib底层使用ASM(一个小而快的字节码操作框架)来动态生成新的Java类(通常是目标类的子类)。这些新生成的类继承自目标类,并在方法调用时插入代理逻辑。
  2. 方法拦截:CGLib的核心功能是实现方法级别的拦截。开发者通过实现MethodInterceptor接口来定义一个方法拦截器,该拦截器会在代理对象的方法调用前后执行自定义逻辑,如预处理、后处理、异常处理等。
  3. FastClass机制:为了提高性能,CGLib采用了FastClass机制。FastClass通过对目标类的方法进行索引,并在调用时直接通过索引来访问目标方法,这种方式比Java反射要快得多

②  CGLib动态代理的实现步骤

  • 引入CGLib依赖:在项目中引入CGLib的Maven或Gradle依赖。
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
  • 定义目标类:定义需要被代理的目标类。
public class Target {public void method1() {System.out.println("method1 running ...");
}public void method2() {System.out.println("method2 running ...");}public int method3(Integer i) {System.out.println("method3 running ...");return i;}
}
  • 实现MethodInterceptor接口:创建一个实现了MethodInterceptor接口的类,并重写intercept方法。在该方法中编写代理逻辑。
  • 创建代理对象:使用CGLib提供的Enhancer类来创建代理对象。需要设置被代理的类(通过setSuperclass方法)和回调(通过setCallback方法设置MethodInterceptor实现类)。
public class TargetProxy {public static <T> Object getProxy(T t) {Enhancer en = new Enhancer(); //帮我们生成代理对象en.setSuperclass(t.getClass());//设置要代理的目标类en.setCallback(new MethodInterceptor() {//代理要做什么@Overridepublic Object intercept(Object object, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {System.out.println("执行方法前。。。");//调用原有方法  Object invoke = methodProxy.invokeSuper(object, args);// Object invoke = method.invoke(t, args);// 作用等同与上面。System.out.println("执行方法后。。。");return invoke;}});return en.create();}
}
  • 使用代理对象:通过代理对象调用目标类的方法时,会触发intercept方法中的代理逻辑。
public class TargetUser {public static void main(String[] args) {Target target = (Target) TargetProxy.getProxy(new Target());System.out.println(target.getClass().getName());target.method1();}}

结果输出:

com.heaboy.aopdemo.cglibproxy.Target$$EnhancerByCGLIB$$f9f41fb8
执行方法前。。。
method1 running ...
执行方法后。。。 

③ CGLib动态代理的适用场景

  1. 需要代理未实现接口的类:当目标类没有实现任何接口时,可以使用CGLib进行代理。
  2. 性能要求较高:在性能要求较高的场景下,如果JDK动态代理无法满足需求,可以考虑使用CGLib。
  3. AOP框架实现:在面向切面编程框架中,如Spring AOP,当需要代理未实现接口的类时,通常会使用CGLib作为底层实现。

 ④ CGLib动态代理的优缺点

优点:

  1. 灵活性高:可以代理没有实现接口的类,拓宽了代理的适用范围。
  2. 性能较好:通过FastClass机制,调用效率高于JDK动态代理的反射机制。
  3. 功能强大:支持在运行时动态地为目标类添加额外功能或逻辑,无需修改原始类代码。

缺点:

  1. 字节码操作开销:动态生成字节码并加载到JVM中会带来一定的性能开销。
  2. 无法代理final类和方法:由于CGLib是通过继承目标类来实现代理的,因此无法代理final修饰的类和方法。
  3. 使用复杂度较高:相比于JDK动态代理,CGLib的使用复杂度较高,需要引入额外的依赖并处理字节码生成的问题。

3)JDK Proxy VS CGLib 

代理对象类型:JDK Proxy只能代理实现了接口的类;而CGLib可以直接代理普通类。

性能:CGLib在运行时生成代理类的子类,通常认为其性能略优于JDK Proxy。但在大多数场景下,这种性能差异不大。

使用场景:如果目标对象已经实现了接口,使用JDK Proxy是一个简单直接的选择。如果需要代理没有实现接口的类,则必须使用CGLib。

依赖:JDK Proxy无需额外依赖,因为它是Java核心库的一部分;而CGLib需要添加CGLib库作为项目依赖。

JDK Proxy 是 Java 语言自带的功能,无需通过加载第三方类实现;

Java 对 JDK Proxy 提供了稳定的支持,并且会持续的升级和更新 JDK Proxy,例如 Java 8 版本中的 JDK Proxy 性能相比于之前版本提升了很多;

JDK Proxy 是通过拦截器加反射的方式实现的;

JDK Proxy 只能代理继承接口的类;

JDK Proxy 实现和调用起来比较简单;

CGLib 是第三方提供的工具,基于 ASM 实现的,性能比较高;

CGLib 无需通过接口来实现,它是通过实现子类的方式来完成调用的。

4、静态代理 VS 动态代理

区别

静态代理: 静态代理是在编译期间就已经确定的,需要为每个被代理的类编写一个代理类,代理类和被代理类实现相同的接口或继承相同的父类。

静态代理的代理类在编译时就存在,所以在程序运行时只能代理特定的类,无法动态地决定代理哪些类。

静态代理对原始对象的方法调用进行了包装,可以在调用前后添加额外的逻辑,但代理类需要提前编写,会增加代码的量。

静态代理在代码中显式指定代理对象,使用起来相对直观,但增加新的代理类需要重新编译。

动态代理: 动态代理是在运行时创建代理对象,无需提前编写代理类。使用Java的反射机制来动态生成代理类和代理对象。

动态代理基于接口进行代理,通过java.lang.reflect.Proxy类和java.lang.reflect.InvocationHandler接口实现。

动态代理可以代理多个接口的类,并动态决定代理哪些类。在运行时,可以根据需要为不同的对象生成代理,更具灵活性。

动态代理不需要为每个被代理类编写特定的代理类,更加灵活和节省代码量。

动态代理在代理对象的方法调用前后可以添加自定义的逻辑,例如日志记录、事务管理等。 动态代理的缺点是相对于静态代理而言,运行时生成代理对象需要一定的性能开销。

适用场景

静态代理适合以下场景:

当目标对象(被代理对象)数量有限且确定时,可以通过手动编写代理类来实现静态代理。静态代理在编译时就创建了代理类,因此在运行时性能较好。

静态代理对目标对象进行了封装,在不修改原有代码的情况下增加了额外的功能。这使得静态代理常被用于日志记录、事务管理等横切关注点。

动态代理适合以下场景:

当目标对象数量不确定或者无法提前确定时,动态代理可以更方便地生成代理对象。它在运行时生成代理类和代理对象,避免了手动编写多个代理类的繁琐工作。

动态代理可以灵活地在运行时为目标对象添加、删除或更改代理行为。这使得动态代理常被用于AOP(面向切面编程)、RPC(远程过程调用)等应用场景。

需要注意的是,由于动态代理在运行时通过反射机制创建代理类和代理对象,因此相比静态代理,其性能可能略低。此外,动态代理只能代理实现了接口的目标对象,而静态代理没有这个限制。

总结起来,静态代理适用于目标对象数量有限且确定、需要封装和增加额外功能的场景;而动态代理适用于目标对象数量不确定或无法提前确定、需要灵活添加、删除或更改代理行为的场景。根据具体需求和情况,选择合适的代理方式。

5、SpringAOP中的代理实现

1)SpringAOP介绍

谈谈对AOP的理解

Spring AOP是Spring框架中的一个重要模块,用于实现面向切面编程。

面对切面编程,这是一种编程模式,他允许程序员通过自定义的横切点进行模块化,将那些影响多个类的行为封装到课重用的模块中。例子:比如日志输出,不使用AOP的话就需要把日志的输出语句放在所有类中,方法中,但是有了AOP就可以把日志输出语句封装一个可重用模块,在以声明的方式将他们放在类中,每次使用类就自动完成了日志输出。

在面向切面编程的思想里面,把功能分为两种

  • 核心业务:登陆、注册、增、删、改、查、都叫核心业务

  • 周边功能:日志、事务管理这些次要的为周边业务

在面向切面编程中,核心业务功能和周边功能是分别独立进行开发,两者不是耦合的,然后把切面功能和核心业务功能 "编织" 在一起,这就叫AOP。

AOP能够将那些与业务无关,却为业务模块所共同调用的逻辑或责任(例如事务处理、日志管理、权限控制等)封装起来,便于减少系统的重复代码降低模块间的耦合度,并有利于未来的可拓展性和可维护性

在 AOP 中有以下几个概念:

  • AspectJ:切面,只是一个概念,没有具体的接口或类与之对应,是 Join point,Advice 和 Pointcut 的一个统称。

  • Join point:连接点,指程序执行过程中的一个点,例如方法调用、异常处理等。在 Spring AOP 中,仅支持方法级别的连接点。

  • Advice:通知,即我们定义的一个切面中的横切逻辑,有“around”,“before”和“after”三种类型。在很多的 AOP 实现框架中,Advice 通常作为一个拦截器,也可以包含许多个拦截器作为一条链路围绕着 Join point 进行处理。

  • Pointcut:切点,用于匹配连接点,一个 AspectJ 中包含哪些 Join point 需要由 Pointcut 进行筛选。

  • Introduction:引介,让一个切面可以声明被通知的对象实现任何他们没有真正实现的额外的接口。例如可以让一个代理对象代理两个目标类。

  • Weaving:织入,在有了连接点、切点、通知以及切面,如何将它们应用到程序中呢?没错,就是织入,在切点的引导下,将通知逻辑插入到目标方法上,使得我们的通知逻辑在方法调用时得以执行。

  • AOP proxy:AOP 代理,指在 AOP 实现框架中实现切面协议的对象。在 Spring AOP 中有两种代理,分别是 JDK 动态代理和 CGLIB 动态代理。

  • Target object:目标对象,就是被代理的对象。

Spring AOP 是基于 JDK 动态代理和 Cglib 提升实现的,两种代理方式都属于运行时的一个方式,所以它没有编译时的一个处理,那么因此 Spring 是通过 Java 代码实现的。

AOP解决了什么问题

一些分散在多个类或对象中的公共行为(如日志记录、事务管理、权限控制、接口限流、接口幂等等),这些行为通常被称为 横切关注点(cross-cutting concerns) 。如果我们在每个类或对象中都重复实现这些行为,那么会导致代码的冗余、复杂和难以维护。

AOP 可以将横切关注点(如日志记录、事务管理、权限控制、接口限流、接口幂等等)从 核心业务逻辑(core concerns,核心关注点) 中分离出来,实现关注点的分离。

AOP的应用场景

  • 日志记录:自定义日志记录注解,利用 AOP,一行代码即可实现日志记录。

  • 性能统计:利用 AOP 在目标方法的执行前后统计方法的执行时间,方便优化和分析。

  • 事务管理:@Transactional 注解可以让 Spring 为我们进行事务管理比如回滚异常操作,免去了重复的事务管理逻辑。@Transactional注解就是基于 AOP 实现的。

  • 权限控制:利用 AOP 在目标方法执行前判断用户是否具备所需要的权限,如果具备,就执行目标方法,否则就不执行。例如,SpringSecurity 利用@PreAuthorize 注解一行代码即可自定义权限校验。

  • 接口限流:利用 AOP 在目标方法执行前通过具体的限流算法和实现对请求进行限流处理。

  • 缓存管理:利用 AOP 在目标方法执行前后进行缓存的读取和更新。

AOP的实现方式

AOP 的常见实现方式有动态代理、字节码操作等方式。

Spring AOP 就是基于动态代理的,如果要代理的对象,实现了某个接口,那么 Spring AOP 会使用 JDK Proxy,去创建代理对象,而对于没有实现接口的对象,就无法使用 JDK Proxy 去进行代理了,这时候 Spring AOP 会使用 Cglib 生成一个被代理对象的子类来作为代理

2)基于JDK Proxy动态代理实现SpringAOP

① 配置SpringAOP

spring-aop.xml配置文件中配置相关的bean和切面

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"><bean id="target" class="com.xxhh.aopdemo.aop.Target"/><bean id="targetAdvice" class="com.xxhh.aopdemo.aop.TargetAdvice"/><bean id="targetProxy" class="org.springframework.aop.framework.ProxyFactoryBean"><property name="target" ref="target"/> <!--被代理的类--><property name="interceptorNames" value="targetAdvice"/>  <!--如果用多种增强方式,value的值使用逗号(,)分割--><property name="proxyTargetClass" value="false"/> <!--如果设置为true,则创建基于类的代理(使用CGLIB);如果设置为false,则创建基于接口的代理(使用JDK动态代理)。--><property name="interfaces" value="com.xxhh.aopdemo.aop.TargetInteface"/>  <!--target实现的接口--></bean>
</beans>

② 定义抽象接口

public interface TargetInteface {void method1();void method2();int method3(Integer i);
}

③ 定义被代理类

public class Target implements TargetInteface{/** 需要增强的方法,连接点JoinPoint**/@Overridepublic void method1() {System.out.println("method1 running ...");}@Overridepublic void method2() {System.out.println("method2 running ...");}@Overridepublic int method3(Integer i) {System.out.println("method3 running ...");return i;}
}

④ 定义代理类(增强方法) 

public class TargetAdvice implements MethodInterceptor, MethodBeforeAdvice, AfterReturningAdvice {/** 通知/增强**/@Overridepublic Object invoke(MethodInvocation methodInvocation) throws Throwable {System.out.println("前置环绕通知");Object proceed = methodInvocation.proceed();System.out.println("后置环绕通知");return proceed;}@Overridepublic void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {System.out.println("后置返回通知");}@Overridepublic void before(Method method, Object[] args, Object target) throws Throwable {System.out.println("前置通知");}
}

⑤ 测试

public class AopTest {public static void main(String[] args) {ApplicationContext appCtx = new ClassPathXmlApplicationContext("spring-aop.xml");TargetInteface targetProxy = (TargetInteface) appCtx.getBean("targetProxy");targetProxy.method1();}
}

输出结果:

前置环绕通知
前置通知
method1 running ...
后置返回通知
后置环绕通知 

3)基于CGLib动态代理实现SpringAOP

① 配置SpringAOP

spring-confaop.xml配置文件中配置相关的bean和切面

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns:context="http://www.springframework.org/schema/context"xmlns:aop="http://www.springframework.org/schema/aop"xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd"><!--先开启cglib代理,开启 exposeProxy = true,暴露代理对象--><aop:aspectj-autoproxy proxy-target-class="true" expose-proxy="true"/><!--扫包--><context:component-scan base-package="com.xxhh.aopdemo.annotationaop"/></beans>

② 定义被代理类

/*
* 目标类
**/
public class Target {public void method1() {System.out.println("method1 running ...");}public void method2() {System.out.println("method2 running ...");}/** 连接点JoinPoint**/public int method3(Integer i) {System.out.println("method3 running ...");
//        int i1 = 1 / i;return i;}
}

③ 定义代理类(切面类)

import org.aspectj.lang.ProceedingJoinPoint;
/*
* 切面类
**/
public class TargetAspect {/** 前置通知**/public void before() {System.out.println("conf前置通知");}public void after() {System.out.println("conf后置通知");}public void afterReturning() {System.out.println("conf后置返回通知");}public void afterThrowing(Exception ex) throws Exception {
//        System.out.println("conf异常通知");
//        System.out.println(ex.getMessage());}public Object around(ProceedingJoinPoint pjp) throws Throwable {Object proceed = null;if (!"".equals("admin")) {System.out.println("conf环绕前置");proceed = pjp.proceed(pjp.getArgs());System.out.println("conf环绕后置");}return proceed;}
}

④ 测试

public class AopTest {public static void main(String[] args) {ApplicationContext appCtx = new ClassPathXmlApplicationContext("spring-confaop.xml");Target targetProxy = (Target) appCtx.getBean("target");System.out.println(targetProxy.method3(0));}
}

输出结果:

conf前置通知
conf环绕前置
method3 running ...
conf后置返回通知
conf环绕后置
conf后置通知

4)基于注解动态代理实现SpringAOP

① 配置SpringAOP

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns:context="http://www.springframework.org/schema/context"xmlns:aop="http://www.springframework.org/schema/aop"xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd"><!--先开启cglib代理,开启 exposeProxy = true,暴露代理对象--><aop:aspectj-autoproxy proxy-target-class="true" expose-proxy="true"/><!--扫包--><context:component-scan base-package="com.xxhh.aopdemo.annotationaop"/></beans>

② 定义注解

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface TestAnnotation{
}

③ 定义切面类

/*
* 切面类
**/
@Aspect
@Component
public class AnnotationAspect {// 定义一个切点:所有被RequestMapping注解修饰的方法会织入advice@Pointcut("@annotation(TestAnnotation)")private void advicePointcut() {}/** 前置通知**/@Before("advicePointcut()")public void before() {System.out.println("annotation前置通知");}@After("advicePointcut()")public void after() {System.out.println("annotation后置通知");}@AfterReturning(pointcut = "advicePointcut()")public void afterReturning() {System.out.println("annotation后置返回通知");}@AfterThrowing(pointcut = "advicePointcut()", throwing = "ex")public void afterThrowing(Exception ex) throws Exception {System.out.println("annotation异常通知");System.out.println(ex.getMessage());}@Around("advicePointcut()")public Object around(ProceedingJoinPoint pjp) throws Throwable {Object proceed = null;if (!"".equals("admin")) {System.out.println("annotation环绕前置");proceed = pjp.proceed(pjp.getArgs());System.out.println("annotation环绕后置");}return proceed;}
}

④ controller添加注解

@Controller
public class TestController {@RequestMapping("/test.do")@ResponseBodypublic String testController() {TestController o = (TestController) AopContext.currentProxy();o.test();
//        System.out.println("tewt");return "ok";}@TestAnnotationpublic void test() {System.out.println("test running");}}

⑤ 测试


http://www.ppmy.cn/news/1474646.html

相关文章

服务网格新篇章:Eureka与分布式服务网格的协同共舞

服务网格新篇章&#xff1a;Eureka与分布式服务网格的协同共舞 引言 在微服务架构的浪潮中&#xff0c;服务网格&#xff08;Service Mesh&#xff09;技术以其微服务间通信的精细化控制而备受瞩目。Eureka作为Netflix开源的服务发现框架&#xff0c;虽然本身不直接提供服务网…

前端面试题47(在动态控制路由时,如何防止未授权用户访问受保护的页面?)

在Vue中&#xff0c;防止未授权用户访问受保护页面通常涉及到使用路由守卫&#xff08;Route Guards&#xff09;。路由守卫允许你在路由发生改变前或后执行一些逻辑&#xff0c;比如检查用户是否已登录或者有访问某个页面的权限。下面是一些常见的路由守卫类型及其使用方式&am…

C++相关概念和易错语法(19)(继承规则、继承下的构造和析构、函数隐藏)

1.继承规则 继承的本质是复用&#xff0c;是结构上的继承而不是内容上的继承&#xff0c;近似于在子类中声明了父类的成员变量。 &#xff08;1&#xff09;写法&#xff1a;class student : public person 派生类&#xff08;子类&#xff09;&#xff0c;继承方式&…

数据库doris中的tablet底层解析

在Doris中,tablet(数据片)是数据存储和管理的最小单元。理解tablet的底层原理有助于更好地理解Doris的高可用性、负载均衡和查询优化等特性。 Tablet 的概念 Tablet:Tablet是Doris中用于存储数据的最小物理单元。每个tablet通常对应于一个数据分区和一个分桶组合的子集。…

网工内推 | 网络运维、云计算工程师,NP以上认证,平均薪资10K

01 网络运维 &#x1f537;岗位职责 1、至少3年以上的网络运维相关工作经验; 2、熟悉VLAN、STP、OSPF、RIP、BGP等网络技术; 3、熟悉IPsec、SSL等VPN技术; 4、熟悉主流网络安全厂商的各种产品; 5、精通TCP/IP协议&#xff0c;熟悉主流网络产品设备的调试、配置方法: 6、有…

人工智能笔记分享

文章目录 人工智能图灵测试分类分类与聚类的区别&#xff08;重点&#xff09;分类 (Classification)聚类 (Clustering) 特征提取 分类器&#xff08;重点&#xff09;特征提取为什么要进行特征提取&#xff1f;&#xff08;重点&#xff09;分类器 训练集、测试集大小&#x…

Spring Boot与Jenkins的集成

Spring Boot与Jenkins的集成 大家好&#xff0c;我是免费搭建查券返利机器人省钱赚佣金就用微赚淘客系统3.0的小编&#xff0c;也是冬天不穿秋裤&#xff0c;天冷也要风度的程序猿&#xff01; 一、引言 Jenkins作为一个开源的持续集成&#xff08;CI&#xff09;和持续交付…

妙笔生词智能写歌词软件:科技赋能艺术还是冲淡原味?

在当今数字化的时代&#xff0c;科技的触角延伸至艺术创作的各个领域&#xff0c;妙笔生词智能写歌词软件便是其中一个引人瞩目的产物。然而&#xff0c;它的出现引发了一场关于科技与艺术关系的深刻思考&#xff1a;究竟是为艺术创作赋予了新的能量&#xff0c;还是在不经意间…

【NLP】利用 RAG 模分块技术提升文档处理效能

将大型文档划分为较小的部分是一项至关重要但又复杂的任务&#xff0c;它对检索增强生成 (RAG) 系统的性能有重大影响。这些系统旨在通过结合基于检索和基于生成的方法&#xff0c;提高输出的质量和相关性。有效的分块&#xff0c;即将文档拆分为可管理的片段的过程&#xff0c…

【区块链+跨境服务】基于区块链的离岸贸易综合服务平台 | FISCO BCOS应用案例

离岸贸易是一种新型的国际贸易模式&#xff0c;指在一个国家或地区的境内&#xff0c;通过一定的方式&#xff0c;将两个或多个国家或地区 之间的贸易活动&#xff0c;从货物流、资金流和信息流三个方面分离开来&#xff0c;实现货物不经过境内&#xff0c;直接从一个国家或地区…

qt 的表格控件有什么

在Qt中&#xff0c;表格控件主要用于显示和编辑表格数据。以下是Qt中常用的表格控件及其相关信息的详细归纳&#xff1a; QTableWidget 介绍&#xff1a;QTableWidget是Qt框架下的一个表格控件&#xff0c;它是基于QTableView的封装&#xff0c;并提供了更方便的方式来操作和呈…

uboot学习:(二)uboot命令

目录 uboot命令 常见命令 内存操作命令 网络操作命令 EMMC/SD卡操作命令: FAT格式文件系统操作命令: EXT格式文件系统操作命令 NAND操作命令 BOOT操作命令 其他命令 uboot命令 在烧录uboot到板子中后&#xff0c;开机三秒后才会进入系统&#xff0c;在这三秒按enter…

【机器学习】特征选择:精炼数据,提升模型效能

&#x1f308;个人主页: 鑫宝Code &#x1f525;热门专栏: 闲话杂谈&#xff5c; 炫酷HTML | JavaScript基础 ​&#x1f4ab;个人格言: "如无必要&#xff0c;勿增实体" 文章目录 特征选择&#xff1a;精炼数据&#xff0c;提升模型效能引言为何进行特征选择&a…

【LeetCode】633. 平方数之和

1. 题目 2. 分析 典型双指针的题了&#xff0c;不知道为啥LeetCode会把这题放到二分类别下&#xff1f; 需要知道math.ceil()是向上取整&#xff1b; 3. 代码 class Solution:def judgeSquareSum(self, c: int) -> bool:upper math.ceil(sqrt(c))print(upper)left, ri…

微信小程序之使用上拉加载实现图片懒加载

在微信小程序中&#xff0c;有2个事件&#xff0c;相信大家都很熟悉 下拉重新加载 上拉加载更多 事件是这么个事件&#xff0c;至于事件触发后干嘛&#xff0c;那就看代码了 首先要在对应得地方xxxxpage.json打开这个 "onReachBottomDistance": 100至于这个值100还是…

SpringSecurity 三更草堂学习笔记

0.简介 Spring Security是Spring家族中的一个安全管理框架。相比与另外一个安全框架Shiro&#xff0c;它提供了更丰富的功能&#xff0c;社区资源也比Shiro丰富。 一般来说中大型的项目都是使用SpringSecurity来做安全框架。小项目有Shiro的比较多&#xff0c;因为相比与Spring…

合并pdf的方法,如何合并pdf文件到一个pdf,简单方法

在现代办公和学习中&#xff0c;pdf格式的文件因其跨平台兼容性和安全性得到了广泛应用。然而&#xff0c;有时我们需要将多个pdf文件合并成一个&#xff0c;以便于管理和分享。本文将详细介绍几种合并pdf的方法&#xff0c;帮助读者轻松完成pdf文件的合并工作。 方法一、使用p…

urlib Python爬虫

要使用Python进行爬虫&#xff0c;可以使用Python自带的urllib库。 urllib是Python内置的HTTP请求库&#xff0c;用于发送HTTP请求、处理响应和处理URL编码等任务。它提供了很多方法和函数&#xff0c;可以方便地进行网络数据的获取和处理&#xff0c;因此在Python爬虫中被广泛…

极速目标检测:算法加速的策略与实践

标题&#xff1a;极速目标检测&#xff1a;算法加速的策略与实践 目标检测算法在计算机视觉任务中扮演着重要角色&#xff0c;但其计算成本往往较高。优化目标检测算法的速度&#xff0c;不仅可以提升效率&#xff0c;还能使算法适用于实时系统。本文将深入探讨如何优化目标检…

JS进阶-解析赋值

学习目标&#xff1a; 掌握解析赋值 学习内容&#xff1a; 解构赋值数组解构对象解构筛选数组filter方法&#xff08;重点&#xff09; 解构赋值&#xff1a; 解构赋值是一种快速为变量赋值的简洁语法&#xff0c;本质上仍然是为变量赋值。 分为&#xff1a; 数组解构对象解…