爱游戏全站app官网入口-爱游戏官网

spring aop 分享 -爱游戏全站app官网入口

2023-08-10,,

初级篇

aop是什么?

aspect-oriented programming (aop) 即面向切面编程。
简单来说,aop 是一种编程范式,允许我们模块化地定义横跨多个对象的行为。aop 可以帮助我们将应用程序的关注点分离,使得代码更加清晰、易于维护和扩展。

大白话:在方法执行前后运行指定代码,比如日志记录、事务开启/提交/回滚等。

为什么要aop?

aop可以帮助我们解决在代码中耦合度高的问题,让我们的代码更加模块化和易于维护。
具体来说,aop可以通过在运行时动态地将通用功能(例如日志记录、性能分析、事务管理)应用于多个模块,而无需修改它们的代码。这样可以避免代码重复和嵌套,提高代码的复用性和可维护性。
另外,在复杂的业务场景中,多个模块可能需要共享某些共同的功能,而aop可以让这些功能从模块中抽离出来,以便更好地进行组织和重用。
总的来说,aop可以让我们更好地实现代码的分离和聚合,从而获得更高效、更可靠的代码。

大白话:增强原方法的功能,解耦通用功能,透明化静默操作。

举例 事务切面切面:

增强原方法的功能:原本方法使用的是数据库连接默认的策略自动提交事务的,有了切面能够保证方法内同一事务了;

解耦通用功能:但是很多方法都需要做事务的控制,有了切面不需要我们每一个方法都加几行相同的代码;

透明化静默操作:方法本身需要知道我怎么开的事务?需要知道我什么时候多打印了日志吗?

伪代码:没有使用aop前,每个方法都要cv一遍打印方法执行日志

@override
public userpo findbyusername(@autotrim string username) {
log.info("execute findbyusername by username={}", username);
optional opt = userrepository.findone((root, query, criteriabuilder) -> criteriabuilder.equal(root.get("username"), username));
assert.istrue(opt.ispresent(), "没有找到用户");
userpo userpo = opt.get();
log.info("execute findbyusername by username={}; return {} ", username, userpo);
return userpo;
} @override
public userpo findbyemail(@autotrim string email) {
log.info("execute findbyemail by email={}", email);
optional opt = userrepository.findone((root, query, criteriabuilder) -> criteriabuilder.equal(root.get("email"), email));
assert.istrue(opt.ispresent(), "没有找到用户");
userpo userpo = opt.get();
log.info("execute findbyemail by email={}; return {} ", email, userpo);
return userpo;
}

伪代码:使用aop后,无需关心方法执行日志的打印

@override
public userpo findbyusername(@autotrim string username) {
optional opt = userrepository.findone((root, query, criteriabuilder) -> criteriabuilder.equal(root.get("username"), username));
assert.istrue(opt.ispresent(), "没有找到用户");
return opt.get();
} @override
public userpo findbyemail(@autotrim string email) {
optional opt = userrepository.findone((root, query, criteriabuilder) -> criteriabuilder.equal(root.get("email"), email));
assert.istrue(opt.ispresent(), "没有找到用户");
return opt.get();
}

此处代码演示 【part1】
演示内容:

展示未使用aop前,使用userserviceimpl注入bean,测试接口查看日志打印;
展示使用aop后,使用userserviceaopimpl注入bean,测试接口查看日志打印;
展示切面打印非项目内的类方法执行日志,切换pointcutlogpointcut2()
展示灵活使用配置,控制切面开启日志,开启@conditionalonproperty,修改yml文件;例如:测试环境要开启日志,生产环境要关闭日志,通过配置灵活控制。

ps:我们在此暂不考虑基于xmlapplicationcontext系列;

怎么实现aop?

aop的实现依赖于多态和动态代理。
为了更好的理解,我们可以先举例一个静态代理的类来分析。
此处代码演示 【part2】
演示内容:

展示打印日志的静态代理,使用userservicestaticaopimpl注入bean,测试接口查看日志打印;
展示多种通知类型的静态代理,使用userservicestaticaopanyimpl注入bean,测试接口查看日志打印;

ps:我们在此暂不考虑基于xmlapplicationcontext系列;

aop的代码结构与核心概念

静态代理的aop结构(简单易理解版):

aspect

切面:指横跨多个类的一个关注点,它与不同类中相似的方法相对应。如保存数据时添加日志,可以新建一个切面配置日志记录逻辑。

大白话:一个模块化的切面程序,也可以理解为是一个实现切面功能的类;

用@aspect定义的bean class,或者spring xml配置里的aop:aspect标签:



...


join point

连接点:指在应用程序执行过程中的某个特定位置,如方法调用或异常处理等。

可以理解为要切面的对象类型,比如要加切面的目标是构造器,或者一个方法,或者是一个属性的赋值;

aspectj中可以有很多种,详见aspectj join points;

springaop中只有一种,就是方法执行(method execution);

advice

通知:指在切面的某个连接点上执行的代码。通知有许多类型,包括“前置通知”、“后置通知”、“返回通知”、“异常通知”、“环绕通知”,其中“环绕通知”能够完全控制目标方法的执行。

pointcut

切入点:指一个或多个连接点,通常定义在一个正则表达式中,描述哪些方法会被拦截。

参考:

join points and pointcuts

spring 之aop aspectj切入点语法详解

例:

within(com.supalle.springaop..*.userserviceaopimpl)

execution(* com.supalle.springaop.*.*(..))

within(com.supalle.springaop.testcontroller) && @annotation(com.supalle.springaop.log)

其它

introduction
target
aop proxy
weaving

详见:aop concepts

spring aop常用的方式

此处代码演示 【part3】

@aspect

xmlapplicationcontext的应用需要开启

annotationconfigapplicationcontext的应用需要开启@enableaspectjautoproxy;springboot环境下可以不用,在 org.springframework.boot.autoconfigure.aop.aopautoconfiguration里已经默认启用;

package com.supalle.springaop.aspect;
import lombok.extern.slf4j.slf4j;
import org.aspectj.lang.joinpoint;
import org.aspectj.lang.proceedingjoinpoint;
import org.aspectj.lang.signature;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.methodsignature;
import org.slf4j.logger;
import org.slf4j.loggerfactory;
import org.springframework.core.annotation.order;
import org.springframework.stereotype.component; import java.util.stream.collectors;
import java.util.stream.intstream; /**
* 日志切面
*
* @author supalle
* @see {@link https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#aop-pointcuts}
*/
@slf4j
@aspect
@component
//@conditionalonproperty(value = "log-execution", havingvalue = "true")
public class logaspect { @pointcut("within(com.supalle.springaop..*.userserviceaopimpl)")
// the pointcut within *aopimpl
private void logpointcut() {
} @pointcut("within(com.supalle.springaop..*.userserviceaopimpl) || within(com.zaxxer.hikari.hikaridatasource)")
private void logpointcut2() {
} @before("logpointcut()")
public void beforelog(joinpoint joinpoint) {
log.info("before advice 1 execute {}", joinpoint.getsignature().getname());
} // 同一个aspect内,@order排序无效,需要靠方法名排序,比如把当前方法改为beforelog2,就能运行在beforelog前面
@before("logpointcut()")
public void beforelog2(joinpoint joinpoint) {
log.info("before advice 2 execute {}", joinpoint.getsignature().getname());
} @afterreturning(value = "logpointcut()", returning = "returnvalue")
public void afterreturninglog(joinpoint joinpoint, object returnvalue) {
log.info("afterreturning advice execute {}; {} ", joinpoint.getsignature().getname(), returnvalue);
} @afterthrowing(value = "logpointcut()", throwing = "throwable")
public void afterthrowinglog(joinpoint joinpoint, throwable throwable) {
log.info("afterthrowing advice execute {}; {}", joinpoint.getsignature().getname(), throwable.getmessage());
} @after("logpointcut()")
public void afterlog(joinpoint joinpoint) {
log.info("after advice execute {}", joinpoint.getsignature().getname());
} @around("logpointcut()")
public object aroundlog(proceedingjoinpoint joinpoint) throws throwable {
signature joinpointsignature = joinpoint.getsignature();
string name = joinpointsignature.getname();
logger logger = loggerfactory.getlogger(joinpointsignature.getdeclaringtypename()); object[] args = joinpoint.getargs();
string[] argnames = ((methodsignature) joinpointsignature).getparameternames();
string argstring = "";
if (args != null && args.length > 0) {
argstring = " by " intstream.range(0, args.length)
.maptoobj(index -> argnames[index] "=" args[index])
.collect(collectors.joining(" , "));
} logger.info("around advice execute {}{}", name, argstring);
object returnvalue = joinpoint.proceed();
logger.info("around advice execute {}{}; return {} ", name, argstring, returnvalue); return returnvalue;
} }
xml配置

advisor

org.springframework.aop.framework.autoproxy.abstractadvisorautoproxycreator#findeligibleadvisors中会查找容器中所有的advisor类型bean,也属于autoproxy的支持;

logbeforeadvice:定义一个advice,加@component注册为bean

package com.supalle.springaop.advice;
import lombok.extern.slf4j.slf4j;
import org.springframework.aop.methodbeforeadvice;
import org.springframework.stereotype.component; import java.lang.reflect.method; /**
* org.springframework.aop.framework.adapter.defaultadvisoradapterregistry的支持
*/
@slf4j
@component
public class logbeforeadvice implements methodbeforeadvice { @override
public void before(method method, object[] args, object target) throws throwable {
log.info("logbeforeadvice execute {}", method.getname());
} }

logbeforeadvisor:定义一个advisor,加@component注册为bean

package com.supalle.springaop.advisor;
import com.supalle.springaop.advice.logbeforeadvice;
import lombok.requiredargsconstructor;
import org.aopalliance.aop.advice;
import org.springframework.aop.classfilter;
import org.springframework.aop.methodmatcher;
import org.springframework.aop.pointcut;
import org.springframework.aop.support.abstractpointcutadvisor;
import org.springframework.stereotype.component; @component
@requiredargsconstructor
public class logbeforeadvisor extends abstractpointcutadvisor {
private final logbeforeadvice advice; @override
public pointcut getpointcut() {
pointcut pointcut = new pointcut() {
@override
public classfilter getclassfilter() {
return clazz -> "userserviceaopimpl".equals(clazz.getsimplename());
} @override
public methodmatcher getmethodmatcher() {
return methodmatcher.true;
}
};
return pointcut;
} @override
public advice getadvice() {
return advice;
}
}

运行代码后调试接口,看控制台打印输出:

: logbeforeadvice  execute findbyusername
程序员创建

用的不多,可以参考programmatic creation of @aspectj proxies

进阶篇

spring aop与动态代理

动态代理

动态代理是指在程序运行时动态生成代理类的技术,即不需要手工编写代理类的源代码,而是在程序运行期间通过反射等机制动态地生成。

动态代理模式可以帮助我们减少重复的代码,提高代码的可维护性和可扩展性。通常情况下,我们都是通过实现接口来创建代理对象,但是如果一个类没有实现任何接口,我们仍然可以通过动态代理来创建代理对象。动态代理模式适用于一些横切关注点(cross-cutting concerns)的处理,例如日志、安全、事务等功能。

在 java 中,动态代理模式主要有两种实现方式:

基于 jdk 动态代理(jdk dynamic proxy):jdk 提供了一个 java.lang.reflect.proxy 类,可以动态地创建实现一组给定接口的代理类。要求被代理对象必须实现至少一个接口,并通过 proxy 类的静态方法 newproxyinstance 来创建代理对象。

基于 cglib 动态代理:cglib(code generation library) 是一个基于 asm(java 字节码操作框架)的高性能字节码生成库,可以在运行时动态生成字节码,并生成对应的代理类。要求被代理对象必须有默认构造函数,并通过 cglib 库提供的代理工厂(enhancer 类)来创建代理对象。

总之,动态代理模式可以帮助我们在程序运行期间动态地生成代理类,从而达到增强功能、添加处理逻辑等目的。

spring aop

spring aop 就是动态代理的一种落地实现,底层是通过jdk proxy和cglib&asm策略来进行动态生成代理类;
spring native应用下实现aop的话,刚出来的时候我看过一遍,因为无法动态生成字节码,所以是在aot编译时,给应用中所有的类都生成了一个代理类,当这个类在实际运行时需要动态代理时,则加载这个代理类直接使用。(时过境迁,现在不知道实现方式变了没有)

aot(ahead-of-time )compiler:预先编译器;

jit(just-in-time)compiler:即时编译器;

spring aop的结构与核心类

除了上文说的aop基本概念在spring中有对应的类外,spring aop apis中还有其它几个核心类,用于具体的aop实现。

advised:代理类的顶层接口,包含委托对象和其信息,以及应用的advisor的集合;
advisor:一个pointcut advice的组合;
aopproxy:aop具体的代理实现,可能是jdk proxy,也可能是cglib proxy;

jdkdynamicaopproxy
objenesiscglibaopproxy

org.springframework.aop.framework.defaultaopproxyfactory#createaopproxy

用uml画出他们的结构图如下:

userservice为例,spring aop生成的代理类大致结构:

spring aop的调用链

仔细思考,上文举例的静态代理实现aop的代码,是不够健全的,尤其是在advice的顺序上;
此处代码演示 【part2】:beforeaspects切面中添加 system.out.println(1/0);使其抛出异常
引用spring官方文档的原文:

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.

这句“many aop frameworks, including spring, model an advice as an interceptor and maintain a chain of interceptors around the join point.”翻译过来就是“包括spring在内的许多aop框架都将advice建模为拦截器,并在连接点周围维护拦截器链。”
很好,使用类似于javax.servlet.filter拦截器的方式,就可以很好的控制advice的执行顺序。

详见:org.springframework.aop.framework.advisedsupport#getinterceptorsanddynamicinterceptionadvice

时序图大致如下:

advisor/advice的执行顺序

调用链的顺序,基于advised中的advisors集合顺序,主要的排序策略如下:

    advisororder值,没有则取adviceorder值,还没有则为null,约等于最低优先级;
    同一@aspect类中定义多个advice时,先按around, before, after, afterreturning, afterthrowing排序,再按方法名排序;
    同一个advice类实现多个advice类型时,按 methodinterceptorbeforeadviceafterreturningadviceafterthrowsadvice排序;

通过abstractadvisingbeanpostprocessor的后置处理器添加的advisor,后置处理器可以通过beforeexistingadvisors属性控制是否排序在所有已经存在的advisor前;

spring官方文档:advice ordering
排序advisors:org.springframework.aop.aspectj.autoproxy.aspectjawareadvisorautoproxycreator#sortadvisors
排序同一aspect中的advice:org.springframework.aop.aspectj.annotation.reflectiveaspectjadvisorfactory#getadvisormethods

ps:如果advisor/advice是实现了ordered接口的话,@order注解是不生效的。

spring aop添加advisor的途径

proxyconfig下分几个家族,都可以添加advisor,创建aop代理类。

proxyprocessorsupport家族

abstractadvisingbeanpostprocessor
abstractautoproxycreator

advisedsupport家族

proxyfactorybean

abstractsingletonproxyfactorybean家族

scopedproxyfactorybean家族

常见的advisor & advice

asyncannotationadvisor & asyncexecutioninterceptor

排序:ordered=ordered.highest_precedence; // integer.min_value ;方法内写死,继承才可以修改

注意:asyncannotationadvisor是在asyncannotationbeanpostprocessor后置处理器中创建和匹配,beforeexistingadvisors=true;排序在所有已经存在的advisor前。

spring异步执行的aop支持。

asyncannotationadvisor & annotationasyncexecutioninterceptor

排序:ordered=ordered.highest_precedence; // 继承自父类asyncexecutioninterceptor

spring注解@async异步执行的aop支持。

retryconfiguration & annotationawareretryoperationsinterceptor

排序:ordered=ordered.lowest_precedence; // 继承自父类abstractpointcutadvisor,可set;

注意:retryconfiguration实现introductionadvisor,排序优先于同order的未实现introductionadvisor的advisor。

spring注解@retryable方法失败重试的aop支持。

beanfactorycacheoperationsourceadvisor & cacheinterceptor

排序:ordered=ordered.lowest_precedence; // 继承自父类abstractpointcutadvisor,可set,

默认来自@enablecachingorder属性,也是lowest_precedence,可以直接在注解里修改。

spring注解缓存@enablecaching@cacheable等的aop支持。

beanfactorytransactionattributesourceadvisor & transactioninterceptor

排序:ordered=ordered.lowest_precedence; // 继承自父类abstractpointcutadvisor,可set,

默认来自@enabletransactionmanagementorder属性,也是lowest_precedence,可以直接在注解里修改。

spring注解事务@transactional的aop支持。

高级篇

spring自动创建aop代理的时机

spring aop代理类创建的时机,主要是在bean被引用之前,依靠beanpostprocessor来实现;
这就要靠proxyprocessorsupport家族的abstractadvisingbeanpostprocessorabstractautoproxycreator了。

abstractadvisingbeanpostprocessor对单个advisor进行aop切面的添加;
abstractautoproxycreator对所有满足pointcut的bean进行切面的添加;

两者大部分情况会在postprocessafterinitialization()钩子方法中创建bean的aop代理对象,但也有一些特殊情况,比如自定义targetsource的时候,会在postprocessbeforeinstantiation()钩子方法中创建代理对象;
还有一种情况是在getearlybeanreference()钩子方法时创建,也会提前创建好aop代理对象;

详见源码:

org.springframework.aop.framework.abstractadvisingbeanpostprocessor#postprocessafterinitialization
org.springframework.aop.framework.autoproxy.abstractautoproxycreator#postprocessafterinitialization

另外注释一下:postprocessbeforeinstantiation()钩子执行时,bean已经完成了 创建对象、属性填充、依赖注入、初始化方法的执行;

其它自动创建的还有proxyfactorybeanabstractsingletonproxyfactorybeanscopedproxyfactorybean的派生类也会在初始化bean时自动创建;

为什么beanpostprocessor可能无法使用aop的功能

spring ioc容器在扫描所有的beandefinition后,会先按顺序初始化完所有的beanpostprocessor,在初始化其它普通的bean;而abstractadvisingbeanpostprocessorabstractautoproxycreator这两个处理自动aop的后置处理器排序都是 ordered=lowest_precedence,因此,在这两后置处理器之前初始化的bean都无法被他们进行自动aop代理。

customizing beans by using a beanpostprocessor

spring官方提示:

beanpostprocessor instances and aop auto-proxying

classes that implement the beanpostprocessor interface are special and are treated differently by the container. all beanpostprocessor instances and beans that they directly reference are instantiated on startup, as part of the special startup phase of the applicationcontext. next, all beanpostprocessor instances are registered in a sorted fashion and applied to all further beans in the container. because aop auto-proxying is implemented as a beanpostprocessor itself, neither beanpostprocessor instances nor the beans they directly reference are eligible for auto-proxying and, thus, do not have aspects woven into them.

源码详见:org.springframework.context.support.postprocessorregistrationdelegate#registerbeanpostprocessors

spring如何防止反复创建aop代理类

    首先abstractautoproxycreator中,有三个map对象保存aop代理类的创建信息,分别是 targetsourcedbeansadvisedbeansearlyproxyreferences,当三者内存在bean的代理对象时,则不会重复创建;
    abstractadvisingbeanpostprocessor中会判断 bean instanceof advised,如果已经是aop代理对象,则不会再创建新的代理对象,直接在此代理对象上添加advisor

spring aop与常说的spring三级缓存的关系

常说的spring三级缓存指的是org.springframework.beans.factory.support.defaultsingletonbeanregistry类中的三个map类型的属性,分别是:

map singletonobjects; // 存放初始化完成的单例bean
map earlysingletonobjects; // 存放getearlybeanreference()钩子创建的早期bean
map> singletonfactories; // 存放earlysingletonobject的工厂实例

ioc默认开启允许循环引用,但是在依赖注入时,但是spring并不想把创建aop代理的行为提前,因此设计了getearlybeanreference钩子和singletonfactoriesearlysingletonobjects,以支持循环引用。
在实例化bean后,先创建出earlysingletonobject的工厂实例,以备后续依赖注入的其它bean引用当前bean,当真实存在循环依赖时,调用earlysingletonobject的工厂的方法构建早期bean供其注入,并将这个早期bean存放到earlysingletonobjects中,以备循环依赖不止一次;最终在当前bean初始化完成后,移除早期bean,将最终的bean放入singletonfactories中,成为正式暴露的bean实例;
org.springframework.beans.factory.support.abstractautowirecapablebeanfactory#docreatebean

// eagerly cache singletons to be able to resolve circular references
// even when triggered by lifecycle interfaces like beanfactoryaware.
boolean earlysingletonexposure = (mbd.issingleton() && this.allowcircularreferences &&
issingletoncurrentlyincreation(beanname));
if (earlysingletonexposure) {
if (logger.istraceenabled()) {
logger.trace("eagerly caching bean '" beanname
"' to allow for resolving potential circular references");
}
addsingletonfactory(beanname, () -> getearlybeanreference(beanname, mbd, bean));
}

org.springframework.beans.factory.support.abstractbeanfactory#dogetbean

// eagerly check singleton cache for manually registered singletons.
object sharedinstance = getsingleton(beanname);

org.springframework.beans.factory.support.defaultsingletonbeanregistry#getsingleton(java.lang.string, boolean)

@nullable
protected object getsingleton(string beanname, boolean allowearlyreference) {
// quick check for existing instance without full singleton lock
object singletonobject = this.singletonobjects.get(beanname);
if (singletonobject == null && issingletoncurrentlyincreation(beanname)) {
singletonobject = this.earlysingletonobjects.get(beanname);
if (singletonobject == null && allowearlyreference) {
synchronized (this.singletonobjects) {
// consistent creation of early reference within full singleton lock
singletonobject = this.singletonobjects.get(beanname);
if (singletonobject == null) {
singletonobject = this.earlysingletonobjects.get(beanname);
if (singletonobject == null) {
objectfactory singletonfactory = this.singletonfactories.get(beanname);
if (singletonfactory != null) {
singletonobject = singletonfactory.getobject();
this.earlysingletonobjects.put(beanname, singletonobject);
this.singletonfactories.remove(beanname);
}
}
}
}
}
}
return singletonobject;
}

扩展篇

asm/cglib动态字节码生成

篇幅太长,略

spring缓存失效与aop的瓜葛

spring缓存切面主要依赖于aop的实现,当对象内进行this.dosomething()调用时,因为没有经过代理对象,所以也没有缓存切面的功能;

spring事务失效与aop的瓜葛

与缓存切面一样,spring事务切面主要依赖于aop的实现,当对象内进行this.dosomething()调用时,因为没有经过代理对象,所以也没有事务切面的功能;

synchronized和@transactional 使用时,出现的问题

因为事务切面会在执行方法之前开启事务,之后再加锁,当锁住的代码执行完成后,在提交事务,
因此synchronized代码块执行是在事务之内执行的,可以推断在代码块执行完时,事务还未提交,其他线程进入synchronized代码块后,读取的库存数据不是最新的。
解决上面的方法,比较简单的可以在update方法之前加上synchronizedq在还没有开事务之间就加锁,那么就可以保证线程同步

其他开发语言也可实现aop

javascript的proxy实现aop

思考篇

aop的优点

降低了代码的耦合度:aop可以将横切逻辑与业务逻辑分离,从而避免了代码的重复和冗余,提高了代码的可维护性和可读性。
提高了代码的复用性:aop可以将通用的横切逻辑抽象出来,作为一个切面,从而可以在不同的业务逻辑中重复使用,提高了代码的复用性。
提高了系统的可扩展性:aop可以动态地将横切逻辑织入到业务逻辑中,从而可以方便地添加新的功能,提高了系统的可扩展性。

aop的缺点

aop增加了系统的复杂度:aop需要额外的配置和代码,增加了系统的复杂度,降低了系统的可读性和可维护性。
aop可能会影响系统的性能:aop需要在运行时进行额外的处理,可能会影响系统的性能,尤其是在大规模应用中。
aop可能会引入新的问题:aop可能会引入新的问题,如死锁、并发问题等,需要特别注意。

使用aop的建议

不要把应该与业务耦合的代码放到切面中
注意切面的顺序
注意this调用可能导致aop失效

springboot项目中使用aop的最佳实践

spring aop 分享的相关教程结束。

网站地图