代理详解之静态代理、动态代理、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.rhkb.cn/news/373213.html

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

相关文章

未羽研发测试管理平台

突然有一些觉悟&#xff0c;程序猿不能只会吭哧吭哧的低头做事&#xff0c;应该学会怎么去展示自己&#xff0c;怎么去宣传自己&#xff0c;怎么把自己想做的事表述清楚。 于是&#xff0c;这两天一直在整理自己的作品&#xff0c;也为接下来的找工作多做点准备。接下来…

wordpress外贸建站公司案例英文模板

Indirect Trade WP外贸网站模板 WordPress Indirect Trade外贸网站模板&#xff0c;建外贸独立站用wordpress模板&#xff0c;快速搭建十分便捷。 衣物清洁wordpress独立站模板 洗衣粉、洗衣液、衣物柔顺剂、干洗剂、衣领净、洗衣皂等衣物清洁wordpress独立站模板。 家具wordpr…

Apache部署与配置

概述 介绍 Apache HTTP Server(简称Apache)是Apache的一个开源的网页服务器&#xff0c;它源自NCSAhttpd服务器&#xff0c;并经过多次修改和发展&#xff0c;如今已经成为全球范围内广泛使用的Web服务器软件之一 特点 跨平台&#xff1a;可以运行在几乎所有广泛使用的计算机平…

哪个充电宝口碑比较好?怎么选充电宝?2024年口碑优秀充电宝推荐

在如今快节奏的生活中&#xff0c;充电宝已然成为我们日常生活中的必备品。然而&#xff0c;市场上充电宝品牌众多&#xff0c;质量参差不齐&#xff0c;如何选择一款安全、可靠且口碑优秀的充电宝成为了消费者关注的焦点。安全性能不仅关系到充电宝的使用寿命&#xff0c;更关…

【正点原子i.MX93开发板试用连载体验】项目计划和开箱体验

本文最早发表于电子发烧友&#xff1a;【   】【正点原子i.MX93开发板试用连载体验】基于深度学习的语音本地控制 - 正点原子学习小组 - 电子技术论坛 - 广受欢迎的专业电子论坛! (elecfans.com)https://bbs.elecfans.com/jishu_2438354_1_1.html 有一段时间没有参加电子发…

clickhouse-jdbc-bridge rce

clickhouse-jdbc-bridge 是什么 JDBC bridge for ClickHouse. It acts as a stateless proxy passing queries from ClickHouse to external datasources. With this extension, you can run distributed query on ClickHouse across multiple datasources in real time, whic…

支持向量机 (support vector machine,SVM)

支持向量机 &#xff08;support vector machine&#xff0c;SVM&#xff09; flyfish 支持向量机是一种用于分类和回归的机器学习模型。在分类任务中&#xff0c;SVM试图找到一个最佳的分隔超平面&#xff0c;使得不同类别的数据点在空间中被尽可能宽的间隔分开。 超平面方…

MMGPL: 多模态医学数据分析与图提示学习| 文献速递-基于深度学习的多模态数据分析与生存分析

Title 题目 MMGPL: Multimodal Medical Data Analysis with Graph Prompt Learning MMGPL: 多模态医学数据分析与图提示学习 01 文献速递介绍 神经学障碍&#xff0c;包括自闭症谱系障碍&#xff08;ASD&#xff09;&#xff08;Lord等&#xff0c;2018年&#xff09;和阿…

kafka的副本replica

指定topic的分区和副本 通过kafka命令行工具 kafka-topics.sh --create --topic myTopic --partitions 3 --replication-factor 1 --bootstrap-server localhost:9092 执行代码时指定分区个数

基于Spring Boot框架的EAM系统设计与实现

摘 要&#xff1a;文章设计并实现一个基于Spring Boot框架的EAM系统&#xff0c;以应对传统人工管理模式存在的低效与信息管理难题。系统利用Java语言、JSP技术、MySQL数据库等技术栈&#xff0c;构建了一个B/S架构的高效管理平台&#xff0c;提升了资产管理的信息化水平。该系…

大小端详解

引例 我们知道整形(int)是4个字节&#xff0c;例如随便举个例子&#xff1a;0x01020304&#xff0c;它一共占了四个地址位&#xff0c;01,02,03,04分别占了一个字节&#xff08;一个字节就对应了一个地址&#xff09;。 那么就会有个问题&#xff1a;我们的01到底是存储在高地…

STM32的 DMA(直接存储器访问) 详解

STM32的DMA&#xff08;Direct Memory Access&#xff0c;直接存储器存取&#xff09;是一种在单片机中用于高效实现数据传输的技术。它允许外设设备直接访问RAM&#xff0c;不需要CPU的干预&#xff0c;从而释放CPU资源&#xff0c;提高CPU工作效率&#xff0c;本文基于STM32F…

C++基础(1)

目录 C的输入输出&#xff1a; 命名空间域&#xff1a; 缺省&#xff08;默认&#xff09;参数&#xff1a; 函数重载&#xff1a; 引用&#xff1a; 内联函数inline&#xff1a; 指针空值nullptr&#xff1a; C的输入输出&#xff1a; 输入&#xff1a; int a; char …

社交论坛圈子系统APP开发社交圈子小程序系统源码开源,带语音派对聊天室/圈子社交论坛及时聊天

功能// 首页左右滑动切换分类 使用资讯类app常见的滑动切换分类&#xff0c;让用户使用更方便。 2信息卡片流展示 每条信息都是一个卡片&#xff0c;头像展示会员标签&#xff0c;单图自动宽度&#xff0c;多图九宫格展示&#xff0c;底部展示信息发布地址&#xff0c;阅读量、…

采用3种稀疏降噪模型对心电信号进行降噪(Matlab R2021B)

心电信号采集自病人体表&#xff0c;是一种无创性的检测手段。因此&#xff0c;心电信号采集过程中&#xff0c;本身也已经包含了机体内部其他生命活动带来的噪声。同时&#xff0c;由于采集设备和环境中存在电流的变化&#xff0c;产生电磁发射等物理现象&#xff0c;会对心电…

Java项目:基于SSM框架实现的中小型企业财务管理系统【ssm+B/S架构+源码+数据库+答辩PPT+开题报告+毕业论文】

一、项目简介 本项目是一套基于SSM框架实现的中小型企业财务管理系统 包含&#xff1a;项目源码、数据库脚本等&#xff0c;该项目附带全部源码可作为毕设使用。 项目都经过严格调试&#xff0c;eclipse或者idea 确保可以运行&#xff01; 该系统功能完善、界面美观、操作简单…

浅谈VPS主机上的数据库性能优化

如何提高网站性能&#xff1f;一个显而易见的解决方案是升级托管账户。您的网站将拥有更多硬件资源&#xff0c;因此可以同时处理更多请求并更快地传递数据。 无论如何&#xff0c;人们都是这么认为的。但事实总是不一样。 现代网站是一个复杂的系统&#xff0c;包含许多必须…

效果惊人!LivePortrait开源数字人技术,让静态照片生动起来

不得了了,快手已经不是众人所知的那个短视频娱乐平台了。 可灵AI视频的风口尚未过去,又推出了LivePortrait--开源的数字人项目。LivePortrait让你的照片动起来,合成逼真的动态人像视频,阿里通义EMO不再是唯一选择。 让图像动起来 LivePortrait 主要提供了对眼睛和嘴唇动作的…

实体构件库设计说明方案

实体构件库设计说明方案 一、引言 在数字化设计与制造日益普及的今天&#xff0c;实体构件库作为连接设计创意与物理实现的关键桥梁&#xff0c;其重要性不言而喻。实体构件库不仅存储了各类标准化、参数化的零部件模型&#xff0c;还提供了便捷的检索、配置、优化及自动化生…

昇思MindSpore学习总结十二 —— ShuffleNet图像分类

当前案例不支持在GPU设备上静态图模式运行&#xff0c;其他模式运行皆支持。 1、ShuffleNet网络介绍 ShuffleNetV1是旷视科技提出的一种计算高效的CNN模型&#xff0c;和MobileNet, SqueezeNet等一样主要应用在移动端&#xff0c;所以模型的设计目标就是利用有限的计算资源来达…