Spring核心技术详解(五)

AspectJ详解

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

Spring2.0之后增加了对AspectJ切点表达式的支持。@AspectJ是AspectJ1.5新增的功能,通过JDK的注解技术,允许开发者在Bean上直接通过注解定义切面。Spring使用和@AspectJ相同风格的注解,并通过AspectJ提供的注解库和解析库来处理切点。



1、AspectJ切点表达式


@AspectJ支持三种通配符
  • *匹配任意字符,只匹配一个元素

  • ..匹配任意字符,可以匹配多个元素 ,在表示类时,必须和*联合使用

  • +表示按照类型匹配指定类的所有类,必须跟在类名后面,如com.cad.Car+,表示继承该类的所有子类包括本身


逻辑运算符

切点表达式由切点函数组成,切点函数之间还可以进行逻辑运算,组成复合切点。

  • && 与操作符:相当于切点的交集运算。xml配置文件中使用切点表达式,&是特殊字符,所以需要转义字符&来表示。

  • || 或操作符:相当于切点的并集运算。

  • 非操作符:相当于切点的反集运算。


表达式函数

Spring支持9个@AspectJ切点表达式函数,它们用不同的方式描述目标类的连接点。我们来了解几个常用的:

  • execution()

execution()是最常用的切点函数,用来匹配方法,语法如下

execution(<修饰符><返回类型><包.类.方法(参数)><异常>) 

修饰符和异常可以省略。

execution(public * *(..)):匹配目标类的所有public方法,第一个*代表返回类型,第二个*代表方法名,..代表方法的参数。  

execution(**User(..)):匹配目标类所有以User为后缀的方法。第一个*代表返回类型,*User代表以User为后缀的方法 

execution(* com.cad.demo.User.*(..)):匹配User类里的所有方法 

execution(* com.cad.demo.User+.*(..)):匹配该类的子类包括该类的所有方法 

execution(* com.cad.*.*(..)):匹配com.cad包下的所有类的所有方法   

execution(* com.cad..*.*(..)):匹配com.cad包下、子孙包下所有类的所有方法  

execution(* addUser(Spring,int)):匹配addUser方法,且第一个参数类型是String,第二个是int
  • args()

该函数接受一个类名,表示目标类方法参数是指定类时(包含子类),则匹配切点。

args(com.cad.User):匹配addUser(User user)方法等
  • within()

匹配类,语法:within(<类>)

within(com.cad.User):匹配User类下的所有方法
  • target()

target()函数通过判断目标类是否按类型匹配指定类决定连接点是否匹配。

target(com.cad.User):如果目标类类型是User没那么目标类所有方法都匹配切点。
  • this()

this()函数判断代理对象的类是否按类型匹配指定类。



2、AspectJ增强类型

  • Before:前置增强。相当于BeforeAdvice的功能,方法执行前执行。

  • AfterReturning:后置增强。相当于AfterReturningAdvice,方法执行后执行。

  • Around:环绕增强。

  • AfterThrowing:异常抛出增强。

  • After:不管是抛出异常还是正常退出,该增强都会执行,类似于finally块。

  • DeclareParents:引介增强。



3、AspectJ基于XML配置切面

使用AspectJ我们需要的jar包有aopalliance-1.0.jaraspectjweaver-1.8.10.jarspring-aop-4.3.8.RELEASE.jarspring-aspects-4.3.8.RELEASE.jar

我们先来一个简单的配置,看看怎么使用:

    //我们先创建Dog类
    public class Dog {
        public void eat(){
            System.out.println("狗在吃饭");
        }

        public void run(){
            System.out.println("狗在跑步");
        }
    }
    //我们创建一个增强类 
    public class AdviceMethod {
        public void before(){
            System.out.println("主人发出命令");
        }
    }
    <!--配置文件-->
    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xmlns:context="http://www.springframework.org/schema/context"   
        xmlns:aop="http://www.springframework.org/schema/aop"     //声明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            //引入aop xsd文件http://www.springframework.org/schema/aop/spring-aop.xsd">

    <!-实例Dog类和增强类-->
    <bean id="dog" class="com.cad.aspectj.Dog"></bean>
    <bean id="advices" class="com.cad.aspectj.AdviceMethod"></bean>

    <!-配置aop,proxy-target-class属性设定为true时,使用CGLib,为false时,使用JDK动态代理-->
    <aop:config proxy-target-class="true"> 
        <!--使用<aop:aspect>标签定义切面,ref引入增强-->
        <aop:aspect ref="advices">
            <!--通过<aop:before>声明一个前置增强,pointcut属性使用切点表达式,method指定使用增强类中方法-->
            <aop:before pointcut="execution(* *(..))"   method="before" />
        </aop:aspect>
    </aop:config>

    </beans>    
    //我们测试一下
    public class TestDemo {
        @Test
        public void test(){
            ApplicationContext ac=new ClassPathXmlApplicationContext("bean.xml");
            Dog dog=(Dog)ac.getBean("dog");
            dog.eat();
            dog.run();
        }
    }

运行结果:
这里写图片描述


配置切点

我们上面的代码中,直接在< aop: before>前置增强标签里使用了表达式来声明切点。
我们还可以在外面配置一个切点,使用的时候直接引用即可。

<aop:config proxy-target-class="true">
    <aop:aspect ref="advices"> 
        //定义一个切点
        <aop:pointcut expression="execution(* *(..))" id="mypointcut"/>
        //直接通过id引用即可
        <aop:before pointcut-ref="mypointcut"   method="before" />
    </aop:aspect>
</aop:config> 

    <aop:pointcut>元素如果位于 <aop:aspect>元素之中,则只能被当前<aop:aspect>中的元素访问到。为了能被整个 <aop:config>元素中定义的所有切面访问到,必须在<aop:config>下定义。

后置增强
    //我们在我们的增强类里添加一个后置增强方法after
    public class AdviceMethod {
        public void before(){
            System.out.println("主人发出命令");
        }

        public void after(){
            System.out.println("主任给予奖励");

        }
    }
<bean id="dog" class="com.cad.aspectj.Dog"></bean>
<bean id="advices" class="com.cad.aspectj.AdviceMethod"></bean> 

<aop:config proxy-target-class="true">
    <aop:aspect ref="advices">
        <aop:pointcut expression="execution(* *(..))" id="mypointcut"/>
        <aop:before pointcut-ref="mypointcut"   method="before" /> 
        //配置后置增强
        <aop:after-returning pointcut-ref="mypointcut" method="after"/>
    </aop:aspect>
</aop:config>

这里写图片描述
< aop:after-returning >后置增强有一个returning属性,该属性对应后置方法里的参数值,并且必须与方法里的参数值名称相同,该参数用来接收目标方法执行后的返回值。

//我们run()方法返回一个String字符串 
public class Dog {
    public void eat(){
        System.out.println("狗在吃饭");
    }

    public String run(){
        System.out.println("狗在跑步");
        return "跑完了";
    }
}
//增强类的后置方法接收目标方法返回的参数
public class AdviceMethod {
    public void before(){
        System.out.println("主人发出命令");
    }

    public void after(String arg){
        System.out.println("主任给予奖励");
        System.out.println(arg);

    }
}
//配置文件中配置,别的和前面的没区别
<aop:after-returning pointcut-ref="mypointcut" method="after" returning="arg"/>

运行结果:
这里写图片描述


环绕增强
    //定义环绕增强方法,参数为ProceedingJoinPoint,返回值为Object,这是连接点信息,后面会详解
    public class AdviceMethod {

        public Object around(ProceedingJoinPoint pjp) throws Throwable{
            System.out.println("环绕增强前");
            Object obj=pjp.proceed();
            System.out.println("环绕增强后");
            return obj;
        }


    }
    <!--配置环绕增强-->

    <aop:config proxy-target-class="true">
        <aop:aspect ref="advices">
            <aop:pointcut expression="execution(* *(..))" id="mypointcut"/>
            <aop:around method="around" pointcut-ref="mypointcut"/>
        </aop:aspect>
    </aop:config>
    //我们测试一下
    public class TestDemo {
        @Test
        public void test(){
            ApplicationContext ac=new ClassPathXmlApplicationContext("bean.xml");
            Dog dog=(Dog)ac.getBean("dog");
            dog.eat();
        }
    }

运行结果:
这里写图片描述

其他三个配置起来都大同小异,这里就不再一一演示。



4、增强方法访问连接点信息

AspectJ使用JoinPoint接口表示目标类的连接点对象,如果是环绕增强访问的话,则必须使用ProceedingJoinPoint表示连接点对象,该类是JoinPoint子接口

JoinPoint接口主要方法
  • Object [] getArgs():获取连接点方法的参数

  • Signature getSignatrue():获取连接点的方法签名对象,方法签名由方法名称和形参列表组成。

  • Object getTarget():获取连接点所在的目标对象

  • Object getThis():获取代理对象本身

ProceedingJoinPoint主要方法
  • Object proceed()throws Throwable:通过反射执行目标对象的连接点方法

  • Object proceed(Object[] args)throws Throwable:通过反射执行目标对象的连接点方法,使用我们提供的参数。

这样,我们就可以在我们的增强方法中使用JoinPoint或者ProceedingJoinPoint参数,来获得连接点方法的一些信息。



5、AspectJ基于注解配置切面

    //先使用注解来实例我们的Bean
    @Component("dog")
    public class Dog {
        public void eat(){
            System.out.println("狗在吃饭");
        }

        public void run(){
            System.out.println("狗在跑步");
        }
    }
    //实例增强类
    @Component("advices") 
    //使用Aspect
    @Aspect
    public class AdviceMethod { 
        //使用环绕增强,里面参数是切点表达式
        @Around("execution(* com.cad.anno.*.*(..))")
        public Object around(ProceedingJoinPoint pjp) throws Throwable{
            System.out.println("环绕增强前");
            Object obj=pjp.proceed();
            System.out.println("环绕增强后");
            return obj;
        }
    }
    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xmlns:context="http://www.springframework.org/schema/context"  
        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.cad.anno"></context:component-scan> 
    //使用aspectj自动代理,自动为匹配@AspectJ切面的Bean创建代理
    <aop:aspectj-autoproxy></aop:aspectj-autoproxy>

    </beans>    
    //我们测试一下
    public class TestDemo {
        @Test
        public void test(){
            ApplicationContext ac=new ClassPathXmlApplicationContext("bean.xml");
            Dog dog=(Dog)ac.getBean("dog");

            dog.eat();
        }
    }

运行结果:
这里写图片描述
增强类中的方法可以使用不同的增强类型来定义。

  • @Before:前置增强
  • @AfterReturning:后置增强
  • @Around:环绕增强
  • @AfterThrowing:抛出异常增强
  • @After:Final增强
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值