AOP
面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术
主要的功能是:日志记录,性能统计,安全控制,事务处理,异常处理等等
实现方式
预编译:AspectJ
运行期动态代理(JDK动态代理,CGLib动态代理):SpringAOP,JbossAOP
AOP的几个概念
切面(Aspect):一个关注点的模块化,这个关注点可能会横切多个对象;
连接点(Joinpoint):程序执行过程中的某个特定的点
通知(Advice):在切面的某个特定的连接点上的执行的动作
切入点(Pointcut):匹配连接点的断言,在AOP中通知和一个切入点表达式关联
引入(Introduction):在不修改类代码的前提下,为类添加新的方法和属性
目标对象(Target Object):被一个或者多个切面所通知的对象
AOP代理(AOP Proxy):AOP框架创建的对象,用来实现切面契约
织入(Weaving):把切面连接到其它的应用程序类型或者对象上,并创建一个被通知的对象
Spring所有的切面和通知器都必须放在一个< aop:config >内(可以配置多个),每一个< aop:config >可以包含pointcut,advisor和aspect元素,它们必须按照这个顺序进行配置
PointCut
expression表达式:
execution(* com.xyz.A.*(..)) 切入点执行A类中的所有方法
execution(* com.xyz..(..)) 切入点执行xyz包下的所有方法
execution(* com.xyz…(..)) 切入点执行xyz包及其自包下的所有方法
(* com.evan.crm.service..(..))中几个通配符的含义:
参数 | 说明 |
---|---|
第一个 * | 通配 随便率性返回值类型 |
第二个 * | 通配包com.evan.crm.service下的随便率性class |
第三个 * | 通配包com.evan.crm.service下的随便率性class的随便率性办法 |
第四个 .. | 通配 办法可以有0个或多个参数 |
Advice的类型
前置通知(Before advice):在某连接点(join point)之前执行的通知,但不能阻止连接点前的执行(除非它抛出一个异常)
返回后通知(After returning advice):在某连接点(join point)正常完成后执行的通知
抛出异常后通知(After throwing advice):在方法抛出异常退出时执行的通知
后通知(After advice):当某连接点退出的时候执行的通知(不论是正常返回还是异常退出)
环绕通知(Around advice):包围一个连接点(join point)的通知。通知方法的第一个参数必须是ProceedingJoinPoint类型,通过使用其的proceed()方法来执行正常的业务方法
<?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:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
<bean id="aspectConfig" class="com.lmr.spring.aop.advice.AspectConfig"></bean>
<bean id="adviceService" class="com.lmr.spring.aop.advice.AdviceService"></bean>
<aop:config>
<aop:aspect id="aspectAOP" ref="aspectConfig">
<aop:pointcut expression="execution(* com.lmr.spring.aop.advice.AdviceService.*(..))" id="adviceServicePointCut"/>
<aop:before method="before" pointcut-ref="adviceServicePointCut"/>
<aop:after-returning method="afterReturning" pointcut-ref="adviceServicePointCut"/>
<aop:after-throwing method="afterThrowing" pointcut-ref="adviceServicePointCut"/>
<aop:after method="after" pointcut-ref="adviceServicePointCut"/>
<aop:around method="around" pointcut-ref="adviceServicePointCut"/>
<!-- <aop:around method="aroundParameter" pointcut="execution(* com.lmr.spring.aop.advice.AdviceService.sayParameter(String, int)) and args(name, count)"/> -->
</aop:aspect>
</aop:config>
</beans>
public class AspectConfig {
public void before(){
System.out.println("AspectConfig before");
}
public void afterReturning(){
System.out.println("AspectConfig afterReturning");
}
public void afterThrowing(){
System.out.println("AspectConfig afterThrowing");
}
public void after(){
System.out.println("AspectConfig after");
}
public Object around(ProceedingJoinPoint pjp){
Object obj=null;
try {
System.out.println("AspectConfig around 1");
obj = pjp.proceed();
System.out.println("AspectConfig around 2");
} catch (Throwable e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return obj;
}
public Object aroundParameter(ProceedingJoinPoint pjp,String name,int count){
System.out.println("AspectConfig aroundParameter "+name+" "+count);
Object obj=null;
try {
System.out.println("AspectConfig aroundParameter 1");
obj = pjp.proceed();
System.out.println("AspectConfig aroundParameter 2");
} catch (Throwable e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return obj;
}
}
public class AdviceService {
public void say(){
System.out.println("AdviceService say");
// throw new RuntimeException();
}
public void sayParameter(String name, int count){
System.out.println("AdviceService sayParameter "+name+" "+count);
}
}
@Test
public void TestAdvice(){
AdviceService service=super.getBean("adviceService");
service.say();
}
结果:消息显示的顺序跟其在xml中配置的顺序有关
AspectConfig before
AspectConfig around 1
AdviceService say
AspectConfig around 2
AspectConfig after
AspectConfig afterReturning
Introductions:允许一个切面声明一个实现指定接口的通知对象,并且提供了一个接口实现类来代表这些对象;即是为这个对象指定了一个新的父类(接口实现类)
<aop:declare-parents types-matching="com.lmr.spring.aop.advice.AdviceService+" implement-interface="com.lmr.spring.aop.advice.Filter" default-impl="com.lmr.spring.aop.advice.FilterImpl"/>
public interface Filter {
public void control();
}
public class FilterImpl implements Filter {
@Override
public void control() {
// TODO Auto-generated method stub
System.out.println("FilterImpl control");
}
}
@Test
public void TestDeclareparents(){
Filter filter=(Filter)super.getBean("adviceService");
filter.control();
}
结果:为AdviceService指定了一个新的父类Filter
FilterImpl control
Spring AOP API
Pointcut
实现之一:NameMatchMethodPointcut,根据方法名字进行匹配
成员变量:mappedNames,匹配的方法名集合
<bean id="pointcutBean" class="org.springframework.aop.support.NameMatchMethodPointcut">
<property name="mappedNames">
<list>
<value>sa*</value>
</list>
</property>
</bean>
Before Advice
一个简单的通知了类型
只是在进入方法之前被调用,不需要MethodInvocation对象
前置通知可以在连接点执行之前插入自定义行为,但不能改变返回值
public class MyBeforeAdvice implements MethodBeforeAdvice{
@Override
public void before(Method method, Object[] args, Object target) throws Throwable {
// TODO Auto-generated method stub
System.out.println("MyBeforeAdvice "+method.getName()+" "+target.getClass().getName());
}
}
Throws Advice
如果连接点抛出异常,throws advice在连接点返回后被调用
如果throws-advice的方法抛出异常,那么它将覆盖原有异常
接口org.springframework.aop.ThrowsAdvice不包含任何方法,仅仅是一个声明,实现类需要实现对应的方法
public class MyThrowsAdvice implements ThrowsAdvice {
public void afterThrowing(Exception e)throws Throwable{
System.out.println("MyThrowsAdvice afterThrowing1 ");
}
public void afterThrowing(Method method, Object[] args, Object target, Exception e)throws Throwable{
System.out.println("MyThrowsAdvice afterThrowing2 "+method.getName()+" "+target.getClass().getName());
}
}
AfterReturing Advice
后置通知必须实现org.springframework.aop.AfterReturningAdvice接口
可以访问返回值(但不能进行修改)、被调用的方法、方法的参数和目标
如果抛出异常,将会抛出拦截器链,替代返回值
public class MyAfterReturnAdvice implements AfterReturningAdvice {
@Override
public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
// TODO Auto-generated method stub
System.out.println("MyAfterReturnAdvice "+method.getName()+" "+target.getClass().getName()+" "+returnValue);
}
}
Interception Around Advice
1、自定义切入点,直接继承Interceptor接口,实现invoke函数。
2、自定义调试拦截器,这个拦截器和相应的切入点互相工作,将匹配到的函数执行前后都打印控制台
public class MyAroundAdvice implements MethodInterceptor {
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
// TODO Auto-generated method stub
System.out.println("MyAroundAdvice 1 "+invocation.getMethod().getName()+" "+invocation.getStaticPart().getClass().getName());
Object object=invocation.proceed();
System.out.println("MyAroundAdvice 2 " + object);
return object;
}
}
Advisor通知常用bean:DefaultPointcutAdvisor
*必备属性:advice和pointcut
*可以使用构造注入或设置注入的形式注入两个属性
ProxyFactoryBean
创建Spring AOP代理的基本方法是使用org.springframework.aop.framework.ProxyFactoryBean
- 使用ProxyFactoryBean或者其他IoC相关类来创建AOP代理的最重要好处是通知和切入点可以由IOC容器管理
- 被代理类没有实现任何接口,使用CGLIB动态代理,否则使用的是JDK代理
- ProxyFactoryBean为true,无论是否实现了接口,可强制使用CGLIB
- 如果proxyInterfaces属性设置为一个或者多个全限定接口名,使用的是JDK代理
如果ProxyFactoryBean的proxyInterfaces属性没有被设置,但是目标类实现了一个(或者更多)接口,那么ProxyFactoryBean将自动检测到这个目标类已经实现了至少一个接口,创建一个基于JDK的代理。
*必备属性:代理目标类(target,可以使用引用bean或匿名bean,推荐后者)、通知(advisor或advice)
*可选属性:代理接口(proxyInterfaces,使用JDK动态代理)、代理类属性(proxyTargetClass的值为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" xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
<bean id="myBeforeAdvice" class="com.lmr.spring.aop.api.MyBeforeAdvice"></bean>
<bean id="myThrowsAdvice" class="com.lmr.spring.aop.api.MyThrowsAdvice"></bean>
<bean id="myAfterReturnAdvice" class="com.lmr.spring.aop.api.MyAfterReturnAdvice"></bean>
<bean id="myAroundAdvice" class="com.lmr.spring.aop.api.MyAroundAdvice"></bean>
<bean id="pointcutBean" class="org.springframework.aop.support.NameMatchMethodPointcut">
<property name="mappedNames">
<list>
<value>sa*</value>
</list>
</property>
</bean>
<bean id="defaultAdvisor" class="org.springframework.aop.support.DefaultPointcutAdvisor">
<property name="advice" ref="myBeforeAdvice"></property>
<property name="pointcut" ref="pointcutBean"></property>
</bean>
<!-- <bean id="myLogicImplTarget" class="com.lmr.spring.aop.api.MyLogicImpl"></bean> -->
<bean id="baseProxyBean" class="org.springframework.aop.framework.ProxyFactoryBean" lazy-init="true" abstract="true"></bean>
<!-- 使用父子bean定义以及内部bean定义 -->
<!-- <bean id="myLogicImpl" parent="baseProxyBean"> -->
<bean id="myLogicImpl" class="org.springframework.aop.framework.ProxyFactoryBean">
<property name="proxyInterfaces">
<value>com.lmr.spring.aop.api.MyLogic</value>
</property>
<property name="target">
<!-- <ref bean="myLogicImplTarget" /> -->
<bean class="com.lmr.spring.aop.api.MyLogicImpl"></bean>
<!-- 通过使用匿名内部bean来隐藏目标和代理之间的区别 -->
</property>
<property name="interceptorNames">
<list>
<!-- <value>my*</value> -->
<!-- 用*做通配,匹配所有拦截器加入通知链,注意:只能适用于拥有拦截器接口(Interceptor),不适用于Advice -->
<value>defaultAdvisor</value>
<value>myAfterReturnAdvice</value>
<value>myAroundAdvice</value>
<value>myThrowsAdvice</value>
</list>
</property>
</bean>
</beans>
public class MyLogicImpl implements MyLogic{
@Override
public String save() {
// TODO Auto-generated method stub
System.out.println("MyLogicImpl save");
return "MyLogicImpl save";
}
}
@Test
public void TestLogicSave(){
MyLogic logic=super.getBean("myLogicImpl");
logic.save();
}
结果:
MyBeforeAdvice save com.lmr.spring.aop.api.MyLogicImpl
MyAroundAdvice 1 save java.lang.reflect.Method
MyLogicImpl save
MyAroundAdvice 2 MyLogicImpl save
MyAfterReturnAdvice save com.lmr.spring.aop.api.MyLogicImpl MyLogicImpl save
AspectJ
AspectJ是编译期的AOP
@AspectJ的风格类似纯java注解的普通Java类
对@AspectJ支持可以使用XML或Java风格的配置,要确保AspectJ的aspectjweaver.jar库存在
//两种配置方式
@Configuration
@EnableAspectJAutoProxy
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
@AspectJ切面使用@Aspect注解配置,拥有@Aspect的任何bean将被Spring自动识别并应用。
用@Aspect注解的类可以有方法和字段,也可能包括切入点(pointcut),通知(Advice)和引入(introduction)声明
@Aspect注解是不能够通过类路径自动检测发现的,所以需要配合使用@Component注释或者在xml中配置bean
pointcut
一个切入点通过一个普通的方法定义来提供,并且切入点表达式使用@Pointcut注解,方法返回类型必须为void
execution 匹配方法执行的连接点
within 限定匹配特定类型的连接点
this 匹配特定连接点的bean引用是指定类型的实例的限制
target 限定匹配特定连接点的目标对象是指定类型的实例
args 限定匹配特定连接点的参数是指定类型的实例
切入点表达式可以通过&&、||和!进行组合,也可以通过名字引用切入点表达式
@Component
@Aspect
public class AspectConfig {
@Pointcut("execution(* com.lmr.spring.aop.aspectj.MyBiz.*(..))")
public void pointCut(){
}
@Pointcut("winthin(* com.lmr.spring.aop.aspectj.*)")
public void bizpointCut(){
}
@Before(value = "pointCut()")
public void before(){
System.out.println("AspectConfig before");
}
@Before("pointCut() && args(arg)")
public void beforewithparam(String arg){
System.out.println("AspectConfig beforewithparam : "+arg);
}
@AfterReturning(pointcut="pointCut()", returning="returnValue")
public void afterreturning(Object returnValue){
System.out.println("AspectConfig afterreturning : "+returnValue);
}
@AfterThrowing(pointcut="pointCut()", throwing="e")
public void afterthrowing(Exception e){
System.out.println("AspectConfig afterthrowing : "+e.getMessage());
}
@After("pointCut()")
public void after(){
System.out.println("AspectConfig after");
}
@Around("pointCut()")
public Object around(ProceedingJoinPoint pjp) throws Throwable{
System.out.println("AspectConfig around1");
Object obj=pjp.proceed();
System.out.println("AspectConfig around2");
return obj;
}
}
@Service
public class MyBiz {
public String save(String word){
System.out.println("MyBiz save "+word);
// throw new RuntimeException("MyBiz save RuntimeException");
return "MyBiz save "+word;
}
}
@Test
public void TestSave(){
MyBiz biz=super.getBean("myBiz");
biz.save("123456");
}
结果:
AspectConfig around1
AspectConfig before
AspectConfig beforewithparam : 123456
MyBiz save 123456
AspectConfig around2
AspectConfig after
AspectConfig afterreturning : MyBiz save 123456