Spring有两大核心,IOC和AOP。IOC在java web项目中无时无刻不在使用(上篇博客已经写了IOC基础的使用)。然而AOP用的比较少,但是这并不以为这不使用,在一些系统中,经常需要在一个服务流程中插入一些与业务逻辑无关的系统服务逻辑(最常见的就是记录日志,权限检查等),如果把所有这些与业务逻辑无关的服务与业务逻辑编织在一起,就会使业务逻辑对象的负担加重,因为它不但要具有业务逻辑的功能,还带有例如记录日志等其他功能,这样就容易产生对象的职责混淆。
《Spring参考手册》中定义了以下几个AOP的重要概念:
•切面(Aspect):官方的抽象定义为“一个关注点的模块化,这个关注点可能会横切多个对象”,在本例中,“切面”就是类TestAspect所关注的具体行为
•连接点(Joinpoint):程序执行过程中的某一行为,
•通知(Advice):“切面”对于某个“连接点”所产生的动作,.其中,一个“切面”可以包含多个“Advice”.
•切入点(Pointcut):匹配连接点的断言,在AOP中通知和一个切入点表达式关联。
•目标对象(Target Object):被一个或者多个切面所通知的对象。
• AOP代理(AOP Proxy)在Spring AOP中有两种代理方式,JDK动态代理和CGLIB代理。默认情况下,TargetObject实现了接口时,则采用JDK动态代理.
•前置通知(Before advice):在某连接点(JoinPoint)之前执行的通知,但这个通知不能阻止连接点前的执行。
•后通知(After advice):当某连接点退出的时候执行的通知(不论是正常返回还是异常退出)。ApplicationContext中在<aop:aspect>里面使用<aop:after>元素进行声明。
•返回后通知(After returnadvice):在某连接点正常完成后执行的通知,不包括抛出异常的情况。
•环绕通知(Around advice):包围一个连接点的通知,类似Web中Servlet规范中的Filter的doFilter方法。可以在方法的调用前后完成自定义的行为,也可以选择不执行。ApplicationContext中在<aop:aspect>里面使用<aop:around>元素进行声明。
•出异常后通知(Afterthrowing advice):在方法抛出异常退出时执行的通知。 ApplicationContext中在<aop:aspect>里面使用<aop:after-throwing>元素进行声明。
•常情况下,表达式中使用”execution“就可以满足大部分的要求。表达式格式如下:
Java代码
execution(modifiers-pattern? ret-type-pattern declaring-type-pattern? name-pattern(param-pattern) throws-pattern?)
difiers-pattern:方法的操作权限
t-type-pattern:返回值
claring-type-pattern:方法所在的包
name-pattern:方法名
parm-pattern:参数名
throws-pattern:异常
其中,除ret-type-pattern和name-pattern之外,其他都是可选的。上例中,execution(* com.spring.service.*.*(..))表示com.spring.service包下,返回值为任意类型;方法名任意;参数不作限制的所有方法。
•通知参数
可以通过args来绑定参数,这样就可以在通知(Advice)中访问具体参数了。例如,<aop:aspect>配置如下
Java代码
<aop:config> <aop:aspect id="TestAspect" ref="aspectBean"> <aop:pointcut id="businessService" expression="execution(* com.spring.service.*.*(String,..)) and args(msg,..)" /> <aop:after pointcut-ref="businessService" method="doAfter"/> </aop:aspect> </aop:config>
TestAspect的doAfter方法中就可以访问msg参数,但这样以来AService中的barA()和BServiceImpl中的barB()就不再是连接点,因为execution(*com.spring.service.*.*(String,..))只配置第一个参数为String类型的方法。其中,doAfter方法定义如下:
Java代码
public void doAfter(JoinPoint jp,String msg)
• 访问当前的连接点
任何通知(Advice)方法可以将第一个参数定义为 org.aspectj.lang.JoinPoint 类型。JoinPoint 接口提供了一系列有用的方法,比如 getArgs() (返回方法参数)、getThis()(返回代理对象)、getTarget() (返回目标)、getSignature() (返回正在被通知的方法相关信息)和 toString() 。
首先,我们需要在一个对一个用户的增删改查前或后增加横切方法:
package com.bjpowernode.spring; publicclass UserManagerImpl implements UserManager { @Override publicvoid addUser(Stringusername, String password) { System.out.println("---------UserManagerImpl.addUser()-----------------"); } @Override publicvoid delUser(int userId) { System.out.println("---------UserManagerImpl.delUser()-----------------"); } @Override public String findUserById(int userId) { System.out.println("---------findUserById.addUser()-----------------"); return"张三"; } @Override publicvoid modifyUser(int userId, String usrename, String password) { System.out.println("---------findUserById.modifyUser()-----------------"); } }
调用该方法:
publicstaticvoid main(String[] args) { BeanFactory factory = new ClassPathXmlApplicationContext("applicationContext.xml"); UserManager userManager = (UserManager)factory.getBean("userManager"); userManager.addUser("张三", "123"); }
现在代码已经写好,要求我们在不修改代码的情况下加入该方法。这里有两种方法实现。
加入SecurityHandler类:
package com.bjpowernode.spring; import org.aspectj.lang.annotation.After; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; import org.aspectj.lang.annotation.Pointcut; @Aspect public class SecurityHandler { /** * 定义Pointcut,Pointcut的名称为addAddMethod(),此方法没有返回值和参数 * 该方法就是一个标识,不进行调用 */ @Pointcut("execution(*add*(..))") privatevoid addAddMethod(){}; /** * 定义Advice,表示我们的Advice应用到哪些Pointcut订阅的Joinpoint上 */ @Before("addAddMethod()") //@After("addAddMethod()") privatevoid checkSecurity() { System.out.println("-------checkSecurity-------"); } }
配置文件:
<!-- 启用AspectJ对Annotation的支持 --> <aop:aspectj-autoproxy/> <bean id="userManager" class="com.bjpowernode.spring.UserManagerImpl"/> <bean id="securityHandler" class="com.bjpowernode.spring.SecurityHandler"/> </beans>
<aop:config> <aop:aspect id="securityAspect" ref="securityHandler"> <!-- 以add开头的方法 <aop:pointcut id="addAddMethod" expression="execution(* add*(..))"/> --> <!-- com.bjpowernode.spring包下所有的类所有的方法 <aop:pointcut id="addAddMethod" expression="execution(* com.bjpowernode.spring.*.*(..))"/> --> <aop:pointcut id="addAddMethod" expression="execution(* com.bjpowernode.spring.*.add*(..)) || execution(* com.bjpowernode.spring.*.del*(..))"/> <aop:before method="checkSecurity" pointcut-ref="addAddMethod"/> </aop:aspect> </aop:config>
checkSecurity类为:
在很多情况下,我们需要获取该方法的一些参数,进行分析,比如添加用户是我们这里要或许添加用户的用户名作为记录,那么AOP是怎么实现获取参数的呢?
这里我们写一个循环即可:
for (int i=0;i<joinPoint.getArgs().length; i++) { System.out.println(joinPoint.getArgs()[i]); }
运行结果为:
这样,就在我们需要添加方法的前面或后面加入需要的方法。这里和filter有着异曲同工之处,但是我感觉比起filter更加方便。
到此,spring的两个核心技术都介绍完了,比较基础,我相信以后做项目的时候一定会有更深刻的理解的。