使用 Spring AOP ,配置ApplicationContext.XML 实在是繁杂而乏味的工作。如果有多于10个切入点需要AOP代理,可以想象XML中的装配需要声明多少个切入bean,每个切入点需要4个增强,就需要40个切入点声明。
而在aop装配中,也需要把这些声明一一写到注入的list中。
因此,在大部分情况下,应该使用一些AOP框架来简化这些AOP装配。譬如:AspectJ。
AspectJ:一个扩展了java语言、面向切面的框架。它具有自身的AOP语法,更有专用的编译器生成遵守Java字节编码规范的Class文件。
新建工程:AspectJTest,结构如下:
dao下有接口IUserService
- package com.spring.dao;
- public interface IUserService {
- void create(String name,String pwd);
- void update(String name,String pwd);
- }
- package com.spring.dao.impl;
- import com.spring.dao.IUserService;
- public class UserServiceImpl implements IUserService {
- public void create(String name, String pwd) {
- System.out.println("创建成功:" + name + " " + pwd);
- }
- public void update(String name, String pwd) {
- System.out.println("更新成功:" + name + " " + pwd);
- }
- }
先不去管aspectj包下的aopAspectj类,打开ApplicationContext.xml:
由于AspectJ需要引入AspectJ的库来解析切入点,所以在ApplicationContext.xml的DTD部份,需要引入AspectJ的命名空间:
如下:
- <?xml version="1.0" encoding="UTF-8"?>
- <beans xmlns="http://www.springframework.org/schema/beans"
- xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
- xmlns:aop="http://www.springframework.org/schema/aop"
- xsi:schemaLocation="
- http://www.springframework.org/schema/beans
- http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
- http://www.springframework.org/schema/aop
- http://www.springframework.org/schema/aop/spring-aop-2.0.xsd"
- >
在<beans></beans>节点最后位置输入"<a",试着利用Eclipse的智能提示看提示中是否有<aop:...>选项,如发现含有选项,可以证明AspectJ命名空间引入成功。接着
输入以下配置开启AspectJ解析。
- <aop:aspectj-autoproxy />
开启解析后,把dao包下的实现类作为管理bean声明:
- <bean id="userservice" class="com.spring.dao.impl.UserServiceImpl" />
关闭ApplicationContext.xml,在aspectj下新建类:aopAspectj.java
- package com.spring.aspectj;
- import org.aspectj.lang.ProceedingJoinPoint;
- import org.aspectj.lang.annotation.AfterReturning;
- import org.aspectj.lang.annotation.AfterThrowing;
- import org.aspectj.lang.annotation.Around;
- import org.aspectj.lang.annotation.Aspect;
- import org.aspectj.lang.annotation.Before;
- @Aspect
- public class aopAspectj {
- @Before("execution(* com.spring.dao.IUserService.create(..))")
- public void logBefore(){
- System.out.println("方法开始执行");
- }
- @AfterReturning("execution(* com.spring.dao.IUserService.create(..))")
- public void logAfter(){
- System.out.println("方法结束执行");
- }
- @Around("execution(* com.spring.dao.IUserService.create(..))")
- public Object logAround(ProceedingJoinPoint pjp)throws Throwable {
- System.out.println("方法正在执行" + pjp.getArgs()[0]);
- return pjp.proceed();
- }
- @AfterThrowing(
- pointcut="execution(* com.spring.dao.IUserService.create(..))",
- throwing = "e"
- )
- public void logFailure(RuntimeException e){
- System.out.println("方法执行发生错误");
- }
- }
可以看到这个类与普通的类有所区别:多了很多注解。
AspectJ就是利用特有的自动代理bean:AnnotationAwareAspectJAutoProxyCreator对ApplicationContext.xml中所有的管理bean扫描解析,检查是否含有AspectJ约定的特殊注解。如发现注解,则根据注解的不同作出不同的装配,最后组合为AOP代理对象。
而管理bean中的所有方法一旦作出注解,则表示此方法应通过AspectJ解析转换为实现了AOP联盟规定接口的某个类。
其实可以把含有AspectJ注解的方法看作实现AOP接口的类,一个方法一个类。
如@Before:表示对应实现了MethodBeforeAdvice接口的类;
如@AfterReturning:对应实现AfterReturningAdvice接口的类;
如@Around:对应实现MethodInterceptor接口的类;
如@AfterThrowing:对应实现ThrowsAdvice接口的类;
这些注解都是AspectJ规定死的,不能改变(除非自己更改AspectJ的底层配置)。
execution(* com.spring.dao.IUserService.create(..)),执行条件使用正则来判断
execution:表示执行切入点的条件,上一句意为执行com.spring.dao.IUserService类中名为create,且参数任意(考虑重载)的方法。
注意:create(..)中的点只能是两点,不可多不可少,表示任意。
aopAspectj类完成后,需要在ApplicationContext.xml中声明成管理bean。
- <bean id="aopaspect" class="com.spring.aspectj.aopAspectj" ></bean>
在main包下新建测试类TestMain.java
- package com.spring.main;
- import org.springframework.context.ApplicationContext;
- import org.springframework.context.support.ClassPathXmlApplicationContext;
- import com.spring.dao.IUserService;
- public class TestMain {
- public static void main(String[] args){
- ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
- IUserService userservice = (IUserService) context.getBean("userservice");
- try{
- userservice.create("ddd", "fff");
- }catch(Exception e){
- }
- }
- }
将TestMain.java做为java Application程序运行,可以看到结果:
方法开始执行
方法正在执行
创建成功:ddd fff
方法结束执行
表明AspectJ解析了aopAspectJ类的注解,将方法自动装配为代理对象并成功运行了。
这时例子中的每个方法都没有拦截业务类的参数,更改业务流程。现在试着就加入参数:
- @Before("execution(* com.spring.dao.IUserService.create(..)) && args(name,..)")
- public void logBefore(String name){
- System.out.println("方法开始执行" + name);
- }
- @AfterReturning("execution(* com.spring.dao.IUserService.create(..)) && args(name,..)")
- public void logAfter(String name){
- System.out.println("方法结束执行" + name);
- }
- @Around("execution(* com.spring.dao.IUserService.create(..))")
- public Object logAround(ProceedingJoinPoint pjp)throws Throwable {
- System.out.println("方法正在执行" + pjp.getArgs()[0]);
- if(pjp.getArgs()[0].equals("ddd")){
- return pjp.proceed();
- }
- throw new RuntimeException("错误了");
- }
- @AfterThrowing(
- pointcut="execution(* com.spring.dao.IUserService.create(..)) && args(name,..)",
- throwing = "e"
- )
- public void logFailure(RuntimeException e,String name){
- System.out.println("方法执行发生错误" + name);
- }
("execution(* com.spring.dao.IUserService.create(..)) && args(name,..)")
表示AspectJ在进行代理拦截时,会将参数name也拦截下来交给增强方法处理。而在增强方法logBefore的参数中,也必须对应args的参数,不可多不可少。
否则会抛出IllegalArgumentException错误。
在没有用AspectJ的AOP设计中,实现了MethodInterceptor接口的增加可以拦截连接点,改变整个应用程序的运行流程。同样的注解为@Around的方法也拥有同样效果。
public Object logAround(ProceedingJoinPoint pjp)throws Throwable
方法声明传入一个连接点对象返回对象,抛出AOP代理错误。和实现MethodInterceptor接口的方法炉出一辄。
public Object usernow(MethodInvocation invocation)throws Throwable
将TestMain.java做为java Application程序运行,可以看到结果:
方法开始执行ddd
方法正在执行ddd
创建成功:ddd fff
方法结束执行ddd
可以看到参数被成功拦截。现在再把@Around注解的增强方法中的判断更改为:
- .....
- if(pjp.getArgs()[0].equals("fff")){
- .....
运行程序,结果为:
方法开始执行ddd
方法正在执行ddd
方法执行发生错误ddd
可以看到@Around注解的增强方法抛出了错误并被注解为@AfterThrowing的增强方法捕获了,程序的流程改变。可以想想如是把AOP运用在权限、日志等大范围验证记录方面,的确可以节约很多重复性工作。
现在看看aopAspectJ类的注解,一共有4个execution执行注解,AspectJ提供了一个叫@Pointcut的注解,可以将有需要的注解归纳到一起定义。
将aopAspectJ类更改如下:
- package com.spring.aspectj;
- import org.aspectj.lang.ProceedingJoinPoint;
- import org.aspectj.lang.annotation.AfterReturning;
- import org.aspectj.lang.annotation.AfterThrowing;
- import org.aspectj.lang.annotation.Around;
- import org.aspectj.lang.annotation.Aspect;
- import org.aspectj.lang.annotation.Before;
- import org.aspectj.lang.annotation.Pointcut;
- @Aspect
- public class aopAspectj {
- @Before("logApjectJ() && args(name,..)")
- public void logBefore(String name){
- System.out.println("方法开始执行" + name);
- }
- @AfterReturning("logApjectJ() && args(name,..)")
- public void logAfter(String name){
- System.out.println("方法结束执行" + name);
- }
- @Around("logApjectJ()")
- public Object logAround(ProceedingJoinPoint pjp)throws Throwable {
- System.out.println("方法正在执行" + pjp.getArgs()[0]);
- if(pjp.getArgs()[0].equals("ddd")){
- return pjp.proceed();
- }
- throw new RuntimeException("错误了");
- }
- @AfterThrowing(
- pointcut="logApjectJ() && args(name,..)",
- throwing = "e"
- )
- public void logFailure(RuntimeException e,String name){
- System.out.println("方法执行发生错误" + name);
- }
- @Pointcut("execution(* com.spring.dao.IUserService.create(..))")
- public void logApjectJ(){};
- }
可以看到4个execution注解已经移到新增的@Pointcut注解方法中,原先注解的方法写上了@Pointcut注解方法名。这样就达到了将相同注解集中在一起定义的目的。
运行TestMain.java,结果与单独进行execution注解的效果没有差别。