使用AspectJ开发AOP更加便捷,你不知道嘛

前文 中,已经讲解了Spring传统的AOP开发,但在实际开发中,我们都是使用AspectJ进行AOP开发。


AspectJ 简介

  • AspectJ 是一个基于Java语言的独立的AOP框架。

  • 在没有AspectJ 之前,Spring在进行AOP开发的时候,都是使用 前文 中讲过的传统的AOP开发方式,而有了AspectJ 之后,就使得Spring的AOP开发变的简单灵活,大大提高了程序的开发效率 和 后期的维护效率

  • Spring 2.0 后,新增了对AspectJ 切点表达式支持

  • AspectJ 1.5 后,新增了@AspectJ 注解开发功能,通过JDK5 技术,可以直接在Bean类中定义切面

  • 新版本Spring框架,建议使用灵活简单的AspectJ 方式 取代 繁杂的Spring传统AOP开发方式



Spring 使用AspectJ进行AOP开发所需jar包:

  • 4个spring基本jar包

    • spring-core-4.3.22.RELEASE.jar

    • spring-beans-4.3.22.RELEASE.jar

    • spring-expression-4.3.22.RELEASE.jar

    • spring-context-4.3.22.RELEASE.jar

  • spring aop相关jar包

    • spring-aop-4.3.22.RELEASE.jar
    • aopalliance-1.0.jar ,aop联盟
  • aspectj 相关jar包

    • aspectjweaver-1.9.2.jar
  • spring整合aspectj 相关jar包

    • spring-aspects-4.3.22.RELEASE.jar

当然,为了方便测试,你可以添加 junit-4.12.jarspring-test-4.3.22.RELEASE.jar



AspectJ 定义切点的方式

前文介绍的Spring传统AOP开发中,我们是通过pattern正则表达式 或 默认切点方式来定义切点;而在AspectJ中,则是通过execution函数来定义切入点。

通过execution函数构建的切入点表达式: cd4356
例如:

匹配com.cd4356.spring_aspectj.annotation包及其子孙包下,的任意类,的任意方法,参数可以是任意参数

  • execution(* com.cd4356.spring_aspectj.annotation..*.*(..))

匹配指定包下所有类方法

  • execution(* com.cd4356.spring_aspectj.annotation.*.*(..))

匹配指定UserService类的方法

  • execution(* com.cd4356.spring_aspectj.annotation.UserService.*(..))

匹配指定UserService类的save方法

  • execution(* com.cd4356.spring_aspectj.annotation.UserService.save(..))

匹配指定UserService类的以save开头的方法

  • execution(* com.cd4356.spring_aspectj.annotation.UserService.save*(..))

匹配所有以save开头的方法

  • execution(* save*(..))

匹配所有不以save开头的方法

  • !execution(* save*(..))


Spring使用AspectJ实现AOP的两种方式

  • 注解方式

  • XML方式


cd4356

@AspectJ 提供的通知类型

  • @Before(前置通知),相当于BeforeAdvice

  • @AfterReturning(后置通知),相当于AfterReturningAdvice,拦截的目标方法异常,该通知不起作用,对应的方法不会执行

  • @Around(环绕通知),相当于MethodIntercepter

  • @AfterThrowing(异常抛出通知),相当于ThrowAdvice,拦截的目标方法无异常,该通知不起作用

  • @After(最终通知),不管是否发生异常,该通知都会执行,相当于 finally 代码块

通知执行的现后次序
cd4356


需求:对save方法进行前置通知、对delete方法进行后置通知、对update方法进行环绕通知、对find方法进行异常抛出通知、对findAll方法进行最终通知

定义目标类

package com.cd4356.spring_aspectj.annotation;

import org.springframework.stereotype.Service;

@Service("userService")
public class UserService {

    public void save(){
        System.out.println("保存用户");
    }

    public String delete(){
        System.out.println("删除用户");
        return "我是返回值!";
    }

    public void update(){
        System.out.println("修改用户");
    }

    public void find(){
        System.out.println("查找用户");
    }

    public void findAll(){
        System.out.println("查找所有用户");
    }
}

定义切面类,别忘了在类上添加@Aspect注解
细看,精华都在该切面类中了,内含注释说明

package com.cd4356.spring_aspectj.annotation;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;

@Component
@Aspect
public class AspectAnno {

    /**
     * 前置通知
     * @param joinPoint , 通过在方法中传入的JoinPoint对象,用来获取切点(目标方法)信息
     */
    @Before(value = "execution(* com.cd4356.spring_aspectj.annotation.UserService.save(..))")
    public void before(JoinPoint joinPoint){
    	//打印切点所属类名
        System.out.println(joinPoint.getTarget().getClass().getSimpleName());
        //打印切点对应方法名
        System.out.println(joinPoint.getSignature().getName());
        //打印切点对应方法的第一个参数
        //System.out.println(joinPoint.getArgs()[0]);
        System.out.println(". . . 前置通知 . . . ");
    }

    /**
     * 后置通知
     * @param returning , 通过returning属性(也可以不定义该属性),可以获取目标方法的返回值,作为参数传入后置方法中
     */
    @AfterReturning(value = "execution(* com.cd4356.spring_aspectj.annotation.UserService.delete(..))",returning = "returning")
    public void afterReturning(Object returning){
        System.out.println(". . . 后置通知 . . . "+returning);
    }

    /**
     * 环绕通知
     * @param joinPoint , 参数为ProceedingJoinPoint,用来调用拦截的目标方法执行
     * @return , around方法的返回值,就是目标代理方法执行返回值
     * @throws Throwable , 目标方法有异常,则只执行环绕前通知,不执行环绕后通知
     */
    @Around(value = "execution(* com.cd4356.spring_aspectj.annotation.UserService.update(..))")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
        System.out.println(". . . 环绕前通知 . . . ");
        //注意,如果不调用ProceedingJoinPoint的proceed方法,那么目标方法就被拦截了,不会执行该目标方法
        Object obj = joinPoint.proceed();  //执行目标方法
        System.out.println(". . . 环绕后通知 . . . ");
        return obj;
    }

    /**
     * 异常抛出通知
     * @param e , 通过throwing属性,获取拦截目标方法的异常
     */
    @AfterThrowing(value = "execution(* com.cd4356.spring_aspectj.annotation.UserService.find(..))",throwing = "e")
    public void afterThrowing(Throwable e){
        System.out.println(". . . 异常抛出通知 . . . "+e.getMessage());
    }

    /**
     * 最终通知
     */
    @After(value = "execution(* com.cd4356.spring_aspectj.annotation.UserService.findAll(..))")
    public void after(){
        System.out.println(". . . 最终通知 . . . ");
    }
}

在resource目录下,新建spring目录,用来spring配置文件

<?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:aop="http://www.springframework.org/schema/aop"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">

    <!--开启AspectJ的注解开发,自动代理-->
    <aop:aspectj-autoproxy/>

    <!--开启全局扫描-->
    <context:component-scan base-package="com.cd4356.spring_aspectj.annotation"/>

</beans>

定义测试类

package com.cd4356.spring_aspectj.annotation;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

@RunWith(SpringJUnit4ClassRunner.class)
//加载spring配置文件
@ContextConfiguration("classpath:spring/applicationContext1.xml")
public class SpringDemo {

    @Autowired
    private UserService userService;

    @Test //使用了@Test注解的方法作用相当于main方法
    public void demo(){
        userService.save();
        userService.update();
        userService.delete();
        userService.find();
        userService.findAll();
    }
}

看一下运行结果
cd4356


              通过@Pointcut 为切点命名

前面已经介绍了AspectJ的通知类型,并通过案例进行了演示。
  回顾一下,前面案例中切面类的代码,需求较为简单,都是一个通知作用再一个方法(切点)中,这样看起来没有什么不足之处。
  但如果有多个类型的通知作用再同一个方法上,再使用前面案例中的这种方式就会显得很麻烦,而且维护量也会很大。
  针对这种情况,AspectJ提供了@Pointcut注解,为切点名,进行统一管理


@Pointcut切入点

  • 再每个通知内定义切点,会造成工作量大,不易维护,所以对于重复使用的切点,可以使用@Pointcut 进行定义

  • 切点方法格式:private void 切点名(){} ,( 因为切点方法仅在本切面类中使用,且不会有返回值,方法体中也不需要任何内容,切点方法名切点名一致方便阅读 )

  • 当通知多个切点时,可以使用||进行连接


下面通过案例来对@Pointcut进一步了解取前面案例,改变需求( 对save和delete方法使用,前置通知、后置通知和异常抛出通知 )提示,只修改切面类的代码


不使用@Pointcut的做法,直接在每通知内定义切点

package com.cd4356.spring_aspectj.annotation;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;

@Component
@Aspect
public class AspectAnno {

    @Before(value = "execution(* com.cd4356.spring_aspectj.annotation.UserService.save(..))"
            + "||execution(* com.cd4356.spring_aspectj.annotation.UserService.delete(..))")
    public void before(){
        System.out.println(". . . 前置通知 . . . ");
    }

    @AfterReturning(value = "execution(* com.cd4356.spring_aspectj.annotation.UserService.save(..))"
            + "||execution(* com.cd4356.spring_aspectj.annotation.UserService.delete(..))")
    public void afterReturning(){
        System.out.println(". . . 后置通知 . . . ");
    }

    @AfterThrowing(value = "execution(* com.cd4356.spring_aspectj.annotation.UserService.save(..))"
            + "||execution(* com.cd4356.spring_aspectj.annotation.UserService.delete(..))")
    public void afterThrowing(){
        System.out.println(". . . 异常抛出通知 . . . ");
    }
}

使用@Pointcut的做法,将每个切点独立出来统一管理

package com.cd4356.spring_aspectj.annotation;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;

@Component
@Aspect
public class AspectAnno {

    @Before(value = "save()||delete()")
    public void before(){
        System.out.println(". . . 前置通知 . . . ");
    }

    @AfterReturning(value = "save()||delete()")
    public void afterReturning(){
        System.out.println(". . . 后置通知 . . . ");
    }

    @AfterThrowing(value = "save()||delete()")
    public void afterThrowing(){
        System.out.println(". . . 异常抛出通知 . . . ");
    }

    @Pointcut(value = "execution(* com.cd4356.spring_aspectj.annotation.UserService.save(..))")
    private void save(){}

    @Pointcut(value = "execution(* com.cd4356.spring_aspectj.annotation.UserService.delete(..))")
    private void delete(){}
}

对比这两种方式,是不是发现使用@Pointcut,代码量降低了,而且看起开显得更加整洁,没有那么辣眼睛。


CD4356


需求:对save方法和update方法,进行前置通知和后置通知
该案例以实现了接口的类进行演示

package com.cd4356.spring_aspectj.xml;

public interface UserService {
    void save();
    void delete();
    void update();
    void find();
    void findAll();
}
package com.cd4356.spring_aspectj.xml;

public class UserServiceImpl implements UserService {

    public void save(){
        System.out.println("保存用户");
    }

    public void delete(){
        System.out.println("删除用户");
    }

    public void update(){
        System.out.println("修改用户");
    }

    public void find(){
        System.out.println("查找用户");
    }

    public void findAll(){
        System.out.println("查找所有用户");
    }
}

定义切面类,类中定义了5种通知类型的方法

package com.cd4356.spring_aspectj.xml;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;

public class AspectXml {

    /**
     * 前置通知
     * @param joinPoint , 通过在方法中传入的JoinPoint对象,用来获取切点(目标方法)信息
     */
    public void before(JoinPoint joinPoint){
        //打印切点所属类名
        System.out.println(joinPoint.getTarget().getClass().getSimpleName());
        //打印切点对应方法名
        System.out.println(joinPoint.getSignature().getName());
        //打印切点对应方法的第一个参数
        //System.out.println(joinPoint.getArgs()[0]);
        System.out.println(". . . 前置通知 . . . ");
    }

    /**
     * 后置通知
     * @param obj, 通过returning属性(也可以不定义该属性),可以获取目标方法的返回值,作为参数传入后置方法中
     */
    public void afterReturning(Object obj){
        System.out.println(". . . 后置通知 . . . "+obj);
    }

    /**
     * 环绕通知
     * @param joinPoint , 参数为ProceedingJoinPoint,用来调用拦截的目标方法执行
     * @return , around方法的返回值,就是目标代理方法执行返回值
     * @throws Throwable , 目标方法有异常,则只执行环绕前通知,不执行环绕后通知
     */
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
        System.out.println(". . . 环绕前通知 . . . ");
        Object obj = joinPoint.proceed();
        System.out.println(". . . 环绕后通知 . . . ");
        return obj;
    }

    /**
     * 异常抛出通知
     * @param e , 通过throwing属性,获取拦截目标方法的异常
     */
    public void afterThrowing(Throwable e){
        System.out.println(". . . 异常抛出通知 . . . "+e.getMessage());
    }

    /**
     * 最终通知
     */
    public void after(){
        System.out.println(". . . 最终通知 . . . ");
    }
}

AspectJ基于XML的方式重点了解 < aop:config > 标签

< aop:config >标签有三个子标签:

  • < aop:pointcut /> ,用来配置切入点
  • < aop:advisor /> ,用来配置切面(用来配置一个切入点和一个通知的切面组合
  • < aop:aspect /> , 用来配置切面(可以用来一个切入点和一个通知的切面组合,也可用来配置多个切入点和多个通知的切面组合),开发中一般使用该标签

事大数

< aop:aspect />标签中有与各种类型通知相对应的子标签

  • <aop:before /> ,用来配置前置通知
  • <aop:after-returning /> ,用来配置后置通知
  • <aop:around /> ,用来配置环绕置通知
  • <aop:after-throwing /> ,用来配置异常抛出通知
  • <aop:after /> ,用来配置最终通知
<?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: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/aop http://www.springframework.org/schema/aop/spring-aop.xsd">

    <!--配置目标类-->
    <bean id="userService" class="com.cd4356.spring_aspectj.xml.UserServiceImpl"/>

    <!--配置通知类-->
    <bean id="aspectXml" class="com.cd4356.spring_aspectj.xml.AspectXml"/>

    <!--配置aop完成增强-->
    <aop:config>
        <!--配置切入点: 哪些类的哪些方法需要增强-->
        <aop:pointcut id="save" expression="execution(* com.cd4356.spring_aspectj.xml.UserService.save(..))"/>
        <aop:pointcut id="update" expression="execution(* com.cd4356.spring_aspectj.xml.UserService.update(..))"/>
        <!--配置切面-->
        <aop:aspect ref="aspectXml">
            <!--配置前置通知-->
            <aop:before method="before" pointcut-ref="save"/>
            <!--配置后置通知-->
            <aop:after-returning method="afterReturning" pointcut-ref="save" returning="obj"/>
            <!--配置前置通知-->
            <aop:before method="before" pointcut-ref="update"/>
            <!--配置后置通知-->
            <aop:after-returning method="afterReturning" pointcut-ref="update" returning="obj"/>
        </aop:aspect>
    </aop:config>

</beans>
package com.cd4356.spring_aspectj.xml;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

@RunWith(SpringJUnit4ClassRunner.class)
//加载spring配置文件
@ContextConfiguration("classpath:spring/applicationContext2.xml")
public class SpringDemo {

    @Autowired
    private UserService userService;

    @Test //使用了@Test注解的方法作用相当于main方法
    public void demo(){
        userService.save();
        userService.update();
        userService.delete();
        userService.find();
        userService.findAll();
    }
}

运行效果:


前面案例我们是,对save方法和update方法,进行前置通知和后置通知。下面改变需求:对每个方法使用一种不同类型的增强(通知)

<?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: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/aop http://www.springframework.org/schema/aop/spring-aop.xsd">

    <!--配置目标类-->
    <bean id="userService" class="com.cd4356.spring_aspectj.xml.UserServiceImpl"/>

    <!--配置通知类-->
    <bean id="aspectXml" class="com.cd4356.spring_aspectj.xml.AspectXml"/>

    <!--配置aop完成增强-->
    <aop:config>
        <!--配置切入点: 哪些类的哪些方法需要增强-->
        <aop:pointcut id="save" expression="execution(* com.cd4356.spring_aspectj.xml.UserService.save(..))"/>
        <aop:pointcut id="update" expression="execution(* com.cd4356.spring_aspectj.xml.UserService.update(..))"/>
        <aop:pointcut id="delete" expression="execution(* com.cd4356.spring_aspectj.xml.UserService.delete(..))"/>
        <aop:pointcut id="find" expression="execution(* com.cd4356.spring_aspectj.xml.UserService.find(..))"/>
        <aop:pointcut id="findAll" expression="execution(* com.cd4356.spring_aspectj.xml.UserService.findAll(..))"/>
        <!--配置切面-->
        <aop:aspect ref="aspectXml">
            <!--对save方法使用前置增强-->
            <aop:before method="before" pointcut-ref="save"/>
            <!--对update方法使用后置增强,后置通知方法中若没有定义参数,则无需使用returning属性-->
            <aop:after-returning method="afterReturning" pointcut-ref="update" returning="obj"/>
            <!--对delete方法使用环绕增强-->
            <aop:around method="around" pointcut-ref="delete"/>
            <!--对find方法使用异常抛出增强-->
            <aop:after-throwing method="afterThrowing" pointcut-ref="find" throwing="e"/>
            <!--对findAll方法使用最终增强-->
            <aop:after method="after" pointcut-ref="findAll"/>
        </aop:aspect>
    </aop:config>

</beans>

运行效果:


案例源码:

  链接:https://pan.baidu.com/s/1MUJZyPG2aMp41rV-oFX3Uw
  提取码:wthk

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

家师曹先生

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值