通知(Advice),切面的一种实现,可以完成简单织入功能(织入功能就是在这里完成的)。常用通知有:前置通知、后置通知、环绕通知、异常处理通知。
1.通知的用法步骤
- 定义目标类
- 定义通知类
- 注册目标类
- 注册通知切面
- 注册代理工厂Bean类对象ProxyFactoryBean
- 客户端访问动态代理对象
- 在容器中的整体配置
2.通知详解
(1)前置通知MethodBeforeAdvice
定义前置通知,需要实现MethodBeforeAdvice接口。该接口中有一个方法before(),会在目标方法执行之前执行。
前置通知的特点:
- 在目标方法执行之前先执行。
- 不改变目标方法的执行流程,前置通知代码不能阻止目标方法执行。
- 不改变目标方法执行的结果。
目标类:
package com.lmm.aop01;
// 目标类
public class SomeServiceImpl implements ISomeService {
@Override
public void doFirst() {
System.out.println("执行doFirst()方法");
}
@Override
public void doSecond() {
System.out.println("执行doSecond()方法");
}
}
定义前置通知类:
package com.lmm.aop01;
import java.lang.reflect.Method;
import org.springframework.aop.MethodBeforeAdvice;
// 前置通知
public class MyMethodBeforeAdvice implements MethodBeforeAdvice {
// 当前方法在目标方法执行之前执行
// method:目标方法
// args:目标方法的参数列表
// target:目标对象
@Override
public void before(Method method, Object[] args, Object target)
throws Throwable {
// 对于目标方法的增强代码就应该写在这里
System.out.println("执行前置通知方法");
}
}
配置文件配置:
<?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">
<!-- 注册目标对象 -->
<bean id="someService" class="com.lmm.aop01.SomeServiceImpl"/>
<!-- 注册切面:通知 -->
<bean id="myAdvice" class="com.lmm.aop01.MyMethodBeforeAdvice"/>
<!-- 生成代理对象 -->
<bean id="serviceProxy" class="org.springframework.aop.framework.ProxyFactoryBean">
<!-- <property name="targetName" value="someService"/> 用的是对象名-->
<!-- 指定目标对象 用的是对象-->
<property name="target" ref="someService"/>
<!-- 指定切面 -->
<property name="interceptorNames" value="myAdvice"/>
</bean>
</beans>
测试类:
package com.lmm.aop01;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class MyTest {
@Test
public void test01() {
// 创建容器对象,加载Spring配置文件
String resource = "com/lmm/aop01/applicationContext.xml";
ApplicationContext ac = new ClassPathXmlApplicationContext(resource);
ISomeService service = (ISomeService) ac.getBean("serviceProxy");
service.doFirst();
System.out.println("==================");
service.doSecond();
}
}
(2)后置通知AfterReturningAdvice
定义后置通知,需要实现接口AfterReturningAdvice。该接口中有一个方法afterReturning(),会在目标方法执行之后执行。后置通知的特点:
- 在目标方法执行之后执行。
- 不改变目标方法的执行流程,后置通知代码不能阻止目标方法执行。
- 不改变目标方法执行的结果。
- 后置通知可以获取到目标方法的返回结果,但无法改变目标方法的结果。
实现方法和前置通知类似,不在赘述!不过比前值方法多了一个返回值记录一下:
package com.lmm.aop02;
import java.lang.reflect.Method;
import org.springframework.aop.AfterReturningAdvice;
// 后置通知:可以获取到目标方法的返回结果,但无法改变目标方法的结果
public class MyAfterReturningAdvice implements AfterReturningAdvice {
// 在目标方法执行之后执行
// returnValue:目标方法的返回值
@Override
public void afterReturning(Object returnValue, Method method,
Object[] args, Object target) throws Throwable {
System.out.println("执行后置通知方法 returnValue = " + returnValue);
if (returnValue != null) {
returnValue = ((String) returnValue).toUpperCase();
System.out.println("修改过的结果 returnValue = " + returnValue);
}
}
}
(3)环绕通知MethodInterceptor
定义环绕通知,需要实现MethodInterceptor接口。环绕通知,也叫方法拦截器,可以在目标方法调用之前及之后做处理,可以改变目标方法的返回值,也可以改变程序执行流程。
注意, org.aopalliance.intercept.MethodInterceptor才是需要的包。(jar包不是Spring框架的,而是AOP联盟的)
package com.lmm.aop03;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
// 环绕通知:可以修改目标方法的返回结果
public class MyMethodInterceptor implements MethodInterceptor {
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
System.out.println("执行环绕通知:目标方法执行之前");
// 执行目标方法
Object result = invocation.proceed();
System.out.println("执行环绕通知:目标方法执行之后");
if(result != null) {
//可以改变目标方法的返回值
result = ((String)result).toUpperCase();
}
return result;
}
}
(4)异常通知ThrowsAdvice
定义异常通知,需要实现ThrowsAdvice接口。该接口的主要作用是,在目标方法抛出异常后,根据异常的不同做出相应的处理。当该接口处理完异常后,会简单地将异常再次抛出给目标方法。
不过,这个接口较为特殊,从形式上看,该接口中没有必须要实现的方法。但,这个接口却确实有必须要实现的方法afterThrowing()。这个方法重载了四种形式。由于使用时,一般只使用其中一种,若要都定义到接口中,则势必要使程序员在使用时必须要实现这四个方法。这是很麻烦的。所以就将该接口定义为了标识接口(没有方法的接口)。
测试程序
(5)通知的其它用法
(1)给目标方法织入多个切面
若要给目标方法织入多个切面,则需要在配置代理对象的切面属性时,设定为list。
<?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">
<!-- 注册目标对象 -->
<bean id="someService" class="com.lmm.aop06.SomeServiceImpl"/>
<!-- 注册切面:通知 -->
<bean id="myBeforeAdvice" class="com.lmm.aop06.MyMethodBeforeAdvice"/>
<bean id="myAfterAdvice" class="com.lmm.aop06.MyAfterReturningAdvice"/>
<!-- 生成代理对象 -->
<bean id="serviceProxy" class="org.springframework.aop.framework.ProxyFactoryBean">
<property name="target" ref="someService"/>
<property name="interceptorNames" value="myBeforeAdvice,myAfterAdvice"/>
<!-- <property name="interceptorNames">
<array>
<value>myBeforeAdvice</value>
<value>myAfterAdvice</value>
</array>
</property> -->
</bean>
</beans>
(2)无接口的CGLIB代理生成
前面调用的都是jdk的Proxy代理,若不存在接口,则ProxyFactoryBean会自动采用CGLIB方式生成动态代理。
(有接口自动选择jdk的Proxy代理,无接口自动选择CGLIB方式生成动态代理)
(3)有接口的CGLIB代理生成- proxyTargetClass属性
- 若存在接口,但又需要使用CGLIB生成代理对象,此时,只需要在配置文件中增加一个proxyTargetClass属性设置,用于指定强制使用CGLIB代理机制。
- 也可指定optimize(优化)的值为true,强制使用CGLIB代理机制。
<?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">
<!-- 注册目标对象 -->
<bean id="someService" class="com.lmm.aop08.SomeServiceImpl"/>
<!-- 注册切面:通知 -->
<bean id="myAdvice" class="com.lmm.aop08.MyAfterReturningAdvice"/>
<!-- 生成代理对象 -->
<bean id="serviceProxy" class="org.springframework.aop.framework.ProxyFactoryBean">
<property name="target" ref="someService"/>
<property name="interceptorNames" value="myAdvice"/>
<!-- 也可指定optimize(优化)的值为true,强制使用CGLIB代理机制。 -->
<property name="optimize" value="true"/>
<!-- 在配置文件中增加一个proxyTargetClass属性设置,用于指定强制使用CGLIB代理机制 -->
<!-- <property name="proxyTargetClass" value="true"/> -->
</bean>
</beans>
参考:北京动力节点视频课