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的核心
写一个接口类 IBookBbiz
package com.ht.biz; public interface IBookBiz { // 购书 public boolean buy(String userName, String bookName, Double price); // 发表书评 public void comment(String userName, String comments); }
接口实现类BookBizIpml
package com.ht.biz.ipml; import com.ht.biz.IBookBiz; import com.ht.exception.PriceException; /** * 书本 * @author Administrator * */ 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.ht.biz.ipml.BookBizImpl"></bean>
<!-- 前置通知 -->
<bean id="myMethodBeforeAdvice" class="com.ht.advise.MyMethodBeforeAdvice"></bean>
<!-- 目标+通知=代理对象 -->
<bean id="bookbizproxy" class="org.springframework.aop.framework.ProxyFactoryBean">
<!-- 目标 -->
<property name="target" ref="bookbiz"></property>
<!-- 代理的接口(目标对象所实现的接口) -->
<property name="proxyInterfaces">
<list>
<value>com.ht.biz.IBookBiz</value>
</list>
</property>
<!-- 通知 -->
<property name="interceptorNames">
<list>
<value>myMethodBeforeAdvice</value>
<!-- <value>myAfterReturningAdvice</value> -->
<value>myAfterReturningAdvicePlus</value>
<!-- <value>myMethodInterceptor</value> -->
<value>myThrowsAdvice</value>
</list>
</property>
</bean>
MyMethodBeforeAdvice类(前置通知org.springframework.aop.MethodBeforeAdvice)
在连接点之前执行的通知()
案例:在购书系统当中使用AOP方式实现日志系统
package com.ht.advise; import java.lang.reflect.Method; import java.util.Arrays; import org.springframework.aop.MethodBeforeAdvice; /** * 前置通知 * 买书、评论前加系统日志 * */ public class MyMethodBeforeAdvice implements MethodBeforeAdvice { @Override public void before(Method arg0, Object[] arg1, Object arg2) throws Throwable { // 哪个类被调用了 String clzName=arg2.getClass().getName(); // 哪个方法被调了 String methodNname=arg0.getName(); // 参数 String params=Arrays.toString(arg1); System.out.println("【系统日志】:"+clzName+"."+methodNname+"("+params+")"); } }
测试类:Demo1
package com.ht.test; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; import com.ht.biz.IBookBiz; public class Demo1 { public static void main(String[] args) { // 从spring中获取上下文 ApplicationContext context=new ClassPathXmlApplicationContext("/spring-context.xml"); // IBookBiz bean = (IBookBiz) context.getBean("bookbiz"); // System.out.println(bean.getClass()); //代理对象 IBookBiz bean = (IBookBiz) context.getBean("bookbizproxy"); // System.out.println(bean.getClass()); // 报错之后,程序终止 bean.buy("青灯", "青灯不归客", 55d); bean.comment("青灯", "青灯不归客"); } }
MyAfterReturningAdvice(后置通知org.springframework.aop.AfterReturningAdvice)
在连接点正常完成后执行的通知
案例:在线购书系统中,要求不修改BookBizImpl代码的情况下增加如下功能:对买书的用户进行返利:每买本书返利3元。(后置通知)
package com.ht.advise; import java.lang.reflect.Method; import java.util.Arrays; import org.springframework.aop.AfterReturningAdvice; /** * 后置通知 * 买书返利 * */ public class MyAfterReturningAdvice implements AfterReturningAdvice { @Override public void afterReturning(Object arg0, Method arg1, Object[] arg2, Object arg3) throws Throwable { String clzName=arg3.getClass().getName(); String methodNname=arg1.getName(); String params=Arrays.toString(arg2); System.out.println("【后置通知(买书返利)】:"+clzName+"."+methodNname+"("+params+")"); } }
spring-context.xml
<!-- 后置通知 -->
<bean id="myAfterReturningAdvice" class="com.ht.advise.MyAfterReturningAdvice"></bean>
<value>myAfterReturningAdvicePlus</value>
测试类
package com.ht.test; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; import com.ht.biz.IBookBiz; public class Demo1 { public static void main(String[] args) { // 从spring中获取上下文 ApplicationContext context=new ClassPathXmlApplicationContext("/spring-context.xml"); // IBookBiz bean = (IBookBiz) context.getBean("bookbiz"); // System.out.println(bean.getClass()); //代理对象 IBookBiz bean = (IBookBiz) context.getBean("bookbizproxy"); // System.out.println(bean.getClass()); // 报错之后,程序终止 bean.buy("青灯", "青灯不归客", 55d); // bean.comment("青灯", "青灯不归客"); } }
注意:这里有一个bug,会出现每次都返利的情况,后期会进行过滤处理
MyMethodInterceptor(环绕通知org.aopalliance.intercept.MethodInterceptor)
包围一个连接点的通知,最大特点是可以修改返回值,由于它在方法前后都加入了自己的逻辑代码,因此功能异常强大。
它通过MethodInvocation.proceed()来调用目标方法(甚至可以不调用,这样目标方法就不会执行)
package com.ht.advise; import java.util.Arrays; import org.aopalliance.intercept.MethodInterceptor; import org.aopalliance.intercept.MethodInvocation; /** * 环绕通知 * 类似拦截器,会包括切入点,目标类前后都会执行代码。 */ public class MyMethodInterceptor implements MethodInterceptor { @Override public Object invoke(MethodInvocation arg0) throws Throwable { String clzName=arg0.getThis().getClass().getName(); String methodNname=arg0.getMethod().getName(); String params=Arrays.toString(arg0.getArguments()); System.out.println("【环绕通知(前后都有)】:"+clzName+"."+methodNname+"("+params+")"); // returnValue是代理对象调用目标方法的返回值 Object returnValue=arg0.proceed(); System.out.println("【环绕通知(前后都有)】:"+clzName+"."+methodNname+"("+params+")"+"方法调用的返回值:"+returnValue); return returnValue; } }
MyThrowsAdvice 异常通知(org.springframework.aop.ThrowsAdvice):
这个通知会在方法抛出异常退出时执行
package com.ht.advise; import org.springframework.aop.ThrowsAdvice; import com.ht.exception.PriceException; /** * 异常通知 * @author ASUS * */ public class MyThrowsAdvice implements ThrowsAdvice { public void afterThrowing( PriceException ex ) { System.out.println("价格输入有误,购买失败,请重新输入!!!"); } }
spring-context.xml
<!-- 异常通知 -->
<bean id="myThrowsAdvice" class="com.ht.advise.MyThrowsAdvice"></bean>
<value>myThrowsAdvice</value>
过滤通知(适配器)
org.springframework.aop.support.RegexpMethodPointcutAdvisor
处理买书返利的bug
在spring-context.xml中配置就行
<!-- 过滤通知 -->
<bean id="myAfterReturningAdvicePlus" class="org.springframework.aop.support.RegexpMethodPointcutAdvisor">
<!-- 过滤规则 -->
<property name="advice" ref="myAfterReturningAdvice"></property>
<!-- <property name="pattern" value=".*buy"></property> --><!-- 过滤单个 -->
<property name="patterns">
<list>
<value>.*buy</value>
</list>
</property>
</bean>
测试类:
package com.ht.test; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; import com.ht.biz.IBookBiz; public class Demo1 { public static void main(String[] args) { // 从spring中获取上下文 ApplicationContext context=new ClassPathXmlApplicationContext("/spring-context.xml"); // IBookBiz bean = (IBookBiz) context.getBean("bookbiz"); // System.out.println(bean.getClass()); //代理对象 IBookBiz bean = (IBookBiz) context.getBean("bookbizproxy"); // System.out.println(bean.getClass()); // 报错之后,程序终止 bean.buy("青灯", "青灯不归客", 55d); bean.comment("青灯", "青灯不归客"); } }
效果如下:
谢谢观看!