public class JDKProxyFactory implements InvocationHandler {
private Object target;
public JDKProxyFactory(Object target) {
super();
this.target = target;
}
// 创建代理对象
public Object createProxy() {
// 1.得到目标对象的类加载器
ClassLoader classLoader = target.getClass().getClassLoader();
// 2.得到目标对象的实现接口
Class<?>[] interfaces = target.getClass().getInterfaces();
// 3.第三个参数需要一个实现invocationHandler接口的对象
Object newProxyInstance = Proxy.newProxyInstance(classLoader, interfaces, this);
return newProxyInstance;
}
// 第一个参数:代理对象.一般不使用;第二个参数:需要增强的方法;第三个参数:方法中的参数
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("这是增强方法前......");
Object invoke = method.invoke(target, args);
System.out.println("这是增强方法后......");
return invoke;
}
public static void main(String[] args) {
// 1.创建对象
FoodServiceImpl foodService = new FoodServiceImpl();
// 2.创建代理对象
JDKProxyFactory proxy = new JDKProxyFactory(foodService);
// 3.调用代理对象的增强方法,得到增强后的对象
FoodService createProxy = (FoodService) proxy.createProxy();
createProxy.makeChicken();
}
}
public class CglibProxyFactory implements MethodInterceptor {
//得到目标对象
private Object target;
//使用构造方法传递目标对象
public CglibProxyFactory(Object target) {
super();
this.target = target;
}
//创建代理对象
public Object createProxy(){
//1.创建Enhancer
Enhancer enhancer = new Enhancer();
//2.传递目标对象的class
enhancer.setSuperclass(target.getClass());
//3.设置回调操作
enhancer.setCallback(this);
return enhancer.create();
}
//参数一:代理对象;参数二:需要增强的方法;参数三:需要增强方法的参数;参数四:需要增强的方法的代理
public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
System.out.println("这是增强方法前......");
Object invoke = methodProxy.invoke(target, args);
System.out.println("这是增强方法后......");
return invoke;
}
public static void main(String[] args) {
// 1.创建对象
FoodServiceImpl foodService = new FoodServiceImpl();
// 2.创建代理对象
CglibProxyFactory proxy = new CglibProxyFactory(foodService);
// 3.调用代理对象的增强方法,得到增强后的对象
FoodService createProxy = (FoodService) proxy.createProxy();
createProxy.makeChicken();
}
}
@Aspect //该注解声明这个类为一个切面类
@Component
class HandlerAspect{
@Autowired
private lateinit var handlerService: HandlerService
@Pointcut("execution(* com.winsafe.business.task..*Task.*(..))") // (PointCut)
public void taskPointCut(){}
@Around("taskPointCut") //当有函数注释了注解,将会在函数正常返回后在执行我们定义的方法 (Advice)
public Object doAroundTranceIdLog(ProceedingJoinPoint joinPoint) throws Throwable {
return handleTranceId(joinPoint); // joinPoint
}
}
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(AspectJAutoProxyRegistrar.class)
public @interface EnableAspectJAutoProxy {
/**
* Indicate whether subclass-based (CGLIB) proxies are to be created as opposed
* to standard Java interface-based proxies. The default is {@code false}.
*/
boolean proxyTargetClass() default false;
}
// Aop开启实际逻辑 调用 AopConfigUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary(registry);
class AspectJAutoProxyRegistrar implements ImportBeanDefinitionRegistrar {
/**
* Register, escalate, and configure the AspectJ auto proxy creator based on the value
* of the @{@link EnableAspectJAutoProxy#proxyTargetClass()} attribute on the importing
* {@code @Configuration} class.
*/
@Override
public void registerBeanDefinitions(
AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
AopConfigUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary(registry);
AnnotationAttributes enableAJAutoProxy =
AnnotationConfigUtils.attributesFor(importingClassMetadata, EnableAspectJAutoProxy.class);
if (enableAJAutoProxy.getBoolean("proxyTargetClass")) {
AopConfigUtils.forceAutoProxyCreatorToUseClassProxying(registry);
}
}
}
核心实现类 AnnotationAwareAspectJAutoProxyCreator 实现 BeanPostProcessor#postProcessAfterInitialization,在Bean初始化结束会根据需要返回代理对象
调用核心方法wrapIfNecessary
public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException {
if (!NativeDetector.inNativeImage() &&
(config.isOptimize() || config.isProxyTargetClass() || hasNoUserSuppliedProxyInterfaces(config))) {
Class<?> targetClass = config.getTargetClass();
if (targetClass == null) {
throw new AopConfigException("TargetSource cannot determine target class: " +
"Either an interface or a target is required for proxy creation.");
}
if (targetClass.isInterface() || Proxy.isProxyClass(targetClass)) {
return new JdkDynamicAopProxy(config);
}
return new ObjenesisCglibAopProxy(config);
}
else {
return new JdkDynamicAopProxy(config);
}
}
利用@Import注入TransactionManagementConfigurationSelector.class(通过ConfigurationClassPostProcessor类的postProcessorBeanFactory()解析到BeanDefinitions中)
// 启动Spring事务配置
@Configuration
@EnableTransactionManagement
public class TransactionManagerConfig implements TransactionManagementConfigurer {
@Resource
DataSource dataSource;
@Override
public TransactionManager annotationDrivenTransactionManager() {
// 返回一个事务管理器,设置数据源
return new DataSourceTransactionManager(dataSource);
}
}
//
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(TransactionManagementConfigurationSelector.class)
public @interface EnableTransactionManagement {
boolean proxyTargetClass() default false;
AdviceMode mode() default AdviceMode.PROXY;
int order() default Ordered.LOWEST_PRECEDENCE;
}
public class TransactionManagementConfigurationSelector extends AdviceModeImportSelector<EnableTransactionManagement> {
/**
* {@inheritDoc}
* @return {@link ProxyTransactionManagementConfiguration} or
* {@code AspectJTransactionManagementConfiguration} for {@code PROXY} and
* {@code ASPECTJ} values of {@link EnableTransactionManagement#mode()}, respectively
*/
@Override
protected String[] selectImports(AdviceMode adviceMode) {
switch (adviceMode) {
case PROXY:
return new String[] {AutoProxyRegistrar.class.getName(), ProxyTransactionManagementConfiguration.class.getName()};
case ASPECTJ:
return new String[] {TransactionManagementConfigUtils.TRANSACTION_ASPECT_CONFIGURATION_CLASS_NAME};
default:
return null;
}
}
}
注册一个 InfrastructureAdvisorAutoProxyCreator extends AbstractAutoProxyCreator 对象,主要目的是创建代理对象ProxyTransactionManagementConfiguration Spring 事务代理配置类
创建 BeanFactoryTransactionAttributeSourceAdvisor (由InfrastructureAdvisorAutoProxyCreator创建代理对象),根据JDK动态代理可知Transaction具体执行是有TransactionInterceptor执行
// Spring事务管理配置类 BeanFactoryTransactionAttributeSourceAdvisor TransactionInterceptor TransactionAttributeSource
@Configuration
public class ProxyTransactionManagementConfiguration extends AbstractTransactionManagementConfiguration {
@Bean(name = TransactionManagementConfigUtils.TRANSACTION_ADVISOR_BEAN_NAME)
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
public BeanFactoryTransactionAttributeSourceAdvisor transactionAdvisor() {
BeanFactoryTransactionAttributeSourceAdvisor advisor = new BeanFactoryTransactionAttributeSourceAdvisor();
advisor.setTransactionAttributeSource(transactionAttributeSource());
advisor.setAdvice(transactionInterceptor());
advisor.setOrder(this.enableTx.<Integer>getNumber("order"));
return advisor;
}
@Bean
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
public TransactionAttributeSource transactionAttributeSource() {
return new AnnotationTransactionAttributeSource();
}
@Bean
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
public TransactionInterceptor transactionInterceptor() {
TransactionInterceptor interceptor = new TransactionInterceptor();
interceptor.setTransactionAttributeSource(transactionAttributeSource());
if (this.txManager != null) {
interceptor.setTransactionManager(this.txManager);
}
return interceptor;
}
}
// 具体执行事务逻辑 invoke() -> TransactionAspectSupport#invokeWithinTransaction
@SuppressWarnings("serial")
public class TransactionInterceptor extends TransactionAspectSupport implements MethodInterceptor, Serializable {
@Override
public Object invoke(final MethodInvocation invocation) throws Throwable {
// Work out the target class: may be {@code null}.
// The TransactionAttributeSource should be passed the target class
// as well as the method, which may be from an interface.
Class<?> targetClass = (invocation.getThis() != null ? AopUtils.getTargetClass(invocation.getThis()) : null);
// Adapt to TransactionAspectSupport's invokeWithinTransaction...
return invokeWithinTransaction(invocation.getMethod(), targetClass, new InvocationCallback() {
@Override
public Object proceedWithInvocation() throws Throwable {
return invocation.proceed();
}
});
}
}
// 事务执行核心方法 invokeWithinTransaction
protected Object invokeWithinTransaction(Method method, Class<?> targetClass, final InvocationCallback invocation)
throws Throwable {
// If the transaction attribute is null, the method is non-transactional.
final TransactionAttribute txAttr = getTransactionAttributeSource().getTransactionAttribute(method, targetClass);
final PlatformTransactionManager tm = determineTransactionManager(txAttr);
final String joinpointIdentification = methodIdentification(method, targetClass);
if (txAttr == null || !(tm instanceof CallbackPreferringPlatformTransactionManager)) {
// Standard transaction demarcation with getTransaction and commit/rollback calls.
TransactionInfo txInfo = createTransactionIfNecessary(tm, txAttr, joinpointIdentification);
Object retVal = null;
try {
// This is an around advice: Invoke the next interceptor in the chain.
// This will normally result in a target object being invoked.
retVal = invocation.proceedWithInvocation();
}
catch (Throwable ex) {
// target invocation exception
completeTransactionAfterThrowing(txInfo, ex);
throw ex;
}
finally {
cleanupTransactionInfo(txInfo);
}
commitTransactionAfterReturning(txInfo);
return retVal;
}
else {
// It's a CallbackPreferringPlatformTransactionManager: pass a TransactionCallback in.
try {
Object result = ((CallbackPreferringPlatformTransactionManager) tm).execute(txAttr,
new TransactionCallback<Object>() {
@Override
public Object doInTransaction(TransactionStatus status) {
TransactionInfo txInfo = prepareTransactionInfo(tm, txAttr, joinpointIdentification, status);
try {
return invocation.proceedWithInvocation();
}
catch (Throwable ex) {
if (txAttr.rollbackOn(ex)) {
// A RuntimeException: will lead to a rollback.
if (ex instanceof RuntimeException) {
throw (RuntimeException) ex;
}
else {
throw new ThrowableHolderException(ex);
}
}
else {
// A normal return value: will lead to a commit.
return new ThrowableHolder(ex);
}
}
finally {
cleanupTransactionInfo(txInfo);
}
}
});
// Check result: It might indicate a Throwable to rethrow.
if (result instanceof ThrowableHolder) {
throw ((ThrowableHolder) result).getThrowable();
}
else {
return result;
}
}
catch (ThrowableHolderException ex) {
throw ex.getCause();
}
}
}
创建 AnnotationTransactionAspect 父类包含了@Aspect注解 可以由Spring AnnotationAwareAspectJAutoProxyCreator 解析返回代理对象,其父类 包含@Around->TransactionAspectSupport#invokeWithinTransaction()具体实现事务逻辑
public abstract aspect AbstractTransactionAspect extends TransactionAspectSupport implements DisposableBean {
protected AbstractTransactionAspect(TransactionAttributeSource tas) {
setTransactionAttributeSource(tas);
}
@Override
public void destroy() {
clearTransactionManagerCache(); // An aspect is basically a singleton
}
@SuppressAjWarnings("adviceDidNotMatch")
Object around(final Object txObject): transactionalMethodExecution(txObject) {
MethodSignature methodSignature = (MethodSignature) thisJoinPoint.getSignature();
// Adapt to TransactionAspectSupport's invokeWithinTransaction...
try {
return invokeWithinTransaction(methodSignature.getMethod(), txObject.getClass(), new InvocationCallback() {
public Object proceedWithInvocation() throws Throwable {
return proceed(txObject);
}
});
}
catch (RuntimeException ex) {
throw ex;
}
catch (Error err) {
throw err;
}
catch (Throwable thr) {
Rethrower.rethrow(thr);
throw new IllegalStateException("Should never get here", thr);
}
}
}
@Component
@Aspect
@Order(1)
public class AspectJTest1 {
@Pointcut("execution(public * com.winsafe.spring.aspect.TargetTest.testAop(..))")
public void performance() {
}
@Before("performance()")
public void before() {
System.out.println("AspectJTest1-Before");
}
@AfterReturning("performance()")
public void afterReturn() {
System.out.println("AspectJTest1-AfterReturning");
}
@After("performance()")
public void after() {
System.out.println("AspectJTest1-After");
}
@Around("performance()")
public Object around(ProceedingJoinPoint joinPoint) {
Object obj = null;
try {
System.out.println("AspectJTest1-AroundFirst");
obj = joinPoint.proceed();
System.out.println("AspectJTest1-AroundSecond");
} catch (Throwable e) {
e.printStackTrace();
}
return obj;
}
public void testAop(){
System.out.println("test aop");
}
}
@Component
@Aspect
@Order(2)
public class AspectJTest2 {
@Pointcut("execution(public * com.winsafe.spring.aspect.TargetTest.testAop(..))")
public void performance() {
}
@Before("performance()")
public void before() {
System.out.println("AspectJTest2-Before");
}
@AfterReturning("performance()")
public void afterReturn() {
System.out.println("AspectJTest2-AfterReturning");
}
@After("performance()")
public void after() {
System.out.println("AspectJTest2-After");
}
@Around("performance()")
public Object around(ProceedingJoinPoint joinPoint) {
Object obj = null;
try {
System.out.println("AspectJTest2-AroundFirst");
obj = joinPoint.proceed();
System.out.println("AspectJTest2-AroundSecond");
} catch (Throwable e) {
e.printStackTrace();
}
return obj;
}
}
结论:
public class ReflectiveMethodInvocation implements ProxyMethodInvocation, Cloneable {
protected final Object proxy;
protected final Object target;
protected final Method method;
protected Object[] arguments;
private final Class<?> targetClass;
/**
* Lazily initialized map of user-specific attributes for this invocation.
*/
private Map<String, Object> userAttributes;
/**
* List of MethodInterceptor and InterceptorAndDynamicMethodMatcher
* that need dynamic checks.
*/
protected final List<?> interceptorsAndDynamicMethodMatchers; // 该对象中存储了需要拦截的方法 其中的顺序决定了增强方法执行的顺序!!
/**
* Index from 0 of the current interceptor we're invoking.
* -1 until we invoke: then the current interceptor.
*/
private int currentInterceptorIndex = -1;
/**
* Construct a new ReflectiveMethodInvocation with the given arguments.
* @param proxy the proxy object that the invocation was made on
* @param target the target object to invoke
* @param method the method to invoke
* @param arguments the arguments to invoke the method with
* @param targetClass the target class, for MethodMatcher invocations
* @param interceptorsAndDynamicMethodMatchers interceptors that should be applied,
* along with any InterceptorAndDynamicMethodMatchers that need evaluation at runtime.
* MethodMatchers included in this struct must already have been found to have matched
* as far as was possibly statically. Passing an array might be about 10% faster,
* but would complicate the code. And it would work only for static pointcuts.
*/
protected ReflectiveMethodInvocation(
Object proxy, Object target, Method method, Object[] arguments,
Class<?> targetClass, List<Object> interceptorsAndDynamicMethodMatchers) {
this.proxy = proxy;
this.target = target;
this.targetClass = targetClass;
this.method = BridgeMethodResolver.findBridgedMethod(method);
this.arguments = AopProxyUtils.adaptArgumentsIfNecessary(method, arguments);
this.interceptorsAndDynamicMethodMatchers = interceptorsAndDynamicMethodMatchers;
}
@Override
public final Object getProxy() {
return this.proxy;
}
@Override
public final Object getThis() {
return this.target;
}
@Override
public final AccessibleObject getStaticPart() {
return this.method;
}
/**
* Return the method invoked on the proxied interface.
* May or may not correspond with a method invoked on an underlying
* implementation of that interface.
*/
@Override
public final Method getMethod() {
return this.method;
}
@Override
public final Object[] getArguments() {
return (this.arguments != null ? this.arguments : new Object[0]);
}
@Override
public void setArguments(Object... arguments) {
this.arguments = arguments;
}
@Override
public Object proceed() throws Throwable {
// We start with an index of -1 and increment early.
if (this.currentInterceptorIndex == this.interceptorsAndDynamicMethodMatchers.size() - 1) {
return invokeJoinpoint();
}
Object interceptorOrInterceptionAdvice =
this.interceptorsAndDynamicMethodMatchers.get(++this.currentInterceptorIndex);
if (interceptorOrInterceptionAdvice instanceof InterceptorAndDynamicMethodMatcher) {
// Evaluate dynamic method matcher here: static part will already have
// been evaluated and found to match.
InterceptorAndDynamicMethodMatcher dm =
(InterceptorAndDynamicMethodMatcher) interceptorOrInterceptionAdvice;
if (dm.methodMatcher.matches(this.method, this.targetClass, this.arguments)) {
return dm.interceptor.invoke(this);
}
else {
// Dynamic matching failed.
// Skip this interceptor and invoke the next in the chain.
return proceed();
}
}
else {
// It's an interceptor, so we just invoke it: The pointcut will have
// been evaluated statically before this object was constructed.
return ((MethodInterceptor) interceptorOrInterceptionAdvice).invoke(this);
}
}
/**
* Invoke the joinpoint using reflection.
* Subclasses can override this to use custom invocation.
* @return the return value of the joinpoint
* @throws Throwable if invoking the joinpoint resulted in an exception
*/
protected Object invokeJoinpoint() throws Throwable {
return AopUtils.invokeJoinpointUsingReflection(this.target, this.method, this.arguments);
}
}
通过CglibAopProxy的DynamicAdvisedInterceptor的intercept方法new CglibMethodInvocation(proxy, target, method, args, targetClass, chain, methodProxy)CglibMethodInvocation继承自ReflectiveMethodInvocation,传入的chain作为连接器链
开始寻找chain的来源,通过下图,找到getInterceptorsAndDynamicInterceptionAdvice方法最终找到DefaultAdvisorChainFactory.getInterceptorsAndDynamicInterceptionAdvice方法
发现config.getAdvisors()中的拦截器顺序和属性interceptorsAndDynamicMethodMatchers顺序一致
config.getAdvisors()
属性interceptorsAndDynamicMethodMatchers
开始寻找config.getAdvisors()顺序规则:最终发现config中的advisors是在Spring启动时AbstractAutoProxyCreator作为BeanPostProcessor(Bean后置处理器)在Bean初始化后进行创建代理时产生
查看buildAdvisors(beanName, specificInterceptors)未进行排序,发现顺序取决于specificInterceptors,继续查找specificInterceptors的来源getAdvicesAndAdvisorsForBean
查看advisors是如何拿到的,其顺序又是什么? BeanFactoryAspectJAdvisorsBuilder#buildAspectJAdvisors->ReflectiveAspectJAdvisorFactory#getAdvisors->ReflectiveAspectJAdvisorFactory#getAdvisorMethods,通过Class反射获取类的所有方法并进行adviceMethodFilter和adviceMehtodComparator排序(排序规则为@Around, @Before, @After, @AfterReturning, @AfterThrowing)
点击里面发现由org.aspectj.util.PartialOrder + AspectJPrecedenceComparator类(通过实现Order接口or@Order注解+declarationOrder) 实现的排序
// AspectJPrecedenceComparator类 compare 分析
@Override
public int compare(Advisor o1, Advisor o2) {
int advisorPrecedence = this.advisorComparator.compare(o1, o2); // 先用使用Order排序器进行排序,值越小,优先级越高
if (advisorPrecedence == SAME_PRECEDENCE && declaredInSameAspect(o1, o2)) { // Order顺序值相同,且Advisor在同一个Aspect
advisorPrecedence = comparePrecedenceWithinAspect(o1, o2); // 使用declarationOrder的值进行排序(详细分析见下), 这里涉及到关于declarationOrder的来源见下分析
}
return advisorPrecedence;
}
// spirng5.xdeclarationOrder的来源分析: 在步骤6中可以看到advisor的来源(ReflectiveAspectJAdvisorFactory#getAdvisors)
@Override
public List getAdvisors(MetadataAwareAspectInstanceFactory aspectInstanceFactory) {
// // sprng 5.2.7之前
List advisors = new LinkedList();
for (Method method : getAdvisorMethods(aspectClass)) { // 重点1:获取所有的advisorMethods通过class 按照 @Around, @Before, @After, @AfterReturning, @AfterThrowing优先排序,注解相同则按照方法名按照自然排序
Advisor advisor = getAdvisor(method, lazySingletonAspectInstanceFactory, advisors.size(), aspectName); // 重点2: advisors.size()就是Advisor的declarationOrder值
if (advisor != null) {
advisors.add(advisor);
}
}
// 3.分析同一个Aspect获取的Method按照 @Around, @Before, @After, @AfterReturning, @AfterThrowing 排序,它们的declarationOrder值依次递增分别是0,1,2,3,4
// ...
// sprng 5.2.7
for (Method method : getAdvisorMethods(aspectClass)) {
// Prior to Spring Framework 5.2.7, advisors.size() was supplied as the declarationOrderInAspect to getAdvisor(...) to represent the "current position" in the declared methods list. However, since Java 7 the "current position" is not valid since the JDK no longer returns declared methods in the order in which they are declared in the source code. Thus, we now hard code the declarationOrderInAspect to 0 for all advice methods discovered via reflection in order to support reliable advice ordering across JVM launches. Specifically, a value of 0 aligns with the default value used in AspectJPrecedenceComparator.getAspectDeclarationOrder(Advisor).
Advisor advisor = getAdvisor(method, lazySingletonAspectInstanceFactory, 0, aspectName);
if (advisor != null) {
advisors.add(advisor);
}
}
}
/**
AspectJPrecedenceComparator类 comparePrecedenceWithinAspect 分析
由上述declarationOrder的来源分析 @Around, @Before, @After, @AfterReturning, @AfterThrowing它们的declarationOrder值依次递增分别是0,1,2,3,4
如果有一个是afterAdvice即 afte, afterreturn,afterthrowing
则排序时值越低,优先级越低, 说明排序时afterAdvice大于非afterAdvice(Around,Before) 即所有的 afterAdvice(afterthrowing > afterreturn > after) > 非afterAdvice(Around,Before)
否则 值越低优先级越高,Around > Before
结合上述得出Afterthrowing > Afterreturn > After > Around > Before (afterAdvice优先级最高且afterAdvice是值越低优先级越高,反之非afterAdvice值越低优先级越高)
*/
private int comparePrecedenceWithinAspect(Advisor advisor1, Advisor advisor2) {
boolean oneOrOtherIsAfterAdvice =
(AspectJAopUtils.isAfterAdvice(advisor1) || AspectJAopUtils.isAfterAdvice(advisor2));
int adviceDeclarationOrderDelta = getAspectDeclarationOrder(advisor1) - getAspectDeclarationOrder(advisor2);
if (oneOrOtherIsAfterAdvice) {
// the advice declared last has higher precedence
if (adviceDeclarationOrderDelta < 0) {
// advice1 was declared before advice2
// so advice1 has lower precedence
return LOWER_PRECEDENCE;
}
else if (adviceDeclarationOrderDelta == 0) {
return SAME_PRECEDENCE;
}
else {
return HIGHER_PRECEDENCE;
}
}
else {
// the advice declared first has higher precedence
if (adviceDeclarationOrderDelta < 0) {
// advice1 was declared before advice2
// so advice1 has higher precedence
return HIGHER_PRECEDENCE;
}
else if (adviceDeclarationOrderDelta == 0) {
return SAME_PRECEDENCE;
}
else {
return LOWER_PRECEDENCE;
}
}
}
总结执行链顺序
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-abvOMySj-1689236128862)(…/spring/assets/aop.drawio)]