AOP概念
AOP又称面向切面编程,是Spring中除ioc外另一个重要的内容,使用AOP可以降低代码耦合,提高代码重用性。将日志记录,性能统计,安全控制,事务处理,异常处理等代码从业务逻辑代码中划分出来,通过对这些行为的分离,将它们独立到非指导业务逻辑的方法中,进而改变这些行为的时候不影响业务逻辑的代码。
AOP中关键性概念:
连接点(Joinpoint)
:程序执行过程中明确的点,如方法的调用,或者异常的抛出;
目标(Target)
:被通知(被代理)的对象----->完成具体的业务逻辑;
通知(Advice)
:在某个特定的连接点上执行的动作,同时Advice也是程序代码的具体实现,例如一个实现日志记录的代码(通知有些书上也称为处理);
<tx:advice id="txAdvice"
transaction-manager="transactionManager">
<tx:attributes>
<tx:method name="add*" propagation="REQUIRED" />
<tx:method name="save*" propagation="REQUIRED" />
<tx:method name="insert*" propagation="REQUIRED" />
<tx:method name="edit*" propagation="REQUIRED" />
<tx:method name="update*" propagation="REQUIRED" />
<tx:method name="del*" propagation="REQUIRED" />
<tx:method name="remove*" propagation="REQUIRED" />
<!-- 只读,不开启事务 -->
<tx:method name="load*" propagation="REQUIRED"
read-only="true" />
<tx:method name="list*" propagation="REQUIRED"
read-only="true" />
<tx:method name="select*" propagation="REQUIRED"
read-only="true" />
<tx:method name="query*" propagation="REQUIRED"
read-only="true" />
<tx:method name="do*" propagation="REQUIRED" />
</tx:attributes>
</tx:advice>
代理(Proxy)
:将通知应用到目标对象后创建的对象(代理=目标+通知),只有代理对象才有AOP功能,而AOP的代码是写在通知的方法里面的;
切入点(Pointcut)
:多个连接点的集合,定义了通知应该应用到那些连接点;
适配器(Advisor)
:适配器=通知(Advice)+切入点(Pointcut)
<!--4) 定义切入点 -->
<aop:config>
<!-- pointcut属性用来定义一个切入点,分成四个部分理解 [* ][*..][*Biz][.*(..)] -->
<!-- A: 返回类型,*表示返回类型不限 -->
<!-- B: 包名,*..表示包名不限 -->
<!-- C: 类或接口名,*Biz表示类或接口必须以Biz结尾 -->
<!-- D: 方法名和参数,*(..)表示方法名不限,参数类型和个数不限 -->
<aop:advisor advice-ref="txAdvice"
pointcut="execution(* *..*Biz.*(..))" />
</aop:config>
注意:
目标对象只负责业务逻辑代码
通知对象负责AOP代码,这二个对象都没有AOP的功能,只有代理对象才有
下面有个案例,例如项目中有一个增加方法:
add(){
dao.add();
}
这只是某个接口的一个实现类,如果我要给这个方法增加日志功能,那么就会在增加方法中添加具体的业务代码:
add(){
logDao.add(session.get("current_user"),user);
dao.add(user);
}
那么一旦接口实现类变多,日志实现代码就会重复写,而把这些共有的业务扩展代码交由给别人,你只需要专注于实现具体的业务逻辑实现,对很多功能都有的重复的代码抽取,再在运行的时候往业务方法上动态植入“切面类代码”,这就是面向切面(aop)
案例
接口:
package com.xiaoyang.aop;
public interface IBookBiz {
// 购书
public boolean buy(String userName, String bookName, Double price);
// 发表书评
public void comment(String userName, String comments);
}
实现类:
package com.xiaoyang.aop;
public class BookBizImpl implements IBookBiz {
public BookBizImpl() {
super();
}
public boolean buy(String userName, String bookName, Double price) {
// 通过控制台的输出方式模拟购书
if (null == price || price <= 0) {
throw new PriceException("book price exception");
}
System.out.println(userName + " buy " + bookName + ", spend " + price);
return true;
}
public void comment(String userName, String comments) {
// 通过控制台的输出方式模拟发表书评
System.out.println(userName + " say:" + comments);
}
}
spring-context.xml:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
default-autowire="byType"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- aop -->
<!-- 目标对象 -->
<bean class="com.xiaoyang.aop.BookBizImpl" id="bookBiz"></bean>
<!-- 前置通知 -->
<bean class="com.xiaoyang.aop.advice.BeforeAdvice"
id="beforeAdvice"></bean>
<!-- 后置通知 -->
<bean class="com.xiaoyang.aop.advice.AfterAdvice" id="afterAdvice"></bean>
<!-- 环绕通知 -->
<bean class="com.xiaoyang.aop.advice.NotificationAdvice"
id="notificationAdvice"></bean>
<!-- 异常通知 -->
<bean class="com.xiaoyang.aop.advice.ThrowAdvice" id="throwAdvice"></bean>
<!-- 过滤通知 -->
<bean class="org.springframework.aop.support.RegexpMethodPointcutAdvisor" id="regexpMethodPointcutAdvisor">
<property name="advice" ref="afterAdvice"></property>
<property name="pattern" value=".*buy"></property>
</bean>
<!-- 由代理工厂来组装目标对象及通知 -->
<bean class="org.springframework.aop.framework.ProxyFactoryBean"
id="bookProxy">
<property name="target" ref="bookBiz"></property>
<property name="proxyInterfaces">
<list>
<value>com.xiaoyang.aop.IBookBiz</value>
</list>
</property>
<property name="interceptorNames">
<list>
<value>beforeAdvice</value>
<!-- <value>afterAdvice</value> -->
<value>regexpMethodPointcutAdvisor</value>
<value>notificationAdvice</value>
<value>throwAdvice</value>
</list>
</property>
</bean>
</beans>
前置通知
对上面两个类进行前置通知的操作,在连接点之前执行的通知就是前置通知:
package com.xiaoyang.aop.advice;
import java.lang.reflect.Method;
import java.util.Arrays;
import org.springframework.aop.MethodBeforeAdvice;
/**
* 买书、评论加系统日志
* @author xiaoyang
*
*/
public class BeforeAdvice implements MethodBeforeAdvice{
@Override
public void before(Method method, Object[] object, Object target) throws Throwable {
String clzName=target.getClass().getName();
String methodName=method.getName();
String params=Arrays.toString(object);
System.out.println("前置通知,买书、评论加系统日志:"+clzName+"调用方法:"+methodName+params);
}
}
测试一下:
package com.xiaoyang.test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import com.xiaoyang.action.UserAction;
import com.xiaoyang.aop.IBookBiz;
public class AopTest {
public static void main(String[] args) {
ApplicationContext springContext = new ClassPathXmlApplicationContext("/spring-context.xml");
IBookBiz ibookBiz = (IBookBiz) springContext.getBean("bookProxy");
boolean buy = ibookBiz.buy("sss", "圣墟", 66d);
ibookBiz.comment("sss", "真的虚了");
}
}
前置通知,买书、评论加系统日志:com.xiaoyang.aop.BookBizImpl调用方法:buy[test, testName, 66.0]
test buy testName, spend 66.0
后置通知
后置通知:在连接点正常完成后执行的通知
package com.xiaoyang.aop.advice;
import java.lang.reflect.Method;
import java.util.Arrays;
import org.springframework.aop.AfterReturningAdvice;
/**
* 后置通知
*
* @author xiaoyang
*
*/
public class AfterAdvice implements AfterReturningAdvice {
@Override
public void afterReturning(Object returnValue, Method method, Object[] object, Object target) throws Throwable {
String clzName = target.getClass().getName();
String methodName = method.getName();
String params = Arrays.toString(object);
System.out.println("后置通知:" + clzName + "调用方法:" + methodName + params+"\t目标对象方法调用后的返回值"+returnValue);
}
}
后置通知:com.xiaoyang.aop.BookBizImpl调用方法:buy[test, testName, 66.0] 目标对象方法调用后的返回值true
环绕通知
实际开发中常用环绕通知代替前置和后置通知,环绕通知是两者的结合,包围一个连接点的通知,最大特点是可以修改返回值,由于它在方法前后都加入了自己的逻辑代码,因此功能异常强大。:
package com.xiaoyang.aop.advice;
import java.util.Arrays;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
/**
* 环绕通知
* @author xiaoyang
*
*/
public class NotificationAdvice implements MethodInterceptor{
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
String clzName = invocation.getThis().getClass().getName();
String methodName = invocation.getMethod().getName();
String params = Arrays.toString(invocation.getArguments());
Object returnValue = invocation.proceed();
System.out.println("环绕通知:" + clzName + "调用方法:" + methodName + params+"\t目标对象方法调用后的返回值"+returnValue);
return returnValue;
}
}
环绕通知:com.xiaoyang.aop.BookBizImpl调用方法:buy[test, testName, 66.0] 目标对象方法调用后的返回值true
环绕通知:com.xiaoyang.aop.BookBizImpl调用方法:comment[test, testName环绕] 目标对象方法调用后的返回值null
异常通知
异常通知实现对异常的处理通知,这里对价格赋值为负数:
package com.xiaoyang.aop.advice;
import org.springframework.aop.ThrowsAdvice;
import com.xiaoyang.aop.PriceException;
/**
* 异常通知
* @author xiaoyang
*
*/
public class ThrowAdvice implements ThrowsAdvice{
public void afterThrowing(PriceException pe) {
System.out.println("价格输入有误");
}
}
package com.xiaoyang.aop;
/**
* 异常处理类
* @author xiaoyang
*
*/
public class PriceException extends RuntimeException {
public PriceException() {
super();
}
public PriceException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
super(message, cause, enableSuppression, writableStackTrace);
}
public PriceException(String message, Throwable cause) {
super(message, cause);
}
public PriceException(String message) {
super(message);
}
public PriceException(Throwable cause) {
super(cause);
}
}
过滤通知
过滤通知主要通过适配器进行过滤处理(这里模仿买书返利功能处理,一般来说买书是可以返利的,但是发表书评是不能返利的,但是如果不用过滤通知处理,前置后置通知都会输出,也就是买书评论都能返利,这是bug):
<!-- 过滤通知 -->
<bean class="org.springframework.aop.support.RegexpMethodPointcutAdvisor" id="regexpMethodPointcutAdvisor">
<property name="advice" ref="afterAdvice"></property>
<property name="pattern" value=".*buy"></property>
</bean>
<!-- 由代理工厂来组装目标对象及通知 -->
<bean class="org.springframework.aop.framework.ProxyFactoryBean"
id="bookProxy">
<property name="target" ref="bookBiz"></property>
<property name="proxyInterfaces">
<list>
<value>com.xiaoyang.aop.IBookBiz</value>
</list>
</property>
<property name="interceptorNames">
<list>
<value>beforeAdvice</value>
<value>regexpMethodPointcutAdvisor</value>
<value>throwAdvice</value>
</list>
</property>
</bean>
前置通知,买书、评论加系统日志:com.xiaoyang.aop.BookBizImpl调用方法:buy[test, testName, 66.0]
test buy testName, spend 66.0
后置通知:com.xiaoyang.aop.BookBizImpl调用方法:buy[test, testName, 66.0] 目标对象方法调用后的返回值true
前置通知,买书、评论加系统日志:com.xiaoyang.aop.BookBizImpl调用方法:comment[test, testName777]
test say:testName777
可以看到只有买书功能织入了后置通知,评论功能并没有后置通知,也就是返利功能。
over…