Spring中的增强类型

时间:2021-08-10 16:06:31

Spring通过增强类型定义横切逻辑,同时由于Spring只支持方法连接点,增强还包括了在方法的哪一点加入横切代码的方位信息,所以增强既包括横切逻辑,还包括部分连接点的信息。

增强包括以下几类:

  1. 前置增强:org.springframework.aop.BeforeAdvice代表前置增强,表示在目标方法整形前实施增强

  2. 后置增强:org.springframework.aop.AfterReturningAdvice代表后置增强,表示在目标方法执行后实施增强

  3. 环绕增强:org.springframework.aop.MethodInterceptor代表环绕增强,表示在目标方法执行前后实施增强

  4. 异常抛出增强 :org.springframework.aop.ThrowsAdvice代表抛出异常增强,表示在目标方法抛出异常后实施增强

  5. 引介增强:org.springframework.aop.IntroductionInterceptor代表引介增强,表示在目标类中添加一些新的方法和属性

前置增强

下面通过一个服务生服务前的礼貌用语来举例增强:

Waiter.java

public interface Waiter {
void greetTo(String name);
void serveTo(String name);
}

未添加前置增强前:
NaiveWaiter.java

public class NaiveWaiter implements Waiter {

public void greetTo(String name) {
System.out.println("greet to "+name+"...");
}

public void serveTo(String name){
System.out.println("serving "+name+"...");
}
}

前置增强类如下:
GreetingBeforeAdvice.java

public class GreetingBeforeAdvice implements MethodBeforeAdvice {
public void before(Method method, Object[] args, Object obj) throws Throwable {
String clientName = (String)args[0];
System.out.println("How are you!Mr."+clientName+".");
}
}

MethodBeforeAdvice是前置增强BeforeAdvice接口的子类。其中仅有唯一的方法:before(Method method, Object[] args, Object obj) throws Throwable,method为目标类的方法,args为目标类方法的入参,obj为目标类的实例,当该方法发生异常时,将会阻止目标类方法的执行。

前置增强应用如下:
TestBeforeAdvice.java

public class TestBeforeAdvice {
public static void main(String[] args) {
Waiter target = new NaiveWaiter();
//创建增强
BeforeAdvice advice = new GreetingBeforeAdvice();
//Spring提供的代理工厂
ProxyFactory pf = new ProxyFactory();
//指定对接口进行代理
pf.setInterfaces(target.getClass().getInterfaces());
//启用优化
pf.setOptimize(true);
//设置代理目标
pf.setTarget(target);
//为代理目标添加增强
pf.addAdvice(advice);
//生成代理实例
Waiter proxy = (Waiter)pf.getProxy();
proxy.greetTo("John");
proxy.serveTo("Tom");
}
}

运行结果如下:可以发现每个方法执行前都引入了前置增强的语句

Spring中的增强类型

在上面的代理工厂中就是通过JDK代理或CGLib代理的技术,如果通过setInterfaces方法指定对接口进行代理就会使用JdkDynamicAopProxy,如果是针对类的代理,则使用Cglib2AopProxy。同时,如上如果启动了优化代理的方式,针对接口的代理也会使用Cglib2AopProxy。

在上面的源码中可以通过ProxyFactory的addAdvice()方法添加一个增强,用户可以添加多个增强,多个增强的调用顺序和添加顺序一致,也可以通过addAdvice(int Advice)将增强添加到增强链的具体位置(第一个位置为0)

Spring中配置如下:

<bean id="greetingBefore" class="com.baobaotao.advice.GreetingBeforeAdvice" />

<bean id="target" class="com.baobaotao.advice.NaiveWaiter" />
<bean id="waiter"
class="org.springframework.aop.framework.ProxyFactoryBean"
p:proxyInterfaces="com.baobaotao.advice.Waiter" p:target-ref="target"
p:interceptorNames="greetingAdvice"/>

ProxyFactoryBean的常用属性如下:

  • target:代理的目标对象

  • proxyInterfaces:代理所要实现的接口,可以是多个接口。属性的另一个别名interfaces

  • interceptorNames:需要植入目标的Bean列表,采用Bean的名称指定,指定使用的增强。这些Bean必须是实现了org.aopalliance.intercept.MethodInterceptor或org.springframework.aop.Advisor的Bean,即就是一个增强

  • singleton:返回的代理是否是单实例,默认为单实例

  • optimize:设置为true时,强制使用CGLib代理,对于singleton的代理,推荐使用CGLib,因为CGLib创建代理速度慢,而创建出的对象运行效率高,JDK代理的正好相反

  • proxyTargetClass:是否对类进行代理,设置为true时,使用CGLib代理

测试增强的代码如下:

public class TestAdvice1 {
public static void main(String[] args) {
String configPath = "com/baobaotao/advice/beans.xml";
ApplicationContext ctx = new ClassPathXmlApplicationContext(configPath);
Waiter waiter = (Waiter)ctx.getBean("waiter");
waiter.greetTo("John");
}
}

后置增强

后置增强在目标类方法调用后执行如下新建一个后置增强

GreetingAfterAdvice.java

public class GreetingAfterAdvice implements AfterReturningAdvice {

//在目标方法调用后执行
public void afterReturning(Object returnObj, Method method, Object[] args,
Object obj) throws Throwable {
System.out.println("Please enjoy yourself!");
}
}

后置增强需要实现AfterReturningAdvice来定义后置增强的逻辑,AfterReturningAdvice接口定义了唯一的方法afterReturning(Object returnObj, Method method, Object[] args, Object obj) throws Throwable,returnObj为目标类实例。如果在后置增强中抛出异常,若该异常是目标方法生命的异常,则该异常归并到目标方法中;如果不是目标方法所生命的异常,则将其作为运行期异常抛出

环绕增强

环绕增强综合实现了前置、后置增强两者的功能。

如下示例:
GreetingInterceptor.java

public class GreetingInterceptor implements MethodInterceptor {
//用来截获目标方法的执行,并在前后添加横切逻辑
public Object invoke(MethodInvocation invocation) throws Throwable {
Object[] args = invocation.getArguments();
String clientName = (String)args[0];
//目标方法执行前的调用
System.out.println("How are you!Mr."+clientName+".");
//通过反射机制调用目标方法
Object obj = invocation.proceed();
//目标方法执行后的调用
System.out.println("Please enjoy yourself!");

return obj;
}
}

如上源码所示,MethodInterceptor作为环绕增强的接口。该接口拥有唯一的接口方法Object invoke(MethodInvocation var1) throws Throwable;

MethodInvocation封装了目标方法、入参数组以及目标方法所在的实例对象,通过getArguments()可以获得目标方法的入参数组,通过proceed()反射调用目标实例相应的方法。

异常抛出增强

异常抛出增强适合事务管理的场景,这里用一个模拟出错抛出异常,来使用异常抛出增强的例子:

ForumService.java

public class ForumService {
public void removeForum(int forumId) {
// do sth...
throw new RuntimeException("运行异常。");
}
public void updateForum(Forum forum) throws Exception{
// do sth...
throw new SQLException("数据更新操作异常。");

}
}

如下定义一个异常抛出增强:
TransactionManager.java

public class TransactionManager implements ThrowsAdvice {
public void afterThrowing(Method method, Object[] args, Object target,
Exception ex) throws Throwable {
System.out.println("-----------");
System.out.println("method:" + method.getName());
System.out.println("抛出异常:" + ex.getMessage());
System.out.println("成功回滚事务。");
}
}

afterThrowing(Method method, Object[] args, Object target, Exception ex) throws Throwable

ThrowsAdvice 异常增强接口并无任何方法,是一个标识接口,在运行期间使用反射机制自行判断,采用方法名为afterThrowing的方法定义增强方法。方法入参中前三个是可选的(一起选或不选),最后一个参数为Throwable或其子类,可以在一个异常抛出增强中同时定义多个afterThrowing(), Spring会选择最匹配的增强方法,即就是类的继承关系越近相似度越高,匹配度就越高。

例如,若定义了afterThrowing(SQLException e)和afterThrowing(Throwable e);而抛出了SQLException时就会匹配afterThrowing(SQLException e)。

引介增强

引介增强并非在目标方法周围织入增强,而是为目标类创建新的方法和属性,所以引介增强的连接点是类级别的,而非方法级别的,通过引介增强,可以为目标类添加一个接口的实现。如下面这个例子:

Monitorable.java

public interface Monitorable {
void setMonitorActive(boolean active);
}

ControllablePerformaceMonitor.java

public class ControllablePerformaceMonitor
extends
DelegatingIntroductionInterceptor implements Monitorable {

private ThreadLocal<Boolean> MonitorStatusMap = new ThreadLocal<Boolean>();
public void setMonitorActive(boolean active) {
MonitorStatusMap.set(active);
}
public Object invoke(MethodInvocation mi) throws Throwable {
Object obj = null;
if (MonitorStatusMap.get() != null && MonitorStatusMap.get()) {
PerformanceMonitor.begin(mi.getClass().getName() + "."
+ mi.getMethod().getName());
obj = super.invoke(mi);
PerformanceMonitor.end();
} else {
obj = super.invoke(mi);
}
return obj;
}
}

第一段源码定义了一个接口,第二段源码定义了一个引介增强,继承了DelegatingIntroductionInterceptor类,实现了Monitorable接口,在类中实现了setMonitorActive方法,该方法设置了类中的一个变量,这个变量用来控制是否开启性能监控功能。

实现接口的方法配合拦截的方法就可以支持性能监视可控代理。

引介增强的配置如下:

    <bean id="pmonitor" class="com.baobaotao.introduce.ControllablePerformaceMonitor" />
<bean id="forumServiceTarget" class="com.baobaotao.introduce.ForumService" />
<bean id="forumService" class="org.springframework.aop.framework.ProxyFactoryBean"
p:interfaces="com.baobaotao.introduce.Monitorable"
p:target-ref="forumServiceTarget"
p:interceptorNames="pmonitor"
p:proxyTargetClass="true" />

引介增强的配置需要制定引介增强所实现的接口,其次需要将proxyTargetClass设置为true。

其他类的代码和本文开头的类一致,引介增强测试类如下:
TestIntroduce.java

public class TestIntroduce {
public static void main(String[] args) {
String configPath = "beans.xml";
ApplicationContext ctx = new ClassPathXmlApplicationContext(configPath);
ForumService forumService = (ForumService)ctx.getBean("forumService");

forumService.removeForum(10);
forumService.removeTopic(1022);

Monitorable moniterable = (Monitorable)forumService;
moniterable.setMonitorActive(true);
forumService.removeForum(10);
forumService.removeTopic(1022);
}
}

执行结果如下:

Spring中的增强类型

从运行结果可以看出在未开启性能监控时是按照正常的逻辑运行的,开启性能监控之后就横切入了增强的逻辑。