Spring之aop
什么是Aop?
AOP为Aspect Oriented Programming的缩写,是面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。
AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。
AOP的出现弥补了OOP的这点不足,AOP 是一个概念,一个规范,本身并没有设定具体语言的实现,AOP是基于动态代理模式。
AOP是方法级别的,要测试的方法不能为static修饰,因为接口中不能存在静态方法,编译就会报错。
AOP可以分离业务代码和关注点代码(重复代码),在执行业务代码时,动态的注入关注点代码。
切面就是关注点代码形成的类。Spring AOP中的动态代理主要有两种方式,JDK动态代理和CGLIB动态代理。
JDK动态代理通过反射来接收被代理的类,并且要求被代理的类必须实现一个接口。
AOP中关键性概念
连接点(Joinpoint):程序执行过程中明确的点,如方法的调用,或者异常的抛出.
目标(Target):被通知(被代理)的对象
通知(Advice):在某个特定的连接点上执行的动作,同时Advice也是程序代码的具体实现,例如一个实现日志记录的代码(通知有些书上也称为处理)
代理(Proxy):将通知应用到目标对象后创建的对象(代理=目标+通知)
切入点(Pointcut):多个连接点的集合,定义了通知应该应用到那些连接点。
(也将Pointcut理解成一个条件 ,此条件决定了容器在什么情况下将通知和目标组合成代理返回给外部程序)
适配器(Advisor):适配器=通知(Advice)+切入点(Pointcut)
AOP的核心
写一个接口类 IBookBiz
package com.Liuyujian.aop.biz;
public interface IBookBiz {
// 购书
public boolean buy(String userName, String bookName, Double price);
// 发表书评
public void comment(String userName, String comments);
}
接口实现类BookBizIpml
package com.Liuyujian.aop.biz.impl;
import com.Liuyujian.aop.biz.IBookBiz;
import com.Liuyujian.aop.ex.PriceException;
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配置文件
<!-- 目标对象 -->
<bean id="bookBiz" class="com.Liuyujian.aop.biz.impl.BookBizImpl"></bean>
<!-- 通知 -->
<bean id="myBefore" class="com.Liuyujian.aop.advice.MethodBeforeAdvice"></bean>
<!-- 后置通知 -->
<bean id="AyBefore" class="com.Liuyujian.aop.advice.AfterReturningAdvice"></bean>
<!-- 环绕通知 -->
<bean id="myeBefore" class="com.Liuyujian.aop.advice.MethodInterceptor"></bean>
<!-- 异常通知 -->
<bean id="MyThce" class="com.Liuyujian.aop.advice.MyThrowsAdvice"></bean>
<!-- 过滤通知(适配器) -->
<beanid="AyBefore2"class="org.springframework.aop.support.RegexpMethodPointcutAdvisor">
<property name="advice" ref="AyBefore"></property>
<property name="pattern" value=".*buy"></property>
</bean>
<!-- 代理工厂来组装目标对象及通知 -->
<bean id="bookProxy" class="org.springframework.aop.framework.ProxyFactoryBean">
<property name="target" ref="bookBiz"></property>
<property name="proxyInterfaces">
<list>
<value>com.Liuyujian.aop.biz.IBookBiz</value>
</list>
</property>
<property name="interceptorNames">
<list>
<value>myBefore</value>
<!-- <value>AyBefore</value> -->
<value>AyBefore2</value>
<value>myeBefore</value>
<value>MyThce</value>
</list>
</property>
</bean>
MethodBeforeAdvice类(前置通知org.springframework.aop.MethodBeforeAdvice)
在连接点之前执行的通知()
案例:在购书系统当中使用AOP方式实现日志系统
package com.Liuyujian.aop.advice;
import java.lang.reflect.Method;
import java.util.Arrays;
public class MethodBeforeAdvice implements org.springframework.aop.MethodBeforeAdvice {
@Override
public void before(Method arg0, Object[] arg1, Object target) throws Throwable {
// TODO Auto-generated method stub
String clzName=target.getClass().getName();
String methoName=arg0.getName();
String params=Arrays.toString(arg1);
System.out.println("[买书,评论加系统日志:]"+clzName+"."+methoName+"("+params+")");
}
}
测试类
Aoptext
package com.Liuyujian.aop.test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import com.Liuyujian.aop.biz.IBookBiz;
import com.Liuyujian.ioc.biz.web.OrederAction;
import com.Liuyujian.ioc.biz.web.UserAction;
public class Aoptext {
public static void main(String[] args) {
ApplicationContext springContext =new ClassPathXmlApplicationContext("/spring-other.xml");
IBookBiz ibook=(IBookBiz) springContext.getBean("bookProxy");
ibook.buy("liuyujian", "叱诧风云", 888d);
ibook.comment("liuyujian", "强壮的雅匹");
}
}
运行结果
AfterReturningAdvice (后置通知org.springframework.aop.AfterReturningAdvice)
在连接点正常完成后执行的通知
案例:在线购书系统中,要求不修改BookBizImpl代码的情况下增加如下功能:对买书的用户进行返利:每买本书返利3元。(后置通知)
package com.Liuyujian.aop.advice;
import java.lang.reflect.Method;
import java.util.Arrays;
/**
* 买书返利(存在bug)
* @author asdfg
*
*/
public class AfterReturningAdvice implements org.springframework.aop.AfterReturningAdvice {
@Override
public void afterReturning(Object returnValue, Method method, Object[] arg2, Object target) throws Throwable {
// TODO Auto-generated method stub
String clzName=target.getClass().getName();
String methoName=method.getName();
String params=Arrays.toString(arg2);
System.out.println("[买书返利的后置通知:]"+clzName+"."+methoName+"("+params+")"+"\t目标对象方法调用侯的返回值"+true);
}
}
MyMethodInterceptor(环绕通知org.aopalliance.intercept.MethodInterceptor)
包围一个连接点的通知,最大特点是可以修改返回值,由于它在方法前后都加入了自己的逻辑代码,因此功能异常强大。
它通过MethodInvocation.proceed()来调用目标方法(甚至可以不调用,这样目标方法就不会执行)
package com.Liuyujian.aop.advice;
import java.util.Arrays;
import org.aopalliance.intercept.MethodInvocation;
public class MethodInterceptor implements org.aopalliance.intercept.MethodInterceptor {
@Override
public Object invoke(MethodInvocation arg0) throws Throwable {
// TODO Auto-generated method stub
String clzName=arg0.getThis().getClass().getName();
String methoName=arg0.getMethod().getName();
String params=Arrays.toString(arg0.getArguments());
System.out.println("[环绕通知:]"+clzName+"."+methoName+"("+params+")");
Object proceed = arg0.proceed();
System.out.println("[环绕通知:]"+"\t目标对象方法调用侯的返回值"+proceed);
System.out.println("[买书,评论加系统日志:]"+clzName+"."+methoName+"("+params+")");
return proceed;
}
}
MyThrowsAdvice 异常通知(org.springframework.aop.ThrowsAdvice):
这个通知会在方法抛出异常退出时执行
package com.Liuyujian.aop.advice;
import org.springframework.aop.ThrowsAdvice;
import com.Liuyujian.aop.ex.PriceException;
/**
* 异常通知
* 案列:zs向李四转账
* biz.transfer(user1,user2)
* UserDao.update(user1)
* UserDao.update(user2)
* @author Administrator
*
*/
public class MyThrowsAdvice implements ThrowsAdvice {
//方法名必须是afterThrowing,不允许自己写
public void afterThrowing( PriceException ex ) {
System.out.println("价格输入有误,购买失败,请重新输入!!!");
}
}
过滤通知(适配器)
org.springframework.aop.support.RegexpMethodPointcutAdvisor
处理买书返利的bug
在spring-context.xml中配置就行
<!-- 过滤通知(适配器) -->
<beanid="AyBefore2"class="org.springframework.aop.support.RegexpMethodPointcutAdvisor">
<property name="advice" ref="AyBefore"></property>
<property name="pattern" value=".*buy"></property>
</bean>
效果如下: