Spring第四章:基于AspectJ的AOP开发

该系列为imooc Spring从入门到进阶笔记,跟随课程加入自己见解,同时也为项目中碰到一些问题做了解答

1、AspectJ简介

  • AspectJ是一个基于Java语言的AOP框架
  • Spring2.0以后新增了对AspectJ切点表达式支持
  • @AspectJ 是AspectJ1.5新增功能,通过JDK5注解技术,允许直接在Bean类中定义切面
  • 新版本Spring框架,建议使用AspectJ方式来开发AOP
  • 使用AspectJ 需要导入Spring AOP和 AspectJ相关jar包
    • spring-aop-4.2.4.RELEASE.jar
    • com.springsource.org.aopalliance-1.0.0.jar
    • spring-aspects-4.2.4.RELEASE.jar
    • com.springsource.org.aspectj.weaver-1.6.8.RELEASE.jar

2、AspectJ的注解开发AOP

2.1、开发环境准备

在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"
       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">
    <!-- 开启AspectJ自动代理-->
    <aop:aspectj-autoproxy />
</beans>

2.2、AspectJ提供不同的通知类型

  • @Before 前置通知,相当于BeforeAdvice
  • @AfterReturning 后置通知,相当于AfterReturningAdvice
  • @Around 环绕通知,相当于MethodInterceptor
  • @AfterThrowing异常抛出通知,相当于ThrowAdvice
  • @After 最终final通知,不管是否异常,该通知都会执行
  • @DeclareParents 引介通知,相当于IntroductionInterceptor (不要求掌握)

2.3、切入点表达式的定义

通过execution函数,可以定义切点的方法切入

  • 语法:

    • execution(<访问修饰符>?<返回类型><方法名>(<参数>)<异常>)
  • 例如

    • 匹配所有类public方法
      (第一个*表示任意返回值类型,第二个*表示任意名称,..表示任意参数)
      execution(public * *(..))

    • 匹配指定包下所有类方法
      execution(* com.imooc.dao.*(..)) //不包含子包
      execution(* com.imooc.dao..*(..)) ..*//表示包、子孙包下所有类

    • 匹配指定类所有方法
      execution(* com.imooc.service.UserService.*(..))

    • 匹配实现特定接口所有类方法
      execution(* com.imooc.dao.GenericDAO+.*(..))

    • 匹配所有save开头的方法
      execution(* save*(..))

注意:以上表达式第一个*后有空格

2.4、入门案例

演示实现前置通知案例

  1. 使用idea Maven创建Web项目
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
  2. 配置项目pom文件
<dependencies>
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>4.12</version>
    </dependency>

    <!--引入Spring的基本开发包-->
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-core</artifactId>
      <version>4.2.4.RELEASE</version>
    </dependency>

    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-context</artifactId>
      <version>4.2.4.RELEASE</version>
    </dependency>

    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-beans</artifactId>
      <version>4.2.4.RELEASE</version>
    </dependency>

    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-expression</artifactId>
      <version>4.2.4.RELEASE</version>
    </dependency>

    <dependency>
      <groupId>aopalliance</groupId>
      <artifactId>aopalliance</artifactId>
      <version>1.0</version>
    </dependency>

    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-aop</artifactId>
      <version>4.2.4.RELEASE</version>
    </dependency>

    <dependency>
      <groupId>org.aspectj</groupId>
      <artifactId>aspectjweaver</artifactId>
      <version>1.8.9</version>
    </dependency>

    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-aspects</artifactId>
      <version>4.2.4.RELEASE</version>
    </dependency>

    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-test</artifactId>
      <version>4.2.4.RELEASE</version>
    </dependency>

  </dependencies>
  1. 在java文件夹下>新建com.imooc.aspectJ.demo1包>新建ProductDao类
package com.imooc.aspectJ.demo1;

public class ProductDao {
    public void save(){
        System.out.println("保存商品...");
    }

    public String update(){
        System.out.println("修改商品...");
        return "hello";
    }

    public void delete(){
        System.out.println("删除商品...");
    }

    public void findOne(){
        System.out.println("查询一个商品...");
        //int i = 1/0;
    }

    public void findAll(){
        System.out.println("查询所有商品...");
        //int j = 1/0;
    }
}

  1. 在java文件夹下>com.imooc.aspectJ.demo1包>新建MyAspectAnno类(切面类)

针对ProductDao 的save()方法做前置通知
定义切面

@Aspect
public class MyAspectAnno {
}
package com.imooc.aspectJ.demo1;

import org.aspectj.lang.annotation.*;

/**
 * 切面类
 */
@Aspect//定义切面
public class MyAspectAnno {

    @Before(value="execution(* com.imooc.aspectJ.demo1.ProductDao.save(..))")
    public void before(){
        System.out.println("前置通知==================");
    }


}

  1. 在resources下>新建applicationContext.xml(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"
       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">
    <!-- 开启AspectJ自动代理-->
    <aop:aspectj-autoproxy />

    <!--定义被代理的目标类-->
    <bean id="productDao" class="com.imooc.aspectJ.demo1.ProductDao"/>

    <!--定义切面-->
    <bean class="com.imooc.aspectJ.demo1.MyAspectAnno"/>

</beans>
  1. 在java文件夹下>com.imooc.aspectJ.demo1包>新建SpringDemo1 类(测试类)
package com.imooc.aspectJ.demo1;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

import javax.annotation.Resource;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class SpringDemo1 {

    @Resource(name = "productDao")
    private ProductDao productDao;

    @Test
    public void demo1(){
        productDao.save();
        productDao.update();
        productDao.delete();
        productDao.findAll();
        productDao.findOne();
    }
}

启动测试
在这里插入图片描述
可见使用AspectJ的注解方式实现前置通知案例成功

2.5、通知类型

2.5.1、@Before 前置通知

可以在方法中传入JoinPoint对象,用来获得切点信息
修改MyAspectAnno类before方法

	//需要增强的代码
 	@Before(value="execution(* com.imooc.aspectJ.demo1.ProductDao.save(..))")
    public void before(JoinPoint joinPoint){
        System.out.println("前置通知=================="+joinPoint);
    }

启动测试
在这里插入图片描述
可见已经获取到了切点的信息

2.5.2、@AfterReturing 后置通知

通过returning属性 可以获得 被代理的目标类的 切入点方法的返回值,作为参数

在MyAspectAnno类中新增afterReturing方法

    @AfterReturning(value = "execution(* com.imooc.aspectJ.demo1.ProductDao.update(..))",returning = "result")
    public void afterReturing(Object result){
        System.out.println("后置通知=================="+result);

启动测试
在这里插入图片描述
可见已经对update方法进行了后置通知,并获取到了该方法的返回值

2.5.3、@Around 环绕通知

  • around方法的返回值就是被代理的目标的 切入点方法的返回值
  • 调用ProceedingJoinPoint的proceed方法,被代理的目标类的切入点方法才会执行

在MyAspectAnno类中新增around方法

    @Around(value = "execution(* com.imooc.aspectJ.demo1.ProductDao.delete(..))")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
        System.out.println("环绕前通知==================");
        Object obj=joinPoint.proceed();
        System.out.println("环绕后通知==================");
        return obj;
    }

启动测试在这里插入图片描述
可见已经对delete方法进行了环绕通知

2.5.4、@AfterThrowing 异常抛出通知

  • 通过设置throwing属性,可以获取被代理的目标类 切入点方法发生异常时异常对象参数

在MyAspectAnno类中新增afterThrowing方法

    @AfterThrowing(value ="execution(* com.imooc.aspectJ.demo1.ProductDao.findOne(..))",throwing = "e")
    public void afterThrowing(Throwable e){
        System.out.println("异常抛出通知=================="+e.getMessage());
    }

修改Product类中的findOne方法 ,制造异常

    public void findOne(){
        System.out.println("查询一个商品...");
        int i = 1/0;
    }

启动测试代码
在这里插入图片描述
可见已经对findOne方法进行了异常抛出通知

2.5.5、@After 最终通知

无论被代理的目标类 切入点方法是否出现异常,最终通知总是会被执行的

在MyAspectAnno类中新增after方法

    @After(value = "execution(* com.imooc.aspectJ.demo1.ProductDao.findAll(..))")
    public void after(){
        System.out.println("最终通知==================");
    }

启动测试
在这里插入图片描述
可见已经对findAll方法进行了最终通知

2.6、通过@Pointcut为切点命名

在每个通知内定义切点,会造成工作量大,不易维护,对于重复的切点,可以使用@Pointcut进行定义来定义一个方法,该方法无任何实际操作,无参数无返回值,方法名可自定义,然后在@Pointcut注解中通过execution表达式(匹配被代理类中的方法)来配置value属性

定义切点方法的方式:

@Pointcut(value="execution(表达式内容)")
private void 名称自定义(){}

当多个切点使用一个通知时可有以下三种方式

  1. 为多个切点都配置切点方法,然后 在通知的注解value属性中使用||为切点方法名进行链接(最实用)
  2. 将多个切点定义在一个切点方法中,在@Pointcut注解value属性中使用||为execution表达式进行链接,多个切点共用一个方法名
  3. 直接看代码,觉得不太实用
@Pointcut("execution(* com.savage.aop.MessageSender.*(..))")
private void logSender(){}

@Pointcut("execution(* com.savage.aop.MessageReceiver.*(..))")
private void logReceiver(){}

@Pointcut("logSender() || logReceiver()")
private void logMessage(){}

代码演示
在MyAspectAnno类中定义切点方法

package com.imooc.aspectJ.demo1;

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

/**
 * 切面类
 */
@Aspect
public class MyAspectAnno {

    //演示多个切点使用同一个增强的情况
    @Before(value="myPointcut2()||myPointcut1()")
    public void before(JoinPoint joinPoint){
        System.out.println("前置通知=================="+joinPoint);
    }


    @AfterReturning(value = "myPointcut2()",returning = "result")
    public void afterReturing(Object result){
        System.out.println("后置通知=================="+result);
    }



    @Around(value = "myPointcut3()")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
        System.out.println("环绕前通知==================");
        Object obj=joinPoint.proceed();
        System.out.println("环绕后通知==================");
        return obj;
    }

    @AfterThrowing(value ="myPointcut4()",throwing = "e")
    public void afterThrowing(Throwable e){
        System.out.println("异常抛出通知=================="+e.getMessage());
    }

    @After(value = "myPointcut5()")
    public void after(){
        System.out.println("最终通知==================");
    }


    @Pointcut(value="execution(* com.imooc.aspectJ.demo1.ProductDao.save(..))")
    private void myPointcut1(){}

    @Pointcut(value="execution(* com.imooc.aspectJ.demo1.ProductDao.update(..))")
    private void myPointcut2(){}

    @Pointcut(value="execution(* com.imooc.aspectJ.demo1.ProductDao.delete(..))")
    private void myPointcut3(){}

    @Pointcut(value="execution(* com.imooc.aspectJ.demo1.ProductDao.findOne(..))")
    private void myPointcut4(){}

    @Pointcut(value="execution(* com.imooc.aspectJ.demo1.ProductDao.findAll(..))")
    private void myPointcut5(){}

}

启动测试
在这里插入图片描述
可见通过@Pointcut注解来注解方法定义切点的方式成功

3、AspectJ的XML方式开发AOP

直接看代码
在理解注解方式的基础上可通过以下案例理解AspectJ的XML方式开发AOP

  1. 引入的依赖与注解方式相同
  2. 在java文件夹下>新建com.imooc.aspectJ.demo2包>新建CustomerDao类(接口)
package com.imooc.aspectJ.demo2;

public interface CustomerDao {
    public void save();

    public String update();

    public void delete();

    public void findOne();

    public void findAll();
}

  1. 在java文件夹下>com.imooc.aspectJ.demo2包>新建CustomerDao类(接口实现)
package com.imooc.aspectJ.demo2;

public class CustomerDaoImpl implements CustomerDao{
    @Override
    public void save() {
        System.out.println("保存客户...");
    }

    @Override
    public String update() {
        System.out.println("修改客户...");
        return "Spring";
    }

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

    @Override
    public void findOne() {
        System.out.println("查询一个客户...");
        //int a=1/0;
    }

    @Override
    public void findAll() {
        System.out.println("查询所有客户...");
    }
}

  1. 在java文件夹下>com.imooc.aspectJ.demo2包>新建MyAspectXml类(通知类)
package com.imooc.aspectJ.demo2;

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

public class MyAspectXml {

    // 前置通知
    public void before(JoinPoint joinPoint){
        System.out.println("XML方式的前置通知=============="+joinPoint);
    }

    // 后置通知
    public void afterReturing(Object result){
        System.out.println("XML方式的后置通知=============="+result);
    }

    // 环绕通知
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
        System.out.println("XML方式的环绕前通知==============");
        Object obj = joinPoint.proceed();
        System.out.println("XML方式的环绕后通知==============");
        return obj;
    }

    // 异常抛出通知
    public void afterThrowing(Throwable e){
        System.out.println("XML方式的异常抛出通知============="+e.getMessage());
    }

    // 最终通知
    public void after(){
        System.out.println("XML方式的最终通知=================");
    }
}

  1. 在resources下新建applicationContext2.xml
<?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">
    <!--XML的配置方式来完成AOP的开发===================-->
    <!--配置被代理的目标类-->
    <bean id="customerDaoImpl" class="com.imooc.aspectJ.demo2.CustomerDaoImpl"/>

    <!--配置切面类-->
    <bean id="myAspectXml" class="com.imooc.aspectJ.demo2.MyAspectXml"/>

    <!--植入的相关配置-->
    <aop:config>
        <!--配置切入点-->
        <aop:pointcut id="pointcut1" expression="execution(* com.imooc.aspectJ.demo2.CustomerDao.save(..))"/>
        <aop:pointcut id="pointcut2" expression="execution(* com.imooc.aspectJ.demo2.CustomerDao.update(..))"/>
        <aop:pointcut id="pointcut3" expression="execution(* com.imooc.aspectJ.demo2.CustomerDao.delete(..))"/>
        <aop:pointcut id="pointcut4" expression="execution(* com.imooc.aspectJ.demo2.CustomerDao.findOne(..))"/>
        <aop:pointcut id="pointcut5" expression="execution(* com.imooc.aspectJ.demo2.CustomerDao.findAll(..))"/>
        <!--配置AOP的切面-->
        <aop:aspect ref="myAspectXml">
            <!--配置前置通知-->
            <aop:before method="before" pointcut-ref="pointcut1"/>
            <!--配置后置通知-->
            <aop:after-returning method="afterReturing" pointcut-ref="pointcut2" returning="result"/>
            <!--配置环绕通知-->
            <aop:around method="around" pointcut-ref="pointcut3"/>
            <!--配置异常抛出通知-->
            <aop:after-throwing method="afterThrowing" pointcut-ref="pointcut4" throwing="e"/>
            <!--配置最终通知-->
            <aop:after method="after" pointcut-ref="pointcut5"/>
        </aop:aspect>

    </aop:config>


</beans>
  1. 在java文件夹下>com.imooc.aspectJ.demo2包>新建SpringDemo2 类(测试类)
package com.imooc.aspectJ.demo2;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

import javax.annotation.Resource;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(value="classpath:applicationContext2.xml")
public class SpringDemo2 {

    @Resource(name="customerDaoImpl")
    private CustomerDao customerDao;

    @Test
    public void demo1(){
        customerDao.save();
        customerDao.update();
        customerDao.delete();
        customerDao.findOne();
        customerDao.findAll();
    }
}

启动代码
在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

翘脚猴子耍把戏

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

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

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

打赏作者

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

抵扣说明:

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

余额充值