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();
@Override
public void method1() {
System.out.println("执行方法前...");
target.method1();
System.out.println("执行方法后...");
}
@Override
public void method2() {
System.out.println("执行方法前...");
target.method2();
System.out.println("执行方法后...");
}
@Override
public int method3(Integer i) {
System.out.println("执行方法前...");
int method3 = target.method3(i);
System.out.println("执行方法后...");
return method3;
}
}
3)定义被代理类
public class Target implements TargetInteface {
@Override
public void method1() {
System.out.println(" Target method1 running ...");
}
@Override
public void method2() {
System.out.println("Target method2 running ...");
}
@Override
public 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
类,并实现指定的接口。在代理类的方法中,会调用InvocationHandler
的invoke
方法,将方法调用转发给处理器处理。
此外,为了提高性能,JDK Proxy还提供了一个缓存机制,用于缓存已经生成的代理类的Class对象。这样,当需要创建相同类型的代理对象时,可以直接从缓存中获取代理类的Class对象,而无需重新生成。缓存是通过WeakCache
类实现的,它利用弱引用来缓存对象,以便在JVM进行垃圾回收时能够自动清理不再使用的缓存项。
② JDK Proxy的实现步骤
- 定义接口和被代理类:首先定义一个或多个接口,这些接口将被代理类实现。
public interface TargetInteface {
void method1();
void method2();
int method3(Integer i);
}
public class Target implements TargetInteface {
@Override
public void method1() {
System.out.println("method1 running ...");
}
@Override
public void method2() {
System.out.println("method2 running ...");
}
@Override
public 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() {
@Override
public 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;
}
});
}
}
- 使用代理对象:通过代理对象调用方法时,实际上是调用了
InvocationHandler
的invoke
方法,在该方法中执行自定义逻辑后,再调用原始类的方法。
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的特点
- 接口代理:JDK Proxy只能代理实现了接口的类,不能代理没有实现接口的普通类。
- 动态生成:代理类是在运行时动态生成的,开发者无需手动编写代理类的代码。
- 灵活性强:可以在不修改原始类代码的情况下,为原始类添加额外的功能或逻辑。
- 性能考虑:由于涉及到反射和动态类的生成,JDK Proxy的性能可能略低于静态代理或直接调用原始类的方法。
2)CGLib
① CGLib动态代理的核心原理
- 字节码操作:CGLib底层使用ASM(一个小而快的字节码操作框架)来动态生成新的Java类(通常是目标类的子类)。这些新生成的类继承自目标类,并在方法调用时插入代理逻辑。
- 方法拦截:CGLib的核心功能是实现方法级别的拦截。开发者通过实现
MethodInterceptor
接口来定义一个方法拦截器,该拦截器会在代理对象的方法调用前后执行自定义逻辑,如预处理、后处理、异常处理等。 - 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() {//代理要做什么
@Override
public 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动态代理的适用场景
- 需要代理未实现接口的类:当目标类没有实现任何接口时,可以使用CGLib进行代理。
- 性能要求较高:在性能要求较高的场景下,如果JDK动态代理无法满足需求,可以考虑使用CGLib。
- AOP框架实现:在面向切面编程框架中,如Spring AOP,当需要代理未实现接口的类时,通常会使用CGLib作为底层实现。
④ CGLib动态代理的优缺点
优点:
- 灵活性高:可以代理没有实现接口的类,拓宽了代理的适用范围。
- 性能较好:通过FastClass机制,调用效率高于JDK动态代理的反射机制。
- 功能强大:支持在运行时动态地为目标类添加额外功能或逻辑,无需修改原始类代码。
缺点:
- 字节码操作开销:动态生成字节码并加载到JVM中会带来一定的性能开销。
- 无法代理final类和方法:由于CGLib是通过继承目标类来实现代理的,因此无法代理final修饰的类和方法。
- 使用复杂度较高:相比于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
**/
@Override
public void method1() {
System.out.println("method1 running ...");
}
@Override
public void method2() {
System.out.println("method2 running ...");
}
@Override
public int method3(Integer i) {
System.out.println("method3 running ...");
return i;
}
}
④ 定义代理类(增强方法)
public class TargetAdvice implements MethodInterceptor, MethodBeforeAdvice, AfterReturningAdvice {
/*
* 通知/增强
**/
@Override
public Object invoke(MethodInvocation methodInvocation) throws Throwable {
System.out.println("前置环绕通知");
Object proceed = methodInvocation.proceed();
System.out.println("后置环绕通知");
return proceed;
}
@Override
public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
System.out.println("后置返回通知");
}
@Override
public 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后置通知
0
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")
@ResponseBody
public String testController() {
TestController o = (TestController) AopContext.currentProxy();
o.test();
// System.out.println("tewt");
return "ok";
}
@TestAnnotation
public void test() {
System.out.println("test running");
}
}