面向切面编程 ( Aspect Oriented Programming with Spring )
Aspect Oriented Programming with Spring 1. 简介AOP是与OOP不同的一种程序结构。在OOP编程中,模块的单位是class(类);然而,在AOP编程中模块的单位是aspect(切面)。也就是说,OOP关注的是类,而AOP关注的是切面。 Spring AOP是用纯Java实现的。目前,只支持方法执行级别的连接点。
对于AOP代理,Spring AOP默认使用JDK动态代理。这意味着任意接口都可以被代理。 Spring AOP也可以用CGLIB代理。CGLIB代理的是类,而不是接口。如果一个业务对象没有实现一个接口,那么默认用CGLIB代理。这是一种很好的实践,面向接口编程,而不是面向类;业务类通常会实现一个或者多个业务接口。可以强制使用CGLIB代理,这种情况下你需要通知一个方法而不是一个接口,你需要传递一个代理对象而不是一个具体的类型给一个方法。 2. @AspectJ支持@AspectJ是一种声明切面的方式(或者说风格),它用注解来标注标准的Java类。 2.1. 启用@AspectJ支持autoproxying(自动代理)意味着如果Spring检测到一个Bean被一个或者多个切面通知,那么它将自动为这个Bean生成一个代理以拦截其上的方法调用,并且确保通知被执行。 可以使用XML或者Java方式来配置以支持@AspectJ。为此,你需要aspectjweaver.jar 启用@AspectJ用Java配置的方式为了使@AspectJ生效,需要用@Configuration和@EnableAspectJAutoProxy注解 1 @Configuration 2 @EnableAspectJAutoProxy 3 public class AppConfig { 4 5 } 启用@AspectJ用XML配置的方式为了使@AspectJ生效,需要用到aop:aspectj-autoproxy元素 <aop:aspectj-autoproxy/> 2.2. 声明一个切面下面的例子显示了定义一个最小的切面: 首先,定义一个标准的Java Bean bean id="myAspect" class="org.xyz.NotVeryUsefulAspect"> 2 <!-- configure properties of aspect here as normal --> </bean> 其次,用org.aspectj.lang.annotation.Aspect注解标注它 package org.xyz; import org.aspectj.lang.annotation.Aspect; 3 4 @Aspect 5 NotVeryUsefulAspect { 6 7 } 一个切面(PS:被@Aspect注解标注的类)可以向其它的类一样有方法和字段。这些方法可能包含切点、通知等等。
2.3. 声明一个切入点切入点是用来控制什么时候执行通知的,简单地来讲,就是什么样的方法会被拦截。Spring AOP目前只支持方法级别的连接点。 一个切入点声明由两部分组成:第一部分、由一个名称和任意参数组成的一个签名;第二部分、一个切入点表达式。 在@AspectJ注解方式的AOP中,一个切入点签名就是一个标准的方法定义,而切入点表达式则是由@Pointcut注解来指明的。 (作为切入点签名的方法的返回值类型必须是void) 下面是一个简单的例子: 1 @Pointcut("execution(* transfer(..))")// the pointcut expression private void anyOldTransfer() {} the pointcut signature 支持的切入点标识符
组合切入点表达式Pointcut expressions can be combined using '&&','||' and '!' 请看下面的例子: 1 @Pointcut("execution(public * *(..))") void anyPublicOperation() {} 4 "within(com.xyz.someapp.trading..*)" inTrading() {} 7 "anyPublicOperation() && inTrading()"8 void tradingOperation() {} 在企业应用开发过程中,你可能想要经常对一下模块应用一系列特殊的操作。我们推荐你定义一个“系统架构”层面的切面用来捕获公共的切入点。下面是一个例子: 1 com.xyz.someapp; 2 3 4 org.aspectj.lang.annotation.Pointcut; 5 6 7 SystemArchitecture { 8 9 /** 10 * 匹配定义在com.xyz.someapp.web包或者子包下的方法 11 */ 12 @Pointcut("within(com.xyz.someapp.web..*)"13 inWebLayer() {} 14 15 16 * 匹配定义在com.xyz.someapp.service包或者子包下的方法 17 18 @Pointcut("within(com.xyz.someapp.service..*)"19 inServiceLayer() {} 20 21 22 * 匹配定义在com.xyz.someapp.dao包或者子包下的方法 23 24 @Pointcut("within(com.xyz.someapp.dao..*)"25 inDataAccessLayer() {} 26 27 28 * 匹配定义在com.xyz.someapp下任意层级的service包下的任意类的任意方法 29 30 @Pointcut("execution(* com.xyz.someapp..service.*.*(..))"31 businessService() {} 32 33 34 * 匹配定义在com.xyz.someapp.dao包下的所有类的所有方法 35 36 @Pointcut("execution(* com.xyz.someapp.dao.*.*(..))"37 dataAccessOperation() {} 38 39 } execution表达式
通配符*表示任意字符,(..)表示任意参数 下面是一些例子
2.4. 声明通知前置通知2 org.aspectj.lang.annotation.Before; 3 5 BeforeExample { 6 7 @Before("execution(* com.xyz.myapp.dao.*.*(..))" 8 doAccessCheck() { 9 ... } 11 12 } 返回通知org.aspectj.lang.annotation.AfterReturning; AfterReturningExample { @AfterReturning( 8 pointcut="com.xyz.myapp.SystemArchitecture.dataAccessOperation()", 9 returning="retVal"10 doAccessCheck(Object retVal) { 11 12 13 14 } 异常通知org.aspectj.lang.annotation.AfterThrowing; AfterThrowingExample { @AfterThrowing( 9 throwing="ex" doRecoveryActions(DataAccessException ex) { 14 } 后置通知(最终通知)org.aspectj.lang.annotation.After; AfterFinallyExample { 7 @After( doReleaseLock() { 12 } 环绕通知环绕通知用@Around注解来声明,第一个参数必须是ProceedingJoinPoint类型的。在通知内部,调用ProceedingJoinPoint的proceed()方法造成方法执行。 org.aspectj.lang.annotation.Around; org.aspectj.lang.ProceedingJoinPoint; 4 AroundExample { 7 8 @Around("com.xyz.myapp.SystemArchitecture.businessService()"public Object doBasicProfiling(ProceedingJoinPoint pjp) throws Throwable { 10 start stopwatch 11 Object retVal = pjp.proceed(); 12 stop stopwatch 13 return retVal; 14 15 16 } 如果用户明确指定了参数名称,那么这个指定的名称可以用在通知中,通过argNames参数来指定: @Before(value="com.xyz.lib.Pointcuts.anyPublicMethod() && target(bean) && @annotation(auditable)""bean,auditable" audit(Object bean,Auditable auditable) { AuditCode code = auditable.value(); ... use code and bean } 3. 代理机制Spring AOP用JDK动态代理或者CGLIB来为给定的目标对象创建代理。(无论你怎么选择,JDK动态代理都是首选) 如果目标对象实现了至少一个接口,那么JDK动态代理将会被使用。 目标对象实现的所有接口都会被代理。 如果目标对象没有实现任何接口,那么使用CGLIB代理。 如果你强制用CGLIB代理,那么下面这些问题你需要注意:
为了强制使用CGLIB代理,需要在<aop:config>中的proxy-target-class属性设置为true aop:config proxy-target-class="true"> other beans defined here... aop:config> 当使用@AspectJ自动代理的时候强制使用CGLIB代理,需要将<aop:aspectj-autoproxy>的aop:aspectj-autoproxy />
Spring AOP是基于代理的。这一点极其重要。 考虑下面的代码片段 如果你调用一个对象中的一个方法,并且是这个对象直接调用这个方法,那么下图所示。 Main { static main(String[] args) { Pojo pojo = new SimplePojo(); this is a direct method call on the 'pojo' reference pojo.foo(); } } 如果引用对象有一个代理,那么事情变得不一样了。请考虑下面的代码片段。 main(String[] args) { ProxyFactory factory = new ProxyFactory( SimplePojo()); factory.addInterface(Pojo.); factory.addAdvice( RetryAdvice()); Pojo pojo = (Pojo) factory.getProxy(); this is a method call on the proxy! pojo.foo(); } } 上面的代码中,为引用对象生成了一个代理。这就意味着在引用对象上的方法调用会传到代理上的调用。 有一点需要注意,调用同一个类中的方法时,被调用的那个方法不会被代理。也就是说调用foo()的时候是拦截不到bar()的。 Examplefoo; org.springframework.util.StopWatch; org.springframework.core.annotation.Order; @Aspect ProfilingAspect { @Around("methodsToBeProfiled()") public Object profile(ProceedingJoinPoint pjp) Throwable { StopWatch sw = StopWatch(getClass().getSimpleName()); try { sw.start(pjp.getSignature().getName()); pjp.proceed(); } finally { sw.stop(); System.out.println(sw.prettyPrint()); } } @Pointcut("execution(public * foo..*.*(..))" methodsToBeProfiled(){} } ? 1 @Pointcut("@within(com.cjs.log.annotation.SystemControllerLog) " + 2 "|| @within(com.cjs.log.annotation.SystemRpcLog) " + 3 "|| @within(com.cjs.log.annotation.SystemServiceLog)"4 pointcut() { 5 6 } ? (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |