Spring——AOP之Spring1中的配置
Posted KLeonard
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Spring——AOP之Spring1中的配置相关的知识,希望对你有一定的参考价值。
AOP介绍
AOP(Aspect-Oriented Programming),即面向切面编程,作为面向对象(OOP,Object-Oriented Programming)的一种补充,广泛应用于处理一些具有横切性质的系统级服务,比如安全性检查、日志记录、事务管理等。
在OOP中,我们以类(class)作为基本单元,而AOP中的基本单元是Aspect(切面)。
定义AOP术语
- 1.切面(Aspect)
要实现的交叉功能,是系统模块化的一个切面或领域。如日志记录。
Aspect
由pointcut
和advice
组成,它既包含了横切逻辑的定义,也包括了连接点的定义。Spring AOP就是负责实施切面的框架,它将切面所定义的横切逻辑织入到切面所指定的连接点中。
- 2.连接点(Join Point)
应用程序执行过程中插入切面的地点,可以是方法调用,异常抛出,或者要修改的字段。
在Spring AOP中,
Join Point
总是方法的执行点,即只有方法连接点。
- 3.通知(Advice)
切面的实际实现,他通知系统新的行为。如在日志通知包含了实现日志功能的代码,如向日志文件写日志。通知在连接点插入到应用系统中。
许多AOP框架中,包括Spring AOP,会将Advice
模拟为一个拦截器(Interceptor),并且在Join Point
上维护多个Advice
,进行层层拦截。
- 4.切入点(Point Cut)
定义了通知应该应用在哪些连接点,通知可以应用到AOP框架支持的任何连接点。Advice
是和特定的Point Cut
关联的,并且在Point Cut
相匹配的Join Point
中执行。
Spring中,所有的方法都可以认为是
Join Point
,但是我们并不希望在所有的方法上都添加Advice
,而Point Cut
的作用就是提供一组规则(可以使用Aspect pointcut expression language
来描述)来匹配Join Point
,给满足规则的Join Point
添加Advice
。
- 5.引入(Introduction)
为类添加新方法和属性。Spring AOP 允许我们为目标对象Adviced Object
引入新的接口(和对应的实现)。
- 6.目标对象(Adviced Object)
被通知的对象。既可以是你编写的类,也可以是第三方的类。
- 7.代理对象(AOP Proxy)
将通知(Advice
)应用到目标对象后创建的对象,应用系统的其他部分不用为了支持代理对象而改变。
Spring AOP使用运行时代理的方式来实现aspect。在Spring AOP中,一个AOP代理是一个JDK动态代理对象或者CGLIB代理对象。
- 8.织入(Weaving)
将切面(Aspect)应用到目标对象从而创建一个新代理对象的过程。织入发生在目标对象生命周期的多个点上:
编译期:切面在目标对象编译时织入,这需要一个特殊的编译器。
类装载期:切面在目标对象被载入JVM时织入,这需要一个特殊的类载入器。
运行期:切面在应用系统运行时织入。
Spring采用动态代理织入,而
AspectJ
采用编译期织入和类装载期织入。
创建通知
本文将通过Spring1的配置方式来讲解相关例子,使用ProxyFactoryBean
来创建AOP代理,并且使用Advisor
指定需要增加的方法。
主要有四大通知类型,如下表:
通知类型 | 接口 | 描述 |
---|---|---|
Around | org.aopalliance.intercept.MethodInterceptor | 拦截对目标方法调用 |
Before | org.springframework.aop.MethodBeforeAdvice | 在目标方法调用前调用 |
After | org.springframework.aop.AfterReturningAdvice | 在目标方法调用后调用 |
Throws | org.springframework.aop.ThrowsAdvice | 当目标方法抛出异常时调用 |
注意:异常通知ThrowsAdvice
是标识性接口,没有任何方法,但实现该接口的类必须要有如下形式的方法:
void afterThrowing(Throwable throwable);
void afterThrowing(Method m, Object[] objects, Object target, Exception throwable);
第一个方法只接受一个参数:需要抛出的异常。
第二个方法接受异常、被调用的方法、参数以及目标对象。
引入通知
引入通知可以自定义切入点。
自定义切入点可以通过正则表达式匹配。
案例
1.定义接口
两个接口ITestService
和ITestService2
public interface ITestService
void sayHello();
public interface ITestService2
void sayBye();
2.编写对象(被代理的对象,也即目标对象)
目标对象具有name
属性,并且有sayHello()
方法和sayBye()
方法。
public class TestService1 implements ITestService, ITestService2
private String name;
public String getName()
return name;
public void setName(String name)
this.name = name;
@Override
public void sayHello()
System.out.println("Hello, " + name);
@Override
public void sayBye()
System.out.println("Bye, " + name);
try
int i = 1 / 0;
catch (Exception e)
3.编写通知
这里我们编写上面介绍的4个通知。
public class MyMethodBeforeAdvice implements MethodBeforeAdvice
/**
*
* @param method 被调用的方法
* @param objects 给方法传递的参数
* @param o 被代理的对象,目标对象
* @throws Throwable
*/
@Override
public void before(Method method, Object[] objects, Object o) throws Throwable
System.out.println("=============");
System.out.println("记录日志:" + method.getName());
public class MyAfterReturningAdvice implements AfterReturningAdvice
@Override
public void afterReturning(Object o, Method method, Object[] objects, Object o1) throws Throwable
System.out.println("关闭资源....");
public class myMethodInterceptor implements MethodInterceptor
@Override
public Object invoke(MethodInvocation methodInvocation) throws Throwable
System.out.println("调用方法前执行!");
// proceed()真正执行方法,object为返回的对象
Object object = methodInvocation.proceed();
System.out.println("方法调用后!");
return object;
public class MyThrowsAdvice implements ThrowsAdvice
public void afterThrowing(Method m, Object[] objects, Object target, Exception throwable)
System.out.println("出大事了!" + throwable.getMessage());
4.在beans.xml文件中配置
4.1 先配置被代理对象
4.2 配置通知
4.3 配置代理对象,是ProxyFactoryBean
的实例
4.3.1 配置代理接口集
4.3.2 把通知织入到代理对象
4.3.3 指定被代理对象
- 配置文件如下:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<!--配置被代理的对象-->
<bean id="testService1" class="com.gavin.aop.TestService1">
<property name="name" value="Gavin"/>
</bean>
<!--配置前置通知-->
<bean id="myMethodBeforeAdvice" class="com.gavin.aop.MyMethodBeforeAdvice"/>
<!--配置后置通知-->
<bean id="myAfterReturningAdvice" class="com.gavin.aop.MyAfterReturningAdvice"/>
<!--配置环绕通知-->
<bean id="myMethodInterceptor" class="com.gavin.aop.myMethodInterceptor"/>
<!--配置异常通知-->
<bean id="myThrowsAdvice" class="com.gavin.aop.MyThrowsAdvice"/>
<!--使用Advisor来定义前置通知的切入点,该定义只允许sayHello使用前置通知,sayBye不使用前置通知-->
<bean id="myMethodBeforeAdviceFilter" class="org.springframework.aop.support.NameMatchMethodPointcutAdvisor">
<property name="advice" ref="myMethodBeforeAdvice"/>
<property name="mappedNames">
<list>
<value>sayHello</value>
</list>
</property>
</bean>
<!--配置代理对象-->
<bean id="proxyFactoryBean" class="org.springframework.aop.framework.ProxyFactoryBean">
<!--代理接口集-->
<property name="proxyInterfaces">
<list>
<value>com.gavin.aop.ITestService</value>
<value>com.gavin.aop.ITestService2</value>
</list>
</property>
<!--把通知织入到代理对象-->
<property name="interceptorNames">
<!--相当于把前置通知和代理对象关联起来-->
<!--我们也可以把通知看成拦截器-->
<list>
<!--使用自定义切入点,来控制前置通知-->
<value>myMethodBeforeAdviceFilter</value>
<value>myAfterReturningAdvice</value>
<value>myMethodInterceptor</value>
<value>myThrowsAdvice</value>
</list>
</property>
<!--指定被代理对象-->
<property name="target">
<ref bean="testService1"/>
</property>
</bean>
</beans>
在这里,如果只使用ProxyFactoryBean
来配置代理对象的话,会把代理接口中的所有方法都代理了,也就是每个方法都被增强了,如果不想被增强,我们可以通过配置Advisor
来指定切入点。这里我们使用了NameMatchMethodPointcutAdvisor
,通过指定具体方法名称来指定需要被增强的方法。另外如果方法很多,也可以通过RegexpMethodPointcutAdvisor
,该Advisor可以通过正则表达式来匹配要增强的方法。
5.测试类:
public class App1
public static void main(String[] args)
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("com/gavin/aop/beans.xml");
ITestService iTestService = (ITestService) applicationContext.getBean("proxyFactoryBean");
iTestService.sayHello();
System.out.println();
((ITestService2)iTestService).sayBye();
6.输出
可以看到,TestService
中的sayHello()
和sayBye()
方法都被增强了。其中由于我们对前置通知配置了Advisor
指定了只有sayHello()
方法织入了前置通知,所以sayHello()
方法记录了日志,而sayBye()
方法没有。
在sayBye()
方法中,由于我们捕获并处理了异常,所以程序的执行过程中并没有遇到异常报错,异常通知也就没有被调用。如果在sayBye()
方法中,我们对int i = 1 / 0;
这句话不处理异常,异常在执行的时候就会被抛出,异常通知就会被调用。
本文使用了Spring1中配置AOP的方式进行配置,但是这里有一个问题,当我们的业务增多了之后,ProxyFactoryBean
的配置也会增多,导致xml
迅速变得很臃肿。
后面会介绍AspectJ
框架,以及Spring2以后的版本中加入的使用@AspectJ
的方式进行配置的方式。要知道的是Spring只是引入了@AspectJ
方式配置,但其底层实现AOP的方式仍是通过代理的方式,不同于AspectJ
框架。
以上是关于Spring——AOP之Spring1中的配置的主要内容,如果未能解决你的问题,请参考以下文章