Spring—AOP

目录

1. AOP中的基本概念

2. Spring中建立AOP应用的步骤

2.1. 添加依赖

2.2. 启用Aspect J注解支持

2.3. 定义切面

2.4. 定义切点

2.5. 定义通知方法

2.6. 编写业务类

2.7. 运行应用并测试

3. @within注解

4. AOP的优点


       面向方面编程(Aspect-Oriented Programming,简称AOP)是一种编程范式,旨在通过分离横切关注点(cross-cutting concerns)来提高代码的模块化和可维护性。横切关注点是指那些影响多个模块但又不属于核心业务逻辑的功能,比如日志记录、安全检查、事务管理等。

1. AOP中的基本概念

        AOP中主要概念有:连接点、切点、通知、目标对象、引介、织入、代理和切面。

(1)连接点(JointPoint)

       一个类或一段程序代码拥有一些具有边界性质的特定点,这些代码中的特定点就称为连接点。例如,类开始初始化前、类初始化后,类某个方法调用前、调用后,方法抛出异常后。Spring AOP仅支持方法的连接点,即仅能在方法调用前、方法调用后和方法抛出异常时这些连接点织入通知(Advice)。连接点有两个信息确定:用方法表示的执行点和用相对点表示的方位。

(2)切点(Pointcut)

       每个类都拥有多个连接点,如一个拥有两个方法的类,这两个方法都是连接点,即连接点是程序类中客观存在的事物。但是在这为数众多的连接点中,如何定位到某个感兴趣的连接点上呢?AOP通过“切点”定位特定连接点。可以通过数据库查询的概念来理解切点和连接点的关系,连接点相当于数据库中的记录,而切点相当于查询条件。切点和连接点不是一对一的关系,一个切点可以匹配多个连接点。

(3)通知(Advice)

       通知是织入到目标类连接点上的一段程序代码。在Spring AOP中,通知除了用于描述一段程序代码外,还拥有另一个和连接点相关的信息,这便是执行点的方位。结合执行点方位信息和切点信息,我们就可以找到特定的连接点。

(4)目标对象(Target)

       目标对象就是通知逻辑织入的目标类。如果没有AOP,目标业务类需要自己实现所有的逻辑。在AOP的帮助下,业务类只实现那些非横切逻辑的程序逻辑,而性能监视和事务管理等这些横切逻辑可以使用AOP动态织入到特定的连接点上。

(5)引介(Introduction)

       引介是一种特殊的通知,它为类添加一些属性和方法。即使一个业务类原本没有实现某个接口,通过AOP的引介功能,我们可以动态地为该业务类添加接口的实现逻辑,让业务类成为这个接口的实现。

(6)织入(Weaving)

       织入是将通知添加到目标类具体连接点上的过程。AOP就像一台织布机,将目标类、通知或者引介通过AOP这台织布机天衣无缝地编织到一起。根据不同的实现技术,AOP有三种织入方式:

  • 编译期织入:这Java编译器。
  • 类装载期织入:这要求要求使用特殊的使用特殊的类装载器。
  • 动态代理织入:在运行期为目标类添加通知生成子类的方式。

      Spring AOP采用动态代理织入,AspectJ采用编译期织入和类装载期织入。

(7)代理(Proxy)

       一个类被AOP织入通知后,就产生了一个结果类,它是融合了原类和通知逻辑的代理类。根据不同的代理方式,代理类既可能是和原类具有相同接口的类,也可能就是原类的子类,所以我们可以采用调用原类相同的方式调用代理类。

(8)切面(Aspect)

       切面由切点和通知(引介)组成,它既包括了横切逻辑的定义,也包括了连接点的定义。Spring AOP就是负责实施切面的框架,它将切面所定义的横切逻辑织入到切面所指定的连接点中。

2. Spring中建立AOP应用的步骤

2.1. 添加依赖

       首先确保项目中包含Spring AOP和AspectJ的依赖。对于Maven项目,可以在pom.xm中添加以下依赖:

<dependency>

  <groupId>org.springframeworkgroupId>

  <artifactId>spring-aopartifactId>

  <version>5.2.25.RELEASEversion>

dependency>

 
      org.springframework
      spring-aspects
      5.2.25.RELEASE
 

2.2. 启用Aspect J注解支持

       在Spring配置类上添加@EnableAspectJAutoProxy注解,开启Spring对AspectJ自动代理功能的支持。示例代码如下:

package com.my.examples.example4;

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

@Configuration  //定义Java配置类
@EnableAspectJAutoProxy  //开启Spring对AspectJ自动代理的支持
@ComponentScan("com.my.examples.example4") //自动扫描包com.my.examples.example4下所有的Spring组件
public class MyAOPConfig {
}

2.3. 定义切面

      创建一个类,使用@Aspect注解将其标识为一个切面类,使用@Component注解将切面类标识为Spring组件。示例代码如下:

package com.my.examples.example4;

import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;

@Aspect  //声明一个切面
@Component //让此切面成为一个Spring组件
public class MyAspect {
    
}

2.4. 定义切点

       在切面类中使用@Pointcut注解定义切点,指定需要拦截的方法或类。示例代码如下:

package com.my.examples.example4;

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;

@Aspect  //声明一个切面
@Component //让此切面成为一个Spring组件
public class MyAspect {
    /**
     * 定义切点,该切点表达式的含义是匹配包com.my.examples.example4中任意类的任意方法
     */
    @Pointcut("execution(* com.my.examples.example4.*.*(..))")
    private void myPointcut(){
    }
}

 execution是使用最为广泛的切点表达式,其表示满足某一匹配模式的所有目标类方法连接点,定义如下:

execution(modifiers-pattern? ret-type-pattern

                 declaring-type-pattern?name-pattern(param-pattern)

                throws-pattern?)

其中,

  • modifiers-pattern方法的可见性,如publicprotected
  • ret-type-pattern方法的返回值类型,如intvoid等;
  • declaring-type-pattern方法所在类的全路径名,如com.my.examples.exampe4
  • name-pattern方法名,如print()
  • param-pattern方法的参数类型表,参数类型之间用逗号隔开,如果是任意多个参数,用通配符".."表示,如上述切点所示。
  • throws-pattern方法抛出的异常类型,如java.lang.Exception

切点表达式中的通配符主要有两种:

*通配符:该通配符主要用于匹配单个单词,或者是以某个词为前缀或后缀的单词。例如,在上述切点表达式中,*表示方法返回任意数据类型。

..通配符:该通配符表示0个或多个项,主要用于declaring-type-patternparam-pattern中,如果用于declaring-type-pattern中,则表示匹配当前包及其子包,如果用于param-pattern中,则表示匹配0个或多个参数。

2.5. 定义通知方法

      在切面类中定义通知方法,使用相应的注解,如@Before、@After、@AfterReturning、@AfterThrowing或@Around来指定通知的类型和执行时机。通知方法中编写需要执行的增强逻辑,如日志记录、性能监测等。示例代码如下:

package com.my.examples.example4;

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

@Aspect  //声明一个切面
@Component //让此切面成为一个Spring组件
public class MyAspect {
    /**
     * 定义切点,该切点表达式的含义是匹配包com.my.examples.example4
     * 中任意类的任意方法
     */
    @Pointcut("execution(* com.my.examples.example4.*.*(..))")
    private void myPointcut(){
    }
    //前置通知
    @Before("myPointcut()") //使用切点myPointcut()
    public void before(JoinPoint jp){
        System.out.println("前置通知: " + jp.getSignature().getName());
    }
    //后置返回通知
    @AfterReturning("myPointcut()") //使用切点myPointcut()
    public void afterReturning(JoinPoint jp){
        System.out.println("后置返回通知: " + jp.getSignature().getName());
    }
    //环绕通知
    @Around(value="myPointcut()") //使用切点myPointcut()
    public Object around(ProceedingJoinPoint pjp) throws Throwable {
        Object obj=null;
        System.out.println("环绕方法开始...");
        System.out.println("目标对象:"+pjp.getTarget()+",被织入的方法:"+pjp.getSignature().getName());
        obj=pjp.proceed();
        return obj;
    }

    //后置通知
    @After("myPointcut()")//使用切点myPointcut()
    public void after(JoinPoint jp) {
        System.out.println("后置通知...");
    }
}

Spring AOP定义5类通知(注解方式):@Before、@AfterReturning、@AfterThrowing、@After、@Around,如下表所示:

通知类型 功能描述

@Before

该通知在其切点表达式中定义的方法之前执行。Before通知处理前,目标方法还未执行,所以使用Before通知无法返回目标方法的返回值。

@AfterReturning

该通知在切点表达式中定义的方法后执行。使用该通知时可以指定属性:point/value:用来指定切入点对应的切入点表达式,可以是已定义的切入点,也可以直接定义切入点表达式。returning:指定一个返回值形参名,通过该形参可以访问目标方法。

@After

@AfterReturning通知类似,但也有区别,@AfterReturning通知只有在目标方法成功执行完毕后才会被织入,而@After通知不管目标方法是正常结束还是异常中止,均会被织入。

@AfterThrowing

主要用于处理程序中未处理的异常。使用AfterThrowing通知可以指定如下属性:

point/value:用来指定切入点对应的切入点表达式。

throwing:指定一个返回值形参名,通过该形参访问目标方法中抛出但未处理的异常对象

@Around

该通知等于@Before通知和@AfterReturning通知的总和,但与它们不同的是,@Around通知还可以决定目标方法什么时候执行,如何执行,甚至可以阻止目标方法的执行。@Around可以改变目标方法的参数值,也可以改变目标方法的返回值。

在定义@Around通知的切面逻辑时,必须给方法至少加入ProceedingJoinPoint类型的参数,在方法内调用ProceedingJoinPointproceed()方法才会执行目标方法,调用proceed()方法时还可以传入一个Object[]对象,该数组中的数据将作为目标方法的实参。

在定义多个通知时,通知的执行次序与优先级由低到高为:Before、Around 、After 、AfterReturning

2.6. 编写业务类

业务逻辑类和方法不需要任何AOP相关的代码,只需确保它们符合切点表达式。示例代码如下:

package com.my.examples.example4;

import org.springframework.stereotype.Service;

@Service  //定义Spring构件
public class MyService {
    public void performTask() {
        System.out.println("Performing task");
    }
}

2.7. 运行应用并测试

       启动Spring应用,并调用目标类的方法进行测试。AOP切面会根据定义的切点和通知自动拦截目标方法的执行,并执行相应的通知逻辑。示例代码如下:

package com.my.examples.example4;

import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class Test {
    public static void main(String[] args) {
        //根据Java配置类创建Spring容器
        AnnotationConfigApplicationContext context = new
                AnnotationConfigApplicationContext(MyAOPConfig.class);
        MyService myService=context.getBean(MyService.class);
        myService.performTask();
    }
}

运行类Test,在控制台输出如下信息:

环绕方法开始...
目标对象:com.my.examples.example4.MyService@61710c6,被织入的方法:performTask
前置通知: performTask
Performing task
后置返回通知: performTask
后置通知...

3. @within注解

      在Spring AOP中,@within用于匹配目标类具有指定注解的所有方法,如果一个类上有指定的注解,那么该类的所有方法都会被匹配。示例代码如下:

(1)自定义注解

package com.my.examples.example5;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface MyAnnotation {
}

(2)带自定义注解的类

package com.my.examples.example5;

import org.springframework.stereotype.Service;

@MyAnnotation  //自定义注解
@Service  //定义Spring组件
public class MyService {
    public void performTask() {
        System.out.println("Performing a task.");
    }
}

(3)切面类

package com.my.examples.example5;

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

@Aspect
@Component
public class MyAspect {
    @Before("@within(com.my.examples.example5.MyAnnotation)")
    public void before1(JoinPoint joinPoint) {
        System.out.println("前置通知:"+joinPoint.getSignature().getName());
    }
}

(4)启用Aspect J注解支持

package com.my.examples.example5;

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

@Configuration  //定义Java配置类
@EnableAspectJAutoProxy  //开启Spring对AspectJ自动代理的支持
@ComponentScan("com.my.examples.example5") //自动扫描包com.my.examples.example5下所有的Spring组件
public class MyAOPConfig {
}

(5)运行应用测试

package com.my.examples.example5;

import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class Test {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext context = new
                AnnotationConfigApplicationContext(MyAOPConfig.class);
        MyService service = context.getBean(MyService.class);
        service.performTask();

    }
}

运行类Test,在控制台输入如下信息:

前置通知:performTask
Performing a task.

4. AOP的优点

(1)代码集中。解决了由于OOP跨模块造成的代码纠缠和代码分散问题。

(2)模块化横切关注点。核心业务级关注点与横切关注点分离开,降低横切模块与核心模块的耦合度,实现了软件工程中的高内聚、低耦合的要求。

(3)系统容易扩展。AOP的基本业务模块不知道横切关注点的存在,很容易通过建立新的切面加入新的功能。另外,当系统中加入新的模块时,已有的横切面自动横切进来,使系统易于扩展。

(4)提高代码重用性。AOP把每个Aspect实现为独立的模块,模块之间松散耦合,意味着更高的代码重用性

你可能感兴趣的:(云计算与软件服务,#,Spring框架,spring,java,后端,云计算)