引入 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 的配置文件中注册切面(系统级服务)
<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">
<bean id="studentService" class="com.aop.service.impl.StudentServiceImpl">bean>
<bean id="beforeAdvice" class="com.aop.advice.MyBeforeAdvice">bean>
beans>
注册代理工厂来生成目标类(StudentService)的代理
这个代理其实就是 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.
*
* There are not any methods on this interface, as methods are invoked by
* reflection. Implementing classes must implement methods of the form:
*
*
void afterThrowing([Method, args, target], ThrowableSubclass);
*
* Some examples of valid methods would be:
*
*
public void afterThrowing(Exception ex)
* public void afterThrowing(RemoteException)
* public void afterThrowing(Method method, Object[] args, Object target, Exception ex)
* public void afterThrowing(Method method, Object[] args, Object target, ServletException ex)
*
* The first three arguments are optional, and only useful if we want further
* information about the joinpoint, as in AspectJ after-throwing advice.
*
* Note: 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.
* Do not throw an undeclared checked exception that is incompatible with
* the target method's signature!
*
* @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)由此而生…