Spring — AOP
AOP 简介
-
面向切面的编程,是 OOP 的扩展与补充,可以对业务逻辑的各部分进行隔离,降低各部分之间的耦合度,提高程序的可重用性,提高开发效率。
-
在不修改源码的情况下,对业务功能进行增强。AOP 适用于具有横切逻辑的场合,如日志记录、性能检测、访问控制、事务控制等
-
常用来做增强测试:
Date d = new Date(); // 获取当前日期 SimpleDateFormat sdf = new SimpleDateFormat(“yyyy-MM-dd HH:mm:ss:SSSS”); sdf.format(d); // 格式化过日期结构的日期显示 // 做控制台输出在所需地方,就可以充当日志作用————横切逻辑
- 就像是在进超市时,核心业务是走进超市,由 AOP 来执行开门和关门操作
AOP 相关概念
-
连接点,即被 [ 拦截 ] 到的点,业务层接口中的所有方法都是连接点
- 类里面的方法其实可以说都是连接点
-
织入,悄无声息的,在影响较小的情况下加入功能
- 侵略性不强
-
目标对象,没有被增强的那些对象,接口 / 实现类
-
切入点,即被 [ 拦截并增强 ] 的连接点
-
通知,又称增强,即指拦截到切入点后所做的事情
-
切面,即切入点和通知的结合
-
代理,即一个类被 AOP 织入通知后产生的新类
-
代理对象就是通过后面讲的三种方法实现了增强的接收的对象,可以是接口 / 实现类
-
代理类和实际业务类:应该实现相同的功能,这是代理机制实现的根本
-
代理机制的实现方式:接口方式 + 继承方式
-
代理
静态代理 ( StaticProxy )
-
业务接口与业务实现类 ( 实现方法 )
-
定义增强类并编写增强方法
-
静态代理类,包含业务类和增强类,是手动对所有方法进行增强 ( 直接在现有方法里编写 )
public class StaticMathProxy implements MathInter{ // 继承接口 MathInter mathInter; //业务类 TimeTool timeTool; //增强类 public StaticMathProxy(MathInter mi, TimeTool tt){ this.mathInter = mi; // 用外面传进来的MathInter来完成方法,而不是所在类自己写,方法就return mathInter.xxxx就可(如下面的“方法里:”),即代理来做,工具timeTool也可以进行代理 this.timeTool = tt; // ... 方法里: timeTool.before(); int r = mathInter.add(i, j); timeTool.after(); //... } }
- 在上述代理类中做方法的增强 ( timeTool 操作 ),这样源码:接口、实现类、工具类三个都没改,改的话就是改代理
-
测试 Test
MathInter mi = new MathInterImpl();//业务类 TimeTool tt = new TimeTool(); //增强类 StaticMathProxy proxy = new StaticMathProxy(mi,tt);//代理类
动态代理 ( Proxy )
基于 [ 接口 ] 的动态代理,即 jdk 动态代理
过程
-
同静态定义业务接口与业务实现类
-
同静态定义增强类及增强方法
-
定义 JDK 动态代理类 ( 包含了业务类 + 增强类 ) — implements InvocationHandler
// 注:接口导入的是java.lang.reflect,实现invoke() public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { Object o; timeTool.before(); // 在所有的方法执行前进行增强(before就是工具类的方法) o = method.invoke(mathInter, args); // 这样就对所有的方法都集中进行了执行前后的增强操作,使用静态方法的话,要在每个方法前都调用增强方法(方法多了会很麻烦) timeTool.after(); return o; } // 如果想对个别方法进行增强操作: If(method.getName().equals(“sub”)){ // 就只对sub方法进行增强了 a.before(); o = method.invoke(mathInter, args); a.after(); }
-
Test 测试
MathInter mi = new MathInterImpl();//业务类 TimeTool tt = new TimeTool(); //增强类 JdkMathProxy mathProxy = new JdkMathProxy(mi,tt);//代理类,作为Proxy.newProxyInstance的第3个参数 MathInter mi2 = (MathInter)Proxy.newProxyInstance( // 强转成:接口 (实现类不可以, JDK Proxy工作原理如此,即:不能用接口的实现类来转换Proxy的实现类,它们是同级,应该用共同的接口来转换 —— 可以理解为狗不能强转为猫,但都可以强转成动物——向上转型) mi.getClass().getClassLoader(), // 传入三个参数 mi.getClass().getInterfaces(), // 前两个参数都是固定的 mathProxy); // mi.add(1, 2); //业务对象执行方法,方法未增强 mi2..add(1, 2);//使用Proxy+JdkMathProxy创建的代理对象执行方法,方法已增强
补充
-
使用 java 的 [ Proxy ] 类,要求被代理类至少实现一个 [ 接口 ]
-
提供者:JDK
-
涉及的类:Proxy
-
要点:实现 InvocationHandler 接口
-
创建代理对象的方法:Proxy.newProxyInstance,方法参数:
- 参数1:ClassLoader,类加载器,和被代理对象使用相同的 [ 类加载器 ],固定写法 —— 似先前说的类模板
- 参数2:Class[],字节码数组,和被代理对象具有相同的 [ 行为 ]、实现相同的 [ 接口 ],固定写法
- 参数3:InvocationHandler,接口,代理的具体内容即 [ 增强代码 ],使用匿名内部类实现
基于 [ 子类 ] 的动态代理,即 cglib 动态代理
过程
-
编写业务实现类不继承接口
-
定义增强类及增强方
-
代理类的继承的接口改变了:implements MethodInterceptor
// 注:接口导入的是net.cglib.proxy,实现intercept() public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable { // 接口必须实现的方法 Object o1 = null; rt.before(); //前置通知 try{ // 环绕开始 o1 = methodProxy.invoke(mi, objects); // 环绕结束 }catch(Exception e){ // 异常 } finally { // 后置返回 } rt.after(); //后置通知 return o1; }
-
Test 测试
remathimpl_2 mi = new remathimpl_2(); reTool_3 rt = new reTool_3(); CglibMathProxy_4 proxy = new CglibMathProxy_4(mi, rt); //代理类,作为Enhancer的setCallback()的参数 // Enhancer:cglib里一个重要的类 Enhancer e = new Enhancer(); //定义Enhancer对象,cglib里面带的第三方的类,e是Enhancer的实例——其实感觉就是代理 e.setSuperclass(mi.getClass()); //设置Enhancer对象的父类 e.setCallback(proxy); //设置Enhancer对象的回调/如何处理方法(用的proxy) //e.create返回的是一个Object(ctrl查看),所以需要强转成自己所需要的类 remathimpl_2 mi2 = (remathimpl_2)e.create(); //e来创建代理mi2 // mi.add(1,2); //业务对象执行方法,方法未增强 mi2.add(1,2); //使用Enhancer+CglibMathProxy创建的代理对象执行方法,方法已增强
Spring 使用 ProxyFactoryBean 创建 AOP 代理
- ProxyFactoryBean就像是先前方法中的Enhancer、Proxy —— 创建代理
- 需要导入一个依赖 —— aspectjweaver
- 若缺少 aspectjweaver,则测试类 new AspectJExpressionPointcut() 编译不报错,运行报错
原始编码方式
过程
-
业务接口与业务实现类 ( 实现方法 )
-
定义通知类
// 注:接口导入的是org.aop.......实现invoke() // 实现环绕通知 public class MyAdvice_3 implements MethodInterceptor { // 通知 public Object invoke(MethodInvocation methodInvocation) throws Throwable { Object o; SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss:SSSS"); Date d1 = new Date(); System.out.println("at:" + sdf.format(d1) + ", 开始执行"); // 工具 o = methodInvocation.proceed(); // 代理 Date d2 = new Date(); System.out.println("at:" + sdf.format(d1) + ", 执行结束"); return 0; } // 就像是综合了工具类和动态代理类,做增强 }
-
定义切面 = 切入点 + 通知
// 注意:继承了 PointcutAdvisor 接口,实现 isPerInstance() public class FilterAdvice_4 implements PointcutAdvisor { Pointcut pointcut; //切入点 Advice advice; //通知 public FilterAdvice_4(Pointcut pointcut, Advice advice){ this.pointcut = pointcut; this.advice = advice; } public Pointcut getPointcut() { return this.pointcut; } public Advice getAdvice() { return this.advice; } public boolean isPerInstance() { return false; } }
-
Test 测试
public class Test_Aop { public static void main(String[] args) { // 1.定义被代理对象 Mathimpl_2 mi = new Mathimpl_2(); // 2.定义通知(定义增强类) Advice advice = new MyAdvice_3(); // 3.定义创建被代理对象mi 的代理的对象pfb(用pfb创建mi的代理对象m2) ProxyFactoryBean pfb = new ProxyFactoryBean(); // 设置接口 pfb.setInterfaces(mi.getClass().getInterfaces()); // 设置要代理的对象 pfb.setTarget(mi); // 因为mi 的 Mathimpl_2可能实现了接口也可能没有,所以: // 如果使用jdk基于接口实现动态代理则采用默认(不用写),如果使用cglib则需要设置参数为true pfb.setProxyTargetClass(true); // 4.定义一个切入点 AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut(); // 切入点匹配一个方法:写入类方法的全包名,注意:方法后的括号里面是两个点!! pointcut.setExpression("execution (public int com.qut.aop_4.Mathimpl_2.add(..))"); // 5.定义切面,关联切入点和通知 FilterAdvice_4 advisor = new FilterAdvice_4(pointcut, advice); // 6.切面加入到pfb中 pfb.addAdvisor(advisor); // 7.创建代理对象,强转 Mathimpl_2 mi2 = (Mathimpl_2)pfb.getObject(); System.out.println(mi2.add(3, 2)); } }
Spring 式 —— 配置文件
-
业务类和实现类
-
增强类里定义增强方法
-
注解方式:需要 xml 里加上 aop 的 xmlns 和 xsi
注意:需要导入一个依赖 —— aspectjweaver
<!-- 1.定义被代理对象 --> <bean id="Mathimpl" class="com.qut.aop_bean_5.Mathimpl_1"></bean> <!-- 2.定义增强(配置文件的话不能使Advice,只能用Tool) --> <bean id="reTool" class="com.qut.aop_bean_5.reTool_2"></bean> <aop:config> <!-- 3.定义切入点(明确指出对谁做增强) --> <aop:pointcut id="addPointCut" expression="execution(public int com.qut.*.Mathimpl_1.add(..))"/> <!-- 4.定义切面(切点 + 通知) --> <aop:aspect ref="reTool"> <!-- 4.1 配置切面的before方法为切入点的前置通知 --> <aop:before method="before" pointcut-ref="addPointCut"></aop:before> <!-- 4.2 配置切面的after方法为切入点的后置通知 --> <aop:after method="after" pointcut-ref="addPointCut"></aop:after> </aop:aspect> </aop:config>
-
Test 测试
public class Test_AopBean { public static void main(String[] args) { // 读配置文件,取bean,代理类执行方法 ApplicationContext ctx = new ClassPathXmlApplicationContext("3.xml"); Mathimpl_1 mi = (Mathimpl_1)ctx.getBean("Mathimpl"); System.out.println(mi.add(3, 2)); } }
-
切入点表达式写法
- execution (public int com.qst.pkg4AopXML.A.sub(..))
- 全匹配方式:public void com.qst.bean.User.add()
- 省略访问修饰符:void com.qst.bean.User.add()
- 星号表示任意返回类型,不能省:* com.qst.bean.User.add()
- 星号表示任意包,有几级包写几个* :* * . * . * .User.add()*
- 星号表示任意类,不能省:* * . * . * . *.add()
- 星号表示任意方法,不能省:* * . * . * . * . *()
- .. 表示参数列表中任意参数:* * . * . * . * . *(..)
- 注意:星号不能跨包,只能是一层一个
Spring 式 —— Aspect 注解
-
业务接口和业务类 / 实现类
-
注解业务类 @Component + 配置文件扫描 + 导入依赖 aspectjweaver
// 实现类前 @Component(value = "xxx") // xml中 <!-- 1、配置包自动扫描 --> <context:component-scan base-package="com.qst"></context:component-scan> <!-- 2、配置自动生成aop代理 --> <aop:aspectj-autoproxy></aop:aspectj-autoproxy>
-
定义增强类 / 通知类
-
注解增强类 @Component + 切面 @Aspect
- 对增强类的方法做注解
@Component @Aspect public class TimeTool { @Before(value = "execution(public int com.qst.*.MathInterImpl.sub(..))") public void before(){ // 只要@注解对,方法名都随意 System.out.println("前置通知"); } @After(value = "execution(public int com.qst.*.MathInterImpl.sub(..))") public void after(){ System.out.println("后置(最终)通知"); } @AfterReturning(value = "execution(public int com.qst.*.MathInterImpl.sub(..))") public void afterReturning() { System.out.println("返回通知"); } @AfterThrowing(value = "execution(public int com.qst.*.MathInterImpl.sub(..))") public void afterThrowing() { System.out.println("异常通知"); } @Around(value = "execution(public int com.qst.*.MathInterImpl.sub(..))") public Object arounds(ProceedingJoinPoint jp) throws Throwable { System.out.println("环绕之前"); Object obj=jp.proceed(); System.out.println("环绕之后"); return obj; } } // ———— 即把xml中的一大堆改成了此样式
-
Test 测试 ( 1 读配置文件,2 取 bean,3 代理类执行方法 )
ApplicationContext ctx = new ClassPathXmlApplicationContext("4.xml"); // remath_1 mi = (remath_1) ctx.getBean("xxx"); remath_1 mi = ctx.getBean("xxx", Mathimpl_2.class); System.out.println(mi.add(2,3));
-
采用此方式,就可以在其余地方编写增强 / 通知类(带上 @ 的),然后直接拷贝到项目中,项目就会自行扫描并执行 ——— 典型的 AOP 应用 —— 在外部写增强并添加,不加就不增强
- 感觉就像是:传统方式 — 改动源码 < 配置文件 — 差点灵活性 < 注解方式 — 更便利
-
由于上述方法的切入点表达式稍复杂,所以做了优化 ( 了解 ):
// 类中: @Pointcut(“execution 切入点表达式”) // 切入点表达式就只写一次就够了 Public void 空方法名subPointCut() { 空 } @Before(value = “subPointCut()”) F1方法。。。。。 @After(value = “subPointCut()”) F2方法。。。。。
代码
- 重:最后的 Spring 使用 ProxyFactoryBean 创建 AOP 代理中的 Aspect 注解方式
- 动态代理 ( Proxy ) —— 基于 [ 接口 ] 的动态代理,即 jdk 动态代理