Spring 用法学习总结(三)之 AOP

Spring学习

  • 7 bean的生命周期
  • 8 AOP面向切面编程
    • 8.1 AOP相关术语
    • 8.2 AOP使用


7 bean的生命周期

bean的生命周期主要为bean实例化、bean属性赋值、bean初始化、销毁bean,其中在实例化和初始化前后都使用后置处理器方法,而InstantiationAwareBeanPostProcessor 继承了BeanPostProcessor
可以看下这篇博客大致了解一下:
一文读懂 Spring Bean 的生命周期
Spring 用法学习总结(三)之 AOP_第1张图片
bean的作用域

  • 单例(Singleton):在整个应用中,只创建bean的一个实例
  • 原型(Prototype):每次注入或者通过Spring的应用上下文获取的时候,都会创建一个新的bean实例
  • 会话(Session):在Web应用中,为每个会话创建一个bean实例
  • 请求(Rquest):在Web应用中,为每个请求创建一个bean实例

8 AOP面向切面编程

AOP(Aspect Oriented Programming)面向切面编程,利用 AOP 可以使得业务模块只需要关注核心业务的处理,不用关心其他的(如安全管理)。可以不通过修改源代码的方式,在主干功能里面添加新功能。

使用AOP相关的包:百度网盘
Spring 用法学习总结(三)之 AOP_第2张图片

8.1 AOP相关术语

通知(Advice)
通知定义了何时使用切面,有以下五种类型的通知

  • 前置通知(Before):在目标方法被调用之前调用通知功能
  • 后置通知(After):在目标方法完成之后调用通知,此时不会关心方法的输出是什么
  • 返回通知(After-returning):在目标方法成功执行之后调用通知
  • 异常通知(After-throwing):在目标方法抛出异常后调用通知
  • 环绕通知(Around):通知包裹了被通知的方法,在被通知的方法调用之前和调用之后执行自定义的行为

连接点(Join point)

连接点是在应用执行过程中能够插入切面的一个点。这个点可以是调用方法时、抛出异常时、甚至修改一个字段时。切面代码可以利用这些点插入到应用的正常流程之中,并添加新的行为。

切点(Pointcut)

切点定义了何处使用切面。会匹配通知(Advice)所要织入的一个或多个连接点。我们通常使用
明确的类和方法名称,或利用正则表达式定义所匹配的类和方法名称来指定这些切点。

切面(Aspect)

切面是通知和切点的结合。通知和切点共同定义了切面的全部内容——它是什么,在何时和何处完成其功能。

引入(Introduction)

引入允许我们向现有的类添加新方法或属性。

目标对象(Target)

Target是织入 Advice 的目标对象

织入(Weaving)

织入就是把通知(Advice)添加到目标对象具体的连接点上的过程

Spring 用法学习总结(三)之 AOP_第3张图片

8.2 AOP使用

切入点表达式语法结构: execution([权限修饰符] [返回类型] [类全路径] [方法名称]([参数列表]) ),此外还可以对切入点进行限制,因为“&”在XML中有特殊含义,所以在Spring的XML配置里面描述切点时,我们可以使用and来代替“&&”。同样,or和not可以分别用来代替“||”和“!”

Spring 用法学习总结(三)之 AOP_第4张图片
在这里插入图片描述

基于注解

创建配置类ConfigAop

package springstudy2;

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

//创建ConfigAop配置类
@Configuration //@Configuration标记类作为配置类替换xml配置文件
@EnableAspectJAutoProxy(proxyTargetClass = true) //启用自动代理
@ComponentScan(basePackages = {"springstudy2"}) //开启注解扫描
public class ConfigAop {
}

@Configuration
@EnableAspectJAutoProxy(proxyTargetClass = true)
@ComponentScan(basePackages = {“springstudy2”})

三个注解相当于以下代码


<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:jdbc="http://www.springframework.org/schema/jdbc"  
	   xmlns:jee="http://www.springframework.org/schema/jee" xmlns:tx="http://www.springframework.org/schema/tx"
       xmlns:util="http://www.springframework.org/schema/util" xmlns:task="http://www.springframework.org/schema/task" xsi:schemaLocation="
       http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
       http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd
       http://www.springframework.org/schema/jdbc http://www.springframework.org/schema/jdbc/spring-jdbc-4.0.xsd
       http://www.springframework.org/schema/jee http://www.springframework.org/schema/jee/spring-jee-4.0.xsd
       http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.0.xsd
       http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-4.0.xsd
       http://www.springframework.org/schema/task http://www.springframework.org/schema/task/spring-task-4.0.xsd" default-lazy-init="false">

	
    <context:component-scan base-package="springstudy2">context:component-scan>

    
    <aop:aspectj-autoproxy>aop:aspectj-autoproxy>
beans>

创建User类

package springstudy2;

import org.springframework.stereotype.Component;


//创建User对象
@Component
public class User {
    public void add() {
        //int a = 10 / 0;
        System.out.println("调用add方法...");
    }

    public void test() {
        System.out.println("调用test方法...");
    }
}

创建代理 UserProxy

如果有多个代理,可以通过@Order注解指定优先级,数字越小,优先级越高

package springstudy2;

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

//创建UserProxy对象
//增强的类
@Component
@Aspect //生成代理对象
@Order(1)//代理优先级,数字越小,优先级越高
public class UserProxy {

    //相同切入点抽取
    @Pointcut(value = "execution(* springstudy2.User.add(..))")
    public void pointdemo() { //pointdemo()方法的内容并不重要,在这里它实际上应该是空的
    }


    //前置通知
    //@Before 注解表示作为前置通知
    @Before(value = "pointdemo()")
    public void before() {
        System.out.println("在目标方法被调用之前调用通知功能.........");
    }
    @Before(value = "execution(* springstudy2.User.test(..))")
    public void before1() {
        System.out.println("test 在目标方法被调用之前调用通知功能.........");
    }
    //后置通知
    @After(value = "execution(* springstudy2.User.add(..))")
    public void after() {
        System.out.println("在目标方法完成之后调用通知.........");
    }
    //返回通知
    @AfterReturning(value = "pointdemo()")
    public void afterReturning() {
        System.out.println("在目标方法成功执行之后调用通知.........");
    }
    //异常通知
    @AfterThrowing(value = "execution(* springstudy2.User.add(..))")
    public void afterThrowing() {
        System.out.println("在目标方法抛出异常后调用通知.........");
    }
    //环绕通知
    @Around(value = "execution(* springstudy2.User.add(..))")
    public void around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        System.out.println("环绕之前,在被通知的方法调用之前.........");
        //被增强的方法执行
        proceedingJoinPoint.proceed();
        System.out.println("环绕之后,在被通知的方法调用之后.........");
    }
}

上述代码中,@afterReturning 和 @after 注解的执行顺序存在版本问题(有争议),据说从Spirng 5.2.7那一版开始,通知注解的执行顺序如下(不知道后面改没改。。。我的Spring5.3.9是这样):

  1. @Around注解方法的前半部分业务逻辑
  2. @Before注解方法的业务逻辑
  3. 目标方法的业务逻辑
  4. @AfterThrowing(若目标方法有异常,执行@AfterThrowing注解方法的业务逻辑)
  5. @AfterReturning(若目标方法无异常,执行@AfterReturning注解方法的业务逻辑)
  6. @After(不管目标方法有无异常,都会执行@After注解方法的业务逻辑)
  7. @Around注解方法的后半部分业务逻辑(@Around注解方法内的业务逻辑若对ProceedingJoinPoint.proceed()方法没做捕获异常处理,直接向上抛出异常,则不会执行Around注解方法的后半部分业务逻辑;若做了异常捕获处理,则会执行)

创建 PersonProxy 代理

package springstudy2;

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

//创建PersonProxy对象
//增强的类
@Component
@Aspect //生成代理对象
@Order(2)//代理优先级,数字越小,优先级越高
public class PersonProxy {
    @Before(value = "execution(* springstudy2.User.add(..))")
    public void before() {
        System.out.println("person 在目标方法被调用之前调用通知功能.........");
    }
}

测试类Test

package springstudy2;

import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class Test {
    public static void main(String[] args) {
        ApplicationContext context = new AnnotationConfigApplicationContext(ConfigAop.class);//ConfigAop为配置类
        User user = context.getBean("user", User.class);
        user.add();
        user.test();
    }
}

执行结果
Spring 用法学习总结(三)之 AOP_第5张图片

基于XML

在src目录下创建bean3.xml文件


<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">

    
    <bean id="user" class="springstudy2.User">bean>
    <bean id="userproxy" class="springstudy2.UserProxy">bean>
    <bean id="personproxy" class="springstudy2.PersonProxy">bean>

    <aop:config>
        
        <aop:pointcut id="p" expression="execution(* springstudy2.User.add(..))"/>
        
        <aop:aspect ref="userproxy" order="1"> 
            
            
            <aop:before method="before" pointcut-ref="p"/>
            <aop:before method="before" pointcut="execution(* springstudy2.User.test(..))"/>
            <aop:after method="after" pointcut-ref="p"/>
            <aop:after-returning method="afterReturning" pointcut-ref="p"/>
            <aop:after-throwing method="afterThrowing" pointcut-ref="p"/>
            <aop:around method="around" pointcut-ref="p"/>
        aop:aspect>
        <aop:aspect ref="personproxy" order="2">
            
            <aop:before method="before" pointcut-ref="p"/>
        aop:aspect>
    aop:config>
beans>

修改Test类

package springstudy2;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class Test {
    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("bean3.xml");
        User user = context.getBean("user", User.class);
        user.add();
        user.test();
    }
}

基于XML的AOP执行结果
Spring 用法学习总结(三)之 AOP_第6张图片
基于注解的AOP执行结果
Spring 用法学习总结(三)之 AOP_第7张图片

注:与注解执行顺序不一致,原因是两者使用的AOP代理不同,在基于注解的AOP中,通知的执行顺序是确定的,而多个切面执行顺序由@Order注解来控制,当没有指定@Order注解时,Spring会按照切面类的类名进行排序,从字母顺序最小的切面开始执行,依次递增;在XML配置中,@around注解声明在@before注解前面,则@around注解先执行,否则,@before注解先执行

<aop:after method="after" pointcut-ref="p"/>
<aop:after-returning method="afterReturning" pointcut-ref="p"/>
<aop:after-throwing method="afterThrowing" pointcut-ref="p"/>
<aop:around method="around" pointcut-ref="p"/>
<aop:before method="before" pointcut-ref="p"/>
<aop:before method="before" pointcut="execution(* springstudy2.User.test(..))"/>

Spring 用法学习总结(三)之 AOP_第8张图片

你可能感兴趣的:(Spring,系列,spring,学习,java)