AspectJ简介

image.png
#AspectJ本身是一个单独的框架,spring发现它非常好,就把它融入到spring体系中了

案例下载:git clone https://gitee.com/chenxiaonian/spring_aspect.git

注解开发

环境准备

pom.xml

  1. <dependencies>
  2. <!-- 引入Spring的基本开发包 -->
  3. <dependency>
  4. <groupId>org.springframework</groupId>
  5. <artifactId>spring-core</artifactId>
  6. <version>4.2.4.RELEASE</version>
  7. </dependency>
  8. <dependency>
  9. <groupId>org.springframework</groupId>
  10. <artifactId>spring-context</artifactId>
  11. <version>4.2.4.RELEASE</version>
  12. </dependency>
  13. <dependency>
  14. <groupId>org.springframework</groupId>
  15. <artifactId>spring-beans</artifactId>
  16. <version>4.2.4.RELEASE</version>
  17. </dependency>
  18. <dependency>
  19. <groupId>org.springframework</groupId>
  20. <artifactId>spring-expression</artifactId>
  21. <version>4.2.4.RELEASE</version>
  22. </dependency>
  23. <dependency>
  24. <groupId>junit</groupId>
  25. <artifactId>junit</artifactId>
  26. <version>4.12</version>
  27. <scope>test</scope>
  28. </dependency>
  29. <!-- 引入aop相关的包 -->
  30. <dependency>
  31. <groupId>aopalliance</groupId>
  32. <artifactId>aopalliance</artifactId>
  33. <version>1.0</version>
  34. </dependency>
  35. <dependency>
  36. <groupId>org.springframework</groupId>
  37. <artifactId>spring-aop</artifactId>
  38. <version>4.2.4.RELEASE</version>
  39. </dependency>
  40. <!-- 引入aspectj相关的包 -->
  41. <dependency>
  42. <groupId>org.aspectj</groupId>
  43. <artifactId>aspectjweaver</artifactId>
  44. <version>1.8.9</version>
  45. </dependency>
  46. <dependency>
  47. <groupId>org.springframework</groupId>
  48. <artifactId>spring-aspects</artifactId>
  49. <version>4.2.4.RELEASE</version>
  50. </dependency>
  51. <!-- 引入测试相关的包 -->
  52. <dependency>
  53. <groupId>org.springframework</groupId>
  54. <artifactId>spring-test</artifactId>
  55. <version>4.2.4.RELEASE</version>
  56. </dependency>
  57. </dependencies>

src/main/resources/applicationContext.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">

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

</beans>

通知类型

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

切入点表达式的定义

在通知中通过value属性定义切点
image.png

src/main/resources/applicationContext.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">

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

    <!-- 设置目标类 -->
    <bean id="productDao" class="com.song.aspectJ.demo1.ProductDao"/>

    <!-- 设置切面类 -->
    <bean class="com.song.aspectJ.demo1.MyAspectAnno"/>
</beans>

src/main/java/com/song/aspectJ/demo1/ProductDao.java

package com.song.aspectJ.demo1;

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

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

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

    public void findOne(){
        System.out.println("查询一个商品");
    }

    public void findAll(){
        System.out.println("查询所有商品");
    }
}

src/main/java/com/song/aspectJ/demo1/MyAspectAnno.java

package com.song.aspectJ.demo1;

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;

/**
 * 切面类
 */
@Aspect
public class MyAspectAnno {
    @Before(value = "execution(* com.song.aspectJ.demo1.ProductDao.save(..))")
    public void before(){
        System.out.println("前置通知 ==========");
    }
}

src/test/java/com/song/aspectJ/demo1/Demo1Testor.java

package com.song.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 Demo1Testor {

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

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

运行结果:

前置通知 ==========
保存商品
修改商品
删除商品
查询一个商品
查询所有商品

前置通知

@Before
可以在方法中传入JoinPoint对象,用来获得切点信息

src/main/java/com/song/aspectJ/demo1/MyAspectAnno.java

package com.song.aspectJ.demo1;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;

/**
 * 切面类
 */
@Aspect
public class MyAspectAnno {
    @Before(value = "execution(* com.song.aspectJ.demo1.ProductDao.save(..))")
    public void before(JoinPoint joinPoint){
        System.out.println("前置通知 ==========" + joinPoint);
    }
}

显示内容:
前置通知 ==========execution(void com.song.aspectJ.demo1.ProductDao.save())

后置通知

@AfterReturning
通过returning属性 可以定义方法返回值,作为参数

src/main/java/com/song/aspectJ/demo1/MyAspectAnno.java

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

src/main/java/com/song/aspectJ/demo1/ProductDao.java

package com.song.aspectJ.demo1;

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

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

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

    public void findOne(){
        System.out.println("查询一个商品");
    }

    public void findAll(){
        System.out.println("查询所有商品");
    }
}

运行结果:

前置通知 ==========execution(void com.song.aspectJ.demo1.ProductDao.save())
保存商品
修改商品
后置通知 ==========update
删除商品
查询一个商品
查询所有商品

环绕通知

Around

  • around方法的返回值就是目标代理方法执行返回值
  • 参数为ProceedingJoinPoint 可以调用拦截目标方法执行

src/main/java/com/song/aspectJ/demo1/MyAspectAnno.java

    @Around(value = "execution(* com.song.aspectJ.demo1.ProductDao.delete(..))")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
        System.out.println("环绕前通知=========");
        Object object = joinPoint.proceed();    // 执行目标方法
        System.out.println("环绕后通知=========");

        return object;
    }

如果不写joinPoint.proceed(),目标方法是不会执行的

src/test/java/com/song/aspectJ/demo1/Demo1Testor.java

package com.song.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 Demo1Testor {

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

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

运行结果:

前置通知 ==========execution(void com.song.aspectJ.demo1.ProductDao.save())
保存商品
修改商品
后置通知 ==========update
环绕前通知=========
删除商品
环绕后通知=========
查询一个商品
查询所有商品

异常抛出通知

AfterThrowing
通过设置throwing属性,可以设置发生异常对象参数

src/main/java/com/song/aspectJ/demo1/MyAspectAnno.java

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

比如findOne执行有异常,执行结果:

前置通知 ==========execution(void com.song.aspectJ.demo1.ProductDao.save())
保存商品
修改商品
后置通知 ==========update
环绕前通知=========
删除商品
环绕后通知=========
查询一个商品
异常抛出通知==========/ by zero

最终通知

@After
无论是否出现异常,最终通知都是会被执行的

src/main/java/com/song/aspectJ/demo1/MyAspectAnno.java


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

执行结果:

前置通知 ==========execution(void com.song.aspectJ.demo1.ProductDao.save())
保存商品
修改商品
后置通知 ==========update
环绕前通知=========
删除商品
环绕后通知=========
查询一个商品
查询所有商品
最终通知==========

切点命名

  • 在每个通知内定义切点,会造成工作量大,不易维护,对于重复的切点,可以使用@Pointcut进行定义
  • 切点方法:private void 无参数方法,方法名为切点名
  • 当通知多个切点时,可以使用 || 进行连接

以下修改前后的效果是一样的

[修改前]src/main/java/com/song/aspectJ/demo1/MyAspectAnno.java

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

[修改后]src/main/java/com/song/aspectJ/demo1/MyAspectAnno.java

    @Before(value = "myPointcut1()")
    public void before(JoinPoint joinPoint){
        System.out.println("前置通知 ==========" + joinPoint);
    }

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

执行结果:

前置通知 ==========execution(void com.song.aspectJ.demo1.ProductDao.save())
保存商品
修改商品
后置通知 ==========update
环绕前通知=========
删除商品
环绕后通知=========
查询一个商品
查询所有商品
最终通知==========

XML开发

src/main/java/com/song/aspectJ/demo2/CustomerDAO.java

package com.song.aspectJ.demo2;

public interface CustomerDAO {
    public void save();
    public String update();
    public void delete();
    public void findOne();
    public void findAll();
}

src/main/java/com/song/aspectJ/demo2/CustomerDAOImpl.java

package com.song.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("查询一个客户");
    }

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

src/main/java/com/song/aspectJ/demo2/MyAspectXML.java

package com.song.aspectJ.demo2;

import org.aspectj.lang.ProceedingJoinPoint;

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

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

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

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

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

src/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="customerDao" class="com.song.aspectJ.demo2.CustomerDaoImpl"/>

    <!-- 配置切面类 -->
    <bean id="myAspectXML" class="com.song.aspectJ.demo2.MyAspectXML"/>

    <!-- aop的相关配置 -->
    <aop:config>
        <!-- 配置切入点 -->
        <aop:pointcut id="pointcut1" expression="execution(* com.song.aspectJ.demo2.CustomerDAO.save(..))"/>
        <aop:pointcut id="pointcut2" expression="execution(* com.song.aspectJ.demo2.CustomerDAO.update(..))"/>
        <aop:pointcut id="pointcut3" expression="execution(* com.song.aspectJ.demo2.CustomerDAO.delete(..))"/>
        <aop:pointcut id="pointcut4" expression="execution(* com.song.aspectJ.demo2.CustomerDAO.findOne(..))"/>
        <aop:pointcut id="pointcut5" expression="execution(* com.song.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"/>
            <!-- 最终通知 -->
            <aop:after method="after" pointcut-ref="pointcut5"/>
        </aop:aspect>
    </aop:config>
</beans>

src/test/java/com/song/aspectJ/demo2/Demo2Testor.java

package com.song.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("classpath:applicationContext2.xml")
public class Demo2Testor {

    @Resource(name = "customerDao")
    private CustomerDAO customerDAO;

    @Test
    public void demo(){
        customerDAO.save();
        customerDAO.update();
        customerDAO.delete();
        customerDAO.findOne();
        customerDAO.findAll();
    }
}

运行结果:

XML方式的前置通知==========
保存客户
修改客户
XML方式的后置通知==========spring
XML方式的环绕前通知==========
删除客户
XML方式的环绕后通知==========
查询一个客户
查询所有客户
XML方式的最终通知==========

注解开发

  • 优点:开发便捷
  • 缺点:在维护过程中,需要修改代码

XML开发

  • 优点:配置集中,在维护过程中,不需要修改代码
  • 缺点:开发没有注解方式便捷