1. Spring容器解决了什么问题
优点:
- 让代码架构自上到下实现了低耦合.Spring支持接口注入,变更代码逻辑时,只需要变更实现类即可做到无缝的切换.
- OOP将业务程序分解成各个层次的对象,通过对象联动完成业务
缺点:
- 无法处理分散在各个业务之间的通用系统需求.例如:代码日志、业务功能权限校验、缓存、事务管理...容易发生代码入侵,增加耦合度和维护成本.
2. 面向切面编程
Aspect Oriented Programming
,面向切面编程,即我们常说的AOP.
它提供了一种在编译期间和运行期间对某个功能进行增强的技术,它是OOP的一种补充.在OOP中每个单元模块为Class,而AOP关注的单元模块为切面(Aspect).
举个例子:@Transactional
这个关于事务管理的注解,在方法上进行标记,那么在这么方法的开始和结束都会进行一些事务的操作.
从图上可以看到,当addUser
加上@Transactional
,其中涉及数据库的操作就会被事务管理的类负责代理,进而实现功能上的增强.
Spring基于AspectJ开发了Spring AOP的框架,提供基于schema方式或者@AspectJ
注解风格的切面编程能力.
其中,框架内部提供了一系列声明式的切面服务:例如@Transactional
、@Async
、@Cacheable
...
另外,用户如果想自定义自己的切面,也可以使用Spring AOP
框架进行编程.
注意,AOP是一种思想,并不是Spring所独有的.
2.1 切面编程中的概念
要了解AOP,需要先了解一下AOP中常说的概念:
-
Aspect: 切面,切面是AOP中封装切面逻辑的模块化单元.在
Spring AOP
中,切面往往以类或者XML的形式存在。
打个比方,如果你需要对系统日志做一个切面处理,那么你就需要编写一个类来描述其中的逻辑,那么这个类就是Aspect
. -
Join point: 连接点,指程序执行中的一点。在
Spring AOP
中,连接点始终代表方法的执行. -
Advice: 通知,即在特定的连接点切面执行的时机.
Spring AOP
提供了Around
、Before
、After
等切面逻辑执行的时机. -
Pointcut: 切入点,连接点表示的是所有可以进行切面逻辑的地方,那么切入点则对应切面逻辑需要切入的地方,
Spring AOP
支持使用AspectJ
的切入点表达式来指定切入点.
这里可能会有点绕,这里举一个例子:假设一个类中有3个方法,那么这三个方法都可以作为切面的连接点,此时如果只需要对其中的一个方法做切面逻辑,那么这个方法就是我们的切入点了。 -
Introduction: 在某个被切面逻辑环绕的对象中引入新的接口.在
Spring AOP
中,可以通过introduction
来将一个新的接口"引入"到被切入的类中. -
Target object: 被切面逻辑环绕的对象。在
Spring AOP
中,被切面逻辑环绕的类通常是被代理的,也可以称之为被代理的对象。 -
AOP proxy: 由AOP框架创建出的对象,为了去实现AOP逻辑.在
Spring AOP
中,支持两种代理JDK动态代理
和CGLIB代理
-
Weaving: 将切面与被代理对象进行链接.
Spring AOP
在运行时执行织入逻辑.
2.1.1 Spring AOP中的Advice
Advice(通知) | 描述 |
---|---|
Before advice | 前置通知,在连接点之前执行,不会影响整体的执行流程,除非抛出异常. |
After returning advice | 后置通知,在连接点正常返回之后执行(如果抛出了异常就不会执行此通知). |
After throwing advice | 在某个连接点抛出异常后执行 |
After (finally) advice | 无论连接点是否正常执行,均会执行此通知(相当于try finally中的finally). |
Around advice | 环绕通知可以做到上述通知可以做到的事情.想象一下被代理的方法为a() ,那么环绕可以对a() 做try catch finally ,环绕通知是最常用的一种通知. |
既然
Around advice
是覆盖所有advice的,那么为什么Spring AOP
还需要声明这么多advice,官方的说法是建议使用最小的advice级别来满足你的需求.
打个比方:如果仅仅需要记录每个方法的入参做一个log操作,那么使用before advice就已经可以满足了,而不需要使用到around advice.
2.1.2 advice执行顺序
5.2.7先说结论:Spring从5.2.7后对
@After
的执行顺序进行了调整.如图所示:
按官方的说法是跟随AspectJ的语义.
点我前往
before_5.2.7在5.2.7之前,Spring AOP按照
@Around
,@Before
,After
,AfterReturning
,AfterThrowing
的顺序执行.
2.2 开始简单的AOP编程
- 建立一个简单的service作为被代理对象
package com.xjm.service.impl;
import com.xjm.service.HelloService;
import org.springframework.stereotype.Service;
/**
* @author jaymin
* 2020/11/26 17:04
*/
@Service
public class HelloServiceImpl implements HelloService {
@Override
public String hello() {
return "Hello,Spring Framework!";
}
}
- 编写切面类
package com.xjm.aop;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;
import java.time.Duration;
import java.time.LocalDateTime;
import java.util.logging.Logger;
/**
* @author jaymin. <br>
* 系统基础切面,主要用于研究AOP.
* 2021/2/12 20:56
*/
@Aspect
@Component
public class SystemServiceAspect {
private static final Logger log = Logger.getGlobal();
/**
* 使用常量统一管理切入点
*/
public static final String SYSTEM_SERVICE_POINT_CUT = "systemServicePointCut()";
/**
* <p>pointcut可以使用表达式来指定切入点.
* <p>execution中的内容即为表达式.</p>
* <p>* com.xjm..service..*.*(..)</p>
* <p>表示,拦截com.xjm包下的service包中的所有类的所有方法(包括任意参数)
*/
@Pointcut("execution(* com.xjm..service..*.*(..))")
public void systemServicePointCut() {
}
/**
* 在方法执行前进行切入
* @param joinPoint
*/
@Before(SYSTEM_SERVICE_POINT_CUT)
public void before(JoinPoint joinPoint) {
log.info("before method execute ");
}
/**
* 环绕整个方法的执行
* @param joinPoint
* @return
*/
@Around(SYSTEM_SERVICE_POINT_CUT)
public Object around(JoinPoint joinPoint) {
LocalDateTime startTime = LocalDateTime.now();
log.info("around method starts ");
Object result;
try {
result = ((ProceedingJoinPoint) joinPoint).proceed();
} catch (Throwable throwable) {
throw new RuntimeException(throwable);
} finally {
LocalDateTime endTime = LocalDateTime.now();
long executeTime = Duration.between(startTime, endTime).toMillis();
log.info("method end.");
}
return result;
}
/**
* 在方法返回值后进行切入
* @param joinPoint
* @param result
*/
@AfterReturning(pointcut = SYSTEM_SERVICE_POINT_CUT, returning = "result")
public void afterReturning(JoinPoint joinPoint, Object result) {
log.info("method return");
}
/**
* 在方法抛出异常后进行切入
* @param joinPoint
* @param exception
*/
@AfterThrowing(pointcut = SYSTEM_SERVICE_POINT_CUT, throwing = "exception")
public void afterThrowing(JoinPoint joinPoint, Exception exception) {
log.info("current method throw an exception,message");
}
/**
* 获取连接点方法名
* @param joinPoint
* @return
*/
public String getMethodName(JoinPoint joinPoint) {
return ((MethodSignature) joinPoint.getSignature()).getMethod().getName();
}
/**
* 在方法执行后进行切入
* @param joinPoint
*/
@After(SYSTEM_SERVICE_POINT_CUT)
public void after(JoinPoint joinPoint) {
log.info("after method execute");
}
}
- 执行hello方法
注意,Spring是默认不开启AOP的,如果使用的是SpringBoot,需要在启动类上加上
@EnableAspectJAutoProxy
.
Result:
二月 13, 2021 10:42:38 下午 com.xjm.aop.SystemServiceAspect around
信息: around method starts
二月 13, 2021 10:42:38 下午 com.xjm.aop.SystemServiceAspect before
信息: before method execute
二月 13, 2021 10:42:38 下午 com.xjm.aop.SystemServiceAspect around
信息: method end.
二月 13, 2021 10:42:38 下午 com.xjm.aop.SystemServiceAspect after
信息: after method execute
二月 13, 2021 10:42:38 下午 com.xjm.aop.SystemServiceAspect afterReturning
信息: method return
笔者使用的是Spring 5.1.x版本
总结
- Spring AOP以可插拔的形式提供了面向切面编程的框架能力,以便对OOP做进一步的增强.
- AOP是另一种编程思想,它拥有成熟的一套体系和自己的术语.
- AOP可以通过预编译方式或者运行期动态代理的方式对程序功能进行织入.
- Spring AOP仅支持方法执行连接点,内部使用了JDK动态代理和CGLIB代理对AOP进行支持.
网友评论