概述: 本篇文章很重要! 工作中我们经常会遇到给我们的项目写一个切面,很多开发工程师刚开始的时候都不知道切面应该怎么写,本篇文章就会教大家如何开发一个切面。
我们前面讲解了Spring的AOP编程,本质就是给spring的对象通过创建代理对象的方式添加额外功能。我们前面的方式都是通过在xml配置的方式实现的。我们简单回顾一下之前的步骤。
- 原始对象
- 额外功能
- 切入点
- 组装
一、 开发步骤
1. 额外功能:之前写法 public class MyArround implements MythodInterceptor{ public Object invoke(MethodInvocation invocation){...} } 2. 切入点: 之前写法 <aop:confg> <aop:pointcut id="pc" expression="executionn(* com.xxx..*.*(..))" /> <aop:advisor advice-ref="around" pointcut-ref="pc" /> </aop:confg>
Spring本身为我们提供了注解的方式,来实现AOP的编程,我们来看下代码.
- 创建切面类,通过切面类定义额外功能和切入点。
/** 1. 额外功能:之前写法 public class MyArround implements MythodInterceptor{ public Object invoke(MethodInvocation invocation){} } 2. 切入点: 之前写法 <aop: config> <aop:pointcut id="pc" expression="execution(* login(..))" /> </aop:config> */ @Aspect // 指定是切入类 public class MyAspect{ @Arround("execution(* login(..))") // 指定额外功能和切入点表达式 public Object arround(ProceedingJoinPoint joinPoint) throws Throwable{ System.out.println("---log---") Object ret = joinPoint.proceed(); return ret; } }
- 配置切面:
<bean id="arround" class="com.xxx.MyAspect" /> <!-- 告知spring基于注解进行切面开发 --> <aop:aspectj-autoproxy />
这样就完成了我们之前的那四个步骤,现在我们在从工厂中获取的对象就是代理对象,调用方法时,就会执行额外功能(注意: 要符合切入点表达式的方法)。
二、细节分析
- 切入点复用
切入点复用: 在切面中定义一个函数,上面加上@Pointcut注解,通过这种方式定义切入点表达式,实现了切入点的复用,相当于把切入点抽取了出来,方便切入点增加多个额外功能冗余的问题。这样我们就可以灵活的将切入点和额外功能进行自由组合。
// 指定是切入类publicclassMyAspect{ "execution(* login(..))") (publicvoidmyPointCut(){} value="myPointcut()")) (publicObjectarround(ProceedingJoinPointjoinPoint) throwsThrowable{ System.out.println("---log---") Objectret=joinPoint.proceed(); returnret; } value="myPointcut()") (publicObjectarround1(ProceedingJoinPointjoinPoint) throwsThrowable{ System.out.println("---tx---") Objectret=joinPoint.proceed(); returnret; } }
- 动态代理的创建方式
我们前面说到了Spring底层动态代理的两种方式: 1. JDK动态代理:通过实现接口方式,创建代理对象 2. cglib动态代理: 通过继承父类的方式创建代理对象 那么我们上述代码所创建的代理对象是通过哪种方式创建的呢? **默认情况下,AOP编程底层应用jdk的动态代理方式** 如果我们想要指定cglib进行动态代理创建,可以做如下设置 <aop:aspectj-autoproxy proxy-target-class=true /> 复制代码
设置后我们可以通过断点的方式观察:
那么我们之前没用注解的时候,如何设置使用cglib动态代理呢:
<aop:confgproxy-target-class="true"><aop:pointcutid="pc"expression="@annocation(com.xxx.Log)"/><aop:advisoradvice-ref="around"pointcut-ref="pc"/></aop:confg>
三、AOP开发中的一个坑
我们在使用代理开发的过程中,有时候会遇到一个问题,就是额外功能失效的问题。我们先来看下这个问题是怎么出现的。
- 首先设置一个切面,增加额外功能。
// 指定是切入类publicclassMyAspect{ "execution(* *..UserServiceImpl.*(..))") (publicvoidmyPointCut(){} value="myPointcut()")) (publicObjectarround(ProceedingJoinPointjoinPoint) throwsThrowable{ System.out.println("---log---") Objectret=joinPoint.proceed(); returnret; } value="myPointcut()") (publicObjectarround1(ProceedingJoinPointjoinPoint) throwsThrowable{ System.out.println("---tx---") Objectret=joinPoint.proceed(); returnret; } } 复制代码
- 我们在目标方法中做调用:
publicclassUserServiceImplimplementsUserService { publicvoidregister(Useruser){ System.out.println("registe----") // 调用的是原始对象的login方法,---核心功能,切面功能不执行// 设计目的是: 调用代理对象的login方法this.login("abc", "123456"); } publicbooleanlogin(Stringname, Stringpassword){ System.out.println("login-----") } }
此时要注意,当我们通过工厂获取UserService对象,并调用register方法的时候, register方法在执行的时候,是有额外功能执行的,但是由于register方法中又使用this调用了login,这个时候login方法是不会执行额外功能的。原因就是login方法是用this调用的,获得的并不是代理对象所以不会执行对应的额外功能。相当于是直接调用了原始类中的方法。如果此时让login方法也带上额外功能该怎么办呢,就是我们要通过代理对象去调用login方法。
这个时候就可以用我们之前文章中讲到的ApplicationContextAware来实现,把工厂注入进来,通过工厂去获取对象调用就可以了。
publicclassUserServiceImplimplementsUserServiceimplementsApplicationContextAware{ privateApplicationContextctx; publicvoidsetApplicationContext(AppliactionContextapplication){ this.ctx=application; } publicvoidregister(Useruser){ System.out.println("registe----") // 调用的是原始对象的login方法,---核心功能,切面功能不执行// 设计目的是: 调用代理对象的login方法this.login("abc", "123456"); //获取代理对象UserServiceuserService= (UserService)ctx.getBean("userService"); userService.login("abc", "123456"); } publicbooleanlogin(Stringname, Stringpassword){ System.out.println("login-----") } }
总结一下: 在同一个业务类中,进行业务方法间的相互调用,只有最外层方法才是加入额外功能的,内部方法通过普通方式调用,都是调用原始方法,如果想让内层的方法也调用代理对象的方法,就要通过ApplicationContextAware接口,获取现有工厂 ,进而获得代理对象
AOP总结: