Spring解密(注解部分) 读书笔记二

@AspectJ

  • 支持注解 现在 我们可以直接使用POJO 来定义Aspect以及相关的Advice 并使用一套标准的注解标注这些POJO Spring 会根据注解信息查找相关的Aspect定义 并将其声明的横切逻辑织入当前系统中
  • 简化了XML的配置方式 使用新的基于XSD的配制方式 可以使用aop独有的命名空间 并且注册和使用POJO形式实现的AOP概念实体

在2.0后Spring AOP集成了AspectJ 但这是借用了AspectJ的"皮大衣"(非核心功能) 底层各种概念的试下以及织入方式 依然使用的是原来的实现体系

@AspectJ 代表一种定义Aspect的风格 它让我们能够以POJO的形式定义Aspect 没有其他接口定义限制
Spring AOP只是用 AspectJ的类库进行Poincut解析和匹配 最终实现机制还是Spring AOP最初的架构 也就是使用代理模式横切逻辑的织入

@AspectJ形式AOP使用
//简单的检测系统某些方法的执行性能
public class PerformanceMethodlnterceptor implements Methodlnterceptor {
    private final Log logger = LogFactory.getLog(this.getClass());
    public Object invoke(Methodlnvocation invocation) throws Throwable {
    Stopwatch watch = new Stopwatch();
    ...
    try
    {
        watch.start();
        return invocation.proceed();
    }
    finally
    {
        watch.stop();
        if(logger.islnfoEnabled()))
        {
                logger.info(watch.toString());
        }
    }   
    }
}

注解版本

//该类是一个Aspect类
@Aspect
public class PerfonnanceTraceAspect{
    private final Log logger = LogFactory.getLog(PerfonnanceTraceAspect.class);
    @Pointcut("execution(public void *.methodl()) || execution(public void *.method2())" )
    public void pointcutName(){}
        @Around("pointcutName()")
        public Object performanceTrace(ProceedingJoinPoint joinpoint) throws Throwable
        {
            Stopwatch watch = new StopWatch();
            try
            {
            watch.start();
            return joinpoint.proceed();
            }
            finally
            {
            watch.stop();
            if(logger.isInfoEnabled())
                logger.info("PT in method["
                        +joinpoint.getSignature().getName()+
                        +"]»»>"+watch.toString());
            }
        }
}


定义这么一个Aspect 我们再也无需像1.x时代的Spring AOP那样实现相应的接口 只需为这个Aspect类加上一个@Aspect的注解 我们可以根据这个@Aspect 来判断Classpath中哪些写是我要找的Aspect类

Aspect类是可以定义多个Pointcut以及多个Advice 所以需要使用@Poincut的注解指定Poincut定义 通过Around等注解指定哪些方法定了相应的Advice逻辑

public class Foo {
    public void method1(){
        System.out.println("methodl execution.");
    }
    public void method2(){
        System.out.println("method2 execution.");
    }
)


  1. 编程方式织入
    org.springframework.aop.aspect j .annotation .AspectJProxyFactory 通过该类我们就可以实现Aspect定义到目标对象的织入
AspectJProxyFactory weaver = new AspectJProxyFactory ( ) ;
weaver.setProxyTargetClass (true) ;
weaver.setTarget (new Foo ( ) ) ;

//该方法可以直接为AspectJProxyFactory添加相应的Aspect定义
weaver.addAspect ( PerformanceTraceAspect.class ) ;
Object proxy = weaver.getProxy ( );
( (Foo) proxy) .methodl ( );
( (Foo ) proxy ) .method2 ( ) ;


  1. 通过自动代理织入
    针对@AspectJ风格的AOP AutoProxyCreator实现类进行自动代理 即 org.springframework.aop.aspect j.annotation.AnnotationAwareAspectJAutoProxyCreator 它是在AbstractAdvisorAutoProxyCreator的基础上给出的一个扩展类AspectJAwareAdvisorAutoProxyCreator
<bean class="org.springframswork.aop.aspectj.annotation.
AnnotationAwareAspectJAutoProxyCreator">
    <property name="proxyTargetClaa" valuer"true"> </property>
</bean>
<bean id="performanceAspect" class="org.darrenstudio.books,
unveilspring.aop.aspectj.PerformanceTraceAspect"/>
    <bean id="target" class="...Foo">
</bean>


AnnotationAwareAspectJAutoProxyCreator会自动搜索Ioc容器中注册的Aspect 并应用到Pointcut定义的各个目标对象上(???)

如果把target作为依赖对象注入其他的bean定义 没那么依赖的主体对象现在持有的也是被代理过的目标对象
注意上诉配置是DTD的配置方式

<?xml versions"1.0" encoding="UTF-8"?>
cbeans 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/schenva/aop 
http://www.springframework.org/schema/aop/spring-aop-2.0.xsd">

<aop:aspectj-autoproxy proxy-target-class="true">
</aop:aspectj-autoproxy>
cbean id="target" class="org.darrenstudio.books.unveilspring.aop.aspectj.Foo“>
</bean>
<bean id="performanceAspect * class="org.darrenstudio.books.unveilspring.aop.aspectj. **
PerformanceTraceAspect"/>
</beans>


通过面向命名空间的aop:aspectj-autoproxy 等效于直接声明 AnnotationAwareAspectJAutoProxyCreator 该元素背后的工作实际上就是有该类来做的

注意 只要使用到了@Aspect形式的AOP 就要引入aspectjweaver.jar 和 aspectjrt.jar

@Aspect形式的Pointcut

Spring AOP在没有引入AspectJ时 只支持方法级别的拦截 所以通差情况下 简单的方法名指定以及正则表达式则正表达式可以满足大本分功能 集成AspectJ的部分功能后 就有必要学学AspectJ的Pointcut描述语言支持

  1. @AspectJ形式Pointcut的声明方式
    依赖于org.aspectj.lang.annotation.Pointcut这个注解
©Aspect
public class YourAspect

{
©Pointcut("aspectj style pointcut expression")
public void pointcutMethod(){}
// pointcut_expression
// pontcut_signature

©Pointcut('aspectj style pointcut expression') // pointcut_expression
private void pointcutMethod2(){} // pontcut_signature

}


声明的Pointcut包含两个部分

  1. Pointcut Expression
    @Pointcut 注解是方法级别的注解 所以 Pointcut Expression不能脱离某个方法 Pointcut Expression附着的方法称为Pointcut Expression 的Pointcut Signature Pointcut Exception 是真正规定Pointcut 匹配规则的地方 可以通过@Pointcut直接指定AspectJ形式的Pointcut表达式
  • Poincut 标志符 即该Pointcut将以什么样的行为来匹配表达式
  • 表达式匹配模式 在Pointcut标志符之内可以指定具体的匹配模式 (public??)
  1. Pointcut Signature
    返回类型必须是void 修饰符的所起的作用与Java语言中的语义相同 public 声明的可以在其他Aspect定义中引用 private 则只能在当前Aspect定义中引用
©Aspect
public class YourAspect
{
©Pointcut("execution(void methodl())")
public void methodlExecution(){}
// pointcut_expression
// pointcut_signature
©Pointcut("methodlExecution()") // 该注解引用了该方法的methodlExecution()注解
private void stillMehtodlExecution() {} // pointcut_signature

}

AspectJ的Pointcut表达式支持 && || | !逻辑运算符 进行Pointcut表达式之间的逻辑运算 运算符可以应用于具体的Pointcut表达式 以及相应的Pointcut Signature

@Aspect
public class YourAspect

{
    //pointcut中的方法名 类似于 Namaed..Poincut.setMapper("方法名")??
    @Pointcut("execution(void methodl())")
    private void methodlExec(){}
    ©Pointcut("execution(void method2())")
    private void method2Exec(){}
    @Pointcut("execution(void methodl()) || execution(void method2())")
    public void bothMethodExec(){
    ©Pointcut("methodlExec() || method2Exec()")
    public void bothMethodExec2(){}
    • ••
    }
}


对于系统一个中能够公用或者统一管理的一类Pointcut来说 完全可以申明一个专门的Aspect来定义这些Pointcut

public class SystemCommonsAspect
{
    ©Pointcut void ("pointcut expression")
    public commonPointcutl  (){}


©Pointcut("pointcut expression")
public void commonPointcut2(){}
}

//在其他Aspect定义的Pointcut定义中引用它们
Aspect
public class OtherAspect
{
©Pointcut("package.SystemCommonsAspect.commonPointcutl() ||
package.SystemCommonsAspect.commonPointcut2()")
    public void compostePOintcutDefinition()
//    •••
}
  1. @AspectJ形式Pointcut表达式的标识符
    虽然AspectJ的Pointcut表达式可用的标识符很丰富 基本上可以囊括所有Joinpoint类型的表述 但是SpringAOP 只支持方法级别的Joinpoint 所以通过AspectJExpressionPointcut指定的AspectJ形式的Pointcut表达式是由一定限制
  • execution
    Spring AOP仅支持方法执行类型的Joinpoint 该标志将将帮助我们匹配拥有指定 方法签名的Joinput 规定格式如下
    execution(modifiers-pattem(访问权限控制符)? ret-type-pattern(返回类型) declaring-type-pattem(类的全称)?
    name-pattern(方法名)(param- pattern(变量名)) throws-pattern?)
    其中方法返回类型 方法名以及参数部分的匹配模式是必须指定的
public class FOO{
    public void doSomething(String arg){}
}

//匹配上述方法
execution(public void Foo.doSomething(String))
  • '’ 可以用于任何部分的匹配模式中 表示任意字符
    executions (
    * (String))
    executions (* * (*))//表示一个参数的方法

  • …通配符可以在两个位置使用

execution(void cn.spring21.*.doSomething(*))
//只能匹配cn.spring21这一层下的所有类型
execution(void cn.spring21..*.doSomething(*))
//cn.spring21包下的所有类型 以及cn.spring21下层包下声明的所有类型(包栝子包)
execution(void *.doSomething(..))
// 用于方法参数列表匹配位置 表示0到多个参数
execution (void doSomething (.. ,String))
//匹配拥有多个参数的doSomething方法 之前几个参数类型不限 但是最后一个参数类型必须为String
  1. within
    该标志符 只接受类型声明(类的全称) 它将会匹配指定类型下所有的Joinpoint 所以在我们within指定某个类之后 它将匹配指定类所声明的所有方法执行
within (cn.spring21.aop.target.MockTarget )
//该表达式将在Spring AOP中将会匹配MockTarget类中的所有方法声明
within (cn.spring21.aop.target.* )
//匹配cn.spring21.aop.target报下所欲类型内部的方法级别Joinpoint
within (cn.spring21.aop..*)
//匹配cn.spring21.aop.该层中的所有类 以及其子包下所有类型的内部方法级别的Joinpoint
  1. this和target
    this指代调用方法一方所在的对象(谁代用指的是谁) target指的是被调用方法所在的对象
    可以使用这两个标识符限定 方法的调用关系 比如 如果Object1,Object2都会调用Object3的某个方法
    //A调用B的某个方法 this指代的是A target指代的是B
    this(Object2) && target(Object3)
    //当Object2调用Object3上的方法的时候才会匹配

Spring AOP中的this和target有别于AspectJ this 指代的是目标对象的代理对象,而target指代的是目标对象 this(ObjectType) 作为Pointcut定义 那么当目标对象的代理对象是ObjectType类型的时候 该Pointcut定义将匹配ObjectType类型中所有的Joinput

target(ObjectType) 作为Pointcut定义 那么当目标对象是ObjectType类型的时候 该Pointcut定义将匹配ObjectType类型中所有的Joinput

public class TargetFoo implements Proxylnterfacet
    //...
}
public interface ProxyInterface{
    //...
}
//不论是用接口代理还是类代理  两个Poincut表达式所起的所用的实际上是差不多的
this(ProxyInterface)
target(ProxyInterface)

//基于接口代理 基于类代理这两个方法的意思是相同的
this(TargetFoo)//无法匹配
target(TargetFoo)//可以匹配目标对象的所有Joincut


//通常this和target标识符都是在Pointcut表达式中与其标识符结合使用
execution(void cn.spring21.*.doSomething(*)) && this(TargetFoo)
execution(void cn.spring21.*.doSomething {*)) && target(ProxyInterface)
  1. args
    该标识符的作用是 帮做我们捕捉又有指定参数类型 指定参数数量的方法级别Joinpoint
    args(cn.spring21.unveilspring.domain.User)
//如下方法都将被该Pointut所匹配
public class Foo{
public boolean login(User user){...};
}
public class Bar{
public boolean isLogin(User user){...};
}

与使用execution标识符可以直接明确指定方法参数类型不同 args标识符会在运行期间动态检查参数的类型 即使我们的方法签名声明public boolean login(Object user)
只要login方法传入的是user类型的实例 依然可以捕捉到该Joinpoint
但是类似于executiont * *(User))这样的声明将无法匹配
5. @within
使用@within 指定了某种类型的注解(指定注解) 那么 只要对象标注了该类型的注解 使用了@within 标志符的Pointcut表达式将匹配该对象内部所有Joinpoint 当然是对象内部声明的所有方法级Joinpoint

@Retention(RetentionPolicy.RUNTIME)
@Target((ElementType.METHOD,ElementType.TYPE))
public @interface AnyJoinpontAnnotation {

}
@AnyJoinpontAnnotation
public class Foo{
public void methodl(){...}
public void m.ethod2(){...}

}
@within(AnyJoinpontAnnotation)
// FOO类中的method1,method2等方法将全部被Pointcut表达式所匹配
  1. @Target
    如果目标对象拥有 @Target标识符所指定的注解类型 那么目标对象内部所有的Joinpoint将被匹配
    在Spring AOP中 @within和@Target没有太大的区别 @within属于静态匹配 而@target则是在运行时点动态匹配Joinpoint

  2. @args
    使用@args标志符的Pointcut表达式将会检查当前 方法级的Joinpoint 的方法参数类型 如果该次传入的参数类型拥有@args所指定的注解 当前Joinpoint将被匹配

public class InterceptableOne
implements lnterceptable{
    public boolean i6lnterceptable()
    { 
        return false;
    } 
}
@AnyJoinpontAnnotation
public class InterceptableTwo
implements lnterceptable{
    public boolean i6lnterceptable()
    { 
        return false;
    } 
}

//调用handOut方法
@args(cn.apring21.unveilspring.aop.pointcuts.AnyJoinpontAnnotation)
public void handout(Interceptatfle intJercepcable)
{
}
//如果参数是One类 将无法被Poincut表达式所匹配
//第二次方法调用 传入的参数为Two类型 而它标注了@AnyJoinpontAnnotation 所以这次方法执行将被匹配

@args会尝试对系统中所有对象的每次方法执行的参数 都进行指定的注解的动态类型检查
只有参数类型标注有指定的注解类性就匹配
@args只接受注解类型
7. @annotation
如果被检测的方法标注了该注解指定的注解类型 那么当前方法所在的Joinpoint将被Pointcut表达式所匹配
假设我们要对系统中的事务处理进行统一管理 除了稍后将介绍的声明性事务处理之外 我们可以通过使用注解的方式管理事务 当开发人员希望对某个方法加事务的时候 只要使用相应的注解标注一下即可

public class MockService{
    ©Transactional
    public void service() {

    }
}

//匹配标注了@Transactional的方法
©annotation(org.springframework.transaction.annotation.Transactional)
//并为其织入事务控制逻辑

该注解只支持了(方法级别) 标注了@Target(Element.METHOD)

如果设置了Spring AOP不支持的标识符的Pointcut表达式 Spring AOP将抛出IllegalArgumentException 毕竟Spring AOP只是借用了 AspectJ的Pointcut表述语言
底层的Joinpoint类型匹配却依然是Spring AOP的最初机制

@AspectJ形式的Pointcut在Spring AOP中的真实面目

@AspectJ的所有Pointcut表达式 在Spring AOP内部都会通过解析 转化为具体的Spring AOP 定义的Pointcut对象
org. springframework .aop .aspectj .AspectJExpressionPointcut代表的Spring AOP中面向对象的具体实现

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rSdW1hEu-1583599363418)(./img/AspectJPointcut扩展的类图.png)]

ExpressionPointcut和AbstractExpressionPointcut主要是为了以后的扩展性 即 如果有其他与语言实现了 我们可以在这两个的基础上进行集成
http://blog.spring-source.com/main/2007/09/24/the-new-bcan-pointcut/

AspectJExpressionPointcut内部处理Pointcut匹配的逻辑没有变化 依然是ClassFilter和MethodMatcher相应方法逻辑的时候 会委托给AspectJ类库的相关类来做具体实现

@AspectJ的Advice

该形式下的Advice实际上就是 使用@Aspect标注的Aspeact定义类的普通方法 这些方法需要对不同的Advice类型使用对应的注解进行标注

  • @Before 用于标注Before Advice定义所在的方法
  • @AfterReturning 用于标注After Returning Advice定义所在的方法
  • @AfterThrowing 用于标注After Throwing Advice定义所在的方法
  • @After 用于标注After(finally) Advice定义所在的方法
  • @Around 用于标注Around Advice定义所在的方法 也就是常说的拦截器类型
  • @DeclareParents 用于标注Introduction类型的Advice 该注解对应标注对象的域而不是方法

除了最后一个其他都是方法级别的注解 各种Advice最终织入到什么注解是由相应的Pointcut定义决定的所以需要为这些Advice指定对应的Pointcut
可以直接指定@AspectJ形式的Pointcut表达式 也可以指定单独声明的@Pointcut Signature(不包括@DeclareParents)

@Aspect
public class MockAspect{
    ©Pointcut("execution(* destroy(..)")
    public void destroy (){}
    ©Before("execution(public void *.xnethodName(String))")
    public void setUpResourceFolder()
    {
        ...
    }

    After("destroy()")
    public void cleanUpResourceslfNecessary()
    {}
}

//Before中包含Pointcut表达式
  1. Before Advice
    对应注解是org .aspectj. lang .annotation . Before
//注解版的ResourceSetupBeforeAdvice
public class ResourceSetupJAspect {
    private Resource resource;
©Before("execution(boolean *.execute())")
public void setupResourcesBefore() throws Throwable{
    if(!getResource().exists()){
        FileUtils.forceMkdir(getResource().getFile());
    }
}
public Resource getResource(){
    return resource;
}
public void setResource(Resource resource) {
    this.resource = resource;
}
}

//注解版的ResourceSetupBeforeAdvice
@Aspect
public class ResourceSetupJAspect {
    private Resource resource;
    ©Before("execution(boolean *.execute())")
    private void resourceSetupJoinpoints(){ }
    ©Before("resourceSetupJoinpoints()")
    public void setupResourcesBefore() throws Throwable{
        if(!getResource().exists()){
            FileUtils.forceMkdir(getResource().getFile());
        }
    }
    public Resource getResource(){
        return resource;
    }
    public void setResource(Resource resource) {
        this.resource = resource;
    }
}

在某些情况下我们可能需要在Advice定义中访问 Jointput出的参数

public interface MethodBeforeAdvice extends BeforeAdvice{
    void before(Method method,Object[] args,Object target) throws Throwable;
}

-通过org.aspectj.lang.JoinPoint 在@AspectJ形式的Aspect中 定义Before Advice的方法可以将第一个参数声明为org.aspectj.lang.JoinPoint类型 就是把原来的参数换成JoinPoint 通过这个类型我们可以借助它的getArgs()方法 访问相应的Joinpoint方法的参数值 另外 我们可以借助它的其他方法取得更多信息 比如 getThis()获得当Ian代理对象 getTarget取得当前目标对象??

public void setupResourcesBefore (JoinPoint joinpoint ) throws Throwable{
joinpoint.getArgs ( ) ;

}

  • 通过args标志符绑定 当args标识符接受的不是具体的对象类型而是某个参数名称的时候 它会将这个参数名称对应的参数值绑定到对Advice方法的调用
@Before (value="execution (boolean *.execute (String, .. ) ) && args ( taskName ) " )
public void setupResourcesBefore (String taskName) throws Throwable{
    //访问taskName参数
}

args指定的参数名称必须与Advice定义所在方法的参数名称相同

//引用复用的pointcut
©Pointcut("execution(boolean *.execute(String,..&&)) args(taskName)"
private void resourceSetupJoinpoints(String taskName){}

©Before(value="resourceSetupJoinpoints(taskName)")
public void setupResourcesBefore(String taskName) throws Throwable(

}
//同时使用org.aspect.lang.JoinPoint以及args参数名称绑定功能
@Before(value="execution(boolean *.execute(String,...)) && args(taskName)")
public void setupResourcesBefore(JoinPoint joinpoint,String taskName)throws Throwable{
// access ‘taskName’ argument


}
//需要注意的是 org.aspectj.lang.JoinPoint永远处于第一个参数位置
  • 除了Around Advice和Introduction不可以在地各参数中声明为org.aspectj.lang.JoinPoint其他Advice类型都是的方法都遵循这个规则
  • 向this target @within @target @annotation @args等原本都是指定对象类型的
    如果他们指定的是参数名称,那么 将会把这个参数名称绑定到方法中的名称中去
©Before(value="execution(boolean *.execute(String,..)) && args(taskName)&&
©annotation(txlnfo)")
public void setupResourcesBefore(String taskName,Transactional txlnfo) throws Throwable{
// 
}
//当目标对象上Joinpoint处的方法标注了org.springframework.transaction.annotation.Transactional 我们就可以通过这种方式取得事务设置的详细信息

After Throwing Advice

对应org.aspectj.lang.annotation.AfterThrowing

//将ThrowsAdvice部分的实例ExceptionBarrierThrowsAdvice移植到@AspectJ形式的声明
@Aspect
public class ExceptionBarrierAspect {
private JavaMailSender mailSender;
private String[] receiptions;
@AfterThrowing(pointcut="execution(boolean *.execute(string",
throwings="e")
public void afterThrowing(RuntimeException e)
{
    final String exceptionMessage = ExceptionUtils.getFullStackTrace(e);
    getMailSender().send(new MimeMessagePreparator(){
            public void prepare(MineMeseage message) throws Exception {
            MimeMessageHelper helper new MimeMessageHelper(message);
            helper.setSubject("...");
            helper.setTo(getReceiptions());
            helper.setText(exceptionMessage);
    }});
            
}
public JavaMailSender getMailSender() {
return mailSender;
}
public void setMailSender(JavaMailSender mailSender) {
this.mailSender = mailSender;
)
public Stringf] getReceiptionsO {
return receiptions;
)
public void setReceiptions(String[] receiptions) {
this.receiptions = receiptions;
}
}

@AfterThrowing 有一个独特的属性 即throwing 通过它 我们可以限定Advice定义方法的参数名 并在方法调用的时候 将相应的异常绑定到具体方法参数上
我们使用throwing="e" 将RuntimeException类型的异常绑定到afterThrowing方法

//如果不需要访问具体异常 那么就声明没有任何参数的方法
@AfterThrowing(pointcut="execution(boolean *.execute(string")
public void afterThrowing()
{
   
            
}
//如果不但要访问信息还要访问其他信息 代码如下

SAfterThrowing(pointcut="execution(boolean *.execute(String,...))", throwing="e")
public void afterThrowing(Joinpoint joinpoint,RuntimeException e){

}

如果出现多个异常 我们可以在Aspect中针对多个不同的异常类型 声明不同的After Throwing Advice的方法定义

After Returning Advice

使用org.aspectj.lang.annotation.AfterReturning标注即可

//MTaskExecutionAfterReturningAdvice的AspectJ版
©iAspect 
public class TaskExecutionCompletionAspect {
    private SqlMapClientTemplate sqlMapClientTemplate;
    @AfterRetuming("execution(boolean *.execute(String,..))")
    public void taakBxecutionCompleted(JoinPoint jp){
                Class clazz=jp.getTarget().getClass();
            getSqlMapClientTemplate().insert("BATCH.insertTaskStatus",clazz.getName());

    }
    public SqlMapClientTemplate getSqlMapClientTemplate() {
        return sqlMapClientTemplate;
    }
    public void setSqlMapClientTemplate(SqlMapClientTemplate sqlMapClientTemplate) {
        this.sqlMapClientTemplate = sqlMapClientTemplate;
    }
}

上述 我们只通过声明Joinpoint参数来获取目标对象的信息 但对于After Returning Advice来说 某些时候需要访问方法的返回值 这是只需要通过returning属性将返回值绑定到After Returning Advice 定义所在方法

@AfterRetuming(pointcut="execution(boolean‘.execute(String,..))",returning="retValue")
public void taskExecutionCompleted(JoinPoint jp,boolean retvalue)

{ 
}

After(Finally)Advice

只需要通过org.aspectj.lang.annotation.After对Aspect中定义的相应方法进行标注
对于匹配的JoinPoint出的方法执行来说 不管方法是正常返回还是执行过程中抛出异常而非正常返回 都会触发其上的After(Finally)Advice执行 After(Finally) Advice适用于释放某些系统资源的场景

//假设我们的系统中 某个目录存放的都是临时文件 系统退出后或者某些合适的时机
//这些临时文件需要删除以释放磁盘中
©Aspect
public class SystemCleanerAspect {
    private Resource resource;
    @After("execution(boolean *.execute(String,..))")
     public void cleanUpReBourcealfNecessary() throws Throwable
    {
    
        if(getResourceO.exists())
        {
        Fileutils.forceDelete(getResource().getFile());
        
        }
    }
    public Resource getResource() {
    return resource;
    }
    public void setResource(Resource resource) {
    this.resource = resource; 
    }
}

更多的时候我们可以使用After(Finally) Advice来处理网络连接的释放,数据库资源的释放等 如果需要访问Joinpoint处的方法参数相关信息 可以通过Before Advice部分提及的两种方式进行处理

Around Advice

org .aspectj.lang .annotation . Around来标注Aspect中Around Advice所定义的方法
BeforeAdvice, After Throwing Advice , After Returning Advice以及After Advice的方法定义的第一个参数可以为org.aspectj . lang .JoinPoint类型 而且是可选的
但是对于Around Advice的方法定义来说 它的第一个参数必须是org.aspectj . lang .JoinPoint(属于org.aspectj.lang.JoinPoint的子类)
通常情况下 我们需要通过ProceedingJoinPoint的proceed()方法继续调用链的执行

//将PerformanceMethodInterceptor逻辑移植到@AspectJ形式的Around Advice声明形式
@Aspect
public class PerfomanceTraceAspect{
private final Log logger = LogFactory.getLog(PerformanceTraceAspect.class);
    ©Around("execution(boolean *.execute(String,..))")
    public Object performanceTrace(ProceedingJoinPoint joinpoint) throws Throwable
    {
            StopWatch watch = new Stopwatch();
            try
            (
            watch.start();
            return joinpoint.proceed();
            )
            finally
            {
            watch.stop();
            if(logger.isInfoEnabled())
            logger.info(watch.toString());
            }
    }
}

我们可以根据情况来决定是否要调用proceed()方法 设置调用多少次

//在调用proceed()方法 还可以在调用proceed()方法的时候 传入一个Object[]数组代表方法参数列表??(有什么用途)
//在Around Advice的方法定义增加参数 代表当前任务名称
@Aspect
public class PerfomanceTraceAspect{
private final Log logger = LogFactory.getLog(PerformanceTraceAspect.class);
    ©Around("execution(boolean *.execute(String,..)) && args(taskName)")
    public Object performanceTrace(ProceedingJoinPoint joinpoint,String taskName) throws Throwable
    {
            StopWatch watch = new Stopwatch();
            try
            {
                //对传入参数进行处理
                String modifiedTaskName processTaskNama(taskName);
                watch.start();
                return joinpoint.proceed(new Object[]{modifiedTaskName});
            }
            finally
            {
            watch.stop();
            if(logger.isInfoEnabled())
            logger.info(watch.toString());
            }
    }
}

Introduction

在Springh中 Introduction的实现是通过将需要添加的新的行为逻辑 以新的接口定义增加到目标对象上 所以根据这个逻辑 要以@AspectJ 形式声明Introduction 我们需要在Aspect中声明一个实例变量 这个实例变量 它的类型对应的就是要实现的接口类型org.aspectj.lang.annotation.DeclareParents对其进行标注 通过@DeclareParents指定新接口定义的实现类以及将要加注的目标对象

//将Icounter的行为逻辑加到ITask类型的目标实现类上 假设ITask的实现类是MockTask 而ICounter的实现类是CounterImpl
©Aspect
public class IntroductionAspect {
©DeclareParents{
value= "...MockTask"
defaultImpl=CounterImpl.class
)
public ICounter counter;
}

需要注意的是

  • @DeclareParents所归属的域定义类型为ICounter 也就是将目标对象新增加的对象类型
  • 通过@DeclareParents的value属性 可以指定将要应用到的目标对象 还可以同过通配符指定多个对象
©DeclareParents{
value= "cn.spring21.unveilspring.service.*"
defaultImpl=CounterImpl.class
)
public ICounter counter;
//匹配cn.spring21.unveilspring.service下的类都增加ICounter的行为
  • defaultImpl属性 指定新增加的接口定义的实现类 在这里
    ICouter的实现类为 defaultImpl=CounterImpl.class
<!-- 注册到容器中 -->
<aop:aspectj-autoproxy proxy-target-class="true">
</aop:aspectj-autoproxy>
<bean id="task" class="...MockTask* scope="prototype">
</bean>
<bean id="counterlnroduction" class="...IntroductionAspect" > </bean>


@AspectJ 中的Aspect更过的话题

  1. Advice的执行顺序
  • 当这些Advice都声明在同一个Aspect内的时候 如果匹配同一个Joinpoint的多个Advice都声明在同一个Aspect定义中 这些Advice的执行顺序 由它们在Advice中声明的顺序决定的 最先声明的Advice拥有最高的优先级 对于Before Advice来说 拥有最高优先级的最先运行 而对于AfterReturningAdvice 拥有最高优先级最后运行
@Aspect
public class MultiAdvicesAspect {
@Pointcut("execution(boolean *.execute(String,..))")
public void taskExecution() {}
@Before("taskExecution()")
public void beforeOne()
{
    System.out.println("before One");
} 
@Before("taskExecution()")
public void beforeTwo()
{
    System.out.println("before Two");
} 
@AfterRetuming("taskExecution()")
public void afterRetumingOne()
{
System.out.println("after returning one");
 } 
 @AfterReturning("taskExecution()")
public void afterReturningTwo()
{
    System.out.println("after returning two");
}
}

输出结果为
before One
before Two
task executed
after returning two //只有方法正长返回才会执行 所以先进后出
after returning one

  • 声明在不同的Advice内的时候
    声明在不同的Advice内的相同Ordered接口 这时我们需要用到org.springframework.core.Ordered 只要让相应的Aspect定义实现Ordered接口即可 Ordered.getOrder()方法返回较小值的Aspect 其内部声明的Advice拥有较高的优先级 与配置文件中的ordered一致
public class AnotherAspect implements Ordered{
    private final Log logger = LogFactory.getLog(PerformanceTraceAspect.class);
    @Before("package.MultiAdvicesAspect.taskExecution()")//还可以这样啊
    public void doBefore()
    {
        System.out.println("before in AnotherAspect");
    } 
    public int getOrder() {
        return 100;
    }
}

让MultiAdviciesAspect也实现Ordered接口并先将getOrder()的返回值定为20
before One
before Two
before in AnotherAspect
task executed,
after returning two
after returning one
因为Before内部没有调用链 再加上MultiAdviciesAspect的优先级比AnotherAspec高 所以 先执行before one

注意:

如果使用容器注册并使用这些Aspect 让自动代理机制处理这些横切逻辑到目标对象的织入
多个Aspect内的Advice执行顺序是一样的

如果使用编码 那么Advice的执行顺序完全由添加到AspectJproxyFactory的顺序来决定的
而不是Ordered接口所规定的顺序

AspectJProxyFactory weaver = new AspectJProxyFactory();
weaver.setProxyTargetClass(true);
weaver.setTarget(new MockTask());
weaver.addAspect(new AnotherAspect());
weaver.addAspect(new MultiAdvicesAspect {));
MockTask task = (MockTask)weaver.getProxy();
task.execute(nul1);

Aspect的实例化模式

对于注册到容器的各个Aspec(包括Pointcut??) 他们的默认实例化模式(singleton instantiation model)采用的是sigleton 也就是说 在容器中会实例化并持有每个Aspect的单个实例(行为上恰好与容器的sington型scope的行为相吻合)
除了singleton的实例化模式 AspectJ还支持perthis,pertargetmpercflow,percflowbelow以及perwithin等实例化模式 Spring2.0后只支持默认的singleton,perthis和pertarget三种实例化模式

©Aspect("perthis(execution(boolean *.execute(String )))")
public class MultiAdvicesAspect {

@Pointcut("execution(boolean *.execute(String,..))")
public void taskExecution(){}


}

这样 在指定的Poincut定义匹配之后 会为响应的代理对象 实例化各自的Aspect实例 对于peratarget来说 则是 为匹配单独的目标对象实例化 相应Aspect实例(可以在perthis或者pertarget语句中 使用Pointcut定义的pointcut Signature代替直接的Pointcut表达式定义)
不过 使用perthis或者pertarget指定了Aspect的实例化模式后 将这些Aspect注册到容器时 不能为bean定义指定sigleton的scope 否则会出现异常 因为容器先限定只有一个实例

基于Schema的AOP

新的基于Schema的配置方式为Spring AOP功能提供了独有的命名空间

<?xml version="1.0" encodings"UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" 
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

xmlns:aop="http://www.Bpringframework.org/schema/aop" 
xsi:schemaLocation="http://www.springframework.org/schema/beans 
http://www.springframework.org/schena/beans/spring-beans-2.O.xsd 

http://www.epringframework.org/schema/aop 

http://www.springframework.org/schema/aop/spring-aop-2.0.xsd">
<!-- 不同的 -->
</beans>

基于Schema的AOP配置概览

基于Schema的AOP配置方式 针对Pointcut Advisor Aspect等概念提供了独立的配置元素这些配置元素都包含在统一的配置元素中 aop:config

<?xml version="1.0" encodings"UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" 
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

xmlns:aop="http://www.Bpringframework.org/schema/aop" 
xsi:schemaLocation="http://www.springframework.org/schema/beans 
http://www.springframework.org/schena/beans/spring-beans-2.O.xsd 

http://www.epringframework.org/schema/aop 

http://www.springframework.org/schema/aop/spring-aop-2.0.xsd">
<!-- 不同的 -->
    <aop:config>

    </aop:config> 
</beans>

该标签只有一个属性proxy-target-class 对应ProxyConfig中的proxyTargetClass属性
false表示基于接口??

aop:config内部可以有三个子元素 分别是aop:pointcut,aop:advisor,aop:aspect 他们必须按照顺序配置 这三个标签可以引用bean??

<?xml version="1.0" encodings"UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" 
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

xmlns:aop="http://www.Bpringframework.org/schema/aop" 
xsi:schemaLocation="http://www.springframework.org/schema/beans 
http://www.springframework.org/schena/beans/spring-beans-2.O.xsd 

http://www.epringframework.org/schema/aop 

http://www.springframework.org/schema/aop/spring-aop-2.0.xsd">
<!-- 不同的 -->
    <aop:config>
        <aop:pointcut/>
        <aop:advisor/>
        <aop:aspect > </aop:aspect>
    </aop:config> 
    <aop:config proxy-target-class="true">
        <aop:pointcut/>
        <aop:advisor/>
        <aop:aspect > </aop:aspect >
    </aop:config>
</beans>

向基于Schema的AOP迁移

使用aop:advisor代替各种具体的Advisor实现类的bean定义声明 使用aop:config取代各种AutoProxyCreator

将PerformanceMethodInterceptor向 Schema的方式进行配置

<aop:config>
    <aop:advisor id="performanceAdvisor" 
    pointcut-ref ="targetPointcut" 
    advice-ref ="performanceInterceptor"
    order="l"/>
</aop:config>
<bean id= "targetPointcut" class= "org.springframework.aop.support.JdkRegexpMethodPointcut">
    <property name= "pattern" values="... * />
</bean>

<bean id="perfonnancelnterceptor" class="PerformanceMethodlnterceptor" >
</bean>

  • 对于PerformanceMethodInterceptor和它相应的Pointcut的bean定义来说 与之前基于DTD的配置方式没什么变化

  • 在aop:config中使用aop:advisor配置相应的Advisor

  1. id 指定当前Advisor定义的标志id
  2. pointcut-ref 通过这个属性指定当前Advisor所对应的Pointcut定义是什么 需要指定容器中注册的具体的Pointcut对象引用
  3. advice-ref 通过这个属性指定当前Advisor对应的Advice对象的引用
  4. order 指定当前Advisor的顺序号 因为基本上所有的Advisor实现了Ordered接口
  • 深入挖掘aop:advisor
    对于aop:advisor pointcut属性只能指定AspectJ形式的Pointcut表达式 如果要使用它就必须把AspectJ的内裤添加到应用程序的ClassPath中
<aop:config>
    <aop:advisor id= "performanceAdvisor" 
    pointcut= "execution {public void *.doSomething (.. ) ) " 
    advice-ref = "performancelnterceptor"
    order= "1"/>
</aop:con£ig>

<bean id="performanceInterceptor" class="PerformanceMethodInterceptor">
</bean>

现在可以使用Poincut这个属性指定AspectJ形式的Pointcut表达式就可以 不用为当前的Advisor定义单独声明一个Pointcut实现类的bean定义引用

  • aop:pointcut
    前面的实例 pointcut是独立定义的即 用bean定义 它可以被aop:advisor甚至aop:aspect所享受引用 另一个位置就是aop:aspect元素内部 这种Pointcut只能在其所声明的aop:aspect内部引用
    相当于修饰符为private 的定义
<aop:config>
<aop:pointcut id="myPointcut"
type="regex" 
expreosion="cn\.epring21\..*\.doSomething"/>

    <aop:advisor id="performanceAdvisor"
    pointcut-ref="myPointcut"
    advice-ref="performanceInterceptor"
    order= "l"/>
</aop:config>
<bean id= "performanceInterceptor" class="...PerformanceMethodInterceptor">
</bean>

如果指定aop:pointcut的type为regex 那么可以为expression指定正则表达式形式的Pointcut描述 如果指定aop:pointcut的type为aspectj 就可以为expression指定AspectJ形式的Pointcut表达式 如果type非法将按照第二种进行解析

@AspectJ 到基于Schema的AOP迁移

@AspectJ形式的AOP形式的AOP采用相应的注解来标注必要的信息 如哪个对象是Aspect定义 Aspect定义中哪个方法什么类型的Advice声明 或者Pointcut的定义是什么等

  1. 基于Schema的Aspect声明
  • Aspect的定义
    我们依然需要POJO
public class SchemaBasedAspect{
    public void methodl ( ) {...}
    public void method2 ( ) {... }
}
  • AspectJ定义到容器的配置
<aop:config>
    <aop:aspect id= "myAspect" ref = "shejBaBaBedAapect " order= "2">
    </aop:aspect>
</aop:config>
<bean id="ehemaBaBedAapect" class="...SchemaBasedAspect"></bean>
  1. 基于Shema的Pointcut声明
    前面的实例 pointcut是独立定义的即 用bean定义 它可以被aop:advisor甚至aop:aspect所享受引用 另一个位置就是aop:aspect元素内部 这种Pointcut只能在其所声明的aop:aspect内部引用
<aop:config>
   <aop:aspect id="myAspect" ref="shemaBasedAspect" order="2">
       <aop:pointcut id="privatePointcut"
       expression="execution(public void *.doSth())"/>
   <!-- ... -->
   </aop:aspect>

</aop:config>
<bean id="shemaBasedAspect" class="..SchemaBasedAspect"></bean>
通常 expression指定Pointcut 指定Pointcut表达式就可以 如果我们已经以@AspectJ形式声明了
Aspect和Pointcut(??) 那么还可以在expression中指定Pointcut定义的Pointcut Signature

<aop:pointcut id="privatePointcut" 
expression="cn.spring21.unveilspring.SystemCommonsAspect.servicePointcut()"/>

因为通常Pointcut可以进行逻辑运算 AspectJ语法中的逻辑运算在基于Schema xml中 我们使用and or not 来代替&& || !
3. 基于 Schema的Advice声明

  • Before Advice
public class SchemaBasedAspect {
private final Log logger = LogFactory.getLog(SchemaBasedAspect.class);
public void doBefore(JoinPoint jp)
{
if(logger.isInfoEnabled())
logger.info("before method("+jp.getSignature(),getName()+"l execution.");
}
}


<aop:config>
    <aop:aspect id="myAspect" ref= *shemaBasedAspect * order="2">
        <aop:pointcut id= "privatePointcut"
        expression="execution(public void *.doSth())"/>
        <aop:before pointcut-ref="privatePointcut" method="doBefore"/>
    </aop:aspect>
</aop:config>
<bean id="shemaBasedAspect" class="...SchemaBasedAspect"></bean>

可以使用methhod 属性指定具体的Before Advice对应的方法名称

  • After Returning Advice
public class SchemaBasedAspect {
private final Log logger = LogFactory.getLog(SchemaBasedAspect.class);
    public void doBefore(JoinPoint jp)
    {
        if(logger.isInfoEnabled())
        logger.info("before method("+jp.getSignature(),getName()+"l execution.");
    }
    public void doAfterReturning{JoinPoint jp)
    {
        if(logger.isInfoEnabled())
        logger.info("method t"+jp.getSignature().getName()+
        "] completed successfully.")
    }
}


<aop:config>
    <aop:aspect id="myAspect" ref="shemaBasedAspect" order="2">
        <aop:pointcut id="privatePointcut"
        expressions="execution(public void *.doSth())"/>
    <aop:before pointcut-ref="privatePointcut" method="doBefore"/>
    <aop:after-returning pointcut-ref="privatePointcut" method="doAfterReturning"/>
    </aop:aspect>
</aop:config>

可以通过returning 属性指定返回参数名称 并在After Returning Advie对应的方法定义的参数列表中增加返回值的声明  该属性值需要与方法声明中的参数名称相同


<aop:after-returning pointcut-ref=*privatePointcut" methods"doAfterReturning"
returning="retvalue"/>


public void doAfterReturning{JoinPoint jp,Object retValue)
    {
        if(logger.isInfoEnabled())
        logger.info("method t"+jp.getSignature().getName()+
        "] completed successfully.")
    }


  • After Throwing Advice
public class SchemaBasedAspect{
    public void doAfterReturning{JoinPoint jp)
    {
        if(logger.isInfoEnabled())
            logger.info("method t"+jp.getSignature().getName()+
            "] completed successfully.")
    }
    public void doAfterThrowing(RuntImeException e)
    {
        logger.error(ExceptionUtila.getFullStackTrace(e));
    }
}

<aop:config>
    <aop:aspect id="myAspect" ref= *shemaBasedAspect * order="2">
        <aop:pointcut id= "privatePointcut"
        expression="execution(public void *.doSth())"/>
        <aop:before pointcut-ref="privatePointcut" method="doBefore"/>
        <aop:after-returning pointcut-ref="privatePointcut" method="doAfterReturning"/>
        <aop:after-throwing pointcut-ref="privatePointcut" 
            method="doAfterThrowing" throwings="e"/>
    </aop:aspect>
</aop:config>
<bean id="shemaBasedAspect" class="...SchemaBasedAspect"></bean>

throwing指定的值要与方法定义的参数名称相同

  • After(Finally) Advice
public class SchemaBasedAspect{
    public void doAfterReturning{JoinPoint jp)
    {
        if(logger.isInfoEnabled())
            logger.info("method t"+jp.getSignature().getName()+
            "] completed successfully.")
    }
    public void doAfterThrowing(RuntImeException e)
    {
        logger.error(ExceptionUtila.getFullStackTrace(e));
    }
    public void doAfter()
    {
        logger.warn("release system resources ,etc.");
    }
}


<aop:config>
    <aop:aspect id="myAspect" ref= *shemaBasedAspect * order="2">
        <aop:pointcut id= "privatePointcut"
        expression="execution(public void *.doSth())"/>
        <aop:before pointcut-ref="privatePointcut" method="doBefore"/>
        <aop:after-returning pointcut-ref="privatePointcut" method="doAfterReturning"/>
        <aop:after-throwing pointcut-ref="privatePointcut" 
            method="doAfterThrowing" throwings="e"/>
        <aop:after pointcut-ref "privatePointcut" method="doAfter"/>
    </aop:aspect>
</aop:config>
<bean id="shemaBasedAspect" class="...SchemaBasedAspect"></bean>


  • Around Advice
    Around Advice的第一个参数必须是org.aspectj.lang.ProceedingJoinPoint类型 并且必须指定
public class SchemaBasedAspect{
    public void doAfterReturning{JoinPoint jp)
    {
        if(logger.isInfoEnabled())
            logger.info("method t"+jp.getSignature().getName()+
            "] completed successfully.")
    }
    public void doAfterThrowing(RuntImeException e)
    {
        logger.error(ExceptionUtila.getFullStackTrace(e));
    }
    public void doAfter()
    {
        logger.warn("release system resources ,etc.");
    }
    public Object doProfile(ProceedingJoinPoint pjp) throws Throwable
    {
        Stopwatch watch = new Stopwatch();
        try
        {
        watch.start();
        return pjp.proceed();
        }
        finally
        {
            watch.stop();
            if(logger.islnfoEnabled())
            {
                logger.info(watch);
            }
        }
    }
}


<aop:around pointcut-ref=> *'privatePointcut" method-“doProfile”/> 进行配置

  • Introduction
    根据Introduction需要 实现新的接口以及该接口的实现类 然后基于Schema的配置文件将其注册到容器中
<aop:declare-parents types-matching="cn.spring21.unveilspring.target.*"
implement-interfaces="cn.spring21.unvei1spring.introduction.ICounter"
default-impl="cn.spring21.unveilspring.introduction.CounterXmpl"/>


  • types-matching 用于指定要对哪些目标对象进行Introduction的织入
  • implement-interface 指定 新增加的Introduction行为的接口定义类型
  • default-impl 指定新增加的Introduction行为的接口定义的默认实现类
    所有cn.spring21.unveilspring. target包下的目标对象将被添加到ICouter接口定义的行为
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值