网上关于Spring AOP的文章不少,但大都千篇一律,复制粘贴一把梭。对着源码夸夸其谈,但是一些基础的概念却没怎么解释。很多概念、关系描述不清,含糊其辞。可谓以其昏昏,使人昭昭。
本文旨在梳理Spring AOP相关或由Spring AOP延伸出的概念,参考Spring官方文档,对概念进行解释。
文档地址:AOP Concepts
AOP
AOP即Aspect-oriented Programming,面向切面编程。和OOP相比,是一个全新的编程方式。类比与OOP的概念,OOP核心的概念是Class类,AOP中核心的概念是Aspect切面。AOP可以将跨类型跨对象的关注点模块化。
简单的说就是,AOP可以在不同类、不同对象的指定位置,在不破坏原程序代码的情况下,完成想要完成的事。
需要注意:AOP并不是Spring发明的,也不是Spring中独有的。 AOP中常见的概念如下:
切面 Aspect
横切关注点的一个模块化
- Aspect: A modularization of a concern that cuts across multiple classes. Transaction management is a good example of a crosscutting concern in enterprise Java applications. In Spring AOP, aspects are implemented by using regular classes (the schema-based approach) or regular classes annotated with the
@Aspect
annotation (the @AspectJ style).
通知 Advice
切面在连接点要做的具体行为
- Advice: Action taken by an aspect at a particular join point. Different types of advice include “around”, “before” and “after” advice. (Advice types are discussed later.) Many AOP frameworks, including Spring, model an advice as an interceptor and maintain a chain of interceptors around the join point.
连接点 Join point
程序运行中的一个节点,在spring中通常是一个方法的执行
- Join point: A point during the execution of a program, such as the execution of a method or the handling of an exception. In Spring AOP, a join point always represents a method execution.
切点 Pointcut
注意,切点是 a predicate,切点是对连接点的判断的描述,不是什么具体的东西
- Pointcut: A predicate that matches join points. Advice is associated with a pointcut expression and runs at any join point matched by the pointcut (for example, the execution of a method with a certain name). The concept of join points as matched by pointcut expressions is central to AOP, and Spring uses the AspectJ pointcut expression language by default.
引入 Introduction
给目标类增加一个新的方法的途径,这个可能接触比较少
- Introduction: Declaring additional methods or fields on behalf of a type. Spring AOP lets you introduce new interfaces (and a corresponding implementation) to any advised object. For example, you could use an introduction to make a bean implement an
IsModified
interface, to simplify caching. (An introduction is known as an inter-type declaration in the AspectJ community.)
目标对象 Target object
被切面横切的对象
- Target object: An object being advised by one or more aspects. Also referred to as the “advised object”. Since Spring AOP is implemented by using runtime proxies, this object is always a proxied object.
AOP代理对象 AOP proxy
AOP框架生成的代理对象实现切面功能。Spring中的AOP代理对象通常是JDK动态代理或CGLIB代理生成。
- AOP proxy: An object created by the AOP framework in order to implement the aspect contracts (advise method executions and so on). In the Spring Framework, an AOP proxy is a JDK dynamic proxy or a CGLIB proxy.
织入 Weaving
将切面与程序连接产生代理对象的过程。Spring通常是运行时织入。注意,不仅仅是在运行时织入,也可在编译时、编译后等时机织入,如AspectJ
- Weaving: linking aspects with other application types or objects to create an advised object. This can be done at compile time (using the AspectJ compiler, for example), load time, or at runtime. Spring AOP, like other pure Java AOP frameworks, performs weaving at runtime.
Spring AOP与AspectJ
大多数人接触到AOP,应该都是从Spring AOP开始的。但其实Spring AOP并不是完整的面向切面编程的框架,它的文档中也写了:与其他大多数AOP框架不同,Spring AOP的目标不是提供一个完整的AOP实现,而是提供一定的AOP实现,使得能与Spring的IoC容器紧密结合。
AspectJ是一个独立的、完整的AOP框架,属于Eclipse基金会。AspectJ拥有自己的语法、编译器。它的功能比Spring AOP框架更加完整也更加强大。它支持编译时、编译后、加载时织入。
Spring AOP中是使用了AspectJ框架的一部分内容。它把AspectJ中的注解拿过来,用来方便自己通过注解进行配置。
Spring AOP与动态代理
为什么讲到Spring AOP就会提到动态代理?因为Spring的AOP框架是通过动态代理的方式来产生AOP proxy的。注意,这不是AOP proxy产生的唯一方式。AspectJ可以在编译织入以静态代理的方式产生AOP proxy。
aopalliance-1.0.jar
这里顺便提到一个AOP Alliance(AOP联盟)项目,它是一群对Java和AOP感兴趣的软件工程师维护的,它提供了一个依赖包aopalliance-1.0.jar,包含AOP相关概念的接口定义。
在有的文章中会提到要导入aopalliance-1.0.jar。但其实Spring AOP完全不需要引入这个包。我们可以发现spring-aop包中是完全覆盖了这个aopalliance-1.0.jar中的类,甚至对一部分进行了重写。
实战
项目结构
pom依赖
代码语言:javascript复制<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>cn.dingyufan</groupId>
<artifactId>spring-aop-demo</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>
<spring.version>5.2.12.RELEASE</spring.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>${spring.version}</version>
</dependency>
</dependencies>
</project>
复制代码
编码
LogAspect
代码语言:javascript复制package cn.dingyufan.springaopdemo.aspect;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
@Aspect
@Component
public class LogAspect {
@Pointcut(value = "execution(* cn.dingyufan.springaopdemo.repository..*(..))")
public void repoPointcut(){}
@Pointcut(value = "execution(* cn.dingyufan.springaopdemo.service..*(..))")
public void servicePointcut() {}
@Pointcut(value = "servicePointcut() && args(String)")
public void serviceStringPointcut(){}
@Before(value = "repoPointcut()")
public void logRepoBefore() {
System.out.println("---before repository method---");
}
@Before(value = "servicePointcut()")
public void logServiceBefore() {
System.out.println("---before service method---");
}
@Before(value = "serviceStringPointcut()")
public void logServiceStringBefore() {
System.out.println("---before service method,String---");
}
}
复制代码
MyRepository
代码语言:javascript复制package cn.dingyufan.springaopdemo.repository;
import org.springframework.stereotype.Repository;
@Repository
public class MyRepository {
public void print(String str) {
String className = this.getClass().getSimpleName();
System.out.println(className " string is " str);
}
}
复制代码
MyService
代码语言:javascript复制package cn.dingyufan.springaopdemo.service;
public interface MyService {
void print(String str);
void print(Integer num);
}
复制代码
MyServiceImpl
代码语言:javascript复制package cn.dingyufan.springaopdemo.service;
import org.springframework.stereotype.Service;
@Service
public class MyServiceImpl implements MyService {
@Override
public void print(String str) {
String className = this.getClass().getSimpleName();
System.out.println(className " string is " str);
}
@Override
public void print(Integer num) {
String className = this.getClass().getSimpleName();
System.out.println(className " number is " num);
}
}
复制代码
SpringAopDemoApplication
代码语言:javascript复制package cn.dingyufan.springaopdemo;
import cn.dingyufan.springaopdemo.repository.MyRepository;
import cn.dingyufan.springaopdemo.service.MyService;
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 java.time.LocalDateTime;
@Configuration
@EnableAspectJAutoProxy
@ComponentScan
public class SpringAopDemoApplication {
public static void main(String[] args) {
var applicationContext = new AnnotationConfigApplicationContext(SpringAopDemoApplication.class);
int day = LocalDateTime.now().getDayOfMonth();
var myRepository = applicationContext.getBean(MyRepository.class);
myRepository.print(String.valueOf(day));
System.out.println();
var myService = applicationContext.getBean(MyService.class);
myService.print(String.valueOf(day));
System.out.println();
myService.print(day);
System.out.println();
}
}
复制代码