写在前面
在之前的文章中有写到,Spring在配置中,会存在大量的切面配置。然而在很多情况下,SpringAOP 所提供的切面类真的不是很够用,比如想拦截制定的注解方法,我们就必须扩展DefalutPointAdviso类,自定义一个切面类,然后在Spring配置文件中进行切面的配置。
(Spring+AspectJ) Spring集成了AspectJ,同时保留了以上提到的切面与代理配置方式(为了兼容老的项目)。
使用AspectJ需要引入对于的jar文件
<dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>1.8.6</version> </dependency>
Spring + AspectJ(基于注解:通过AspectJ execution 表达式拦截方法)
下面以一个最简单的例子来实现之前提到的环绕同一种。先定义一个Aspect切面类:
@Aspect @Component public class GreetingAspect { @Around("execution(* aop.demo.GreetingImpl.*())") public Object arount(ProceedingJoinPoint pjp) throws Throwable{ before(); Object obj = pjp.proceed(); after(); return obj; } private void after() { System.out.println("before advice"); } private void before() { System.out.println("after advice"); } }
注意:类上面标志的Aspect注解表明该类是一个Aspect(在Spring 体系这个其实就是一个Advisor)。该类无须实现任何接口,只需要定义一个方法(名称无所谓),在方法上标注Arount注解,在注解中共使用AspectJ 切点表达式。方法中的参数包括一个ProceedingJoinPoint对象,它在AOP 体系中成为Joinpoint(连接点),可以通过该对象获取方法的任何信息。
分析一个这个切点表达式 execution(* aop.demo.GreetingImpl.*())
execution() 表示拦截方法;
第一个* 表示方法的返回值是任意类型;
第二个* 表示匹配该类中的所有方法;
(..)表示方法的参数是任意类型的。
在Spring 中 XML 的配置文件如下(配置就是这么简单):
<!-- 自动扫描注解 --> <context:component-scan base-package="com.xxx.*" /> <!-- 开启切面编程(通过配置织入@Aspectj切面 ) --> <aop:aspectj-autoproxy proxy-target-class="true"/>
Spring + AspectJ(基于注解:通过AspectJ @annotation 表达式拦截方法)
为了拦截指定的注解的方法,我们首先需要来定义一个注解:
@Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface MethodLog { }
将之前的Aspect类的切点表达式稍作一下改动:
@Aspect @Component public class GreetingAspect { @Around("@annotation(aop.demo.MethodLog)") public Object arount(ProceedingJoinPoint pjp) throws Throwable{ before(); Object obj = pjp.proceed(); after(); return obj; } private void after() { System.out.println("before advice"); } private void before() { System.out.println("after advice"); } }
这次使用了@annotation() 表达式,需要要在括号内定义需要拦截的注解名称即可。直接将MethodLog注解定义在需要拦截的方法上,就是这么简单。
@Component public class GreetingImpl { @MehtodLog public void sayHello(){ System.out.println("hello java"); } }
这里只有一个方法,如果有多个方法,我们只想拦截其中的某一个时,这种解决方法会很好。
Spring + AspectJ(引入增强)
为了实现基于AspectJ 的引入增强,我们同样需要定义一个Aspect 类:
@Aspect @Component public class GreetingAspect { @DeclareParents(value="aop.demo.GreetingImpl", defaultImpl = ApologyImpl.class) private Apology Apology; }
在Aspect 类中定义一个需要引入增强的接口,它就是运行时需要动态实现的接口。在这个接口上标注了DeclareParents注解,该注解有两个属性:
value ---目标类;
defaultImpl ---引入接口的默认实现类。
我们需要对引入的接口提供一个默认的实现类就可以完成引入的增强:
public class ApologyImpl implements Apology{ @Override public void saySorry() { System.out.println("我是增强类, 我说sorry"); } }
以上这个实现hi在运行时自动增强到GreetingImpl类中,也就是说,无须修改GreetImpl类的代码,让它去实现Apology 接口,我们单独为该接口提供一个实现类(ApologyImpl)来做GreetingImpl 想做的事情。
测试代码:
ApplicationContext context = new FileSystemXmlApplicationContext("beans-config.xml"); Greeting proxy = (Greeting) context.getBean("greetimpl"); proxy.sayHello(); // 看来好像 Apology 物件动态增加了职责 ((Apology) proxy).saySorry();
运行结果:
hello java
我是增强类, 我说sorry
Aspect其他注解介绍
Before ----- 前置增强;
After ----- 后置增强;
Around ----- 环绕增强;
AfterThrowing ----- 抛出增强;
DeclareParents ----- 引入增强。
Spring + AspectJ(基于配置)
使用AspectJ 的增强 比原来 的Spring AOP 增强很方便,这算是一个巨大的突破。但是任然有用户不能尝试这些特性,因为他们还在使用低于JDK 1.5 (没有注解)。spring 提供配置解决了这个问题。
<bean id="greetingImpl" class="aop.demo.GreetingImpl" /> <bean id="greetingAspect" class="aop.demo.GreetingAspect" /> <aop:config> <aop:aspect ref="greetingAspect"> <aop:around method="around" pointcut="execution(* aop.demo.GreetingImpl.*(..))"/> </aop:aspect> </aop:config>
使用<aop:config> 元素来进行AOP 配置,在其子元素中配置切面,包括增强类型,目标方法,切点等信息。
总结
各类增强类型对于的解决方法:
增强类型 基于AOP 基于AOP注解 基于<aop:cofnig> 配置
BefooreAdvice(前置增强) MethodBeforeAdvice @Before <aop:before>
AfterAdvice(后置增强) AfterReturningAdIve @After <aop:after>
AroundAdvice(环绕增强) MethodInterceptor @Around <aop:around>
ThrowsAdvice(异常增强) ThrowsAdvice @AfterThrowing <aop:after-throwing>
IntroductionAdvice(引入增强) DelegatingIntroductionInteceptor @DeclareParents <aop:declare-parents>
Spring AOP 的整体架构