- Java - 框架 - Spring(AOP)
- 一、代理模式
- 1.1 静态代理
- 1.2 动态代理
- 1.2.1 JDK动态代理
- 1.2.2 CGLIB动态代理
- 1.3 静态代理和动态代理的优缺点
- 1.3.1 静态代理
- 1.3.2 动态代理
- 二、AOP
- 2.1 前置知识
- 2.1.1 常见术语
- 2.1.2 通知类型
- 2.1.3 切入点表达式
- 2.2 AOP实现
为其他对象提供一种代理以控制对这个对象的访问
代理模式的角色:
1.1 静态代理
- Source:代理者与被代理者共同实现的接口,可以理解为需要代理的行为
- SourceImpl:被代理者,其为具有某种特定行为的实现者;
- Proxy:代理者,其会全权代理SourceImpl所具有的功能,在实现其功能的基础上做一些额外的工作;
- Client:客户端,客户端访问代理者与访问被代理者具有类似的效果,其无法区分访问的是代理者还是被代理者。
-
Source
public interface Source { void SourceMethod(); } -
SourceImpl
public class SourceImpl implements Source{ @Override public void SourceMethod(){ System.out.println("this is Source Method!") } } -
Proxy
public class Proxy implements Source{ private Source sourceImpl; //使用构造将被代理对象注入 public Proxy(Source sourceImpl){ this.sourceImpl = sourceImpl; } @Override //方法增强 public void SourceMethod(){ System.out.println("before proxy!") sourceImpl.SourceMethod(); System.out.println("after proxy!") } } -
Client(测试类)
public class Client { @Test public void testProxy() { Source source = new SourceImpl(); Source proxy = new SubjectProxy(source); proxy.SourceMethod(); } } -
输出结果
before proxy!
this is Source Method!
after proxy!
如果目标对象有实现的接口,可以使用JDK共同太代理模式
-
Source
public interface Source { void SourceMethod(); } -
SourceImpl
public class SourceImpl implements Source{ @Override public void SourceMethod(){ System.out.println("this is Source Method!") } }
-
在测试类中使用匿名内部类实现
Client(测试类)
import com.spring.dao.Source; import com.spring.dao.impl.SourceImpl; import org.junit.Test; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; public class TestMain { @Test public void fun2(){ final Source dao = new SourceImpl(); //获取代理对象 Source proxy = (Source)Proxy.newProxyInstance( dao.getClass().getClassLoader(), //类加载器 dao.getClass().getInterfaces(), //目标对象实现的接口数组 new InvocationHandler() {//InvocationHandler内部类实现 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("before proxy!"); method.invoke(dao); System.out.println("after proxy!"); return null; } } ); proxy.SourceMethod(); } } -
定义动态代理类
import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; public class JDKDynamicProxy implements InvocationHandler { private Object target; public JDKDynamicProxy(Object target) { this.target = target; } //定义获取代理对象方法 public Object getJDKProxy(Object targetObject) { //为目标对象target赋值 this.target = targetObject; //JDK动态代理只能针对实现了接口的类进行代理,newProxyInstance 函数所需参数就可看出, //interfaces参数是该动态类所继承的所有接口 return Proxy.newProxyInstance(targetObject.getClass().getClassLoader(), targetObject.getClass().getInterfaces(), this); } public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("before proxy!"); Object result = method.invoke(target, args); System.out.println("after proxy!"); return result; } }测试类:
@Test public void fun3(){ Source dao = new SourceImpl(); //实例化JDKProxy对象 JDKDynamicProxy jdkProxy = new JDKDynamicProxy(dao); Source proxy = (Source) Proxy.newProxyInstance(dao.getClass().getClassLoader(),dao.getClass().getInterfaces(),jdkProxy); //等同于 Source proxy = (Source)jdkProxy.getJDKProxy(dao); proxy.SourceMethod(); }
如果目标对象没有实现任何接口,无法使用JDK动态代理,需要使用CGLIB动态代理
使用CGLIB需要引入pom依赖
!-- https://mvnrepository.com/artifact/cglib/cglib -->cglib cglib 3.3.0
-
Source
public class ServiceImpl { public void SourceMethod() { System.out.println("this is Source Method!"); } }
-
实现CGLIB动态代理类
代理类
import com.spring.dao.ServiceImpl; 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 CglibDynamicProxy implements MethodInterceptor { private ServiceImpl target; public CglibDynamicProxy(ServiceImpl target) { this.target = target; } //创建代理对象 public ServiceImpl createProxy(){ Enhancer enhancer = new Enhancer(); //指定父类 enhancer.setSuperclass(target.getClass()); //指定回调方法 enhancer.setCallback(this); //创建代理对象 return (ServiceImpl) enhancer.create(); } public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable { System.out.println("before proxy!"); Object object = method.invoke(target,objects); System.out.println("after proxy!"); return object; } }测试类
@Test public void fun4(){ //获取目标对象 ServiceImpl target = new ServiceImpl(); //获取代理对象 ServiceImpl proxy = new CglibDynamicProxy(target).createProxy(); proxy.SourceMethod(); } -
通过匿名内部类
测试类
@Test public void fun5(){ //获取目标对象 final ServiceImpl target = new ServiceImpl(); Enhancer enhancer = new Enhancer(); //指定父类 enhancer.setSuperclass(target.getClass()); //指定回调方法 enhancer.setCallback(new MethodInterceptor() { public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable { System.out.println("before proxy!"); Object object = method.invoke(target,objects); System.out.println("after proxy!"); return object; } }); //获取代理对象 ServiceImpl proxy = (ServiceImpl)enhancer.create(); proxy.SourceMethod(); }
-
优点
代理使客户端不需要知道实现类是什么,怎么做的,而客户端只需知道代理即可(解耦合)
-
缺点
- 代理类和委托类实现了相同的接口,代理类通过委托类实现了相同的方法。这样就出现了大量的代码重复。如果接口增加一个方法,除了所有实现类需要实现这个方法外,所有代理类也需要实现此方法。增加了代码维护的复杂度。
- 代理对象只服务于一种类型的对象,如果要服务多类型的对象。势必要为每一种对象都进行代理,静态代理在程序规模稍大时就无法胜任了。
-
优点
动态代理与静态代理相比较,最大的好处是接口中声明的所有方法都被转移到调用处理器一个集中的方法中处理(InvocationHandler.invoke)。这样,在接口方法数量比较多的时候,我们可以进行灵活处理,而不需要像静态代理那样每一个方法进行中转。而且动态代理的应用使我们的类职责更加单一,复用性更强
建议参考具体链接地址:Spring AOP概念术语的通俗理解_Welcome-CSDN博客_aop术语
| 术语 | 解释 |
|---|---|
| 通知(Advice) | AOP 框架中的增强处理。通知描述了切面何时执行以及如何执行增强处理。 |
| 连接点(JoinPoint) | 是spring允许你使用通知的地方,基本每个方法的前,后(两者都有也行),或抛出异常时都可以是连接点,spring只支持方法连接点.其他如aspectJ还可以让你在构造器或属性注入时都行,不过那不是咱关注的,只要记住,和方法有关的前前后后(抛出异常),都是连接点。 |
| 切入点(Pointcut) | 上面说的连接点的基础上,来定义切入点,你的一个类里,有15个方法,那就有几十个连接点了对把,但是你并不想在所有方法附近都使用通知(使用叫织入,以后再说),你只想让其中的几个,在调用这几个方法之前,之后或者抛出异常时干点什么,那么就用切点来定义这几个方法,让切点来筛选连接点,选中那几个你想要的方法。 |
| 切面(Aspect) | 切面是通知和切入点的结合。现在发现了吧,没连接点什么事情,连接点就是为了让你好理解切点,搞出来的,明白这个概念就行了。通知说明了干什么和什么时候干(什么时候通过方法名中的before,after,around等就能知道),而切入点说明了在哪干(指定到底是哪个方法),这就是一个完整的切面定义。 |
| 引入(Introduction) | 允许我们向现有的类添加新方法属性。这不就是把切面(也就是新方法属性:通知定义的)用到目标类中吗 |
| 目标(target) | 引入中所提到的目标类,也就是要被通知的对象,也就是真正的业务逻辑,他可以在毫不知情的情况下,被咱们织入切面。而自己专注于业务本身的逻辑。 |
| 代理(proxy) | 怎么实现整套aop机制的,都是通过代理 |
| 织入(weaving) | 把切面应用到目标对象来创建新的代理对象的过程。有3种方式,spring采用的是运行时, |
前置通知:before 在连接点方法执行之前执行。
后置通知:after 在连接点方法执行之后,无论如何都会执行。
环绕通知:around 在连接点方法执行之前和之后执行。
异常通知:after-throwing 在连接点方法发生异常之后执行。
返回通知:after-returning 在连接点方法返回结果后执行(如果发生异常则不会执行)。
try{
前置通知 before
环绕通知 around 前半部分
调用连接点方法
环绕通知 around 后半部分
返回通知 after-returning
}catch(Exception e){
异常通知after-throwing
} finally{
after 后置通知
}
2.1.3 切入点表达式
详见网址链接:Spring AOP 所有切入点指示符详解(execution,within,this,target,args,@within,@target,@args,@annotation)_全冉 的博客-CSDN博客
| 表达式类型 | 说明 |
|---|---|
| execution | 定位到目标对象的方法上 |
| within | 定位到具体的类型上 |
| this | 代理对象的类型 |
| target | 目标对象的类型 |
| args | 参数的类型 |
| @args | 参数修饰的注解 |
| @within | 类型修饰的注解 |
| @annotation | 方法修饰的注解 |
切入点复用
@Pointcut("execution(* com.spring.service.impl.*.*(..))")
public void point1(){
}
@Before("point1()")
public void aspectMethod01(){
System.out.println("before........");
}
-
execution
语法:
exetution([访问权限类型] 返回值类型 [全限定类名] 方法名 (参数名) [抛出的异常类型])
符号 含义 * 0到多个符合 … 表示参数中的人一个参数,用在包名表示当前包及其子包 + 用在类名后,表示当前类及其子类,用在接口后表示接口及其实现 示例:
@Before("execution(* com.spring.service.impl.*.*(..))") //第一个 * :任意的返回类型 //第二个 * :表示在com.spring.service.impl下的所有类 //第三个 *() :表示在com.spring.service.impl下的所有类的所有方法 //第一个.. :表示所有的参数都符合 public void aspectMethod01(){ System.out.println("before........"); } -
within
示例:
@Before("within(com.spring.service..*)") //表示com.spring.service及其子包下的所有的类里面的方法 public void aspectMethod02(){ System.out.println("before2222........"); }
-
pom文件
org.springframework spring-context 5.2.17.RELEASE junit junit 4.13.2 org.projectlombok lombok 1.18.22 org.aspectj aspectjweaver 1.9.7 -
配置类
@Configuration @EnableAspectJAutoProxy //放开AspectJ的使用 @ComponentScan(basePackages = "com.spring") //不加basePackages,代表扫描当前路径下所有,不包含父路径的同级路径 public class JavaConfig { } -
创建切面类
import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.*; import org.springframework.stereotype.Component; @Component @Aspect //表明当前类是切面类 public class MyAspect { @Pointcut("execution(* com.spring.service.impl.*.*(..))") public void point1(){ } @Before("point1()") public void before(){ System.out.println("before........"); } @AfterReturning(value = "within(com.spring..*)",returning = "res") public void AfterReturning(Object res){ System.out.println("after........" + res); } @Around("within(com.spring.service.impl.*)") //环绕通知 public void arround(ProceedingJoinPoint proceedingJoinPoint){ System.out.println("环绕目标执行之前。。。。。"); Object res = null; try { res= proceedingJoinPoint.proceed(); //可以接收返回值 } catch (Throwable throwable) { throwable.printStackTrace(); } System.out.println("环绕目标执行之后。。。。。" + res); } @AfterThrowing(value = "within(com.spring.service.impl.*)",throwing = "ex") //捕获异常之后,结束运行,只执行before、arround、AfterThrowing、after四个通知 public void afterThorwing(Exception ex){ System.out.println("异常通知"+ex); } @After( "within(com.spring.service.impl.*)") //无论如何都会执行 public void after(){ System.out.println("最终通知......"); } }
参考文档:
- java设计模式(6)-代理模式(必看的springAOP原理) - 云+社区 - 腾讯云 (tencent.com)
- 熟知代理模式,你将明白Spring AOP原理!!! - 码农教程 (manongjc.com)
- Spring AOP概念术语的通俗理解_Welcome-CSDN博客_aop术语



