一. 前言
在看此文章之前,你可能已经知道了,Spring是通过Before、After、AfterRunning、AfterThrowing以及Around 共5中通知方式为目标方法增加切面功能,比如一个需要在目标类执行一个目标方法之前和之后分别打印一份日志,就可以建立一个切面在这个方法前后打印日志。
但是如果我想在此目标类中再增加一个目标方法是,该怎么办呢? 最简单的办法就是在建立此目标类的时候,增加此方法。但是如果原目标类非常复杂,动一发而牵全身,不适合更改源码的时候, 我们可以为需要添加的方法建立一个类,然后建一个代理类,同时代理该类和目标类。
如下图所示:
使用Spring AOP,我们可以为bean引入新的方法,代理拦截调用并委托给实现该方法的其他对象。
图中,A就是原目标类,B就是新添加的方法所在的类,通过建立一个代理类同时代理A和B,调用者调用该代理时,就可以同时A和B中的方法了。
二. 例子
还是一如既往的使用以前所用到的例子观众观看演员表演 ,我们现在的需求是:
希望增加演员在表演结束后,给观众致谢这个流程.(前提是不修改源代码)
解决办法:
当然你可以使用切面编程的后置返回通知来实现该功能,不过本文主要讨论的是另外一种新方式:利用前面编程的引用功能为目标类中增加新的目标方法来实现,具体参考代码实现.
原接口和实现类(A类):
public interface IPerformance {
boolean perform(String programName);
}
public class Performance implements IPerformance {
@Override
public boolean perform(String programName) {
System.out.println("开始表演节目:" + programName);
System.out.println("表演节目结束!");
return true;
}
}
增强接口和增强接口默认实现类(B类):
public interface IEnhancePerformance {
void thank();
}
public class EnhancePerformance implements IEnhancePerformance {
@Override
public void thank() {
System.out.println("感谢大家的观看!");
}
}
代理Schema配置:
<aop:aspectj-autoproxy proxy-target-class="true" expose-proxy="false"></aop:aspectj-autoproxy>
<bean id="performance" class="com.yveshe.Performance" />
<aop:config>
<aop:aspect>
<aop:declare-parents types-matching="com.yveshe.IPerformance+" implement-interface="com.yveshe.aop.IEnhancePerformance" default-impl="com.yveshe.aop.EnhancePerformance"/>
</aop:aspect>
</aop:config>
Spring引入允许为目标对象引入新的接口,通过在< aop:aspect>
标签内使用< aop:declare-parents>
标签进行引入,定义方式如下:
<aop:declare-parents
types-matching="AspectJ语法类型表达式"
implement-interface=引入的接口"
default-impl="引入接口的默认实现"
delegate-ref="引入接口的默认实现Bean引用"/>
types-matching: 匹配需要引入接口的目标对象的AspectJ语法类型表达式;"+"表示IPerformance的所有子类;
implement-interface: 定义需要引入的接口;
default-impl和delegate-ref: 定义引入接口的默认实现,二者选一,default-impl是接口的默认实现类全限定名,而delegate-ref是默认的实现的委托Bean名;
测试
这两个测试方法等价
@Test
public void shouldAnswerWithTrue() {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("/com/yveshe/contextconf/application-context.xml");
com.yveshe.Performance performance = context.getBean(com.yveshe.Performance.class);
String programName = "不良人";
performance.perform(programName);
System.out.println();
IEnhancePerformance ePerformance = (IEnhancePerformance) performance;
ePerformance.thank();
context.close();
}
@Test
public void shouldAnswerWithTrue2() {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("/com/yveshe/contextconf/application-context.xml");
com.yveshe.Performance performance = context.getBean(com.yveshe.Performance.class);
String programName = "不良人";
performance.perform(programName);
System.out.println();
IEnhancePerformance ePerformance = context.getBean("performance", IEnhancePerformance.class);
ePerformance.thank();
context.close();
}
开始表演节目:不良人
表演节目结束!
感谢大家的观看!
分析:
1)目标对象类型匹配:使用types-matching="com.yveshe.IPerformance+"匹配IPerformance接口的子类型,如Performance实现;
2)引入接口定义:通过implement-interface属性表示引入的接口,如“com.yveshe.aop.IEnhancePerformance”。
3)引入接口的实现:通过default-impl属性指定,如“com.yveshe.aop.EnhancePerformance”,也可以使用“delegate-ref”来指定实现的Bean。
4)获取引入接口:如使用“IEnhancePerformance ePerformance = context.getBean(“performance”, IEnhancePerformance.class);”可直接获取到引入的接口。
本文例子:
https://github.com/YvesHe/spring-yveshe/tree/master/spring-aspect-schema-introductions
另外基于Java注解和使用@AspectJ风格的例子:
https://github.com/YvesHe/spring-yveshe/tree/master/spring-aspect-introductions