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

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.mzph.cn/bicheng/43874.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

vue3 + element-plus 表格行内编辑,如何实现表单校验?

问题描述&#xff1a; 当使用table实现行内编辑时&#xff0c;往往需要对必填项增加校验以及错误高度&#xff0c; 预期实现效果如下&#xff1a; 实现思路&#xff1a; 使用el-form表单自身的校验功能&#xff1a;通过el-from绑定对应表格行的prop&#xff0c; 实现校验 页面…

数据结构基础--------【二叉树题型】

1、前提(待补充) 1.**DFS&#xff08;Depth First Search&#xff09;&#x1f617;*递归法得到最终的数组&#xff08;深度优先算法&#xff09; 其过程简要来说是对每一个可能的分支路径深入到不能再深入为止&#xff0c;如果遇到死路就往回退&#xff0c;回退过程中如果遇…

OAuth2.0登录的四种方式

OAuth登录的四种方式 1. 授权码 授权码&#xff08;authorization code&#xff09;方式&#xff0c;指的是第三方应用先申请一个授权码&#xff0c;然后再用该码获取令牌。 这种方式是最常用的流程&#xff0c;安全性也最高&#xff0c;它适用于那些有后端的 Web 应用。授权…

点亿点计划Web3.0广告平台即将发射Clicks科力币

点亿点计划Web3.0广告平台即将发射Clicks科力币 我们很高兴地宣布&#xff0c;点亿点计划Web3.0广告平台即将发射Clicks科力币&#xff01;科力币&#xff08;Clicks&#xff09;是Clicks X Web3.0多功能应用的治理代币&#xff0c;未来将为代币持有人带来巨大的广告收入。 …

计算机的错误计算(二十六)

摘要 结合计算机的错误计算&#xff08;二十四&#xff09;中的 Maple 环境下的计算过程&#xff0c;&#xff08;二十五&#xff09;讨论了&#xff08;不&#xff09;停机问题。事实上&#xff0c;其它数学软件比如 Mathematica 也存在该问题。 &#xff08;不&#xff09;停…

《植物大战僵尸杂交版》2.2版本:全新内容与下载指南

《植物大战僵尸杂交版》2.2版本已经火热更新&#xff0c;带来了一系列令人兴奋的新玩法和调整&#xff0c;为这款经典的塔防游戏注入了新的活力。如果你是《植物大战僵尸》系列的忠实粉丝&#xff0c;那么这个版本绝对值得你一探究竟。 2.2版本更新亮点 新增看星星玩法 这个新…

Linux学习——Linux中无法使用ifconfg命令

Linux学习——Linux中无法使用ifconfg命令&#xff1f; &#x1f49d;&#x1f49d;&#x1f49d;欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅…

【Python基础篇】条件判断和循环判断

文章目录 1. 条件判断1.1 单分支1.2 双分支1.3 多分支 2. 循环判断2.1 while2.2 for2.3 break2.4 continue 1. 条件判断 1.1 单分支 前面学习了打印&#xff0c;但是有时候我们在打印时会面临选择&#xff0c;例如&#xff1a;一个网吧&#xff0c;未满18&#xff0c;禁止进入…

力扣喜刷刷--day1

1.无重复字符的最长子串 知识点&#xff1a;滑动窗口 基本概念 窗口&#xff1a;窗口是一个连续的子序列&#xff0c;可以是固定长度或可变长度。滑动&#xff1a;窗口在数据序列上移动&#xff0c;可以是向左或向右。边界&#xff1a;窗口的起始和结束位置。 应用场景 字符…

OpenAI与Thrive Global推出Thrive AI Health:AI驱动的健康教练应用

每周跟踪AI热点新闻动向和震撼发展 想要探索生成式人工智能的前沿进展吗&#xff1f;订阅我们的简报&#xff0c;深入解析最新的技术突破、实际应用案例和未来的趋势。与全球数同行一同&#xff0c;从行业内部的深度分析和实用指南中受益。不要错过这个机会&#xff0c;成为AI领…

JAVA之开发神器——IntelliJ IDEA的下载与安装

一、IDEA是什么&#xff1f; IEAD是JetBrains公司开发的专用于java开发的一款集成开发环境。由于其功能强大且符合人体工程学&#xff08;就是更懂你&#xff09;的优点&#xff0c;深受java开发人员的喜爱。目前在java开发工具中占比3/4。如果你要走java开发方向&#xff0c;那…

python+pygame实现五子棋人机对战之一

五子棋起源于中国&#xff0c;是全国智力运动会竞技项目之一&#xff0c;是一种两人对弈的纯策略型棋类游戏。双方分别使用黑白两色的棋子&#xff0c;下在棋盘直线与横线的交叉点上&#xff0c;先形成五子连珠者获胜。 本内容仅仅涉及到人机对战版&#xff0c;人人对战版后续…

【大模型LLM面试合集】大语言模型架构_MoE论文

1.MoE论文 参考文章&#xff1a; Mixture of Experts-IntroductionUnderstanding the Mixture-of-Experts Model in Deep Learning 论文相关&#xff1a; 论文名称&#xff1a;Outrageously Large Neural Networks: The Sparsely-Gated Mixture-of-Experts Layer论文地址&a…

秋招突击——7/9——复习{Java实现——LRU,Java实现——搜索插入位置}——新作{二分查找——搜索二维矩阵}

文章目录 引言复习Java实现——LRU缓存对照实现 Java实现——搜索插入位置java实现知识补充 新作搜索二维矩阵个人实现参考实现 总结 引言 以后都要向使用Java刷算法进行过滤了&#xff0c;所以今天主要是复习为主&#xff0c;复习两道之前做过的题目&#xff0c;然后做两道新…

如何在 Microsoft Edge 上使用开发人员工具

Microsoft Edge 提供了一套强大的开发人员工具&#xff0c;可帮助 Web 开发人员检查、调试和优化他们的网站或 Web 应用程序。 无论您是经验丰富的 Web 开发人员还是刚刚起步&#xff0c;了解如何有效地使用这些工具都可以对开发过程产生重大影响。 在本文中&#xff0c;我们…

Java版Flink使用指南——分流导出

大纲 新建工程编码Pom.xml自定义无界流分流 测试工程代码 在之前的案例中&#xff0c;我们一直使用的是单个Sink来做数据的输出。实际上&#xff0c;Flink是支持多个输出流的。本文我们就来讲解如何在Flink数据输出时做分流处理。 我们将基于《Java版Flink使用指南——自定义无…

【目标检测】使用自己的数据集训练并预测yolov8模型

1、下载yolov8的官方代码 地址&#xff1a; GitHub - ultralytics/ultralytics: NEW - YOLOv8 &#x1f680; in PyTorch > ONNX > OpenVINO > CoreML > TFLite 2、下载目标检测的训练权重 yolov8n.pt 将 yolov8n.pt 放在ultralytics文件夹下 3、数据集分布 注…

国际网课平台Udemy上的亚马逊云科技AWS免费高分课程和创建、维护EC2动手实践

亚马逊云科技(AWS)是全球云行业最&#x1f525;火的云平台&#xff0c;在全球经济形势不好的大背景下&#xff0c;通过网课学习亚马逊云科技AWS基础备考亚马逊云科技AWS证书&#xff0c;对于找工作或者无背景转行做AWS帮助巨大。欢迎大家关注小李哥&#xff0c;及时了解世界最前…

文件操作和IO流(Java版)

前言 我们无时无刻不在操作文件。可以说&#xff0c;我们在电脑上能看到的图片、视频、音频、文档都是一个又一个的文件&#xff0c;我们需要从文件中读取我们需要的数据&#xff0c;将数据运算后也需要将结果写入文件中长期保存。可见文件的重要性&#xff0c;今天我们就来简…

分布式锁(仅供自己参考)

分布式锁&#xff1a;满足分布式系统或集群式下多进程可见并且互斥的锁&#xff08;使用外部的锁&#xff0c;因为如果是集群部署&#xff0c;每台服务器都有一个对应的tomcat&#xff0c;则每个tomcat的jvm就不同&#xff0c;锁对象就不同&#xff08;加锁的机制&#xff0c;每…