此处省略三千字

博客为自己学习笔记或感悟,或者转载其他作者的文章!

AspectJ 学习笔记


       学习完AspectJ有段时间了,总体感觉这个编程语言简单易学、功能强大,但是搜到的关于AspectJ的基础内容比较少,so,笔者感觉有必要整理出这部分内容,若有不当之处,欢迎指正共同学习☺

内容:AspectJ概念,和AOP的区别,@AspectJ的配置,Advice的配置,Introduction的配置

      (源码:testSpring

                                          


1 AspectJ简介

    百科上这样解释它:AspectJ是一个面向切面的框架,它扩展了Java语言,定义了AOP语法,所以它有一个专门的编译器用来生成遵守Java字节编码规范的Class文件。

    简单的说,它是用另外一种方式实现的AOP,一句话解释:AspectJ是一种编译期的用注解形式实现的AOP。至于AOP策略的作用,要单独学习一下AOP基础,这里不再细说。

 

2 AOP/Spring AOP/AspectJ的区别


既然说AspectJ是一种AOP框架策略,前面又学习过Spring AOP,那么就说说三者的概念区别关系。

 

AOP:是一种面向切面的编程范式,是一种编程思想,旨在通过分离横切关注点,提高模块化,可以跨越对象关注点。Aop的典型应用即spring的事务机制,日志记录。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。主要功能是:日志记录,性能统计,安全控制,事务处理,异常处理等等;主要的意图是:将日志记录,性能统计,安全控制,事务处理,异常处理等代码从业务逻辑代码中划分出来,通过对这些行为的分离,我们希望可以将它们独立到非指导业务逻辑的方法中,进而改变这些行为的时候不影响业务逻辑的代码。

 

AspectJ和Spring AOP 是AOP的两种实现方案,这一点要搞清楚。

AspectJ:Aspectj是aop的java实现方案,AspectJ是一种编译期的用注解形式实现的AOP。

    (1)AspectJ是一个代码生成工具(Code Generator),其中AspectJ语法就是用来定义代码生成规则的语法。基于自己的语法编译工具,编译的结果是JavaClass文件,运行的时候classpath需要包含AspectJ的一个jar文件(Runtime lib),支持编译时织入切面,即所谓的CTW机制,可以通过一个Ant或Maven任务来完成这个操作。

    (2)AspectJ有自己的类装载器,支持在类装载时织入切面,即所谓的LTW机制。使用AspectJ LTW有两个主要步骤,第一,通过JVM的-javaagent参数设置LTW的织入器类包,以代理JVM默认的类加载器;第二,LTW织入器需要一个 aop.xml文件,在该文件中指定切面类和需要进行切面织入的目标类。

   (3)AspectJ同样也支持运行时织入,运行时织入是基于动态代理的机制。(默认机制)

Spring AOP

      Spring aop是aop实现方案的一种,它支持在运行期基于动态代理的方式将aspect织入目标代码中来实现aop。但是spring aop的切入点支持有限,而且对于static方法和final方法都无法支持aop(因为此类方法无法生成代理类);另外spring aop只支持对于ioc容器管理的bean,其他的普通java类无法支持aop。现在的spring整合了aspectj,在spring体系中可以使用aspectj语法来实现aop。

 

3 AspectJ的定义和实现

3.1@AspectJ的配置

    配置@AspectJ,是为了以便被Bean容器发现。对@AspectJ的支持可以使用XML文件配置或Java风格的配置,其中,@AspectJ注解风格类似于纯Java注解的普通Java类,

注解形式:     

       @Configuration
       @EnableAspectJAutoProxy
       Publicclass AppConfig{
       }

XML配置形式      

        <aop:aspectj-autoproxy/>

     Spring可以使用AspectJ来做切入点解析

     AOP的运行时仍旧是纯的Spring AOP,对AspectJ的编译器或者织入无依赖性,但是要使用AspectJ,必须确保aspectjweaver.jar库包含在应用程序的classpath中,并且必须确定其版本在1.6.8或者更高版本。

     有几点需要了解:

    (1)@Aspect切面使用@Aspect注解配置,拥有@Aspect的任何bean将被Spring自动识别并应用,可以拥有aop配置的一切特点。

    (2)用@Aspect注解的类可以有方法和字段,他们也可能包括切入点pointcut,通知Advice和引入Introduction声明。

    (3)@Aspect注解不能通过类路径被自动检测发现,需要配合使用@Component注释或在xml文件中配置对应的bean项。

两种实现是:

注解形式:

@Component
@Aspect
public class TerenceAspect {}

配置形式:

<bean id=”myAspect” class=”org.xyz.NotVeryUsefulAspect”>
       <!--configureproperties of aspect here as normal-->
</bean>

    需要注意的是,一个类的@Aspect注解只标识该类是一个切面,同时将自己从自动代理中排除出去了,其目的是为了避免出现死循环(例如一个包下包含了业务类和切面类,将自己从自动代理中排除出去,避免了自己代理自己的情况一直寻找自己的循环情况)。


3.2 Pointcut注解

(1)一般pointcut

    一个切入点通过一个普通的方法定义来提供,并且切入点表达式使用@Pointcut注解来声明,注解的方法返回类型必须为void型。

@Component
@Aspect
public class TerenceAspect {
    @Pointcut("execution(*com.terence.aop.aspectjbiz.*Biz.**(..))")
    public void pointcut(){}
   
    @Pointcut("within(com.terence.aop.aspectj.biz.*)")
    public void bizPointcut(){}
}

     上述pointcut切入点的注解:@Pointcut("execution(* com.terence.aop.aspectjbiz.*Biz.**(..))")表示包com.terence.aop.aspectjbiz下任何以Biz结尾的方法都将被执行。

     @Pointcut("within(com.terence.aop.aspectj.biz.*)")表示当前com.terence.aop.aspectj.biz包下的任何类都会匹配给这个方法。

 

(2)组合pointcut

   有时候根据实际情况,要建立复杂的切入点表达式,可以通过&&、||和!进行组合,也可以通过名字引用切入点表达式。

   @Pointcut("execution(public* (..))")
   private void anyPublicOperation(){}
  
   @Pointcut("within(com.xyz.someapp.trading..)")
   private void inTrading(){}
  
   @Pointcut("anyPublicOperation&& inTrading()")
   private void tradingOperation(){}

    上述tradingOperation()的组合表达式,表示同时执行@Pointcut("execution(public * (..))")和@Pointcut("within(com.xyz.someapp.trading..)")。

    虽然根据场景需要,建立复杂的切入点表达式,但是不建议使用过于复杂的表达式,尽量清晰明了简单,因为使用负责的组合表达式,当任一个切入点表达式修改的时候,都很容易造成一些关联性的问题。

 

(3) 如何定义一个良好的pointcut

    AspectJ本身就是一种编译期的AOP,检查代码并匹配连接点于切入点的代价是昂贵的,所以,定义切入点的时候,切入点表达式尽量清晰明了易读,那么,如何定义一个好的切入点呢?一个好的切入点应该包含以下几个方面:

      i.    选择特定类型的连接点:如:execution, get, set, call handler

      ii.   确定连接点范围,如within ,withincode

      iii.  匹配上下文信息,如:this,target,@annotation

      iv.  切入点表达式匹配类型参照下表:

 


3.3 Advice注解

 

1 Before Advice:@Before

2.After returning advice@AfterReturning,可在通知体内得到返回的实际值;
3.After throwing advice@AfterThrowing
4.After (finally) advice : @After 
  最终通知必须准备处理正常和异常两种返回情况,它通常用于释放资源。
5.Around advice : @Around
    环绕通知使用@Around注解来声明,通知方法的第一个参数必须是ProceedingJoinPoint类型,在通知内部调用ProceedingJoinPoint的Proceed()方法会导致执行真正的方法,传入一个Object[]对象,数组中的值将被作为一个参数传递给方法。

上述5种通知的例子:
第一,XML文件配置,添加自动扫描自动代理两项配置

<?xml version="1.0"encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:context="http://www.springframework.org/schema/context"
    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.xsd
       http://www.springframework.org/schema/context
       http://www.springframework.org/schema/context/spring-context.xsd
       http://www.springframework.org/schema/aop
       http://www.springframework.org/schema/aop/spring-aop.xsd"> 
        <context:component-scan base-package="com.terence.aop.aspectj"/>
        <aop:aspectj-autoproxy></aop:aspectj-autoproxy>
 </beans>

第二,业务类。

packagecom.terence.aop.aspectj.biz;
importorg.springframework.stereotype.Service;
/*
 * 业务类
 */
@Service
public class TerenceBiz {
   
    @TerenceMethod("TerenceBiz save with TerenceMethod")
    public Stringsave(String arg)
    {
        System.out.println("TerenceBiz save:"+arg);
        //throw newRuntimeException(" Save failed!");
        return"Save success!";
    }
}

第三,切面类

packagecom.terence.aop.aspectj;
importorg.aspectj.lang.ProceedingJoinPoint;
importorg.aspectj.lang.annotation.After;
importorg.aspectj.lang.annotation.AfterReturning;
importorg.aspectj.lang.annotation.AfterThrowing;
importorg.aspectj.lang.annotation.Around;
importorg.aspectj.lang.annotation.Aspect;
importorg.aspectj.lang.annotation.Before;
importorg.aspectj.lang.annotation.Pointcut;
importorg.springframework.stereotype.Component;
importcom.terence.aop.aspectj.biz.TerenceMethod;
/*
 * 切面类
 */
@Component
@Aspect
public class TerenceAspect {
    @Pointcut("execution(*com.terence.aop.aspectj.biz.*Biz.**(..))")
    public void pointcut(){}
    @Pointcut("within(com.terence.aop.aspectj.biz.*)")
    public void bizPointcut(){}
   
    //@Before("executuion(*com.terence.aop.aspectj.biz.*Biz.*(..))")
    @Before("pointcut()")
    public void before()
    {   System.out.println("Before");   }
   
    @AfterReturning(pointcut="bizPointcut()",returning="returnValue")
    public void afterReturning(Object returnValue)
    {   System.out.println("AfterReturning:"+returnValue);  }
   
    @AfterThrowing(pointcut="pointcut()",throwing="e")
    public void afterThrowing(RuntimeException e)
    {   System.out.println("AfterThrowing:"+e.getMessage());
        System.out.println("AfterThrowing:"+e);
    }
    @After("pointcut()")
    public void after()
    {   System.out.println("After");    }
 
    @Around("pointcut()")
    public Object around(ProceedingJoinPoint pjp) throws Throwable
    {
        System.out.println("Around 1");
        Objectobj=pjp.proceed();
        System.out.println("Around 2");
        return obj;
    }
   
    @Before("pointcut()&&args(arg)")
    public void beforeWithParam(String arg)
    {       System.out.println("Before Param:"+arg);    }
 
    @Before("pointcut()&&@annotation(terenceMethod)")
    public void beforeWithAnnotation(TerenceMethodterenceMethod)
    {System.out.println("beforeWithAnnotation:"+terenceMethod.value()); }
} 

切面类的通知advice

前置通知使用@Before("pointcut()"),表示继承了方法pointcut()的切入表达式:executuion(* com.terence.aop.aspectj.biz.*Biz.*(..))

返回通知使用@AfterReturning(pointcut="bizPointcut()",returning="returnValue"),表示继承了pointcut()方法的切入表达式,并接受了业务类执行方法的返回参数returnValue

异常抛出通知:@AfterThrowing(pointcut="pointcut()",throwing="e")和返回通知一样

后置通知使用@After("pointcut()")

环绕通知使用@Around("pointcut()")

最后的@Before("pointcut()&&@annotation(terenceMethod)"),是下面所说的Advice传参的自定义注解的形式,自定义的注解为:

package com.terence.aop.aspectj.biz;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/*
 * 定义注解TerenceMethod
 */
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interfaceTerenceMethod {
    String value();
}

        通过@Before("pointcut()&&@annotation(terenceMethod)")的声明,首先集成pointcut()方法的切入表达式,然后用@annotation(terenceMethod)引入自定义的注解,和前面继承的表达式组合起来。

 

第四,测试类。

package com.terence.test.aop;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.BlockJUnit4ClassRunner;
import com.terence.aop.aspectj.biz.TerenceBiz;
import com.terence.test.base.UnitTestBase;
@RunWith(BlockJUnit4ClassRunner.class)
public class TestAspectJ extends UnitTestBase {
    publicTestAspectJ()
    {
        super("classpath:spring-aop-aspectj.xml");
    }
   
    @Test
    public voidtest()
    {
        TerenceBizbiz=getBean("terenceBiz");
        biz.save("thisis test");
    }
}

 

第五,执行结果

信息: JSR-330 'javax.inject.Inject' annotation found and supported forautowiring

Around 1

Before

beforeWithAnnotation:TerenceBizsave with TerenceMethod

BeforeParam:this is test

TerenceBizsave:this is test

Around 2

After

AfterReturning:Savesuccess!

二月 19, 2017 2:18:02下午 org.springframework.context.support.AbstractApplicationContextdoClose

信息: Closingorg.springframework.context.support.ClassPathXmlApplicationContext@175390b7:startup date [Sun Feb 19 14:18:01 CST 2017]; root of context hierarchy 


6. Advice扩展

Advice传参 

方法1:一般形式


方法2:分离形式


方法3:自定义注解


Advice的参数及泛型

Spring AOP可以处理泛型类的声明和使用方法的参数:

public interface Sample<T>
{
    void sampleGenericMethod(T param);
    void sampleGenericCollectionMethod(Collection<T> param);
}
@Before("execution(*..Sample+.sampleGenericMethod(*))&& args(param)")
public void beforeSampleMethod(Mytypeparam)
{
    //Advice implementation;
}
@Before("execution(*..Sample+.sampleGenericCollectionMethod(*))&& args(param)")
public void beforeSampleMethod(Collection<Mytype>param)
{
    //Advice implementation;
}

Advice参数名称

通知和切入点注解有一个额外的argNames属性,他可以用来指定所注解的方法的参数名。

  

 @Before(value="com.terence.lib.Pointcuts.anyPublicMehtod()&&target(bean)&&@annotation(auditable)",argNames="bean,auditable")
    public void audit(Object bean,Auditableauditable)
    {
        Auditable code=auditable.value();
        //..use code and  bean
    }

   

   

如果第一个参数是JointPoitn,ProceedingJoinPoint,JoinPoint.StaticPart,那么可以忽略它。

@Before(value="com.terence.lib.Pointcuts.anyPublicMehtod()&&target(bean)&&@annotation(auditable)",argNames="bean,auditable")
    public void audit(JoinPoint jp,Objectbean,Auditable auditable)
    {
        Auditable code=auditable.value();
        //..use code, bean and jp
    }

 

3.4 Introductions

   允许一个切面声明一个通知对象实现指定接口,并且提供了一个接口实现类来代表这些对象,Introduction使用@DeclareParents进行注解,这个注解用来定义匹配的类型有一个新的parent.

例如:给定一个接口UseageTracked,并且该接口拥有DefaultUsageTracked的实现,接下来的切面声明了所有的service接口的实现都实现了UseageTracked接口,使用方式和基于配置的AOP是一样的,区别仅仅是此处用注解声明。 

@Aspect
public class TerenceUsageTracking {
    @DeclareParents(value="com.terence.aop.aspectj.*+",defaultImpl=DefaultUsageTracked.class)
    public static UsageTracked mixin;
    @Before("com.terence.aop.aspectj.biz.businessService()&&this(usageTracked)")
    public void recordUsage(UsageTrackedusageTracked)
    {
        usageTracked.incrementUseCount();
    }
} 

另一个点:切面实例化模型

切面实例化模型是个高级的主题,“perthis”切面通过制定@Aspect注解perthis子句实现

每个独立的service对象执行时都会创建一个切面实例

Service对象的每个方法在第一次执行的时候创建切面实例,切面在service对象失效的同时也失效。

@Aspect("perthis(com.terence.aop.SystemArchitecture.businessService())")
public class MyAspect
{
    private int someState; 
    @Before(com.terence.aop.SystemArchitecture.businessService())
    public void recordingServiceUseage()
    {
        //..
    }
}


阅读更多
版权声明:本文为博主原创文章,未经博主允许请随便转载 https://blog.csdn.net/CSDN_Terence/article/details/55804421
文章标签: AspectJ AOP Spring AOP
个人分类: 【SSM】
相关热词: aspectj aspectj库
上一篇Spring AOP基础
下一篇Spring事务管理
想对作者说点什么? 我来说一句

没有更多推荐了,返回首页

关闭
关闭