前置通知
引入 Spring AOP 的相关 jar 包:
aopalliance-1.0.jar
spring-aop-4.2.1.RELEASE.jar
commons-logging-1.2.jar
spring-beans-4.2.1.RELEASE.jar
spring-core-4.2.1.RELEASE.jar
spring-context-4.2.1.jar
spring-expression-4.2.1.jar编写切面(实现 MethodBeforeAdvice 接口,重写 before 方法):
package com.aop.advice; import java.lang.reflect.Method; import org.springframework.aop.MethodBeforeAdvice; /** * 类名称:前置通知的切面 * 全限定性类名: com.aop.advice.MyBeforeAdvice * @author 曲健磊 * @date 2018年6月30日下午6:45:38 * @version V1.0 */ public class MyBeforeAdvice implements MethodBeforeAdvice { @Override public void before(Method method, Object[] params, Object obj) throws Throwable { System.out.println("这是权限验证的代码"); // 注:代理其实就是copy了一个一模一样的字节码文件 } }
在 Spring 的配置文件中注册切面(系统级服务)
<?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" xmlns:tx="http://www.springframework.org/schema/tx" 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 http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd"> <!-- 注册StudentServiceImpl --> <bean id="studentService" class="com.aop.service.impl.StudentServiceImpl"></bean> <!-- 注册切面 --> <bean id="beforeAdvice" class="com.aop.advice.MyBeforeAdvice"></bean> </beans>
注册代理工厂来生成目标类(StudentService)的代理
<!-- 注册代理生成器,注入目标类接口(jdk 动态代理),目标类,通知 --> <bean id="beforeProxy" class="org.springframework.aop.framework.ProxyFactoryBean"> <property name="target" ref="studentService" /> <property name="interfaces" value="com.aop.service.IStudentService" /> <property name="interceptorNames" value="beforeAdvice" /> </bean>
这个代理其实就是 new 了一个被代理对象所实现的接口的一个实现类对象,不同的是这个代理对象不仅可以执行原目标类的方法,还额外获得了切面的方法。
获取代理对象,调用代理对象的方法:
package com.aop.test; import org.junit.Before; import org.junit.Test; import org.springframework.context.support.ClassPathXmlApplicationContext; import com.aop.service.IStudentService; /** * 类名称:前置通知测试类 * 全限定性类名: com.aop.test.AdviceTest * @author 曲健磊 * @date 2018年6月30日下午6:43:37 * @version V1.0 */ public class AdviceTest { private ClassPathXmlApplicationContext ac = null; @Before public void init() { ac = new ClassPathXmlApplicationContext("applicationContext.xml"); } @Test public void testMyBeforeAdvice() { IStudentService studentService = (IStudentService) ac.getBean("beforeProxy"); studentService.saveStudent(); } }
执行结果如下:
至此,我们就实现了在不改变原 StudentService 代码的基础上,添加了额外功能,减少代码的重复,松耦合(这其实就是我们按照 AOP 思想编程的目的),这就有利于后期 StudentService 代码的复用。
后置通知
编写实现了 AfterReturningAdvice 接口的切面:
package com.aop.advice; import java.lang.reflect.Method; import org.springframework.aop.AfterReturningAdvice; /** * 类名称:后置通知 * 全限定性类名: com.aop.advice.MyAfterAdvice * @author 曲健磊 * @date 2018年6月30日下午8:48:27 * @version V1.0 */ public class MyAfterAdvice implements AfterReturningAdvice { /** * returnValue:方法的返回值 * method:方法前面 * args:参数列表 * target:被代理的目标对象 */ @Override public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable { System.out.println("Object returnValue:" + returnValue); // 方法的返回值 System.out.println("Method method:" + method); // 方法的签名,反射获取 System.out.println("Object[] args:"); // 方法的参数列表 for (Object obj : args) { System.out.println(obj); } System.out.println("Object target:" + target); // 被代理的对象 } }
在 Spring 中注册切面和代理生成器:
<!-- 注册后置切面 --> <bean id="afterAdvice" class="com.aop.advice.MyAfterAdvice"></bean> <!-- 注册代理生成器 --> <bean id="afterProxy" class="org.springframework.aop.framework.ProxyFactoryBean"> <property name="target" ref="studentService" /> <property name="interceptorNames" value="afterAdvice" /> </bean>
注:因为在 ProxyFactoryBean 中,它的 autodetectInterfaces 属性值为 true,它可以自动侦测被代理对象所实现的接口,所以也可以不注入 interfaces 这个属性。
获取代理对象,调用代理对象的方法
/** * 测试后置通知 */ @Test public void testMyAfterAdvice() { IStudentService studentService = (IStudentService) ac.getBean("afterProxy"); studentService.saveStudent("姓名", 666); }
执行结果:
对比 MethodBeforeAdvice 和 AfterReturningAdvice:他们都可以获得方法的签名,参数列表,被代理的目标对象,但是只有后置通知才可以获得方法的返回值。
环绕通知
环绕通知的切面其实并没有使用 Spring 框架里面的 API,它是直接使用的 aopalliace 里面的 MethodInterceptor 接口,只有最后的代理生成器使用的 Spring 里面的 ProxyFactoryBean。
编写实现了 MethodInterceptor 接口的切面:
package com.aop.advice; import org.aopalliance.intercept.MethodInterceptor; import org.aopalliance.intercept.MethodInvocation; /** * 类名称:环绕型通知,可以在方法执行前后插入额外功能 * 全限定性类名: com.aop.advice.MyAroundAdvice * @author 曲健磊 * @date 2018年7月1日上午10:08:14 * @version V1.0 */ public class MyAroundAdvice implements MethodInterceptor { @Override public Object invoke(MethodInvocation invocation) throws Throwable { System.out.println("环绕通知执行前....."); Object result = invocation.proceed(); // 被代理的对象执行的方法的返回值 System.out.println("环绕通知执行后.....返回值" + result); result = 99; return result; } }
注册切面以及代理生成器:
<!-- 注册环绕切面 --> <bean id="aurondAdvice" class="com.aop.advice.MyAroundAdvice"></bean> <!-- 注册代理生成器 --> <bean id="aroundProxy" class="org.springframework.aop.framework.ProxyFactoryBean"> <property name="target" ref="studentService" /> <property name="interceptorNames" value="aurondAdvice" /> </bean>
获取代理对象,调用代理对象的方法:
/** * 测试环绕通知 */ @Test public void testMyAroundAdvice() { IStudentService studentService = (IStudentService) ac.getBean("aroundProxy"); int i = studentService.saveStudent("姓名", 666); System.out.println("最终获取的方法的返回值" + i); }
执行结果:
不难看出虽然我们在 invoke 方法中通过调用 invocation.proceed 方法得到了被代理对象方法的返回值,但是在 invoke 方法返回之前我们仍然可以修改它。
注:使用环绕型通知 MethodInterceptor 时,就没有办法获取方法的参数列表,方法前面,代理对象的信息了。
异常通知
异常通知(ThrowsAdvice)其实是 AfterAdvice 的一个子接口,它和 AfterReturningAdvice 这个接口都是 AfterAdvice 的子接口。
编写实现了 ThrowsAdvice 接口的切面:
package com.aop.advice; import java.lang.reflect.Method; import org.springframework.aop.ThrowsAdvice; /** * 类名称:异常通知 * 全限定性类名: com.aop.advice.MyThrowsAdvice * @author 曲健磊 * @date 2018年7月1日上午11:33:30 * @version V1.0 */ public class MyThrowsAdvice implements ThrowsAdvice { public void afterThrowing(Method method, Object[] args, Object target, Exception ex) { System.out.println("在" + method + "方法上发生了:"); System.out.println(ex.getMessage() + "异常"); System.out.println("被代理的目标对象:" + target); System.out.println("参数列表为:"); for (Object obj : args) { System.out.println(obj); } } }
具体实现哪个方法需要参考 ThrowsAdvice 的类注释:
/* * Copyright 2002-2008 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.springframework.aop; /** * Tag interface for throws advice. * * <p>There are not any methods on this interface, as methods are invoked by * reflection. Implementing classes must implement methods of the form: * * <pre class="code">void afterThrowing([Method, args, target], ThrowableSubclass);</pre> * * <p>Some examples of valid methods would be: * * <pre class="code">public void afterThrowing(Exception ex)</pre> * <pre class="code">public void afterThrowing(RemoteException)</pre> * <pre class="code">public void afterThrowing(Method method, Object[] args, Object target, Exception ex)</pre> * <pre class="code">public void afterThrowing(Method method, Object[] args, Object target, ServletException ex)</pre> * * The first three arguments are optional, and only useful if we want further * information about the joinpoint, as in AspectJ <b>after-throwing</b> advice. * * <p><b>Note:</b> If a throws-advice method throws an exception itself, it will * override the original exception (i.e. change the exception thrown to the user). * The overriding exception will typically be a RuntimeException; this is compatible * with any method signature. However, if a throws-advice method throws a checked * exception, it will have to match the declared exceptions of the target method * and is hence to some degree coupled to specific target method signatures. * <b>Do not throw an undeclared checked exception that is incompatible with * the target method's signature!</b> * * @author Rod Johnson * @author Juergen Hoeller * @see AfterReturningAdvice * @see MethodBeforeAdvice */ public interface ThrowsAdvice extends AfterAdvice { }
注册切面以及代理生成器:
<!-- 注册异常切面 --> <bean id="throwsAdvice" class="com.aop.advice.MyThrowsAdvice"></bean> <!-- 注册代理生成器 --> <bean id="throwsProxy" class="org.springframework.aop.framework.ProxyFactoryBean"> <property name="target" ref="studentService" /> <property name="interceptorNames" value="throwsAdvice" /> </bean>
执行代理对象的方法:
/** * 测试异常通知 */ @Test public void testMyThrowsAdvice() { IStudentService studentService = (IStudentService) ac.getBean("throwsProxy"); studentService.delStudent(3); // 里面写了一个除零语句 }
执行结果:
注:异常通知和后置通知不一样,后置通知可以获取方法的返回值,异常通知无法获取方法的返回值(因为发生异常了)。后置通知和环绕通知还不一样,后置通知无法改变方法的返回值,环绕通知可以修改方法的返回值,因为环绕通知是基于一种回调的方式来进行的代理。
虽然 Spring 通知(Advice)的优点显而易见,但是也有不少的缺点:
1. 一个代理对象只能代理一个目标对象
2. 从容器中获取的 bean 的 id 是代理对象的 id,而不是目标对象的 id
3. 通知只能切入到目标对象的所有方法,不能指定切入具体的某个方法
顾问(Advisor)由此而生…