Spring的AOP,先来了解下英文翻译:Aspect Oriented Programming。中文翻译即为面向切面编程。
Java是面向对象编程,即处理的时候是根据对象进行处理。
AOP是面向切面编程,即处理的时候是相当于一个横切面,部分符合条件的程序都需要经过这个横切面进行处理。
面向对象和面向切面编程是相辅相成的。
先来介绍一下面向切面编程AOP的三个关键概念:Pointcut(切入点)、Advice(通知)、Advisor(建议者)
1、Ponitcut:先来了解Join Point是程序执行过程中的某个阶段点。Pointcut是Joinpoint的集合。简单说来就是切入点,在哪里注入
2、Advice:通知,即注入什么
3、Advisor:建议者,给Ponitcut和Advicor的配置器。将Advice注入到Ponitcut位置的程序。简单说来怎么注入。
所以AOP思想简单说来就是:在哪里注入,注入什么,怎么注入
来依次了解一下这三个概念:
1、Ponitcut接口:import org.springframework.aop.Pointcut;
package org.springframework.aop;
public abstract interface Pointcut {
public static final Pointcut TRUE = TruePointcut.INSTANCE;
public abstract ClassFilter getClassFilter();
public abstract MethodMatcher getMethodMatcher();
}
给了两个方法,ClassFilter用来将切入点指定在给定的目标类中;
MethodMatcher用来判断切入点是否匹配目标类给定的方法中。
切入点的三种实现:静态切入点,动态切入点、自定义切入点(常用为静态切入点,指定类和方法,不传参数,所以只在第一次计算静态切入点的位置,然后放入缓存。因为动态切入点包含参数,所以动态生成不能存入缓存,占用资源影响效率,所以一般优先考虑静态切入点)
2、Advice有5种类型:
(一)Interception Around通知:环绕拦截,在JoinPoint调用的前后执行。实现Interception Around通知的类需要实现接口MethodInterceptor。
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
public class LogIntercaptor implements MethodInterceptor{
@Override
public Object invoke(MethodInvocation paramMethodInvocation)
throws Throwable {
System.out.println("开始...");
Object rval = paramMethodInvocation.proceed();
System.out.println("结束...");
return rval;
}
}
(二)Before通知
Before通知只在Joinpoint前面执行,需要实现的类MethodBeforeAdvice
import java.lang.reflect.Method;
import org.springframework.aop.MethodBeforeAdvice;
public class LogBeforeAdvice implements MethodBeforeAdvice{
@Override
public void before(Method paramMethod, Object[] paramArrayOfObject,
Object paramObject) throws Throwable {
System.out.println("开始...");
}
}
(三)After Returning通知只在Joinpoint后执行,需要实现接口AfterReturningAdvice
import java.lang.reflect.Method;
import org.springframework.aop.AfterReturningAdvice;
public class LogAfterAdvice implements AfterReturningAdvice{
@Override
public void afterReturning(Object paramObject1, Method paramMethod,
Object[] paramArrayOfObject, Object paramObject2) throws Throwable {
System.out.println("结束...");
}
}
(四)Throw通知只在Joinpoint抛出异常时执行,需要实现接口ThrowsAdvice。
import java.rmi.RemoteException;
import org.springframework.aop.ThrowsAdvice;
public class LogThrowAdvice implements ThrowsAdvice{
public void afterThrowing(RemoteException ex) throws Throwable{
System.out.println("抛出异常...");
}
}
(五)Introduction只在Joinpoint调用完毕后执行。需要实现接口IntroductionAdvisor和IntroductionInterceptor
3、Advisor是怎么注入。DefaultPointcutAdvisor是最通用的Advisor类。Spring一般会通过xml配置Advice和Pointcut。
实例说明:ProxyFactoryBean创建AOP代理(该部分转自http://uule.iteye.com/blog/869309)
Spring源码运用AOP的实例
1、使用ProxyFactoryBean代理目标类的所有方法
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN"
"http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
<bean id="log" class="com.gc.action.LogAround"/>
<bean id="timeBook" class="com.gc.action.TimeBook"/>
<!--设定代理类-->
<bean id="logProxy" class="org.springframework.aop.framework.ProxyFactoryBean">
<!--这里代理的是接口-->
<property name="proxyInterfaces">
<value>com.gc.impl.TimeBookInterface</value>
</property>
<!--是ProxyFactoryBean要代理的目标类-->
<property name="target">
<ref bean="timeBook"/>
</property>
<!--程序中的Advice-->
<property name="interceptorNames">
<list>
<value>log</value>
</list>
</property>
</bean>
</beans>
代码说明:
● id为log的Bean,是程序中的Advice。
● id为timeBook的Bean,是ProxyFactoryBean要代理的目标类。
● id为logProxy的Bean,就是ProxyFactoryBean。
● ProxyFactoryBean的proxyInterfaces属性,指明要代理的接口。
● ProxyFactoryBean的target属性,指明要代理的目标类 ,这个目标类实现了上面proxyInterfaces属性指定的接口。
● ProxyFactoryBean的interceptorNames属性,指明要在代理的目标类中插入的Adivce 。
● ProxyFactoryBean还有一个proxyTargetClass属性,如果这个属性被设定为“true”,说明 ProxyFactoryBean要代理的不是接口类,而是要使用CGLIB方式来进行代理,后面会详细讲解使用CGLIB方式来进行代理。
注意: ProxyFactoryBean的proxyInterfaces属性只支持使用字符串的方式进行注入,不支持使用Bean的依赖方式进行注入。
如果ProxyFactoryBean 的proxyInterfaces 属性没有 被设置,但是目标类实现了一个(或者更多) 接口,那么ProxyFactoryBean 将自动检测到这个目标类已经实现了至少一个接口, 一个基于JDK的代理将被创建 。被实际代理的接口将是目标类所实现的全部 接口;实际上,这和在proxyInterfaces 属性中列出目标类实现的每个接口的情况是一样的。然而,这将显著地减少工作量以及输入错误的可能性。
2、使用ProxyFactoryBean代理目标类的指定方法
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN"
"http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
<bean id="log" class="com.gc.action.LogAround"/>
<bean id="timeBook" class="com.gc.action.TimeBook"/>
<!--代理目标类的指定方法-->
<bean id="logAdvisor"class="org.springframework.aop.support.RegexpMethodPointcutAdvisor">
<property name="advice">
<ref bean="log"/>
</property>
<!--指定要代理的方法-->
<property name="patterns">
<value>.*doAuditing.* </value>
</property>
</bean>
<!--设定代理类-->
<bean id="logProxy" class="org.springframework.aop.framework.ProxyFactoryBean">
<property name="proxyInterfaces">
<value>com.gc.impl.TimeBookInterface</value>
</property>
<property name="target">
<ref bean="timeBook"/>
</property>
<property name="interceptorNames">
<list>
<value>logAdvisor</value>
</list>
</property>
</bean>
</beans>
代码说明:
● 在id为logAdvisor的Bean中设定Advice和要指定的方法。
● 把id为logProxy的Bean的interceptorNames属性值改为logAdvisor。
● logAdvisor的advice属性指定Advice。
● logAdvisor的patterns属性指定要代理的方法。“.doAuditing”表示只有doAuditing()方法才使用指定的Advice。
patterns属性值使用的是正则表达式,关于正则表达式的使用,下节将会进行讲解。
注意: 因为要使用正则表达式,所以要把spring-framework-2.0-m1\lib\oro目录下的
jakarta-oro-2.0.8.jar加入到ClassPath下
Java动态代理日志Demo改造,用Spring的AOP实现:
Interception Around通知形式实现:
实现思路:1、实现Interception Around通知的类需要实现接口MethodInterceptor。触发
2、在invoke方法里实现日志输出
3、具体业务逻辑,使用Subject(接口)和RealSubject(类)
4、在Spring的配置文件里配置Pointcut(配置主要涉及三个方面:MethodInterceptor实现类Proxy,RealSubject,Subject)
5、编写测试程序进行测试。接口调用方法
注入什么在代码里写(advice),在哪里注入在xml配置文件里配,怎么注入即为xml的内容
1、2
package com.gc.acion;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.apache.log4j.Level;
import org.apache.log4j.Logger;
public class AopProxyMethodInterceptor implements MethodInterceptor{
private Logger logger = Logger.getLogger(this.getClass().getName());
@Override
//在invoke方法里实现日志输出
public Object invoke(MethodInvocation paramMethodInvocation)
throws Throwable {
logger.log(Level.INFO, "before RealSubject Method");
try{
//类似于method.invoke(subject, arg2);触发
Object result = paramMethodInvocation.proceed();
return result;
}finally{
logger.log(Level.INFO, "after RealSubject Method");
}
}
}
3
package com.gc.impl;
public interface Subject {
public void action();
public void hello(String s);
}
package com.gc.acion;
import com.gc.impl.Subject;
public class RealSubject implements Subject {
@Override
public void action() {
System.out.println("RealSubject Action Method");
}
@Override
public void hello(String s) {
System.out.println("RealSubject Action Method Include Args:"+s);
}
}
4
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN"
"http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
<!-- 使用Spring AOP实现日志输出的Bean -->
<bean id="log" class="com.gc.acion.AopProxyMethodInterceptor"></bean><!-- advice,注入什么 -->
<bean id="RealSubject" class="com.gc.acion.RealSubject"></bean>
<!-- 使用Spring提供的ProxyFactoryBean来实现代理 -->
<bean id="logProxy" class="org.springframework.aop.framework.ProxyFactoryBean">
<property name="proxyinterfaces">
<value>com.gc.impl.Subject</value><!-- 被委托类的接口 -->
</property>
<property name="target">
<ref bean="RealSubject"/> <!-- 被委托类 -->
</property>
<property name="interceptorNames">
<!-- advice(需要注入的内容) -->
<list>
<value>log</value><!-- 即com.gc.acion.AopProxyMethodInterceptor -->
</list>
</property>
</bean>
</beans>
5
package com.gc.test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.FileSystemXmlApplicationContext;
import com.gc.impl.Subject;
public class TestAopProxy {
public static void main(String[] args) {
ApplicationContext context = new FileSystemXmlApplicationContext("config.xml");
Subject subject = (Subject)context.getBean("logProxy");
subject.action();
subject.hello("你好");
}
}
如果需要拓展,对于其他的类业务也需要加其他前后处理的,只需要新建一个接口,一个实现类,然后在xml配置文件中,新增如下:
<bean id="RealSubject1" class="com.gc.acion.RealSubject1"></bean>
<bean id="logProxy1" class="org.springframework.aop.framework.ProxyFactoryBean">
<property name="proxyinterfaces">
<value>com.gc.impl.Subject1</value>
</property>
<property name="target">
<ref bean="RealSubject1"/>
</property>
<property name="interceptorNames">
<list>
<value>log</value>
</list>
</property>
</bean>
advice有5中实现形式,每种实现形式的思路均一致。
AopProxy
Subject
RealSubject
xml配置
测试类
AOP的实质:
业务逻辑不再关注横切关注点,而是由单独的类来封装。当业务需要用到封装的横切关注点时,AOP会自动把封装的横切关注点注入到具体的业务逻辑中。