Spring AOP(面向方面编程)框架,用于在模块化方面的横切关注点。简单得说,它只是一个拦截器,用来拦截一些过程。例如,当一个方法执行,Spring AOP 可以劫持此执行方法,在方法执行之前或之后添加额外的功能。
在Spring AOP中,有 5 种类型通知(advices)的支持:
- 前置通知(Before advice):在目标方法执行之前执行,前置通知不会影响目标方法的执行,除非此处抛出异常。
- 正常返回通知(After returning advice):在目标方法执行完成后执行,如果目标方法抛出异常,则不会执行。目标方法执行后,该方法返回一个结果。
- 异常返回通知(After throwing advice):在目标方法抛出异常后执行。
- 返回通知(After advice):在目标方法执行完成后执行,不管是正常执行完成,还是抛出异常,都会执行返回通知中的内容。
- 环绕通知(Around advice):环绕通知围绕在目标方法前后,即一个方法调用的前后。这是最强大的通知类型,能在方法调用前后自定义一些操作。环绕通知还需要负责决定是继续处理join point(调用ProceedingJoinPoint的proceed方法)还是中断执行。
1. 前置通知
它会在方法执行之前执行。创建一个实现 MethodBeforeAdvice 接口的类。
public class ArticleServiceMethodBeforeAdvice implements MethodBeforeAdvice {
public void before(Method method, Object[] args, Object target) throws Throwable {
System.out.println("Before " + method.getName() + " of " + target.getClass().getSimpleName() + " class.");
}
}
在 bean 配置文件(Spring-Bean.xml)配置代理对象。
<bean id="articleDao" class="com.angelia.spring.dao.ArticleDaoImpl">
<property name="dataSource" ref="dataSource" />
</bean>
<bean id="articleService" class="com.angelia.spring.service.ArticleServiceImpl" scope="prototype">
<property name="articleDao">
<ref bean="articleDao" />
</property>
</bean>
<bean id="articleServiceBeforeAdvice" class="com.angelia.spring.dao.aop.ArticleServiceMethodBeforeAdvice" />
<bean id="articleServiceProxy" class="org.springframework.aop.framework.ProxyFactoryBean">
<property name="target" ref="articleService" />
<property name="interceptorNames">
<list>
<value>articleServiceBeforeAdvice</value>
</list>
</property>
</bean>
‘target’ – 定义你想拦截的bean。‘interceptorNames’ – 定义要应用这个代理/目标对象的类(通知)。
2. 正常返回通知
该方法返回一个结果之后它将执行。创建一个实现AfterReturningAdvice接口的类。
public class ArticleServiceAfterReturningAdvice implements AfterReturningAdvice {
public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
System.out.println("After return result of the " + method.getName() + " of " + target.getClass().getSimpleName() + " class.");
}
}
bean配置文件
<bean id="articleServiceAfterAdvice" class="com.angelia.spring.dao.aop.ArticleServiceAfterReturningAdvice" />
<bean id="articleServiceProxy" class="org.springframework.aop.framework.ProxyFactoryBean">
<property name="target" ref="articleService" />
<property name="interceptorNames">
<list>
<value>articleServiceBeforeAdvice</value>
<value>articleServiceAfterAdvice</value>
</list>
</property>
</bean>
3. 异常返回通知
它将在执行方法抛出一个异常后。创建一个实现ThrowsAdvice接口的类,并创建一个afterThrowing方法拦截抛出:IllegalArgumentException异常
public class ArticleServiceThrowsAdvice implements ThrowsAdvice {
public void afterThrowing(IllegalArgumentException e) throws Throwable {
System.out.println("ArticleServiceThrowsAdvice : Throw exception in ArticleServiceImpl!");
}
}
bean配置文件
<bean id="articleServiceThrowsAdvice" class="com.angelia.spring.dao.aop.ArticleServiceThrowsAdvice" />
<bean id="articleServiceProxy" class="org.springframework.aop.framework.ProxyFactoryBean">
<property name="target" ref="articleService" />
<property name="interceptorNames">
<list>
<value>articleServiceBeforeAdvice</value>
<value>articleServiceAfterAdvice</value>
<value>articleServiceThrowsAdvice</value>
</list>
</property>
</bean>
4. 返回通知
该方法执行完成后执行,不管是正常执行完成,还是抛出异常,都会执行返回通知中的内容。创建一个实现AfterAdvice接口的类。
public class ArticleServiceAfterAdvice implements AfterAdvice {
public void after(Method method, Object[] args, Object target) throws Throwable {
System.out.println("After " + method.getName() + " of " + target.getClass().getSimpleName() + " class.");
}
}
bean配置
<bean id="articleServiceAfterAdvice" class="com.angelia.spring.dao.aop.ArticleServiceAfterAdvice" />
<bean id="articleServiceProxy" class="org.springframework.aop.framework.ProxyFactoryBean">
<property name="target" ref="articleService" />
<property name="interceptorNames">
<list>
<value>articleServiceAfterAdvice</value>
</list>
</property>
</bean>
5. 环绕通知
环绕通知结合了上面的三个通知,在方法执行过程中执行。创建一个实现了MethodInterceptor接口的类。
public class ArticleServiceAroundMethod implements MethodInterceptor {
public Object invoke(MethodInvocation invocation) throws Throwable {
// MethodBeforeAdvice
System.out.println("ArticleServiceMethodBeforeAdvice...");
try {
// 执行原来的方法
Object result = invocation.proceed();
// AfterReturningAdvice
System.out.println("ArticleServiceAfterReturningAdvice...");
return result;
} catch (IllegalArgumentException e) {
// ThrowsAdvice
System.out.println("ArticleServiceThrowsAdvice...");
throw e;
}
}
}
必须调用“invocation.proceed();” 继续在原来的方法执行,否则原来的方法将不会执行。
bean配置文件
<bean id="articleServiceAroundMethod" class="com.angelia.spring.dao.aop.ArticleServiceAroundMethod" />
<bean id="articleServiceProxy" class="org.springframework.aop.framework.ProxyFactoryBean">
<property name="target" ref="articleService" />
<property name="interceptorNames">
<list>
<!-- <value>articleServiceBeforeAdvice</value>
<value>articleServiceAfterAdvice</value>
<value>articleServiceThrowsAdvice</value> -->
<value>articleServiceAroundMethod</value>
</list>
</property>
</bean>
在每一个 ArticleService 方法执行后,将运行 ArticleServiceAroundMethod 的 invoke() 方法。
6. Spring AOP 切入点 (Pointcut,Advisor)
上面的Spring AOP通知的例子,一个类的整个方法被自动拦截。但在大多数情况下,可能只需要一种方式来拦截部分方法,这就是为什么引入'切入点'的原因。它允许你根据method的名字去拦截指定的method。另外,一个Pointcut必须结合一个Advisor来使用。
在Spring AOP中,有3个常用的概念,Advices、Pointcut、Advisor。
- Advices:表示一个method执行前或执行后的动作。
- Pointcut:表示根据method的名字或者正则表达式去拦截一个method。
- Advisor:Advice和Pointcut组成的独立的单元,并且能够传给proxy factory 对象。
前面的例子,ArticleServiceImpl中全部的method方法全部被拦截了,下边将利用Pointcuts只拦截queryArticleById()。我们可以用名字匹配法和正则表达式匹配法去匹配要拦截的method。
切入点 - 名称匹配
创建一个NameMatchMethodPointcut的bean,将你想拦截的方法的名字queryArticleById注入到属性mappedName。创建一个DefaultPointcutAdvisor的advisor bean,将pointcut和advice关联起来。
<bean id="articleServiceBeforeAdvice" class="com.angelia.spring.dao.aop.ArticleServiceMethodBeforeAdvice" />
<bean id="articlePointcut" class="org.springframework.aop.support.NameMatchMethodPointcut">
<property name="mappedName" value="queryArticleById" />
</bean>
<bean id="articleAdvisor" class="org.springframework.aop.support.DefaultPointcutAdvisor">
<property name="pointcut" ref="articlePointcut" />
<property name="advice" ref="articleServiceBeforeAdvice" />
</bean>
<bean id="articleServiceProxy" class="org.springframework.aop.framework.ProxyFactoryBean">
<property name="target" ref="articleService" />
<property name="interceptorNames">
<list>
<!-- <value>articleServiceBeforeAdvice</value> -->
<value>articleAdvisor</value>
</list>
</property>
</bean>
以上配置中pointcut和advisor可以合并在一起配置,即不用单独配置articlePointcut和articleAdvisor,只要配置articleAdvisor时class选择NameMatchMethodPointcutAdvisor如下:
<bean id="articleAdvisor" class="org.springframework.aop.support.NameMatchMethodPointcutAdvisor">
<property name="mappedName" value="queryArticleById" />
<property name="advice" ref="articleServiceBeforeAdvice" />
</bean>
实际上这种做法将 method 名字与具体的advice捆绑在一起,有悖于 Spring 松耦合理念,如果将 method 名字单独配置成pointcut(切入点),advice和pointcut的结合会更灵活,使一个pointcut可以和多个advice结合。
切入点 - 正则表达式
可以用正则表达式匹配需要拦截的method。
<bean id="articleAdvisor" class="org.springframework.aop.support.RegexpMethodPointcutAdvisor">
<property name="patterns">
<list>
<value>.*Article.*</value>
</list>
</property>
<property name="advice" ref="articleServiceBeforeAdvice" />
</bean>
现在,你可以拦截名字中包含Article字符的method了。
7. Spring自动创建代理
前面的 Spring AOP例子 – advice, pointcut 和 advisor, 必须手动创建一个代理bean(ProxyFactryBean),对每个Bean需要AOP支持。这显然不是一种有效的方式。例如,如果想在客户模块,所有的Servic类实现SQL日志支持的AOP功能,那么必须手动创建很多代理工厂bean,因此在 bean配置文件可能会泛滥代理类。幸运的是,Spring有两个自动代理创建者来自动创建代理bean。
BeanNameAutoProxyCreator
自动代理机制,只需要创建一个的 BeanNameAutoProxyCreator,并包含所有你的bean(通过bean的名字,或正则表达式名)和“advisor” 作为一个单位。
<bean class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator">
<property name="beanNames">
<list>
<value>*Service</value>
</list>
</property>
<property name="interceptorNames">
<list>
<value>articleAdvisor</value>
</list>
</property>
</bean>
现在,可以通过“articleService”的原始名称获取bean, 如果知道这个bean已经代理。
DefaultAdvisorAutoProxyCreator
DefaultAdvisorAutoProxyCreator 是非常强大的,如果有 bean 相关连,Spring会自动创建一个代理。
<bean id="articleServiceBeforeAdvice" class="com.angelia.spring.dao.aop.ArticleServiceMethodBeforeAdvice" />
<bean id="articleAdvisor" class="org.springframework.aop.support.NameMatchMethodPointcutAdvisor">
<property name="mappedName" value="queryArticleById" />
<property name="advice" ref="articleServiceBeforeAdvice" />
</bean>
<bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator" />