从代理机制到Spring AOP,这篇给你安排的明明白白的

2023-12-15 05:38

本文主要是介绍从代理机制到Spring AOP,这篇给你安排的明明白白的,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

 

今日推荐

论异步编程的正确姿势:十个接口的活现在只需要一个接口就能搞定!让SpringBoot不再需要Controller、Service、Mapper,这款开源工具绝了!「吐血」我把 10 年的全部学习资源都分享在这里了还在用Spring Security?推荐你一款使用简单、功能强大的权限认证框架干掉 navicat:这款 DB 管理工具才是y(永)y(远)d(的)s(神)

来源:juejin.cn/post/6844903672061624327

这篇文章准备从Java的代理机制讲到Spring的AOP。

1.代理模式

代理模式是很常见的一种设计模式,代理一词拆开来看就是代为受理,那显然是要涉及到请求被代理的委托方,提供代理的代理方,以及想要通过代理来实际联系委托方的客户三个角色。

举个生活中很常见的例子,各路的明星都会有个自己的经纪人来替自己打点各种各样的事情,这种场景下,明星本身是委托方,经纪人是代理方,明星把自己安排演出、出席见面会的时间安排权利委托给经纪人,这样当各个商家作为客户想要请明星来代言时,就只能通过经纪人来进行。

这样明星本身不用暴露身份,而经济人也可以在沟通中告知商家明星出席活动时要吃什么饭,做什么车的一些要求,省去了明星自己操心这些鸡毛蒜皮小事儿。另一方面,当经纪人也可以给多个明星提供服务,这样商家只接触一个经纪人,可以联系到不同的明星,找个适合自己公司的人选。

通过上面的例子,代理模式的优点就显而易见了:

优点一:可以隐藏委托类的实现;

优点二:可以实现客户与委托类间的解耦,在不修改委托类代码的情况下能够做一些额外的处理。

2.字节码与代理模式

Java程序员都应该知道,Java通过Java编译器将.java源文件编译成.class字节码文件,这种.class文件是二进制文件,内容是只有JVM虚拟机能够识别的机器码,JVM虚拟机读取字节码文件,取出二进制数据,加载到内存中,解析.class文件内的信息,生成对应的Class对象,进而使Class对象创建类的具体实例来进行调用实现具体的功能。

30a5879b8fe38b377d14d41615fc617a.png

上图说明了Java加载字节码的流程,但是Java的强大在于不仅仅可以加载在编译期生成好的字节码,还可以在运行期系统中,遵循Java编译系统组织.class文件的格式和结构,生成相应的二进制数据,然后再把这个二进制数据加载转换成对应的类,这样,就完成了在代码中,动态创建一个类的能力了,如下图流程。

3c64c6a48b581326e65e646ed14af576.png

下面举一个动态生成类的实例,通过Javassist实现,Javassist是一个开源的分析、编辑和创建Java字节码的类库,我们可以使用Javasisst工具在运行时动态创建字节码并加载类,如下代码:

public class JavassistDemo {public static void main(String[] args) {makeNewClass();}public static Class<?> makeNewClass() {try {// 获取ClassPoolClassPool pool = ClassPool.getDefault();// 创建Student类CtClass ctClass = pool.makeClass("com.fufu.aop.Student");// 创建Student类成员变量nameCtField name = new CtField(pool.get("java.lang.String"), "name", ctClass);// 设置name为私有name.setModifiers(Modifier.PRIVATE);// 将name写入classctClass.addField(name, CtField.Initializer.constant("")); //写入class文件//增加set方法,名字为"setName"ctClass.addMethod(CtNewMethod.setter("setName", name));//增加get方法,名字为getnamectClass.addMethod(CtNewMethod.getter("getName", name));// 添加无参的构造体CtConstructor cons = new CtConstructor(new CtClass[] {}, ctClass);cons.setBody("{name = \"Brant\";}"); //相当于public Sclass(){this.name = "brant";}ctClass.addConstructor(cons);// 添加有参的构造体cons = new CtConstructor(new CtClass[] {pool.get("java.lang.String")}, ctClass);cons.setBody("{$0.name = $1;}");  //第一个传入的形参$1,第二个传入的形参$2,相当于public Sclass(String s){this.name = s;}ctClass.addConstructor(cons);//反射调用新创建的类Class<?> aClass =  ctClass .toClass();Object student = aClass.newInstance();Method getter = null;getter = student.getClass().getMethod("getName");System.out.println(getter.invoke(student));} catch (Exception e) {e.printStackTrace();}return null;}
}

介绍静态和动态加载字节码的两种方式,是为了引出下面关于两种代理方式的介绍,代理机制通过代理类创建的时间不同分为了静态代理和动态代理:

静态代理:代理类在编译阶段生成,程序运行前就已经存在,那么这种代理方式被成为静态代理,这种情况下的代理类通常都是我们在Java代码中定义的。

动态代理:代理类在程序运行时创建,也就是说,这种情况下,代理类并不是在Java代码中定义的,而是在运行时根据我们在Java代码中的“指示”动态生成的。

目前,静态代理主要有AspectJ静态代理、JDK静态代理技术、而动态代理有JDK动态代理、Cglib动态代理技术,而Spring Aop是整合使用了JDK动态代理和Cglib动态代理两种技术,下面我们结合实例一步一步介绍所有的概念。

3.静态代理

3.1 AspectJ静态代理

对于AspectJ,我们只会进行简单的了解,为后续理解打下基础,现在只需要知道下面这一句定义:

AspectJ是一个Java实现的面向切面的框架,它扩展了Java语言。AspectJ有自定义的语法,所以它有一个专门的编译器用来生成遵守Java字节编码规范的Class文件。

注意上面定义中的“专门的编译器”这个描述,可以看出AspectJ是典型的静态代理技术,因为是在编译时期就生成了代理类,而使用AspectJ也肯定需要指定特定的编译器,下面我们用AspectJ来实现上面的明星和经纪人的模型。

首先在maven工程中引入AspectJ依赖:

<dependency><groupId>org.aspectj</groupId><artifactId>aspectjrt</artifactId><version>1.8.9</version></dependency><dependency><groupId>org.aspectj</groupId><artifactId>aspectjtools</artifactId><version>1.8.9</version></dependency>

然后在idea中将javac编译器改为acj编译器来支持AspectJ语法:

0b2b18f83d091741b75b0bd1af513cc9.png

将明星的表演抽象成一个ShowService接口,包括了唱歌、跳舞的功能

public interface ShowService {// 歌唱表演void sing(String songName);// 舞蹈表演void dance();
}

明星类实现了ShowService接口:

package com.fufu.aop;public class Star implements ShowService{private String name;@Overridepublic void sing(String songName) {System.out.println(this.name + " sing a song: " + songName);}@Overridepublic void dance() {System.out.println(this.name + "dance");}public Star(String name) {this.name = name;}public Star() {}public static void main(String[] args) {Star star = new Star("Eminem");star.sing("Mockingbird");}
}

用AspectJ语法实现一个代理AgentAspectJ:

package com.fufu.aop;public aspect AgentAspectJ {/*** 定义切点*/pointcut sleepPointCut():call(* Star.sing(..));/*** 定义切点*/pointcut eatPointCut():call(* Star.eat(..));/*** 定义前置通知** before(参数):连接点函数{*     函数体* }*/before():sleepPointCut(){getMoney();}/*** 定义后置通知* after(参数):连接点函数{*     函数体* }*/after():sleepPointCut(){writeReceipt();}private void getMoney() {System.out.println("get money");}private void writeReceipt() {System.out.println("write receipt");}
}

创建一个Star并运行方法:

public static void main(String[] args) {Star star = new Star("Eminem");star.sing("Mockingbird");}

输出:

get money
Eminem sing a song: Mockingbird
write receipt

可以看到Star的sing()方法前后输出了我们在AgentAspectJ中定义的前置通知和后置通知,所以是AspectJ在编译期间,根据AgentAspectJ代码中定义的代码,生成了增强的Star类,而我们实际调用时,就会实现代理类的功能。

具体的AspectJ语法我们不深究,只需要知道pointcut是定义代理要代理的切入点,这里是定义了两个pointcut,分别是Star类的sing()方法和dance()方法。而before()和after()分别可以定义具体在切入点前后需要的额外操作。

总结一下,AspctJ就是用特定的编译器和语法,对类实现编译期增强,实现静态代理技术,下面我们看JDK静态代理。

3.2 JDK静态代理

通常情况下, JDK静态代理更多的是一种设计模式,JDK静态代理的代理类和委托类会实现同一接口或是派生自相同的父类,代理模式的基本类图入下:

95999c861d4ebf0fa827a41bb08b8680.png

我们接着通过把上面的明星和经纪人的例子写成代码来实现一个JDK静态代理模式。

经纪人类也实现了ShowService接口,持有了一个明星对象来提供真正的表演,并在各项表演的前后加入了经纪人需要处理的事情,如收钱、开发票等:

package com.fufu.aop;/*** 经纪人*/
public class Agent implements ShowService{private Star star;public Agent(Star star) {this.star = star;}private void getMoney() {System.out.println("get money");}private void writeReceipt() {System.out.println("write receipt");}@Overridepublic void sing(String songName) {// 唱歌开始前收钱getMoney();// 明星开始唱歌star.sing(songName);// 唱歌结束后开发票writeReceipt();}@Overridepublic void dance() {// 跳舞开始前收钱getMoney();// 明星开始跳舞star.dance();// 跳舞结束后开发票writeReceipt();}
}

通过经纪人来请明星表演:

public static void main(String[] args) {Agent agent = new Agent(new Star("Eminem"));agent.sing("Mockingbird");}

输出:

get money
Eminem sing a song: Mockingbird
write receipt

以上就是一个典型的静态代理的实例,很简单但是也能说明问题,我们来看看静态代理的优缺点:

优点: 业务类可以只关注自身逻辑,可以重用,通过代理类来增加通用的逻辑处理。

缺点:

  1. 代理对象的一个接口只服务于一种类型的对象,如果要代理的类很多,势必要为每一个类都进行代理,静态代理在程序规模稍大时就无法胜任了。

  2. 如果接口增加一个方法,除了所有实现类需要实现这个方法外,所有代理类也需要实现此方法。增加了代码维护的复杂度

另外,如果要按照上述的方法使用代理模式,那么真实角色(委托类)必须是事先已经存在的,并将其作为代理对象的内部属性。但是实际使用时,一个真实角色必须对应一个代理角色,如果大量使用会导致类的急剧膨胀;此外,如果事先并不知道真实角色(委托类),该如何使用代理呢?这些问题可以通过Java的动态代理类来解决。

4.动态代理

动态代理类的源码是在程序运行期间由JVM根据反射等机制动态的生成,所以不存在代理类的字节码文件。代理类和委托类的关系是在程序运行时确定。

4.1 动态代理思路

想弄明白动态代理类实现的思路是什么,我们还需用从静态代理的存在的问题入手,因为毕竟动态代理是为了解决静态代理存在问题而出现的,回过头来看静态代理的问题:

  1. 类膨胀:每个代理类都是一个需要程序员编写的具体类,不现实。

  2. 方法级代理:代理类和实现类都实现相同接口,导致代理类每个方法都需要进行代理,你有几个方法我就要有几个,编码复杂,无法维护。

动态代理如何解决:

  1. 第一个问题很容易回答,类似使用Javasisst的例子,在代码中动态的创建代理类的字节码,然后获取到代理类对象。

  2. 第二问题就要引出InvocationHandler了,为了构造出具有通用性和简单性的代理类,可以将所有的触发真实角色动作交给一个触发的管理器,让这个管理器统一地管理触发。这种管理器就是InvocationHandler。静态代理中,代理类无非是在前后加入特定逻辑后,调用对应的实现类的方法,sleep()对应sleep(),run()对应run(),而在Java中,方法Method也是一个对象,所以,动态代理类可以将对自己的所有调用作为Method对象都交给InvocationHandler处理,InvocationHandler根据是什么Method调用具体实现类的不同方法,InvocationHandler负责增加代理逻辑和调用具体的实现类的方法。

也就是说,动态代理类还是和实现类实现相同的接口,但是动态代理类是根据实现类实现的接口动态生成,不需要使用者关心,另外动态代理类的所有方法调用,统一交给InvocationHandler,不用处理实现类每个接口的每个方法。

在这种模式之中:代理Proxy和RealSubject应该实现相同的功能,这一点相当重要。(我这里说的功能,可以理解为某个类的public方法)

在面向对象的编程之中,如果我们想要约定Proxy和RealSubject可以实现相同的功能,有两种方式:

a.一个比较直观的方式,就是定义一个功能接口,然后让Proxy 和RealSubject来实现这个接口。
b.还有比较隐晦的方式,就是通过继承。因为如果Proxy继承自RealSubject,这样Proxy则拥有了RealSubject的功能,Proxy还可以通过重写RealSubject中的方法,来实现多态。

其中JDK中提供的创建动态代理的机制,是以a这种思路设计的,而cglib则是以b思路设计的。

4.1 JDK动态代理(通过接口)

先来看一个具体的例子,还是以上边明星和经纪人的模型为例,这样方便对比理解:

将明星的表演抽象成一个ShowService接口,包括了唱歌、跳舞的功能:

package com.fufu.aop;public interface ShowService {// 歌唱表演void sing(String songName);// 舞蹈表演void dance();
}

明星类实现了ShowService接口:

package com.fufu.aop;/*** 明星类*/
public class Star implements ShowService{private String name;@Overridepublic void sing(String songName) {System.out.println(this.name + " sing a song: " + songName);}@Overridepublic void dance() {System.out.println(this.name + "dance");}public Star(String name) {this.name = name;}public Star() {}
}

实现一个代理类的请求处理器,处理对具体类的所有方法的调用:

package com.fufu.aop;import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;public class InvocationHandlerImpl implements InvocationHandler {ShowService target;public InvocationHandlerImpl(ShowService target) {this.target = target;}@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {// 表演开始前收钱getMoney();// 明星开始唱歌Object invoke = method.invoke(target, args);// 表演结束后开发票writeReceipt();return invoke;}private void getMoney() {System.out.println("get money");}private void writeReceipt() {System.out.println("write receipt");}
}

通过JDK动态代理机制实现一个动态代理:

package com.fufu.aop;import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;public class JDKProxyDemo {public static void main(String[] args) {// 1.创建被代理的具体类Star star = new Star("Eminem");// 2.获取对应的ClassLoaderClassLoader classLoader = star.getClass().getClassLoader();// 3.获取被代理对象实现的所有接口Class[] interfaces = star.getClass().getInterfaces();// 4.设置请求处理器,处理所有方法调用InvocationHandler invocationHandler = new InvocationHandlerImpl(star);/*** 5.根据上面提供的信息,创建代理对象 在这个过程中,*   a.JDK会通过根据传入的参数信息动态地在内存中创建和.class文件等同的字节码*   b.然后根据相应的字节码转换成对应的class,*   c.然后调用newInstance()创建实例*/Object o = Proxy.newProxyInstance(classLoader, interfaces, invocationHandler);ShowService showService = (ShowService)o;showService.sing("Mockingbird");}
}

我们从代理的创建入手,看看JDK的动态代理都做了什么:

Object o = Proxy.newProxyInstance(classLoader, interfaces, invocationHandler);
  1. Proxy.newProxyInstance()获取Star类的所有接口列表(第二个参数:interfaces)

  2. 确定要生成的代理类的类名,默认为:com.sun.proxy.$ProxyXXXX

  3. 根据需要实现的接口信息,在代码中动态创建该Proxy类的字节码;

  4. 将对应的字节码转换为对应的class对象;

  5. 创建InvocationHandler实例handler,用来处理Proxy所有方法调用

  6. Proxy的class对象以创建的handler对象为参数(第三个参数:invocationHandler),实例化一个Proxy对象

而对于InvocationHandler,我们需要实现下列的invoke方法:

public Object invoke(Object proxy, Method method, Object[] args)

在调用代理对象中的每一个方法时,在代码内部,都是直接调用了InvocationHandler的invoke方法,而invoke方法根据代理类传递给自己的method参数来区分是什么方法。

可以看出,Proxy.newProxyInstance()方法生成的对象也是实现了ShowService接口的,所以可以在代码中将其强制转换为ShowService来使用,和静态代理到达了同样的效果。我们可以用下面代码把生成的代理类的字节码保存到磁盘里,然后反编译看看JDK生成的动态代理类的结构。

package com.fufu.aop;import sun.misc.ProxyGenerator;import java.io.FileOutputStream;
import java.io.IOException;public class ProxyUtils {public static void main(String[] args) {Star star = new Star("Eminem");generateClassFile(star.getClass(), "StarProxy");}public static void generateClassFile(Class clazz, String proxyName) {//根据类信息和提供的代理类名称,生成字节码byte[] classFile = ProxyGenerator.generateProxyClass(proxyName, clazz.getInterfaces());String paths = clazz.getResource(".").getPath();System.out.println(paths);FileOutputStream out = null;try {//保留到硬盘中out = new FileOutputStream(paths + proxyName + ".class");out.write(classFile);out.flush();} catch (Exception e) {e.printStackTrace();} finally {try {out.close();} catch (IOException e) {e.printStackTrace();}}}
}

反编译StarPoxy.class文件后得到:

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//import com.fufu.aop.ShowService;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;// 动态代理类StarPoxy实现了ShowService接口
public final class StarProxy extends Proxy implements ShowService {// 加载接口中定义的所有方法private static Method m1;private static Method m3;private static Method m4;private static Method m2;private static Method m0;//构造函数接入InvocationHandler,也就是持有了InvocationHandler对象hpublic StarProxy(InvocationHandler var1) throws  {super(var1);}public final boolean equals(Object var1) throws  {try {return ((Boolean)super.h.invoke(this, m1, new Object[]{var1})).booleanValue();} catch (RuntimeException | Error var3) {throw var3;} catch (Throwable var4) {throw new UndeclaredThrowableException(var4);}}// 自动生成的sing()方法,实际调用InvocationHandler对象h的invoke方法,传入m3参数对象代表sing()方法public final void sing(String var1) throws  {try {super.h.invoke(this, m3, new Object[]{var1});} catch (RuntimeException | Error var3) {throw var3;} catch (Throwable var4) {throw new UndeclaredThrowableException(var4);}}//同理生成dance()方法public final void dance() throws  {try {super.h.invoke(this, m4, (Object[])null);} catch (RuntimeException | Error var2) {throw var2;} catch (Throwable var3) {throw new UndeclaredThrowableException(var3);}}public final String toString() throws  {try {return (String)super.h.invoke(this, m2, (Object[])null);} catch (RuntimeException | Error var2) {throw var2;} catch (Throwable var3) {throw new UndeclaredThrowableException(var3);}}public final int hashCode() throws  {try {return ((Integer)super.h.invoke(this, m0, (Object[])null)).intValue();} catch (RuntimeException | Error var2) {throw var2;} catch (Throwable var3) {throw new UndeclaredThrowableException(var3);}}// 加载接口中定义的所有方法static {try {m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[]{Class.forName("java.lang.Object")});m3 = Class.forName("com.fufu.aop.ShowService").getMethod("sing", new Class[]{Class.forName("java.lang.String")});m4 = Class.forName("com.fufu.aop.ShowService").getMethod("dance", new Class[0]);m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);} catch (NoSuchMethodException var2) {throw new NoSuchMethodError(var2.getMessage());} catch (ClassNotFoundException var3) {throw new NoClassDefFoundError(var3.getMessage());}}
}

通过上面反编译后的代码可以看出,JDK生成的动态代理类实现和具体类相同的接口,并持有InvocationHandler对象(InvocationHandler对象又持有具体类),调用动态代理类中方法,会触发传入InvocationHandler的invoke()方法,通过method参数,来区分调用的是什么具体的方法,具体如下图所示:

db05aa99df8e3776f9d05e103b1d3b95.png
4.2 CGLIB动态代理(通过继承)

JDK中提供的生成动态代理类的机制有个鲜明的特点是:

某个类必须有实现的接口,而生成的代理类也只能代理某个类接口定义的方法,比如:如果上面例子的Star实现了继承自ShowService接口的方法外,另外实现了方法play(),则在产生的动态代理类中不会有这个方法了!更极端的情况是:如果某个类没有实现接口,那么这个类就不能用JDK产生动态代理了!

幸好我们有cglib,“CGLIB(Code Generation Library),是一个强大的,高性能,高质量的Code生成类库,它可以在运行期扩展Java类与实现Java接口。”

cglib 创建某个类A的动态代理类的模式是:

  1. 查找A上的所有非final 的public类型的方法定义;

  2. 将这些方法的定义转换成字节码;

  3. 将组成的字节码转换成相应的代理的class对象;

  4. 实现 MethodInterceptor接口,用来处理对代理类上所有方法的请求(这个接口和JDK动态代理InvocationHandler的功能和角色是一样的)

有了上边JDK动态代理的例子,cglib的理解起来就简单了,还是先以实例说明,ShowService接口和Star类都复用之前的不变:

实现 MethodInterceptor接口:

package com.fufu.aop;import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;import java.lang.reflect.Method;public class MethodInterceptorImpl implements MethodInterceptor {@Overridepublic Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {// 表演开始前收钱getMoney();// 明星开始唱歌Object invoke = methodProxy.invokeSuper(o, objects);// 表演结束后开发票writeReceipt();return invoke;}private void getMoney() {System.out.println("get money");}private void writeReceipt() {System.out.println("write receipt");}
}

创建动态代理:

package com.fufu.aop;import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;public class CglibProxyDemo {public static void main(String[] args) {Star star = new Star("Eminem");MethodInterceptor methodInterceptor = new MethodInterceptorImpl();//cglib 中加强器,用来创建动态代理Enhancer enhancer = new Enhancer();//设置要创建动态代理的类enhancer.setSuperclass(star.getClass());// 设置回调,这里相当于是对于代理类上所有方法的调用,都会调用CallBack,而Callback则需要实行intercept()方法进行拦截enhancer.setCallback(methodInterceptor);ShowService showService = (ShowService) enhancer.create();showService.sing("Mockingbird");}
}

通过以上实例可以看出,Cglib通过继承实现动态代理,具体类不需要实现特定的接口,而且代理类可以调用具体类的非接口方法,更加灵活。

5.Spring AOP

5.1 概念

AOP的具体概念就不再说了,网上一搜一大把,这篇文章主要介绍Spring AOP低层使用的代理技术,因为平时在使用Spring AOP时,很多人都是copy配置,对上面介绍的这些技术概念并不清楚。

Spring AOP采用的是动态代理,在运行期间对业务方法进行增强,所以不会生成新类,对于动态代理技术,Spring AOP提供了对JDK动态代理的支持以及CGLib的支持,然而什么时候用哪种代理呢?

1、如果目标对象实现了接口,默认情况下会采用JDK的动态代理实现AOP

2、如果目标对象实现了接口,可以强制使用CGLIB实现AOP

3、如果目标对象没有实现了接口,必须采用CGLIB库,spring会自动在JDK动态代理和CGLIB之间转换

目前来看,Spring貌似和AspectJ没半毛钱关系,那为什么在许多应用了Spring AOP的项目中都出现了@AspectJ的注解呢?Spring是应用的动态代理,怎么会还和AspectJ有关系呢,原因是Spring AOP基于注解配置

当Spring需要使用@AspectJ注解支持时,需要在Spring配置文件中如下配置:

<aop:aspectj-autoproxy/>

而关于第二点强制使用CGLIB,可以通过在Spring的配置文件如下配置实现:

<aop:aspectj-autoproxy proxy-target-class="true"/>

proxy-target-class属性值决定是基于接口的还是基于类的代理被创建。如果proxy-target-class 属性值被设置为true,那么基于类的代理将起作用(这时需要cglib库)。如果proxy-target-class属值被设置为false或者这个属性被省略,那么标准的JDK 基于接口的代理。

所以,虽然使用了Aspect的Annotation,但是并没有使用它的编译器和织入器。其实现原理是JDK动态代理或Cglib,在运行时生成代理类。

已经写了这么多了,下面再贴两个Spring AOP的demo代码吧,分别是基于XML和注解的:

5.2 基于XML

切面类:

package com.fufu.spring.aop;import org.springframework.stereotype.Component;/*** 基于XML的Spring AOP*/
@Component
public class AgentAdvisorXML {public void getMoney() {System.out.println("get money");}public void writeReceipt() {System.out.println("write receipt");}
}

配置文件:

<?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:aop="http://www.springframework.org/schema/aop"xmlns:context="http://www.springframework.org/schema/context"xsi:schemaLocation="http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/contexthttp://www.springframework.org/schema/context/spring-context.xsdhttp://www.springframework.org/schema/aophttp://www.springframework.org/schema/aop/spring-aop.xsd"><bean id="star" class="com.fufu.proxy.Star"><property name="name" value="Eminem"/></bean><bean id="agentAdvisorXML" class="com.fufu.spring.aop.AgentAdvisorXML"/><!--Spring基于Xml的切面--><aop:config><!-- 定义切点函数 --><aop:pointcut id="singPointCut" expression="execution(* com.fufu.proxy.Star.sing(..))"/><!-- 定义切面 order 定义优先级,值越小优先级越大--><aop:aspect ref="agentAdvisorXML" order="0"><!--前置通知--><aop:before method="getMoney" pointcut-ref="singPointCut"/><!--后置通知--><aop:after method="writeReceipt" pointcut-ref="singPointCut"/></aop:aspect></aop:config></beans>

测试类:

package com.fufu.spring.aop;import com.fufu.proxy.ShowService;
import org.springframework.context.support.ClassPathXmlApplicationContext;public class Main {public static void main(String[] args) {ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring-aop.xml");Object star = applicationContext.getBean("star");ShowService showService = (ShowService)star;showService.sing("Mockingbird");}
}
5.3 基于注解

切面类:

package com.fufu.spring.aop;import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;/*** 基于注解的Spring AOP*/
@Aspect
@Component
public class AgentAdvisor {@Before(value = "execution(* com.fufu.proxy.ShowService.sing(..))")public void getMoney() {System.out.println("get money");}@After(value = "execution(* com.fufu.proxy.ShowService.sing(..))")public void writeReceipt() {System.out.println("write receipt");}
}

配置文件:

<?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:aop="http://www.springframework.org/schema/aop"xmlns:context="http://www.springframework.org/schema/context"xsi:schemaLocation="http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/contexthttp://www.springframework.org/schema/context/spring-context.xsdhttp://www.springframework.org/schema/aophttp://www.springframework.org/schema/aop/spring-aop.xsd"><context:component-scan base-package="com.fufu.proxy, com.fufu.spring.aop"/><aop:aspectj-autoproxy  proxy-target-class="true"/></beans>

测试类:

package com.fufu.spring.aop;import com.fufu.proxy.ShowService;
import org.springframework.context.support.ClassPathXmlApplicationContext;public class Main {public static void main(String[] args) {ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring-aop-annotation.xml");Object star = applicationContext.getBean("star");ShowService showService = (ShowService)star;showService.sing("Mockingbird");}
}

6.总结

以上内容,虽然比较浅显易懂,但是可以对Java代理机制和Spring AOP会有一个全面的理解,如有错误,欢迎指正。

 
推荐文章
 
1、一款高颜值的 SpringBoot+JPA 博客项目2、超优 Vue+Element+Spring 中后端解决方案3、推荐几个支付项目!4、推荐一个 Java 企业信息化系统5、一款基于 Spring Boot 的现代化社区(论坛/问答/社交网络/博客)

这篇关于从代理机制到Spring AOP,这篇给你安排的明明白白的的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/495299

相关文章

JSON字符串转成java的Map对象详细步骤

《JSON字符串转成java的Map对象详细步骤》:本文主要介绍如何将JSON字符串转换为Java对象的步骤,包括定义Element类、使用Jackson库解析JSON和添加依赖,文中通过代码介绍... 目录步骤 1: 定义 Element 类步骤 2: 使用 Jackson 库解析 jsON步骤 3: 添

Java中注解与元数据示例详解

《Java中注解与元数据示例详解》Java注解和元数据是编程中重要的概念,用于描述程序元素的属性和用途,:本文主要介绍Java中注解与元数据的相关资料,文中通过代码介绍的非常详细,需要的朋友可以参... 目录一、引言二、元数据的概念2.1 定义2.2 作用三、Java 注解的基础3.1 注解的定义3.2 内

Java中使用Java Mail实现邮件服务功能示例

《Java中使用JavaMail实现邮件服务功能示例》:本文主要介绍Java中使用JavaMail实现邮件服务功能的相关资料,文章还提供了一个发送邮件的示例代码,包括创建参数类、邮件类和执行结... 目录前言一、历史背景二编程、pom依赖三、API说明(一)Session (会话)(二)Message编程客

Java中List转Map的几种具体实现方式和特点

《Java中List转Map的几种具体实现方式和特点》:本文主要介绍几种常用的List转Map的方式,包括使用for循环遍历、Java8StreamAPI、ApacheCommonsCollect... 目录前言1、使用for循环遍历:2、Java8 Stream API:3、Apache Commons

JavaScript中的isTrusted属性及其应用场景详解

《JavaScript中的isTrusted属性及其应用场景详解》在现代Web开发中,JavaScript是构建交互式应用的核心语言,随着前端技术的不断发展,开发者需要处理越来越多的复杂场景,例如事件... 目录引言一、问题背景二、isTrusted 属性的来源与作用1. isTrusted 的定义2. 为

Java循环创建对象内存溢出的解决方法

《Java循环创建对象内存溢出的解决方法》在Java中,如果在循环中不当地创建大量对象而不及时释放内存,很容易导致内存溢出(OutOfMemoryError),所以本文给大家介绍了Java循环创建对象... 目录问题1. 解决方案2. 示例代码2.1 原始版本(可能导致内存溢出)2.2 修改后的版本问题在

Java CompletableFuture如何实现超时功能

《JavaCompletableFuture如何实现超时功能》:本文主要介绍实现超时功能的基本思路以及CompletableFuture(之后简称CF)是如何通过代码实现超时功能的,需要的... 目录基本思路CompletableFuture 的实现1. 基本实现流程2. 静态条件分析3. 内存泄露 bug

Java中Object类的常用方法小结

《Java中Object类的常用方法小结》JavaObject类是所有类的父类,位于java.lang包中,本文为大家整理了一些Object类的常用方法,感兴趣的小伙伴可以跟随小编一起学习一下... 目录1. public boolean equals(Object obj)2. public int ha

SpringBoot项目中Maven剔除无用Jar引用的最佳实践

《SpringBoot项目中Maven剔除无用Jar引用的最佳实践》在SpringBoot项目开发中,Maven是最常用的构建工具之一,通过Maven,我们可以轻松地管理项目所需的依赖,而,... 目录1、引言2、Maven 依赖管理的基础概念2.1 什么是 Maven 依赖2.2 Maven 的依赖传递机

SpringBoot实现动态插拔的AOP的完整案例

《SpringBoot实现动态插拔的AOP的完整案例》在现代软件开发中,面向切面编程(AOP)是一种非常重要的技术,能够有效实现日志记录、安全控制、性能监控等横切关注点的分离,在传统的AOP实现中,切... 目录引言一、AOP 概述1.1 什么是 AOP1.2 AOP 的典型应用场景1.3 为什么需要动态插