AOP:面向切面编程
OOP主要是通过对象继承、多态的特点,来进行强大的代码重用。
而AOP是将整体的系统分成各个不同模块,形成不同的切面来进行程序设计。
如Struts中数据层的每个方法,就算使用各种ORM框架,如:
...........
boolean backbool = false;
daoManager.startTransaction();
try{
.......
daoManager.commitTransaction();
}catch(Exception ex)
{
ex.printStackTrace();
}
finally{
daoManager.endTransaction();
}
...........
也不能彻底摆脱事务开始、进行数据操作、结束事务的繁杂代码编写。
而AOP模式可以把每个方法的这些相同代码提取出来,放到一个特定的类中去。而且保证在执行这些原本含有繁杂
代码的方法时,你可以自定义在哪里、在什么时候、用什么状态去调用这个特定的类。这样程序设计将会又一次
得到极大的代码重用,而维护也会更加简单。
AOP的几个重要接口记录:
MethodBeforeAdvice:切入到方法执行之前。
MethodInterceptor:切入到方法的具体代码中。
AfterReturningAdvice:切入到方法执行结束之后。
ThrowsAdvice:当AOP监视的业务方法抛出错误,切入到错误方法总拦截错误。
其中,MethodInterceptor可切入到方法的具体代码中,因此可完全控制整个方法的走向,甚至改变返回值。
新建web工程springaop如下结构:
在dao包下新建业务服务接口IUserService.java
- public interface IUserService {
- void create(String username,String password);
- void upuser(String username,String password);
- }
impl包下有实现类UserServiceImpl.java
- package com.springaop.dao.impl;
- import com.springaop.dao.IUserService;
- import com.springaop.data.UserData;
- public class UserServiceImpl implements IUserService {
- private UserData userdata;
- public void create(String username, String password) {
- if(userdata == null)
- userdata = new UserData();
- if(username.equals(userdata.getMap().get("admin"))
- password.equals(userdata.getMap().get("password"))){
- System.out.println("姓名:" + username + " 密码:" + password);
- }
- else{
- throw new RuntimeException("参数错误");
- }
- }
- public void upuser(String username,String password){
- System.out.println("执行到:upuser方法");
- }
- public void setUserdata(UserData userdata) {
- this.userdata = userdata;
- }
- }
data包下有数据类UserData.java
- package com.springaop.data;
- import java.util.HashMap;
- import java.util.Map;
- import java.util.Set;
- public class UserData {
- private Map<String,String>map = new HashMap<String, String>();
- public UserData(){
- map.put("admin", "admin");
- map.put("password", "123456789");
- }
- public Map<String, String> getMap() {
- return map;
- }
- public void setMap(Map<String, String> map) {
- this.map = map;
- }
- }
main包下有测试类TestMain.java
- package com.springaop.main;
- import org.springframework.context.ApplicationContext;
- import org.springframework.context.support.ClassPathXmlApplicationContext;
- import com.springaop.dao.IUserService;
- import com.springaop.dao.impl.UserServiceImpl;
- public class TestMain {
- public static void main(String[] arg0){
- ApplicationContext tx = new ClassPathXmlApplicationContext("applicationContext.xml");
- IUserService user = (UserServiceImpl)tx.getBean("userservice");
- try{
- user.create("admin", "123456789");
- user.upuser("admin", "123456789");
- }catch(Exception e){
- }
- }
- }
将 TestMain.java 作为java Application程序运行,可显示:
姓名:admin 密码:123456789
执行到:upuser方法
这样一切正常,运行成功。
以上的测试是在无AOP模式下的程序。现在加入AOP切面:
1:将 UserServiceImpl .java 中的属性 UserData 做为注入bean引入,放弃在类中实例化该类,这样更符合Spring松耦合思想。
在 ApplicationContext.xml 中加入:
- <bean id="userservice" class="com.springaop.dao.impl.UserServiceImpl" >
- <property name="userdata">
- <bean class="com.springaop.data.UserData"></bean>
- </property>
- </bean>
更改 UserServiceImpl 代码:
- .......
- public void create(String username, String password) {
- //删除此段
- /*if(userdata == null)
- userdata = new UserData();*/
- if(username.equals(userdata.getMap().get("admin"))
- .......
2:加入分别实现了AOP接口(前置、后置、错误通知)的4个类到程序中:
1):加入Spring Core、Spring AOP包到程序中。
2):在aopservice下,新建4个类:
UserBeginPrint.java
实现MethodBeforeAdvice接口,用于在方法执行之前的切入,比如记录日志,检查某些全局变量,开启事务等;
- package com.springaop.aopservice;
- import java.lang.reflect.Method;
- import org.springframework.aop.MethodBeforeAdvice;
- public class UserBeginPrint implements MethodBeforeAdvice {
- public void before(Method method, Object[] args, Object target)
- throws Throwable {
- System.out.println("方法开始执行");
- }
- }
UserNowPrint.java
实现MethodInterceptor接口,用于在方法执行时的切入,如对方法传入参数的检查判断、传入值的通用操作等。
- package com.springaop.aopservice;
- import org.aopalliance.intercept.MethodInterceptor;
- import org.aopalliance.intercept.MethodInvocation;
- public class UserNowPrint implements MethodInterceptor {
- public Object invoke(MethodInvocation method) throws Throwable {
- System.out.println("方法正在执行,参数为:");
- return method.proceed();
- }
- }
UserExitPrint.java
实现AfterReturningAdvice接口,用于在方法执行后的切入,通常都做方法的收尾擦除工作,如释放某些通用变量,关闭事务,记录日志等。
- package com.springaop.aopservice;
- import java.lang.reflect.Method;
- import org.springframework.aop.AfterReturningAdvice;
- public class UserExitPrint implements AfterReturningAdvice {
- public void afterReturning(Object returnValue, Method method,
- Object[] args, Object target) throws Throwable {
- System.out.println("方法执行完成");
- }
- }
UserErrorPrint.java
实现ThrowsAdvice接口,用于当业务方法抛出错误后的处理。
- package com.springaop.aopservice;
- import java.lang.reflect.Method;
- import org.springframework.aop.ThrowsAdvice;
- public class UserErrorPrint implements ThrowsAdvice {
- public void afterThrowing(Method m,Object[] args,Object target,Throwable subclass){
- System.out.println("你输入的姓名不正确!");
- }
- }
3:以上4个AOP类搞好以后,打开ApplicationContext.xml。在管理bean声明中加入以上4个类。
- <bean id="userbegin" class="com.springaop.aopservice.UserBeginPrint"></bean>
- <bean id="usernow" class="com.springaop.aopservice.UserNowPrint"></bean>
- <bean id="userexit" class="com.springaop.aopservice.UserExitPrint" ></bean>
- <bean id="usererror" class="com.springaop.aopservice.UserErrorPrint" ></bean>
随后进行SpringAOP装配:
- <bean id="useraop" class="org.springframework.aop.framework.ProxyFactoryBean">
- <property name="target" ref="userservice" ></property>
- <property name="interceptorNames">
- <list>
- <value>userbegin</value>
- <value>usernow</value>
- <value>userexit</value>
- <value>usererror</value>
- </list>
- </property>
- </bean>
4:更改测试类TestMain.java 代码,把原本转换为UserServiceImpl的代码换为IUserService,把getBean的对象换为AOP代理Bean:useraop
- ........
- ApplicationContext tx = new ClassPathXmlApplicationContext("applicationContext.xml");
- IUserService user = (IUserService)tx.getBean("useraop");
- try{
- user.create("admin", "123456789");
- ........
完成装配之后,同样运行TestMain,一切顺利的话,可以看到运行结果为:
方法开始执行
方法正在执行,参数为:
姓名:admin 密码:123456789
方法执行完成
方法开始执行
方法正在执行,参数为:
执行到:upuser方法
方法执行完成
这段 AOP 装配XML意为:
<property name="target" ref="userservice" ></property>
target属性指需要SpringAOP切面去监控的类 或 方法。指定了ref 后,AOP切面只能作用于ref指定的类 或方法
<property name="interceptorNames">
<list>
<value>userbegin</value>
<value>usernow</value>
<value>userexit</value>
<value>usererror</value>
</list>
</property>
interceptorNames 属性指需要AOP切面去调用的AOP接口实现类(Advice:增强)。调用的顺序是按照list中value的顺序来进行的,如:把usernow、userexit提前,
<value>usernow</value>
<value>userexit</value>
<value>userbegin</value>
<value>usererror</value>
可以看到运行结果也随之改变
方法正在执行,参数为:
方法开始执行
姓名:admin 密码:123456789
方法执行完成
方法正在执行,参数为:
方法开始执行
执行到:upuser方法
方法执行完成
list也可以不必set全部4个AOP接口,可以按照自己的需要set需要的接口,如只有usernow一个,或有2个、3个等。
<bean id="useraop" class="org.springframework.aop.framework.ProxyFactoryBean">
ProxyFactoryBean 是Spring制定的AOP代理bean,具有 target 目标对象和 interceptorNames 声明注入的各种接口对象的工厂Bean,他返回包含target指定类的上层接口,如果这个指定类实现了多重接口,他将返回所有的上层接口,而业务类可根据需要转换接口,也就是TestMain中的 IUserService user = (IUserService)tx.getBean("useraop"); 如业务类需要指定ProxyFactoryBean的返回接口,可在其属性中加上:
<property name="interfaces" value="com.springaop.dao.IUserService"></property> value需要填写指定实现接口类的全路径。
这次装配运行中,可以看到TestMain测试类调用了target代理指定类的两个方法 create 、upuser,而装配没有指定AOP代理具体代理监控哪个方法,因此他默认把所有调用到target代理指定类的操作都代理监控起来了。结果打印了两次开始、正在执行、结束,下面的装配需要实现具体指定target监控代理的方法。不至于IUserService的方法全都被代理。
打开ApplicationContext.xml,增加管理Bean如下:
- <bean id="useraopAdvisor" class="org.springframework.aop.support.NameMatchMethodPointcutAdvisor" >
- <property name="mappedNames">
- <list>
- <value>create</value>
- </list>
- </property>
- <property name="advice" ref="userbegin">
- </property>
- </bean>
NameMatchMethodPointcutAdvisor 类是spring AOP 最基本的 PointcutAdvisor (切入增强),它是Spring中静态Pointcut的实例,这段话是Spring 手册上的译文,没有深入到Spring 的AOP 底层。不知道具体何意,只是觉得这个类是AOP 切入的基类?是Spring初始化时的静态切入类的具体实例。
属性mappedNames指定具体监控 target 代理类的哪个方法,如有多个方法需要监控,可在list中set具体方法名,更可以用正则将符合条件的方法监控起来。
<value>*create*</value>
<value>*upuser*</value>
加入useraopAdvisor注入bean后,更改useraop注入bean的interceptorNames 属性的list。
<value>userbegin</value>
<value>usernow</value>
<value>userexit</value>
<value>usererror</value>
更改为:
<value>useraopAdvisor</value>
最后运行TestMain,可以发现结果为:
方法正在执行,参数为:
姓名:admin 密码:123456789
执行到:upuser方法
target代理监控的UserServiceImpl类只有creat处于监控范围之内 useraopAdvisor — mappedNames — mappedNames — list — value — create。
用了一天的时间看完SpringAOP,忽然感觉这东西好像 Filter ,和过滤器一样针对某个方法、某个类、某个文件夹进行过滤,不知道他的底层实现是怎么样的。另外,在做 NameMatchMethodPointcutAdvisor 实现时没有看到资料 可以在一个bean设置中代理监控N个Advisor ,试过诸如:
<property name="advice">
<list>
<ref="userbegin" />
<ref="usernow" />
</list>
</property>
但还是报错了,后来还是声明了4个NameMatchMethodPointcutAdvisor ,每个advisor 监控一个advice,最后再用useraop装配起来才成功。但感觉这样应该不是最好实现方法,如果哪位高手看到这个疑问,又知道怎么做,请留言或站内联系我,呵呵,小生先谢过了!
另外Spring AOP 诸多术语如Advice(增强)、pointcut(切入点)、AOP Proxy(AOP 代理)等都按自己理解,觉得更符合自己理解的词代替了,如增强 - 监控、切入点 - 切入、AOP Proxy -代理监控,希望随着对Sring了解渐渐深入,有一天会完全理解这些术语的含义。