Spring 模块
Spring 核心容器
容器是Spring框架最核心的部分,它管理着Spring应用中bean的创建、配置和管理。在该模块中,包括了Spring bean工厂,它为Spring提供了DI的功能。基于bean工厂,还有多种Spring应用上下文的实现,每一种都提供了配置Spring的不同方式。
该模块也提供了许多企业服务,例如 E-mail、JNDI、EJB集成和调度。
所有的Spring模块都构建于核心容器之上。当配置应用时,其实隐式地使用了这些类。
Spring的AOP模块
在AOP模块中,Spring对面向切面编程提供了丰富的支持。这个模块是Spring应用系统中开发切面的基础。与DI一样,AOP可以帮助应用对象解耦。借助于AOP,可以将遍布系统的关注点(例如事务和安全)从它们所应用的对象中解耦出来。
数据访问与集成
Spring的JDBC和DAO(Data Access Object)模块抽象了JDBC的样板式代码(例如获取数据库连接、创建语句、关闭连接等),代码简单明了。在多种数据库服务的错误信息之上构建了一个语义丰富的异常层,SQL错误信息相对明确。
ORM(Object-Relational Mapping),Spring的ORM模块建立在对DAO的支持之上,并为多个ORM框架提供了一种构建DAO的简便方式。Spring对许多流行的ORM框架进行了集成,包括Hibernate、Java Persisternce API、IBATIS 等。
Web与远程调用
MVC(Model-View-Controller)模式是普遍被接受的构建Web应用的方法,可以帮助用户将界面逻辑与应用逻辑分离。Spring MVC有助于在Web层提升应用的松耦合水平。
Spring远程调用功能集成了RMI(Remote Method Invocation)、Hessian等,还自带了一个远程调用框架:HTTP invoker。还提供了暴露和使用REST API的良好支持。
Instrumentation
提供了为JVM添加代理(agent)的功能。具体来讲,它为Tomcat提供了一个织入代理,能够为Tomcat传递类文件,就像这些文件是被类加载器加载的一样。使用场景非常有限。。
1 AOP简介
定义:Aspect Oriented Programming 面向切面编程
目的
把核心业务和非核心业务进行分离
把多个非核心业务处理(点)变成一个切面(之前的非核心业务代码需要写多次变成只需要写一次。)
要求:只有当核心类中有多个业务需要进行处理的时候使用AOP较为合理,如果只有一个业务需要被处理那么就没有必要使用AOP了。
1.1 什么是面向切面编程
面向切面编程(aspect-oriented programming, AOP)允许你把遍布应用各处的功能分离出来形成可重用的组件。
切面能帮助我们模块化横切关注点。简而言之,横切关注点可以被描述为影响应用多处的功能。例如,安全就是一个横切关注点,应用中的许多方法都会涉及到安全规则。
图1.1-1直观呈现了横切关注点的概念。
图1.1-1展现了一个被划分为模块的典型应用。每个模块的核心功能都是为特定业务领域提供服务,但是这些模块都需要类似的辅助功能,例如安全和事务管理。
如果要重用通用功能的话,最常见的面向对象技术是继承(inheritance)或委托(delegation) 但是,如果在整个应用中都使用相同的基类,继承往往会导致一个脆弱的对象体系;而使用委托可能需要对委托对象进行复杂的调用。
切面提供了取代继承和委托的另一种可选方案,而且在很多场景下更清晰简洁。在使用面向切面编程时,我们仍然在一个地方定义通用功能,但是可以通过声明的方式定义这个功能要以何种方式在何处应用,而无需修改受影响的类。
横切关注点可以被模块化为特殊的类,这些类被称为切面(aspect) 这样做有两个好处:
首先,现在每个关注点都集中于一个地方,而不是分散到多处代码中;
其次,服务模块更简洁,因为它们只包含主要关注点(或核心功能)的代码,而次要关注点的代码被转移到切面中了。
我们可以把切面想象为覆盖在很多组件之上的一个外壳。可以将安全、事物和日志关注点与核心业务逻辑相分离。
1.2 AOP术语
相关概念
.
.通知(Advice)
当抄表员出现在我们家门口时,他们要登记用电呈并回去向电力公司报告。显然,他们必须
有一份需要抄表的住户清单,他们所汇报的信息也很重要,但记录用电量才是抄表员的主要
工作。
切面也有目标——它必须要完成的工作。在AOP术语中,切面的工作披称为通知。
描述切面要完成的工作
何时执行这个工作
在方法被调用之前?之后?
在方法之前和之后都调用?
只在方法抛出异常时调用?
通知类型:
前置通知(Before):在目标方法被调用之前调用通知功能
后置通知(After):在目标方法完成之后调用通知,此时不会关心方法的输出是什么
返回通知(After-returning):在目标方法成功执行之后调用通知
异常通知(After-throwing):在目标方法抛出异常后调用通知
坏绕通知(Around):通知包裹了被通知的方法,在被通知的方法调用之前和调用之后执行自定义的行为。
连接点(Join point)
电力公司为多个住户提供服务,甚至可能是整个城市。毎家都有一个电表,这些电表上的数
字都需要读取,因此每家都是抄表员的潜在目标。抄表员也许能够读取各种类型的设备,但
是为了完成他的工作,他的目标应该是房屋内所安装的电表。
同样,我们的应用可能也有数以千计的时机应用通知。这些时机被称为连接点。连接点是在
应用执行过程中能够插入切面的一个点。这个点可以是调用方法时、抛出异常时、甚至修改
一个字段时。切面代码可以利用这些点插入到应用的正常流程之中,并添加新的行为。
切点(Poincut)
如果让一位抄表员访问电力公司所服务的所有住户,那肯定是不现实的。实际上,电力公司为每一个抄表员都分别指定某一块区域的住户.类似地,一个切面并不需要通知应用的所有连接点。切点有助于缩小切面所通知的连接点的范围。
如果说通知定义了切面的“什么”和“何时”的话,那么切点就定义了“何处”。切点的定义会匹配通知所要织入的一个或多个连接点.我们通常使用明确的类和方法名称,或是利用正则表达式定义所匹配的类和方法名称来指定这些切点。
切面(Aspect)
当抄表员开始一天的工作时,他知道自己要做的事情(报告用电量)和从哪些房屋收集信息。因此,他知道要完成工作所需要的一切东西。
切面是通知和切点的结合。通知和切点共同定义了切面的全部内容一它是什么,在何时和何处完成其功能.
引入(Introduction)
引入允许我们向现有的类添加新方法或属性。例如,我们可以创建一个Auditable通知类,该类记录了对象最后一次修改时的状态。这很简单,只需一个方法,setLastModified(Date),和一个实例变量来保存这个状态。然后,新方法和实例变量就可以被引入到现有的类中,从而可以在无需修改这些现有的类的情况下,让它们具有新的行为和状态。
织入(Weaving)
(切面发挥效果的过程)
织入是把切面应用到目标对象并创建新的代理对象的过程。切面在指定的连接点被织入到目标对象中。在目标对象的生命周期里有多个点可以进行织入:
编译期:切面在目标类编译时被织入。这种方式需要特殊的编译器。AspectJ的织入编译器就是以这种方式织入切面的。
类加载期:切面在目标类加载到JVM时被织入。这种方式需要特殊的类加载器(ClassLoader),它可以在目标类被引入应用之前增强该目标类的字节码。AspectJ5的加载时织入(load-time weaving, LTW)就支持以这种方式织入切面。
运行期:切面在应用运行的某个时刻被织入。一般情况下,在织入切面时,AOP容器会为目标对象动态地创建一个代理对象。Spring AOP就是以这种方式织入切面的。
1.3 Spring 对 AOP 的支持
Spring捉供了4种类型的AOP支持:
基于代理的经典Spring AOP
纯POJO切面
@AspectJ注解驱动的切面
注入式AspectJ切面(适用于Spring各版本》
前三种都是Spring AOP实现的变体,Spring AOP构建在动态代理基础之上,因此Spring对AOP的支持局限于方法拦截。
Spring的经典AOP编程模型曾经的确非常棒。但是现在Spring提供了更简洁和干净的面向切面编程方式.引入了简单的声明式AOP和基于注解的AOP之后,Spring 经典的AOP看起来就显得非常笨重和过于复杂,直接使用ProxyFactoryBean会让人感觉厌烦。
借助Spring的aop命名空间,我们可以将纯POJO转换为切面。实际上,这些POJO只是提供了满足切点条件时所要调用的方法.遗憾的是,这种技术需要XML配置,但这的确是声明式地将对象转换为切面的简便方式。
Spring借鉴了AspectJ的切面,以提供注解驱动的AOP。本质上,它依然是Spring基于代理的AOP,但是编程模型几乎与编写成熟的AspectJ注解切面完全一致。这种AOP风格的好处在于能够不使用XML来完成功能。
如果你的AOP需求超过了简单的方法调用(如构造器或属性拦截》,那么你需要考虑使用AspectJ来实现切面。在这种情况下,上文所示的第四种类型能够帮助你将值注入到AspectJ驱动的切面中。
了解SpringAOP框架的一些关键知识。
Spring通知是Java编写的
Spring所创建的通知都是用标准的Java类编写的。这样的话,我们就可以使用与普通Java开发一样的集成开发环境(IDE)来开发切面。而且,定义通知所应用的切点通常会使用注解或在Spring配置文件里采用XML来编写,这两种语法对于Java开发者来说都是相当熟悉的。
Spring在运行时通知对象
通过在代理类中包裹切面,Spring在运行期把切面织入到Spring管理的bean中。如图1.3-1所示,代理类封装了目标类,并拦截被通知方法的调用,再把调用转发给真正的目标bean。当代理拦截到方法调用时,在调用目标bean方法之前,会执行切面逻辑。
Spring 只支持方法级别的连接点
因为Spring基于动态代理,所以Spring只支持方法连接点。无法创建更细粒度的通知,例如拦截对象字段的修改。而且也不支持构造器连接点,无法在bean创建时应用通知。
2 通过切点选择连接点
切点用于准确定位应该在什么地方应用切面的通知。通知和切点是切面的最基本元素。因此,了解如何编写切点非常重要。
在Spring AOP中,要使用AspectJ的切点表达式语言来定义切点。
下图列出了Spring AOP所支持的AspectJ切点指示器
注意,只有 execution 指示器是实际执行匹配的,而其他的指示器都是用来限制匹配的。这说明 execution 指示器是我们在编写切点定义时最主要使用的指示器。在此基础上,我们使用其他指示器来限制所匹配的切点。
2.1 编写切点
使用 execution()指示器选择 HistoryService 的 getTeacherRankingList 方法,方法表达式以“*”号开始,表明了我们不关心方法返回值的类型。然后,我们指定了权限定类名和方法名。对于方法参数列表,使用了两个点号(..)表明切点要选择任意的 getTeacherRankingList 方法,无论该方法的入参是什么。
假设我们需要配置的切点仅匹配 rankingserver 包。在此场景下,可以使用 within() 指示器来限制匹配,如图 2.1-2 所示。
注意,使用了“&&”操作符把 execution() 和 within() 指示器连接在一起形成了与(and)关系(切点必须匹配所有的指示器)。类似的,我们可以使用 ”||“操作符来标识或(or)关系,而使用 ”!“ 操作符来标识非(not)操作。
3 使用注解创建切面
3.1 定义切面
五种注解定义通知:
每个模块接口都有自己的核心关注点,而多个模块之间会有一些辅助功能且不是核心业务。比如日志、安全等,可以称为横切关注点。因此,可以将日志收集定义为一个切面。
package com.ztjy.rankingserver.aop;
import lombok.extern.log4j.Log4j2;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;
/**
* 切面测试类1
* 记录接口开始、结束时间
**/
@Component
@Aspect
@Log4j2
public class LogAop1 {
@Before("execution(* com.ztjy.rankingserver.service.HistoryService.getTeacherRankingList(..))")
public void before(){
log.info("接口要开始执行了---->>begin time:{}",System.currentTimeMillis());
}
@AfterReturning("execution(* com.ztjy.rankingserver.service.HistoryService.getTeacherRankingList(..))")
public void after(){
log.info("接口执行结束了---->>begin time:{}",System.currentTimeMillis());
}
@AfterThrowing("execution(* com.ztjy.rankingserver.service.HistoryService.getTeacherRankingList(..))")
public void doAfterThrowing(){
log.info("接口报错了...--->>{}",System.currentTimeMillis());
}
}
LogAop1 类使用了 @Aspect 注解进行了标注。该注解表明 LogAop1 不仅仅是一个 POJO,还是一个切面。LogAop1 类中的方法都是用注解来定义切面的具体行为。
问题,切点表达式重复了三次。
使用 @Pointcut 注解能够在一个切面内定义可重用的切点。如下:
package com.ztjy.rankingserver.aop;
import lombok.extern.log4j.Log4j2;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
/**
* 切面测试类2
* 记录接口开始、结束时间
**/
@Component
@Aspect
@Log4j2
public class LogAop2 {
@Pointcut("execution(* com.ztjy.rankingserver.service.HistoryService.getTeacherRankingList(..))")
public void printLog(){}
@Before("printLog()")
public void before(){
log.info("接口要开始执行了---->>begin time:{}",System.currentTimeMillis());
}
@AfterReturning("printLog()")
public void after(){
log.info("接口执行结束了---->>begin time:{}",System.currentTimeMillis());
}
@AfterThrowing("printLog()")
public void doAfterThrowing(){
log.info("接口报错了...--->>{}",System.currentTimeMillis());
}
}
3.2 创建环绕通知
使用环绕通知重新实现 LogAop 切面
@Pointcut("execution(* com.ztjy.rankingserver.service.HistoryService.getTeacherRankingList(..))")
public void printLog(){}
@Around("printLog()")
public void watchLog(ProceedingJoinPoint jp){
try{
log.info("接口要开始执行了---->>begin time:{}",System.currentTimeMillis());
//执行方法
jp.proceed();
log.info("接口执行结束了---->>begin time:{}",System.currentTimeMillis());
}catch (Throwable e){
log.info("接口报错了...--->>{}",e);
}
}
ProceedingJoinPoint 参数是必须要有的,因为要在通知中通过它来调用被通知的方法。通知方法中可以做任何事情,当要将控制权交给被通知的方法时,需要调用 ProceedingJoinPoint 的proceed()方法。
注意,你可以不调用 proceed() 方法,从而阻塞对被通知方法的访问,与之类似,你也可以在通知中对它进行多次调用。要这样做的一个场景就是实现重试逻辑,也就是在被同志方法失败后,进行重复尝试。
.
demo分享:使用环绕通知,简单实现监控接口访问时长
package com.ztjy.rankingserver.aop;
import lombok.extern.log4j.Log4j2;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
/**
* 切面测试类3
* 记录接口开始、结束时间
**/
@Component
@Aspect
@Log4j2
public class LogAop3 {
@Pointcut("execution(* com.ztjy.rankingserver.service.impl.*.*(..))")
public void service(){}
@Around("service()")
public Object monitorService(ProceedingJoinPoint pjp) {
/** 拦截的方法名称 */
String methodName = pjp.getTarget().getClass().getSimpleName() + "." + pjp.getSignature().getName();
//参数
String strArgs = null;
Object[] args = pjp.getArgs();
Object obj = null;
try {
if(null!=args && args.length>0){
strArgs = args[0].toString();
}
long implTimeOut=200;
long start = System.currentTimeMillis();
log.info("接口要开始执行了---->>begin time:{}",start);
//接口执行
obj = pjp.proceed();
long finish = System.currentTimeMillis();
long useTime = finish - start;
/** 接口响应时间监控 */
if(implTimeOut > useTime){
log.info("service接口访问,interface:[{}].args:[{}].useTime:[{}]",new Object[]{methodName,strArgs, useTime+"ms"});
}else{
log.warn("service接口超时,interface:[{}].args:[{}].useTime:[{}].settingUseTime:[{}]",new Object[]{methodName,strArgs, useTime+"ms", implTimeOut+"ms"});
}
}catch(Throwable e) {
log.error("service接口访问异常,interface:[{}].args:[{}].errorInfo:[{}]",new Object[]{methodName,strArgs, e});
}
return obj;
}
}
日志打印结果
monitorService - service接口超时,interface:[HistoryServiceImpl.getTeacherRankingList].args:[TeacherRankingAO(teacherId=b706e8e4ca8881530b6d, roleType=4, schoolId=1ca4fbd5b6346ef8363b, termType=2, page=0, pageSize=100, isCurrent=1)].useTime:[290ms].settingUseTime:[200ms]
3.3 通过注解引入新功能
注意,当引入接口的方法被调用时,代理会把此调用委托给实现了新接口的某个其他对象。实际上,一个bean的实现被拆分到了多个类中
新接口
package com.ztjy.rankingserver.aop;
/**
* 扩展的新接口
*/
public interface AopExtend {
void extendMethod1();
void extendMethod2();
}
新接口实现类
package com.ztjy.rankingserver.aop;
import lombok.extern.log4j.Log4j2;
/**
* 新接口实现类
*/
@Log4j2
public class AopExtendImpl implements AopExtend{
@Override
public void extendMethod1() {
log.info("新增方法 extendMethod1 .....{}",System.currentTimeMillis());
}
@Override
public void extendMethod2() {
log.info("新增方法 extendMethod2 .....{}",System.currentTimeMillis());
}
}
引入代理类
package com.ztjy.rankingserver.aop;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.DeclareParents;
import org.springframework.stereotype.Component;
/**
* 引入代理类
*/
@Component
@Aspect
public class LogExtendIntroducer {
@DeclareParents(value = "com.ztjy.rankingserver.service.HistoryService+",defaultImpl = AopExtendImpl.class)
/**
* 要引入的接口
*/
public static AopExtend aopExtend;
}
@DeclareParents 注解由三部分组成:
value 属性指定了哪种类型的bean要引入该接口。在本例中,就是所有实现了 HistoryService 接口的类型。(标记符后面的加号表示是HistoryService 的所有子类型,而不是 HistoryService 本身)
defaultImpl 属性指定了为引入功能提供实现的类
标注的静态属性指明了要引入了接口
测试实现
package com.ztjy.rankingserver.service.impl;
import com.ztjy.rankingserver.aop.AopExtend;
import com.ztjy.rankingserver.service.HistoryService;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
@RunWith(SpringRunner.class)
@SpringBootTest
public class ServiceTest {
@Autowired
private HistoryService historyService ;
@Test
public void test(){
AopExtend aopExtend=(AopExtend)historyService;
aopExtend.extendMethod1();
aopExtend.extendMethod2();
}
}
4 XML创建切面
一般原则是,基于注解的配置要优于基于java的配置,基于java的配置要优于基于XML的配置。
以上基于注解实现的功能,也可以基于XML实现。不再一一赘述
5 Spring AOP 自定义注解实现
在实际业务需求中,会存在一些重要的业务接口,它们的地址会比较分散:不同包的类、类中的不同方法等,但它们所要实现的辅助功能是一致的。
比如需要记录关键方法的请求和响应时间,入参、结果等日志信息。
自定义注解可以解决这种问题,只在需要的地方调用即可。
1.编写自定义注解。实现对方法所实现的功能进行描述,以便在通知中获取描述信息
package com.ztjy.rankingserver.aop;
import java.lang.annotation.*;
/**
*
Description: 自定义系统日志注解
**/
@Target({ElementType.PARAMETER, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface SystemLog {
/**
* 功能描述
* @return
*/
String description() default "";
/**
* 操作类型
* @return
*/
String operType() default "";
}
@Target 用于描述注解的使用范围(即:被描述的注解可以用在什么地方),其取值有:
取值描述
CONSTRUCTOR用于描述构造器
FIELD用于描述域
LOCAL_VARIABLE用于描述局部变量
METHOD用于描述方法。
PACKAGE用于描述包
PARAMETER用于描述参数。
TYPE用于描述类或接口(甚至 enum )。
@Retention 用于描述注解的生命周期(即:被描述的注解在什么范围内有效),其取值有:
取值描述
SOURCE注解的信息会被编译器抛弃,不会留在class文件中,注解的信息只会留在源文件中
CLASS注解的信息被保留在class文件(字节码文件)中当程序编译时,但不会被虚拟机读取在运行的时候
RUNTIME注解的信息被保留在class文件(字节码文件)中当程序编译时,会被虚拟机保留在运行时
2.编写操作日志切面通知实现类
package com.ztjy.rankingserver.aop;
import com.alibaba.fastjson.JSON;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
@Aspect
@Component
public class SystemLogAspect {
private static final Logger logger = LoggerFactory.getLogger(SystemLogAspect.class);
@Pointcut("@annotation(com.ztjy.rankingserver.aop.SystemLog)")
public void logAspect(){
}
@After("logAspect()")
public void after(JoinPoint joinPoint){
try {
//获取用户请求方法的参数并序列化为JSON格式字符串
String params = "",paramsType = "";
if (joinPoint.getArgs() != null && joinPoint.getArgs().length > 0) {
for ( int i = 0; i < joinPoint.getArgs().length; i++) {
params += JSON.toJSONString(joinPoint.getArgs()[i]) + ";";
paramsType += joinPoint.getArgs()[i].getClass()+ ";";
}
}
String targetName = joinPoint.getTarget().getClass().getName();
String methodName = joinPoint.getSignature().getName();
Object[] arguments = joinPoint.getArgs();
Class targetClass = Class.forName(targetName);
Method[] methods = targetClass.getMethods();
String description = "";
String operType = "";
for (Method method : methods) {
if (method.getName().equals(methodName)) {
Class[] clazzs = method.getParameterTypes();
if (clazzs.length == arguments.length) {
description = method.getAnnotation(SystemLog.class).description();
operType = method.getAnnotation(SystemLog.class).operType();
break;
}
}
}
Map map=new HashMap();
map.put("params",params);
map.put("paramsType",paramsType);
map.put("description",description);
map.put("operType",operType);
map.put("methodName",(joinPoint.getTarget().getClass().getName() + "." + joinPoint.getSignature().getName() + "()"));
//*****数据入库
logger.info("results:{}",JSON.toJSONString(map));
}catch (Exception e){
logger.error("-----后置通知日志处理失败:{}-----",e.getMessage());
}
}
}
3.执行引用
在需要的方法上引用注解。
/**
* 获取历史榜单列表
* @param teacherRankingAO 入参对象
* @return 榜单列表
*/
@Override
@SystemLog(description = "获取历史榜单列表",operType = "QUERY")
public List
//*******内容忽略
}
4.执行结果集
results:{"description":"获取历史榜单列表","methodName":"com.ztjy.rankingserver.service.impl.HistoryServiceImpl.getTeacherRankingList()","params":"{\"isCurrent\":1,\"page\":0,\"pageSize\":100,\"roleType\":4,\"schoolId\":\"1ca4fbd5b6346ef8363b\",\"teacherId\":\"b706e8e4ca8881530b6d\",\"termType\":2};","operType":"QUERY","paramsType":"class com.ztjy.rankingserver.model.ao.TeacherRankingAO;"}
6 注入 AspectJ 切面
虽然Spring AOP能够满足许多应用的切面需求,但是与AspectJ相比,Spring AOP是一个功能
比较弱的AOP解决方案.AspectJ 提供了Spring AOP所不能支持的许多类型的切点。
Spring AOP 和 AspectJ 之间的关键区别
如何选择
简单地说, 选择很大程度上取决于我们的要求:
框架: 如果应用程序没有使用 spring 框架, 那么我们就别无选择, 只能放弃使用 spring AOP 的想法, 因为它无法管理任何超出 spring 容器范围的东西。但是, 如果我们的应用程序是完全使用 spring 框架创建的, 那么我们可以使用 spring AOP, 因为它是简单的学习和应用
灵活性: 由于有限的 joinpoint 支持, Spring aop 不是一个完整的 aop 解决方案, 但它解决了程序员面临的最常见的问题。尽管如果我们想深入挖掘和开发 AOP 以达到其最大能力, 并希望得到广泛的可用 joinpoints 的支持, 那么最好选择 AspectJ
性能: 如果我们使用的是有限的切面, 那么就会有细微的性能差异。但有时, 应用程序有成千上万个切面的情况。我们不想在这样的情况下使用运行时编织, 所以最好选择 AspectJ。AspectJ 已知的速度比 Spring AOP 快8到35倍
两者的最佳之处: 这两个框架都是完全兼容的。我们总是可以利用 Spring AOP; 只要有可能, 仍然可以在不支持前者的地方使用 AspectJ 获得支持