Spring的AOP详解

AOP(Aspect-Oriented Programming)学前知识点

定义AOP术语

Spring对AOP的支持

使用注解创建切面代码

使用XML创建切面代码


学前知识点:

  • 什么是横切关注点:横切关注点可以描述为影响多个业务模块的功能。
  • 切面来源:横切关注点可以被模块为切面(aspect)。
  • 将横切关注点模块化为切面的好处:1.使横切关注点的代码集中在一个地方。2.使每个业务模块只包含它们需要实现的主要功能,次要功能被转移到切面中。
  • AOP功能:AOP有助于横切关注点与它们所影响的对象之间的解耦。
  • AOP用法过程:横切关注点从多个类中抽取出来模块化成一个切面后,再将它的方法影响到这多个类执行的过程中。
  • AOP使用到的设计模式:代理模式。
  • 因为Spring基于动态代理,所以Spring只支持方法级别的连接点。
  • Spring的通知是Java编写的
  • 运行期,在ApplicationContext下的BeanFactory中加载所有bean,为有@Aspect注解的bean创建一个代理,这个代理会围绕着所有该切面的切点所匹配的bean,当有方法调用时,代理类拦截方法调用,先执行切面逻辑,再调用目标bean的方法。

定义AOP术语:

  • 通知(Adivce):就是在目标对象方法的调用前后调用通知,为对象增加新的行为。

  Spring切面的5种类型的通知:

    1. 前置通知(Before):在目标方法被调用之前调用通知功能。
    2. 后置通知(After):在目标方法完成之后调用通知,此时不会关心方法的输出是什么。
    3. 返回通知(After-returning):在目标方法成功执行之后调用通知。
    4. 异常通知(After-throwing):在目标方法抛出异常后调用通知。
    5. 环绕通知(Around):通知包裹了被通知的方法,在被通知的方法调用之前和调用之后执行自定义的行为。
  • 连接点(Join point):应用执行时能插入切面的点(Spring中类的各种方法都是连接点)。
  • 切点(Point cut):缩小切面通知的连接点范围。用切点表达式匹配到切面要插入的1个或多个连接点。

    

  • 切面(Aspect):切面是通知和切点的结合。是横切关注点模块化出来的一个类。
  • 引入(Introduction):引入允许我们向现有的类添加新方法或属性。
  • 织入(Weaving):切面在指定连接点被织入到目标对象中。织入是把切面应用到目标对象并创建新的代理的过程。  

    切面织入到类的三个时期:

    1. 编译期:在目标编译时织入切面。需要如AspectJ一样的特殊编译器。
    2. 类加载期:在目标类加载到JVM时被织入。这种方式需要特殊的类加载器,对字节码添加新的行为。
    3. 运行期:切面在应用运行的某个时刻被织入。在织入时,Spring AOP容器会为目标对象动态的创建一个代理对象。

 


Spring对AOP的支持:

  • 基于代理的经典Spring AOP:编程模型很复杂很笨重。
  • 纯POJO切面:运用的XML配置将POJO转化为切面。
  • @AspectJ注解驱动的切面:Spring借鉴了AspectJ的切面,用注解将POJO转化为切面。
  • 注入式AspectJ切面:需要拦截构造器或属性的时候。

使用注解创建切面代码:

  需要加入的依赖:

<!-- https://mvnrepository.com/artifact/org.aspectj/aspectjweaver -->
    <dependency>
      <groupId>org.aspectj</groupId>
      <artifactId>aspectjweaver</artifactId>
      <version>1.8.13</version>
    </dependency>
    <!-- https://mvnrepository.com/artifact/org.springframework/spring-test -->
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-test</artifactId>
      <version>4.3.14.RELEASE</version>
    </dependency>
    <!-- https://mvnrepository.com/artifact/org.springframework/spring-context -->
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-context</artifactId>
      <version>4.3.14.RELEASE</version>
    </dependency>
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>4.11</version>
      <scope>test</scope>
    </dependency>
    <dependency>
        <groupId>org.aspectj</groupId>
        <artifactId>aspectjrt</artifactId>
        <version>1.8.10</version>
    </dependency>
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>RELEASE</version>
    </dependency>

  表演接口:

public interface Performance { // 表演接口
    void perform();
}

  音乐剧类实现表演接口:

public class Musical implements Performance {// 音乐剧类
    @Override
    public void perform() {
        System.out.println("开始演奏——啦啦啦啦——演奏完毕");
    }
}

  观众切面:

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

// 定义一个切面
@Aspect
public class Audience {// 观众类

    @Pointcut("execution(* com.qsh.concert.Musical.perform(..))")// 将perform()方法定义为切点,并依附于perform()这个空方法上
    public void perform() {// 用作切点依附的空方法
    }

    @Before("perform()")// 前置通知
    public void closePhone() {// 关闭手机
        System.out.println("Before—Turn off the phone");
    }

    @Before("perform()")// 前置通知
    public void takeSeat() {// 请坐
        System.out.println("Before—Take a seat");
    }

    @After("perform()")// 后置通知
    public void standUp() {// 站起来
        System.out.println("After—stand up");
    }

    @AfterReturning("perform()")// 返回通知
    public void handclap() {// 鼓掌
        System.out.println("AfterReturning—CLAP~CLAP~CLAP!");
    }

    @AfterThrowing("perform()")// 异常通知
    public void demandRefund() {// 要求退款
        System.out.println("AfterThrowing—Demand a refund");
    }

    @Around("perform()")// 环绕通知
    public void doEverything(ProceedingJoinPoint jp) {// 实现上面所有通知
        try {
            System.out.println("Around—Turn off the phone");
            System.out.println("Around—Take a seat");
            jp.proceed();
            System.out.println("Around—stand up");
            System.out.println("Around—CLAP~CLAP~CLAP!");
        } catch (Throwable e) {
            System.out.println("Around—Demand a refund");
        }
    }
}

  配置类:

import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;

@Configuration
@EnableAspectJAutoProxy //AspectJ自动代理会为使用了@Aspect注解的bean创建一个代理
public class ConcertConfig {
    @Bean
    public Audience audience() {
        return new Audience();
    }

    @Bean
    public Musical musical() {
        return new Musical();
    }
}

  测试方法:

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)
@ContextConfiguration(classes = ConcertConfig.class)
public class test {
    @Autowired
    private Performance performance;

    @Test
    public void test() {
        performance.perform();
    }
}

  测试结果:

注:可以看出环绕通知先开始先结束,后置通知在返回通知前先调用。


使用XML创建切面代码:

   表演接口:

public interface Performance { // 表演接口
    void perform();
}

  音乐剧类实现表演接口:

public class Musical implements Performance {// 音乐剧类
    @Override
    public void perform() {
        System.out.println("开始演奏——啦啦啦啦——演奏完毕");
    }
}

  观众类(无注解,一会用XML将这个类声明为切面):

import org.aspectj.lang.ProceedingJoinPoint;

public class Audience {// 观众类

    public void closePhone() {// 关闭手机
        System.out.println("Before—Turn off the phone");
    }

    public void takeSeat() {// 请坐
        System.out.println("Before—Take a seat");
    }

    public void standUp() {// 站起来
        System.out.println("After—stand up");
    }

    public void handclap() {// 鼓掌
        System.out.println("AfterReturning—CLAP~CLAP~CLAP!");
    }

    public void demandRefund() {// 要求退款
        System.out.println("AfterThrowing—Demand a refund");
    }

    public void doEverything(ProceedingJoinPoint jp) {// 实现上面所有通知
        try {
            System.out.println("Around—Turn off the phone");
            System.out.println("Around—Take a seat");
            jp.proceed();// 调用被通知的方法
            System.out.println("Around—stand up");
            System.out.println("Around—CLAP~CLAP~CLAP!");
        } catch (Throwable e) {
            System.out.println("Around—Demand a refund");
        }
    }
}

  XML配置:

<?xml version="1.0" encoding="utf-8" ?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       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-3.2.xsd">

    <!--将观众类装配为bean-->
    <bean id="audience" class="com.qsh.concert.Audience"/>
    <!--将音乐会类装配为bean-->
    <bean id="musical" class="com.qsh.concert.Musical"/>
    <!--将观众类声明为切面-->
    <aop:config>
        <aop:aspect ref="audience">
            <aop:pointcut id="perform" expression="execution(* com.qsh.concert.Performance.perform(..))"/>
            <aop:before pointcut-ref="perform" method="closePhone"/>
            <aop:before pointcut-ref="perform" method="takeSeat"/>
            <aop:after pointcut-ref="perform" method="standUp"/>
            <aop:after-returning pointcut-ref="perform" method="handclap"/>
            <aop:after-throwing pointcut-ref="perform" method="demandRefund"/>
            <aop:around pointcut-ref="perform" method="doEverything"/>
        </aop:aspect>
    </aop:config>
</beans>

  测试类:

import org.springframework.context.support.ClassPathXmlApplicationContext;

public class test {
    public static void main(String[] args) {
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("concertconfig.xml");
        Performance performance = context.getBean(Performance.class);
        performance.perform();
    }
}
原文地址:https://www.cnblogs.com/JimKing/p/8847439.html