1启用Spring的AspectJ注解支持
问题:
Spring支持在其AOP框架中使用AspectJ注解编写的POJO aspect。但是,你必须首先启用Spring中的AspectJ注解支持。
解决方案:
你只需要在Bean配置文件中定义一个空的XML元素<aop:aspectj-autoproxy/>,就可以启用Spring IoC容器中的AspectJ注解支持。
然后,Spring将自动为匹配你的AspectJ aspect的所有Bean创建代理。
对于接口不可用或者没有用于应用设计中的情况,可以依靠CGLIB创建代理。为了启用CGLIB,必须在<aop:aspectj-autoproxy>中设置proxy-targetclass=true属性。
2用AspectJ注解声明aspect
问题:
从第5版与AspectWerkz合并起,AspectJ支持将其aspect编写为带有一租AspectJ注解的POJO。
Spring AOP框架也支持这种aspect,但是这些aspect必须在Spring IoC容器中注册方可生效。
解决方案:
要在Spring中注册AspectJ aspect,只需要将他们声明为IoC容器中的Bean实例就行了。
在Spring IoC容器中启用AspectJ,容器将自动为匹配AspectJ aspect的Bean创建代理。
用AspectJ注解编写的aspect只是一个带有@Aspect注解的java类。通知(Advice)是带有一个通知注解的简单Java方法。
AspectJ注解5中通知注解:@Before、@After、@AfterReturning、@AfterThrowing和@Around。
工作原理:
前置通知:
@Before
方式一:简单方式,只是添加注解
@Aspect
public class LoggingBeforeAspect {
private Log log = LogFactory.getLog(LoggingBeforeAspect.class);
@Before("execution(* *..*.delete(..))")
public void logBefore(){
log.debug("The method add() begins");
}
}
然后把这个class注册进IoC就可以,可以以匿名的方式注册
<aop:aspectj-autoproxy />
<bean id="userDao" class="com.partner4java.aspectj.demo1.UserDaoImpl"/>
<bean class="com.partner4java.aspectj.demo1.LoggingBeforeAspect"/>
方式二:我们还可以拿到连接点
@Aspect
public class LoggingBeforeAspect1 {
private Log log = LogFactory.getLog(LoggingBeforeAspect1.class);
@Before("execution(* *..*.delete(..))")
public void logBefore(JoinPoint joinPoint){
log.debug("The method "+ joinPoint.getSignature().getName() +" add() begins");
}
}
最终通知:
最终通知(after advice)在连接点结束之后执行,不管返回结果还是抛出异常。
@Aspect
public class LoggingAfterAspect {
private Log log = LogFactory.getLog(LoggingAfterAspect.class);
@After("execution(* *..*.delete(..))")
public void logBefore(JoinPoint joinPoint) {
log.debug("The method " + joinPoint.getSignature().getName() + " ends");
}
}
后置通知:
最终通知不管连接点正常返回还是抛出异常都执行。
如果你希望仅当连接点返回时记录,应该用后置通知(after returning advice)替换最终通知。
在后置通知中,你可以在@AfterReturning注解中添加一个returning属性,访问连接点的返回值。这个参数值应该是通知方法的参数名称,用于传入返回值。
@Aspect
public class LoggingAfterReturnAspect {
private Log log = LogFactory.getLog(LoggingAfterReturnAspect.class);
@AfterReturning(pointcut = "execution(* *..*.delete(..))", returning = "result")
public void logBefore(JoinPoint joinPoint, Object result) {
log.debug("The method " + joinPoint.getSignature().getName()
+ " ends with" + result);
}
}
异常通知:
仅当连接点抛出异常时执行。
@Aspect
public class LoggingErrorAspect {
private Log log = LogFactory.getLog(LoggingErrorAspect.class);
@AfterThrowing(pointcut = "execution(* *..*.save(..))", throwing = "throwable")
public void logAfterThrowing(JoinPoint joinPoint, Throwable throwable) {
log.debug("exception " + throwable + " in method"
+ joinPoint.getSignature().getName());
}
}
环绕通知:
功能最强大的,可以满足前面的所有需求,而且可以改变返回的数值。
@Aspect
public class LoggingAroundAspect {
private Log log = LogFactory.getLog(LoggingErrorAspect.class);
@Around("execution(* *..*.find(..))")
public Object logAround(ProceedingJoinPoint joinPoint){
log.debug("being " + joinPoint.getSignature().getName());
try {
Object result = joinPoint.proceed();
log.debug(" end " + result);
return result;
} catch (Throwable e) {
e.printStackTrace();
}
return null;
}
}
选择通知类型的通用原则是使用满足你的要求的最不强大的类型。
3访问连接点信息
问题:
在AOP中,通知适用于不同程序执行点,这些执行点称作连接点。为了让通知采取正确的行动,往往需要连接点的详细信息。
解决方案:
通知能够在通知方法签名中声明一个JoinPoint类型的参数,访问当前连接点信息。
4指定aspect优先级
问题:
当多于一个aspect适用于相同的连接点时,方法的优先级是不明确的,除非你已经显示的做出定义。
解决方案:
aspect的优先级可以通过实现Ordered接口或者使用@Order注解完成。
5重用切入点定义
问题:
在编写aspect时,你可以直接在通知注解中嵌入切入点表达式。但是,相同的切入点表达式在多个通知中必须重复。
解决方案:
和许多其他AOP实现一样,AspectJ也允许独立的定义切入点,在多个通知中重用。
工作原理:
如果你是只在本类中共享,可定义一个private的方法,然后放上切入点,在其他的方法上,直接引用其方法名就可以。
@Aspect
public class LoggingAspectj {
@Pointcut("execution(* *..*.find(..))")
private void loggingOpertion(){}
@Before("loggingOpertion()")
public void logBefore(){
}
}
当然,你也可以把loggingOpertion设置为public的,但是在同包下需要加上类名@Before("LoggingAspectj.loggingOpertion()"),
如果跨包加上包名。
6编写AspectJ切入点表达式
问题:
横切关注点可能发生在不同的程序执行点上,这些执行点被称作连接点。
因为连接点的多样化,你需要一种强大的表达式语言来帮助匹配他们。
解决方案:
...
7在你的Bean中引入行为
问题:
有时候,你可能有一组共享公共行为的类。在OOP中,他们必须扩展相同的基类或者实现相同的接口。这个问题确实是可以用AOP模块化的一个横切点关注点。
此外,Java的单继承机制仅允许一个类最多扩展一个基类。所以,你不能同时从多个实现类中继承行为。
解决方案:
引入(Introduction)是AOP中的一种特殊通知。
他允许为一个接口提供实现类,使对象动态的实现接口。这看上去就像使对象在运行时扩展了实现类。
而且,你可以用多个实现类将多个接口同时引入对象。这可以实现与多重继承相同的效果。
工作原理:
@DeclareParents
8为你的Bean引入状态
问题:
有时候,你可能希望为一组现有的对象添加新的状态,跟踪他们的使用情况,如调用次数、最后修改日期等。
如果所有的对象都有相同的基类,这就不成问题。
但是,如果不同的类不在相同的类层次结构中,添加这样的状态就很难。
解决方案:
你可以为你的对象引入一个新的接口和保存状态字段的实现类。然后,你可以编写另一个通知根绝特定的条件改变状态。
工作原理:
定义一个接入的接口和实现类,然后通过AspectJ引入到目标类中。
<aop:aspectj-autoproxy />
<bean id="userDao" class="com.partner4java.aspectj.demo1.UserDaoImpl"/>
<bean class="com.partner4java.aspectj.declare.DeclareLogAspectj"/>
@Aspect
public class DeclareLogAspectj {
//value属性标识引入的目标类
//引入的接口,为被注解的定义类,实现为defaultImpl指定的类
@DeclareParents(value = "com.partner4java.aspectj.demo1.*", defaultImpl = ExecuteLogImpl.class)
public ExecuteLog executeLog;
//
@Around("execution(* *.*(..))" + " && this(executeLog)")
public Object declareLog(ProceedingJoinPoint joinPoint,
ExecuteLog executeLog) {
executeLog.setBeginDate(new Date());
try {
Object result = joinPoint.proceed();
executeLog.increase();
executeLog.setEndDate(new Date());
return result;
} catch (Throwable e) {
e.printStackTrace();
}
return null;
}
}
获取的时候可以直接让被接入的类强转为接入类的接口类型:
@Test
public void testAsp(){
UserDao userDao = (UserDao) applicationContext.getBean("userDao");
userDao.find(new Integer(22));
ExecuteLog executeLog = (ExecuteLog)userDao;
System.out.println(executeLog.getCount());
System.out.println(executeLog.getBeginDate());
}
9用基于XML的配置声明aspect
问题:
如果你不想以任何理由使用注解的形式。
解决方案:
基于Spring的XML配置方式。
工作原理:
可以去掉<aop:aspectj-autoproxy />开启注解的声明。
<aop:config>
<aop:pointcut id="loggingOperation" expression=
"within(com.apress.springrecipes.calculator.ArithmeticCalculator+) || within(com.apress.springrecipes.calculator.UnitCalculator+)" />
<aop:pointcut id="validationOperation" expression=
"within(com.apress.springrecipes.calculator.ArithmeticCalculator+) || within(com.apress.springrecipes.calculator.UnitCalculator+)" />
<aop:aspect id="loggingAspect" ref="calculatorLoggingAspect">
<aop:before pointcut-ref="loggingOperation"
method="logBefore" />
<aop:after-returning pointcut-ref="loggingOperation"
returning="result" method="logAfterReturning" />
<aop:after-throwing pointcut-ref="loggingOperation"
throwing="e" method="logAfterThrowing" />
<aop:around pointcut-ref="loggingOperation"
method="logAround" />
</aop:aspect>
<aop:aspect id="validationAspect" ref="calculatorValidationAspect">
<aop:before pointcut-ref="validationOperation"
method="validateBefore" />
</aop:aspect>
<aop:aspect id="introduction" ref="calculatorIntroduction">
<aop:declare-parents
types-matching=
"com.apress.springrecipes.calculator.ArithmeticCalculatorImpl"
implement-interface=
"com.apress.springrecipes.calculator.MaxCalculator"
default-impl=
"com.apress.springrecipes.calculator.MaxCalculatorImpl" />
<aop:declare-parents
types-matching=
"com.apress.springrecipes.calculator.ArithmeticCalculatorImpl"
implement-interface=
"com.apress.springrecipes.calculator.MinCalculator"
default-impl=
"com.apress.springrecipes.calculator.MinCalculatorImpl" />
<aop:declare-parents
types-matching=
"com.apress.springrecipes.calculator.*CalculatorImpl"
implement-interface=
"com.apress.springrecipes.calculator.Counter"
default-impl=
"com.apress.springrecipes.calculator.CounterImpl" />
<aop:after pointcut=
"execution(* com.apress.springrecipes.calculator.*Calculator.*(..)) and this(counter)"
method="increaseCount" />
</aop:aspect>
</aop:config>
10Spring中的AspectJ加载时织入aspect
问题:
Spring只支持有限的AspectJ切入点类型,并允许aspect应用到IoC容器中声明Bean。
如果你希望使用更多的切入点类型,或者将aspect应用到Spring IoC容器之外创建的对象,就必须在Spring应用程序中使用AspectJ框架。
解决方案:
织入(Weaving)是aspect应用到你的目标对象的过程。
使用Spring AOP,注入运行时通过动态代理发生。相反,AspectJ框架支持编译时和加载时织入。
AspectJ编译时织入通过特殊的AspectJ编译器ajc完成。这个编译器将aspect织入你的Java资源文件并且输出织入的二进制类文件。
他还能将aspect织入到你编译过的类文件或者JAR文件。这个过程称作编译后织入。
你可以在Spring IoC容器中声明类之前,进行编译时或者后编译时织入。Spring完全不参与织入过程。
AspectJ加载时织入(也称LTW)发生在目标类由类加载器载入到JVM时。对于进行织入的类,需要一个特殊的类加载器来改进目标类的字节码。
AspectJ和Spring都提供加载时织入程序,为类加载器增加加载织入能力。
工作原理:
。。。