Spring AOP 初步
2013-05-02 11:26
162 查看
1. 面向切面编程(AOP)的概念
参见如下链接:http://baike.baidu.com/view/1865230.htm
简单地说,AOP就是在程序的纵向流程上添加横向的切面逻辑,相当于给已有的业务逻辑增加额外的功能,而不改动原有的代码。
2. Spring中的AOP流程
完整的AOP要素包括:切面(Aspect),逻辑(Advice),连接点(Joinpoint),切入点(Pointcut),指令(introduction)目标(Target),代理(Proxy),插入(Weaving)
但是在Spring框架中,AOP的部分要素已经被实现好了,只有如下的流程需要开发者来补充完整,就像IoC一样:
Advice →Pointcut →Proxy
3. Advice
Spring中提供了四种类型的Advice,分别是:前置、后置、拦截、异常。实现这四种类型的Advice分别需要实现下列四个接口:
前置:MethodBeforeAdvice,目标方法调用前执行
后置:AfterReturningAdvice,目标方法调用后执行
拦截:MethodInterceptor,目标方法在调用前被拦截
异常:ThrowsAdvice,目标方法抛出异常时执行
上述Advice在配置xml时只需要指定bean的id和class即可,无特殊要求。
4. Pointcut
默认下,Pointcut是不需要设置的。上述四种Advice对它们所作用的目标对象的每个方法都会被调用。但实际应用中,可能不需要每个方法都调用AOP的逻辑,因此可以指定Pointcut来限制Advice作用的方法。虽然Spring提供了完整接口可供开发者自定义Pointcut,但是也提供更为简便的内置类来简化开发。主要有如下几种:
1) NameMatchMethodPointcut
继承自NameMatchMethodPointcut类的切入点,可以在Advice执行之前自动匹配在切入点中设置好的方法名,目标对象中只有指定的方法才会调用Advice。
继承了NameMatchMethodPointcut的Pointcut类必须覆盖它的matches方法,并且在其中调用它的setMappedName(StringmethodName)来指定要拦截的方法名。methodName字符串还可使用*通配符。
实现后的Pointcut类必须通过DefaultPointcutAdvisor进行装配,以完成Advice的插入(Weaving)。详见示例。
2) RegexpMethodPointcutAdvisor
这种切入点使用Spring中内置的通过正则式来匹配方法名的切入点类,只需要在配置文件中装配RegexpMethodPointcutAdvisor,并在装配的过程在属性名为patterns(或pattern)的property中指定需要匹配的正则式即可。
3) ControlFlowPointcut
与上述两个类相比,ControlFlowPointcut(控制流切入点)的目标更为精细。它也是通过Spring中内置的控制流切入点类实现,并不需要编写额外的切入点类。
在配置的时候,首先需要配置ControlFlowPointcut,并向它的构造方法中传入两个参数,第一个(index=0)为Advice所用的类名,第二个(index=1)为Advice作用的方法名。
之后,再配置DefaultPointcutAdvisor实现Advice的插入。
5. Proxy
可以看出,在Spring中,AOP的要素是逐层插入(Weaving)的,即Advice插入Pointcut,而Pointcut也需要插入Proxy,以实现代理对目标类以及切面逻辑的共同调用。在Spring,对Proxy的插入只需要配置ProxyFactoryBean即可,在其内部进一步配置Advice列表(list)和目标类(target),以及代理调用目标类的接口。其过程与Servlet中配置拦截器颇为相似。详见示例。
6. 示例
前置Advicepackage aop.advice; import java.lang.reflect.Method; import org.springframework.aop.MethodBeforeAdvice; public class MethodBeforeAdviceImpl implements MethodBeforeAdvice { public void before(Method method, Object[] args, Object target) throws Throwable { System.out.println("=====前置开始====="); System.out.println("类名:" + target.getClass().getName() + "\t方法名:" + method.getName()); System.out.print("参数列表:"); for (Object arg: args) { System.out.print(arg + ", " + arg.getClass().getName() + ";\t"); } args[0] = 123; System.out.println(); System.out.println("将第一个参数改为123"); System.out.println("=====前置结束====="); } }
后置Advice
package aop.advice; import java.lang.reflect.Method; import org.springframework.aop.AfterReturningAdvice; public class AfterReturningAdviceImpl implements AfterReturningAdvice { public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable { System.out.println("~~~~~后置开始~~~~~"); System.out.println("返回值为:" + returnValue + ", " + returnValue.getClass().getName()); System.out.print("将第二个参数改为345并再调用一次:"); args[1] = 345; System.out.println(method.invoke(target, args)); System.out.println("~~~~~后置结束~~~~~"); } }
拦截Advice
package aop.advice; import org.aopalliance.intercept.MethodInterceptor; import org.aopalliance.intercept.MethodInvocation; public class MethodInterceptorImpl implements MethodInterceptor { public Object invoke(MethodInvocation invocation) throws Throwable { System.out.println("*****拦截开始*****"); if (invocation.getArguments()[1] instanceof Integer) { System.out.println("若第二个参数为整型则强制设定结果为-100"); System.out.println("*****拦截结束*****"); return -100; } System.out.println("*****拦截结束*****"); return invocation.proceed(); } }
异常Advice
package aop.advice; import java.lang.reflect.Method; import org.springframework.aop.ThrowsAdvice; public class ThrowsAdviceImpl implements ThrowsAdvice { public void afterThrowing(Throwable e) { System.out.println("-----异常开始-----"); System.out.println(e.getClass().getName() + "\t" + e.getMessage()); System.out.println("-----异常结束-----"); } // ThrowsAdvice只是标识接口,实现类中必须至少实现两个不同签名的afterThrowing方法中的一个 // public void afterThrowing(Method method, Object[] args, Object target, Throwable e) { // // } }
按方法名匹配的Pointcut
package aop.pointcut; import java.lang.reflect.Method; import org.springframework.aop.support.NameMatchMethodPointcut; public class NameMatchMethodPointcutImpl extends NameMatchMethodPointcut { private static final long serialVersionUID = -3944947836761348760L; public boolean matches(Method method, Class targetClass) { this.setMappedName("divide"); // this.setMappedNames(new String[]{"add", "divide"}); return super.matches(method, targetClass); } }
目标类及其接口
package aop.service; public interface IMath { public int add(int a, int b); public double add(double a, double b); public double divide(double a, double b); }
package aop.service; public class Math implements IMath { public int add(int a, int b) { return a + b; } public double add(double a, double b) { return a + b; } public double divide(double a, double b) throws IllegalArgumentException { if (b == 0) { throw new IllegalArgumentException("除数不能为0"); } return a / b; } }
配置文件
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd"> <bean id="math" class="aop.service.Math" /> <bean id="before" class="aop.advice.MethodBeforeAdviceImpl" /> <bean id="after" class="aop.advice.AfterReturningAdviceImpl" /> <bean id="exception" class="aop.advice.ThrowsAdviceImpl" /> <bean id="intercept" class="aop.advice.MethodInterceptorImpl" /> <bean id="methodNamePointcut" class="aop.pointcut.NameMatchMethodPointcutImpl" /> <bean id="nameAdvisor" class="org.springframework.aop.support.DefaultPointcutAdvisor"> <property name="pointcut"> <ref bean="methodNamePointcut" /> </property> <property name="advice"> <ref bean="before" /> </property> </bean> <bean id="regexpAdvisor" class="org.springframework.aop.support.RegexpMethodPointcutAdvisor"> <property name="pattern"> <value>aop.service.IMath.divide</value> </property> <property name="advice"> <ref bean="after" /> </property> </bean> <bean id="cfPointcut" class="org.springframework.aop.support.ControlFlowPointcut"> <constructor-arg> <value>aop.Main</value> </constructor-arg> <constructor-arg> <value>add</value> </constructor-arg> </bean> <bean id="cfAdvisor" class="org.springframework.aop.support.DefaultPointcutAdvisor"> <property name="pointcut"> <ref bean="cfPointcut" /> </property> <property name="advice"> <ref bean="intercept" /> </property> </bean> <bean id="proxy" class="org.springframework.aop.framework.ProxyFactoryBean"> <property name="proxyInterfaces"> <list> <value>aop.service.IMath</value> </list> </property> <property name="interceptorNames"> <list> <!-- <value>after</value> --> <value>regexpAdvisor</value> <!-- <value>before</value> --> <value>nameAdvisor</value> <value>exception</value> <!-- <value>intercept</value> --> <value>cfAdvisor</value> </list> </property> <property name="target"> <ref bean="math" /> </property> </bean> </beans>
执行入口
package aop; import org.springframework.context.ApplicationContext; import org.springframework.context.support.FileSystemXmlApplicationContext; import aop.service.IMath; public class Main { public static void main(String[] args) { ApplicationContext context = new FileSystemXmlApplicationContext( "src\\applicationContext.xml"); IMath m = (IMath)context.getBean("proxy"); System.out.println(add(m, 1, 2)); System.out.println(".........................................................................................."); System.out.println(m.add(4.0, 5.5)); System.out.println(".........................................................................................."); System.out.println(m.divide(10, 2)); System.out.println(".........................................................................................."); System.out.println(m.divide(5, 0)); } public static int add(IMath m, int a, int b) { return m.add(a, b); } }
7. 说明
1) 加入AOP之后,方法的执行顺序是:前置Advice →拦截Advice →目标方法 → 后置Advice
若目标方法中抛出了异常,则立即跳转执行异常Advice,再跳转回来执行异常处理(try…catch或throws)
2) 前置Advice和拦截Advice都能改动目标方法的参数值,区别在于,执行前置Advice之后一定要指定目标方法,而拦截Advice能够阻止目标方法的执行;只有在拦截Advice中执行了invocation.proceed()方法才会执行目标方法,其返回值就是目标方法的返回值
3) 因为后置Advice是在目标方法返回(return)之后才执行,因此它不能对目标方法产生影响,但是可以通过反射机制的Method得知目标方法的一些信息
4) 异常Advice实现的ThrowsAdvice接口只是一个标识接口,需要手动实现afterThrowing方法两个签名中的其中一个
5) 代理的拦截列表(interfaceNames)可以接口两种类型的逻辑:Advice和Advisor。后者即是前述的自定义Pointcut的Advice;Spring中提供了许多接口可供实现Advisor,这里只用到了较常用的几个内置比较完整的抽象类
6) 拦截列表(interfaceNames)的顺序不影响Advice/Advisor执行的顺序
7) 完成AOP配置之后,在生成bean时需要用代理的id来替代目标类的id,即如:
IMath m = (IMath)context.getBean("proxy"); // 而不是用目标类的id:math
8) 代理的配置中必须在proxyInterfaces属性中指定代理和目标类共用的接口的全名
相关文章推荐
- Spring的AOP初步了解(四)
- Spring AOP初步了解
- Spring 学习之路(八):Spring 中的AOP(一):aop初步了解
- Spring AOP初步介绍
- spring中的aop初步认识
- 关于SpringAOP的初步认识(个人理解)
- Spring 和aop初步
- Spring学习(二)——Spring中的AOP的初步理解[转]
- spring AOP初步总结
- 关于spring AOP 的初步学习
- Spring学习(二)——Spring中的AOP的初步理解
- spring aop初步学习(概念及)
- 17-spring学习-AOP初步实现
- Spring学习(1)AOP初步—JDK动态代理
- Spring AOP 获得 HttpServletReuest
- Spring AOP 各种通知
- Spring实现AOP的4种方式
- Spring AOP切点和通知
- Spring AOP (3) - 环绕增强 实现
- SpringBoot 通过AOP代理简单打印日志