news 2026/2/24 4:33:25

Spring AOP低级切面到高级切面详解

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Spring AOP低级切面到高级切面详解

Aop基础

ps:欢迎指正错误,涉及了一点点Spring后置处理器的扫描的内容

@AspectstaticclassAspect1{@org.aspectj.lang.annotation.Before("execution(* foo())")publicvoidbefore1(){System.out.println("before1");}}

一个简单的aop类-spring advisor使用

元素说明
@Aspect声明这是一个切面类
@Before("execution(* foo())")定义增强逻辑在foo()方法执行之前触发
foo()方法执行这里就是连接点,也就是增强的“切入位置”
before1()方法这是通知(Advice),在连接点执行前执行

连接点实现

Pointcut用来描述“哪些类的哪些方法需要被代理增强”的规则。

public interface Pointcut { Pointcut TRUE = TruePointcut.INSTANCE; ​ ClassFilter getClassFilter(); // 过滤类 ​ MethodMatcher getMethodMatcher();// 过滤方法 } ClassFilter:决定哪些类匹配 MethodMatcher:决定哪些方法匹配 两者共同决定拦截范围。

切点表达式

切点表达式是在描述匹配规则

execution(*com.xxx.service.*.*(..))

哪些方法需要被织入 advice(通知)

把 method/class 匹配规则用代码封装成 Pointcut 对象而已





这是 AOP 执行链的核心:

每次方法被代理后,都会被包装成一次invoke()调用。

Advisor:Pointcut + MethodInterceptor

MethodInterceptor:增强逻辑(通知)
Proxy:基于 Advisor 判断是否拦截并执行 interceptor.invoke()

代理对象拦截方法 构造一个MethodInvocation调用 interceptor.invoke(invocation)在这个方法里面你可以写增强逻辑 真正执行目标方法是 invocation.proceed()
///通知@FunctionalInterfacepublicinterfaceMethodInterceptorextendsInterceptor{@NullableObjectinvoke(@NonnullMethodInvocationinvocation)throwsThrowable;}
publicclassLoggingInterceptorimplementsMethodInterceptor{//类似与逻辑点 增强处@OverridepublicObjectinvoke(MethodInvocationinvocation)throwsThrowable{System.out.println("方法执行前日志");// 前置增强Objectresult=invocation.proceed();// 调用目标方法System.out.println("方法执行后日志");returnresult;}}
调用方法 AOP 判断:此方法是否匹配Pointcut匹配 → 调用MethodInterceptor.invoke()invoke()内部调用proceed()执行目标方法proceed()执行完后再执行后置增强

//简单切面

1.流程1

方法调用流程

2.流传2

代理类组织阶段

3 流程3

切面组织流程

publicstaticvoidmain(String[]args){// 1. 备好切点AspectJExpressionPointcutpointcut=newAspectJExpressionPointcut();pointcut.setExpression("execution(* foo())");///表达式转为pointcut对象// 2. 备好通知MethodInterceptoradvice=invocation->{System.out.println("before...");Objectresult=invocation.proceed();// 调用目标System.out.println("after...");returnresult;};///通知也做到了 连接点(JoinPoint):被增强的方法执行点// 3. 备好切面DefaultPointcutAdvisoradvisor=newDefaultPointcutAdvisor(pointcut,advice);///切面 这是 AOP 的最小工作单元。Target2target=newTarget2();///配置代理工厂ProxyFactoryfactory=newProxyFactory();//设置目标factory.setTarget(target);//设置切面factory.addAdvisor(advisor);Target2proxy=(Target2)factory.getProxy();/* 4. 创建代理 a. proxyTargetClass = false, 目标实现了接口, 用 jdk 实现 b. proxyTargetClass = false, 目标没有实现接口, 用 cglib 实现 c. proxyTargetClass = true, 总是使用 cglib 实现 */System.out.println(proxy.getClass());proxy.foo();proxy.bar();}
class com.itheima.a15.A15$Target2$$EnhancerBySpringCGLIB$$ea384bdf before... target2 foo after... target2 bar
表达式解析 →PointcutMethodInterceptor→ 通知逻辑Pointcut+AdviceAdvisorProxyFactory↓ 创建代理对象(JDK/CGLIB) ↓ 调用方法 ↓ 匹配切点? 是 → 调用 advice.invoke()beforeproceed()→ 调用真实方法 after 否 → 直接执行真实方法
步骤序号原理流程描述关键类/方法逆向推断的实现细节与原理
1解析切点表达式AspectJExpressionPointcut利用 AspectJ 表达式解析器解析"execution(* foo())",生成能匹配方法的规则对象。
2构建通知实例MethodInterceptor(Lambda)编写通知逻辑,封装成拦截器,待会代理类方法调用时会执行它。
3封装切点与通知为 AdvisorDefaultPointcutAdvisorAdvisor 是切点+通知的组合体,方便统一管理和应用。
4初始化代理工厂ProxyFactory设置目标对象、Advisor 列表、代理类型(JDK或CGLIB),准备生成代理。
5选择代理实现方式AopProxyFactory+JdkDynamicAopProxy/ObjenesisCglibAopProxy根据目标类是否实现接口和配置,决定用 JDK 动态代理或 CGLIB 动态字节码生成代理类。
6动态生成代理类ProxyGenerator(JDK) 或 ASM/Objenesis (CGLIB)反编译/逆向:生成的代理类继承/实现目标类/接口,重写匹配方法,织入拦截器调用逻辑。
7代理类重写匹配方法逻辑代理类的invoke或方法重写调用MethodInterceptor.invoke(),执行通知前置增强,调用目标方法,执行后置增强。
8未匹配方法直接委托调用目标方法代理类的非匹配方法实现直接调用目标类对应方法,不执行通知增强。
9方法调用流程代理对象的调用链代理对象.foo() -> MethodInterceptor.invoke() -> invocation.proceed() -> 目标对象.foo()
10异常处理UndeclaredThrowableException拦截器捕获异常时封装成运行时异常,保证调用链异常统一处理。

代理实现选择

/* 4. 创建代理 a. proxyTargetClass = false, 目标实现了接口, 用 jdk 实现 b. proxyTargetClass = false, 目标没有实现接口, 用 cglib 实现 c. proxyTargetClass = true, 总是使用 cglib 实现 */

jdk代理使用

Target1target=newTarget1();ProxyFactoryfactory=newProxyFactory();factory.setTarget(target);factory.addAdvisor(advisor);factory.setInterfaces(target.getClass().getInterfaces());factory.setProxyTargetClass(false);I1proxy=(I1)factory.getProxy();System.out.println(proxy.getClass());proxy.foo();proxy.bar();
class com.itheima.a15.$Proxy2 before... target1 foo after... target1 bar
a. Spring 的代理选择规则 b. 底层的切点实现 c. 底层的通知实现 d. ProxyFactory 是用来创建代理的核心实现, 用 AopProxyFactory 选择具体代理实现 - JdkDynamicAopProxy - ObjenesisCglibAopProxy

准备aop

创建代理-代理原始类,创建切点 通知 放入创建的切面,由代理类来进行匹配,成功就调用切莫的通知内的代码,没有就调用元方法

切点匹配

切点匹配///1.根据方法名字增强AspectJExpressionPointcutpt1=newAspectJExpressionPointcut();pt1.setExpression("execution(* bar())");System.out.println(pt1.matches(T1.class.getMethod("foo"),T1.class));falseSystem.out.println(pt1.matches(T1.class.getMethod("bar"),T1.class));true
///根据注解匹配AspectJExpressionPointcutpt2=newAspectJExpressionPointcut();pt2.setExpression("@annotation(org.springframework.transaction.annotation.Transactional)");System.out.println(pt2.matches(T1.class.getMethod("foo"),T1.class));System.out.println(pt2.matches(T1.class.getMethod("bar"),T1.class));
staticclassT1{@Transactionalpublicvoidfoo(){}publicvoidbar(){}}

事务不是这样实现–只能匹配方法-事务有类和接口

事务实现扫描

staticclassT1{@Transactionalpublicvoidfoo(){}publicvoidbar(){}}@TransactionalstaticclassT2{publicvoidfoo(){}}@TransactionalinterfaceI3{voidfoo();}staticclassT3implementsI3{publicvoidfoo(){}}

实现注解扫描 重写matches方法

StaticMethodMatcherPointcutpt3=newStaticMethodMatcherPointcut(){@Overridepublicbooleanmatches(Methodmethod,Class<?>targetClass){// 检查方法上是否加了 Transactional 注解MergedAnnotationsannotations=MergedAnnotations.from(method);if(annotations.isPresent(Transactional.class)){returntrue;}// 查看类上是否加了 Transactional 注解annotations=MergedAnnotations.from(targetClass,MergedAnnotations.SearchStrategy.TYPE_HIERARCHY);if(annotations.isPresent(Transactional.class)){returntrue;}returnfalse;}};System.out.println(pt3.matches(T1.class.getMethod("foo"),T1.class));System.out.println(pt3.matches(T1.class.getMethod("bar"),T1.class));System.out.println(pt3.matches(T2.class.getMethod("foo"),T2.class));System.out.println(pt3.matches(T3.class.getMethod("foo"),T3.class));

从Aspect到advisor

高级的@aspect介绍

低级的advisor介绍

对比项高级切面(@Aspect低级切面(Advisor关系与转换
定义位置基于@AspectJ 注解或 XML 声明的“面向切面编程”方式Spring AOP 底层的切面实现接口/类(AdvisorPointcutAdvisor高级切面最终会被解析成一个或多个 Advisor 对象
开发难度简单直观:直接写普通 Java 类,配合@Before/@After/@Around等注解即可繁琐底层:需要手动实现MethodInterceptorPointcutAdvisor等接口Spring 在启动时会解析 @Aspect 并自动生成对应的 Advisor
切点表达使用@Pointcut+ AspectJ 表达式(execution(...)等)使用Pointcut接口或现成类(如AspectJExpressionPointcut@Pointcut会被转为AspectJExpressionPointcut实例
通知定义注解方式直接绑定(@Before@Around等)通过实现 Advice 接口(MethodBeforeAdviceAfterReturningAdviceMethodInterceptor每个注解通知会变成一个对应类型的 Advice 实例
运行机制开发者只看到“声明式”的切面类底层是 Advisor 持有一个 Advice + 一个 PointcutAdvisor = Pointcut + Advice
适用场景业务代码中快速实现切面逻辑框架开发、AOP 底层扩展,需要精细控制切面行为高级切面其实是低级 API 的语法糖
本质面向业务的声明式切面面向框架的底层切面高级切面 = 封装 + 自动生成低级切面对象

高级切面

本质还是转为低级切面处理

低级切面

1.切入点

// 1. 备好切点AspectJExpressionPointcutpointcut=newAspectJExpressionPointcut();pointcut.setExpression("execution(* foo())");// 2. 备好通知MethodInterceptoradvice=invocation->{System.out.println("before...");Objectresult=invocation.proceed();// 调用目标System.out.println("after...");returnresult;};// 3. 备好切面DefaultPointcutAdvisoradvisor=newDefaultPointcutAdvisor(pointcut,advice);切点-通知-切面

切面

通知

test

Target1target=newTarget1();ProxyFactoryfactory=newProxyFactory();factory.setTarget(target);factory.addAdvisor(advisor);factory.setInterfaces(target.getClass().getInterfaces());///如果 Target2 没有实现任何接口 → 会抛异常(JDK 代理没法用)。factory.setProxyTargetClass(false);I1proxy=(I1)factory.getProxy();System.out.println(proxy.getClass());proxy.foo();proxy.bar();

测试

// 代理能否重复被代理publicclassA15_1{publicstaticvoidmain(String[]args){GenericApplicationContextcontext=newGenericApplicationContext();context.registerBean("myConfig",MyConfig.class);context.registerBean(ConfigurationClassPostProcessor.class);context.registerBean("aspect1",Aspect1.class);context.registerBean(AnnotationAwareAspectJAutoProxyCreator.class);//切面扫描context.refresh();Bean1bean1=context.getBean(Bean1.class);bean1.foo();System.out.println(bean1.getClass());// 此代理是为了解决功能增强问题System.out.println(context.getBean("scopedTarget.bean1").getClass());}staticclassMyConfig{// 此代理是为了解决单例注入多例问题@Bean@Scope(proxyMode=ScopedProxyMode.TARGET_CLASS)publicBean1bean1(){returnnewBean1();}}staticclassBean1{publicvoidfoo(){System.out.println("bean1 foo");}}@Aspect//注解扫描staticclassAspect1{@Around("execution(* foo())")publicObjectbefore(ProceedingJoinPointpjp)throwsThrowable{System.out.println("aspect1 around");returnpjp.proceed();}}}

AnnotationAwareAspectJAutoProxyCreator 扫描了aop的注解

使用aop的后缀处理器**

Bean测试

GenericApplicationContextcontext=newGenericApplicationContext();context.registerBean("aspect1",Aspect1.class);context.registerBean("config",Config.class);context.registerBean(ConfigurationClassPostProcessor.class);context.registerBean(AnnotationAwareAspectJAutoProxyCreator.class);// BeanPostProcessor// 创建 -> (*) 依赖注入 -> 初始化 (*)context.refresh();

AnnotationAwareAspectJAutoProxyCreator切面类处理

bean后置处理器 作用-找到切面
高级切面转换低级切面
根据切面创建代理对象

发现处理器阶段

扩展点

创建 -> (*) 依赖注入 -> 初始化 (*)

主动调用bean处理器

作用 : 找到有资格的切面类-查找低级切面-高级切面需要转换

自己调用 查看哪些切面内匹配上了

AnnotationAwareAspectJAutoProxyCreatorcreator=context.getBean(AnnotationAwareAspectJAutoProxyCreator.class);List<Advisor>advisors=creator.findEligibleAdvisors(Target2.class,"target2");for(Advisoradvisor:advisors){System.out.println(advisor);}

创建代理
有资格的切面创建代理

第二个重要方法 wrapIfNecessary a. 它内部调用 findEligibleAdvisors, 只要返回集合不空, 则表示需要创建代理


Objecto1=creator.wrapIfNecessary(newTarget1(),"target1","target1");/// 它内部调用 findEligibleAdvisors, 只要返回集合不空, 则表示需要创建代理System.out.println(o1.getClass());Objecto2=creator.wrapIfNecessary(newTarget2(),"target2","target2");System.out.println(o2.getClass());((Target1)o1).foo();
classorg.springframework.aop.framework.autoproxy.A17$Target1$$EnhancerBySpringCGLIB$$5748c5d2//代理classorg.springframework.aop.framework.autoproxy.A17$Target2///原始aspect1 before1...aspect1 before2...advice3 before...target1 foo advice3 after...



代理创建调用

是否有必要创建代理

代理创建时间

创建 -> () 依赖注入 -> 初始化 ()

情况1

@Configuration@BeanpublicAdvisoradvisor(MethodInterceptoradvice){AspectJExpressionPointcutpointcut=newAspectJExpressionPointcut();pointcut.setExpression("execution(* foo())");returnnewDefaultPointcutAdvisor(pointcut,advice);}@BeanpublicMethodInterceptoradvice(){return(MethodInvocationinvocation)->{System.out.println("before...");returninvocation.proceed();};}@BeanpublicBean1bean1(){returnnewBean1();}@BeanpublicBean2bean2(){returnnewBean2();}}//代理staticclassBean1{publicvoidfoo(){}publicBean1(){System.out.println("Bean1()");}@PostConstructpublicvoidinit(){//初始化System.out.println("Bean1 init()");}}staticclassBean2{publicBean2(){System.out.println("Bean2()");}@AutowiredpublicvoidsetBean1(Bean1bean1){System.out.println("Bean2 setBean1(bean1) class is: "+bean1.getClass());}@PostConstructpublicvoidinit(){System.out.println("Bean2 init()");}}publicstaticvoidmain(String[]args){GenericApplicationContextcontext=newGenericApplicationContext();context.registerBean(ConfigurationClassPostProcessor.class);context.registerBean(Config.class);//配置类context.refresh();context.close();}
Bean1()构造Bean1init()初始化[TRACE]17:15:47.625[main]o.s.a.a.a.AnnotationAwareAspectJAutoProxyCreator-Creatingimplicit proxyforbean'bean1'with0common interceptors and2specific interceptors/// 创建了bean1的代理对象,并且放入容器中Bean2()Bean2setBean1(bean1)classis:classorg.springframework.aop.framework.autoproxy.A17_1$Bean1$$EnhancerBySpringCGLIB$$2fba3bac/// 把bean1的代理对象注入进去Bean2init()初始化

情况2

代理创建时机

循环依赖

Bean1()Bean2()[TRACE]17:17:14.751[main]o.s.a.a.a.AnnotationAwareAspectJAutoProxyCreator-Creatingimplicit proxyforbean'bean1'with0common interceptors and2specific interceptorsBean2setBean1(bean1)classis:classorg.springframework.aop.framework.autoproxy.A17_1$Bean1$$EnhancerBySpringCGLIB$$df1706a0//注入代理对象bean1 bean1提前创建,再初始化前Bean2init()///Bean1setBean2(bean2)classis:classorg.springframework.aop.framework.autoproxy.A17_1$Bean2///再次注入bean2Bean1init()

使用了延迟加载技术,也就是搭了个壳子,但是内容不完整,.之后大家创建好了,再补回去

order注解

@Aspect// 高级切面类@Order(1)staticclassAspect1{@Before("execution(* foo())")publicvoidbefore1(){System.out.println("aspect1 before1...");}@Before("execution(* foo())")publicvoidbefore2(){System.out.println("aspect1 before2...");}}
@Bean// 低级切面publicAdvisoradvisor3(MethodInterceptoradvice3){AspectJExpressionPointcutpointcut=newAspectJExpressionPointcut();pointcut.setExpression("execution(* foo())");DefaultPointcutAdvisoradvisor=newDefaultPointcutAdvisor(pointcut,advice3);advisor.setOrder(0);returnadvisor;}

控制切面顺序 @Order(1) advisor.setOrder(0);

默认低级优先级高

高级切面转换低级切面

定义通知和切面连接点

staticclassAspect{@Before("execution(* foo())")publicvoidbefore1(){System.out.println("before1");}@Before("execution(* foo())")publicvoidbefore2(){System.out.println("before2");}}

test测试

找到类-解析切点,创建切点表达式, 创建通知 创建切面类

publicstaticvoidmain(String[]args)throwsThrowable{//切面实例工厂AspectInstanceFactoryfactory=newSingletonAspectInstanceFactory(newAspect());// 高级切面转低级切面类List<Advisor>list=newArrayList<>();for(Methodmethod:Aspect.class.getDeclaredMethods()){//方法解析if(method.isAnnotationPresent(Before.class)){// 解析切点Stringexpression=method.getAnnotation(Before.class).value();AspectJExpressionPointcutpointcut=newAspectJExpressionPointcut();pointcut.setExpression(expression);//设计切点// 通知类的创建--前置通知AspectJMethodBeforeAdviceadvice=newAspectJMethodBeforeAdvice(method,pointcut,factory);// 切面Advisoradvisor=newDefaultPointcutAdvisor(pointcut,advice);list.add(advisor);}}for(Advisoradvisor:list){System.out.println(advisor);}}
org.springframework.aop.support.DefaultPointcutAdvisor: pointcut [AspectJExpressionPointcut: () execution(* foo())]; advice [org.springframework.aop.aspectj.AspectJMethodBeforeAdvice: advice method [public void org.springframework.aop.framework.autoproxy.A17_2$Aspect.before1()]; aspect name ''] ------------------------------------------ org.springframework.aop.support.DefaultPointcutAdvisor: pointcut [AspectJExpressionPointcut: () execution(* foo())]; advice [org.springframework.aop.aspectj.AspectJMethodBeforeAdvice: advice method [public void org.springframework.aop.framework.autoproxy.A17_2$Aspect.before2()]; aspect name ''] ------------------------------------------

都从高级切面转换成了低级切面

静态通知调用

A18代码
知识点说明关键类 / 接口作用与意义
高级切面 → 低级切面@Before、@AfterReturning、@Around 等注解切面,运行时会被解析为Advisor(包含切点 Pointcut 和通知 Advice)AdvisorPointcutAdviceAspectInstanceFactory高级注解只是语法糖,底层统一转换成低级切面对象,方便后续代理机制处理
通知类型与实现类不同注解对应不同 Advice 实现类AspectJMethodBeforeAdvice(前置)、AspectJAfterReturningAdvice(返回后)、AspectJAroundAdvice(环绕)用具体类封装通知逻辑和切点表达式
统一适配为环绕通知Spring 内部会把所有通知转成MethodInterceptor(环绕调用形式)MethodBeforeAdviceAdapterAfterReturningAdviceAdapter适配器模式:对外保留多种注解,对内统一为 MethodInterceptor,方便调用链执行
调用链机制多个 Advice 嵌套调用目标方法,形成责任链MethodInvocationReflectiveMethodInvocation每个环绕通知调用proceed()进入下一个通知,直到目标方法执行
ExposeInvocationInterceptor在当前线程暴露 MethodInvocation 对象ExposeInvocationInterceptor.INSTANCE让 Before、After 类型的 Advice 可以获取当前调用上下文
动态拦截器获取代理工厂根据方法和类获取执行时的拦截器列表proxyFactory.getInterceptorsAndDynamicInterceptionAdvice(...)在方法调用前,组装出一条包含所有环绕通知的执行链
设计模式应用使用适配器模式和责任链模式Adapter Pattern、Chain of Responsibility适配器用于统一不同通知类型,责任链用于按顺序执行多个通知

其实无论ProxyFactory基于哪种方式创建代理,最后干活(调用 advice)的是一个MethodInvocation对象 a.因为 advisor 有多个,且一个套一个调用,因此需要一个调用链对象,MethodInvocationb.MethodInvocation要知道 advice 有哪些,还要知道目标,调用次序如下 将MethodInvocation放入当前线程|->before1-----------------------------------从当前线程获取MethodInvocation||||->before2--------------------|从当前线程获取MethodInvocation|||||||->target------目标 advice2 advice1||||||->after2---------------------||||->after1------------------------------------c.从上图看出,环绕通知才适合作为 advice,因此其他 before、afterReturning 都会被转换成环绕通知 d.统一转换为环绕通知,体现的是设计模式中的适配器模式-对外是为了方便使用要区分 before、afterReturning-对内统一都是环绕通知,统一用MethodInterceptor表示
18_1
知识点说明关键类 / 方法作用与意义
MethodInvocation 接口统一抽象方法调用过程,包含目标方法、参数、调用链控制等proceed()getMethod()getArguments()封装一次“方法调用”的上下文,支持多层拦截器嵌套
MethodInterceptor 接口环绕通知的统一接口,负责在调用前后织入逻辑invoke(MethodInvocation)用于在目标方法执行前后加入增强逻辑(before/after)
递归调用链proceed()依次调用下一个拦截器;拦截器内部再次调用proceed()MyInvocation.proceed()使用递归(或迭代)模型处理多层嵌套,最终调用目标方法
终止条件调用计数大于拦截器数量时执行目标方法if (count > methodInterceptorList.size())防止无限递归,保证链条最后能到目标方法
顺序控制count++保证按添加顺序调用每个拦截器计数器变量count确保责任链从第一个到最后一个依次执行
a19
分类特点运行时表现核心类 / 接口性能影响适用场景
静态通知调用切点表达式不带参数绑定(例:execution(* foo(..))执行时直接调用拦截器,不需要动态匹配MethodInterceptor高(无额外匹配开销)方法签名固定,不需要访问实参
动态通知调用切点表达式带参数绑定(例:execution(* foo(..)) && args(x)执行时需先执行切点匹配(MethodMatcher.matches())并绑定参数,再调用拦截器InterceptorAndDynamicMethodMatcherMethodMatcherMethodInterceptor低(多一次匹配与绑定步骤)需要访问方法实参或按参数条件增强的方法
切面解析器解析@Aspect并生成 Advisor自动扫描切面类,创建切点和通知AnnotationAwareAspectJAutoProxyCreator-Spring 自动代理机制核心
运行时动态封装对动态通知,Spring 会将切点对象与拦截器封装在一起执行时先匹配后调用InterceptorAndDynamicMethodMatcher略低动态通知必需
调试工具方法反射查看InterceptorAndDynamicMethodMatcher内部结构可看到methodMatchermethodInterceptor反射 API-分析 Spring AOP 内部执行机制
性能建议高频方法使用静态通知,减少运行时匹配开销--性能敏感场景中减少动态通知高频调用、性能敏感的业务逻辑
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/2/23 13:24:32

如何快速掌握沉浸式翻译?5个终极技巧让阅读效率提升300%

如何快速掌握沉浸式翻译&#xff1f;5个终极技巧让阅读效率提升300% 【免费下载链接】immersive-translate 沉浸式双语网页翻译扩展 , 支持输入框翻译&#xff0c; 鼠标悬停翻译&#xff0c; PDF, Epub, 字幕文件, TXT 文件翻译 - Immersive Dual Web Page Translation Extensi…

作者头像 李华
网站建设 2026/2/22 23:34:25

69、Subversion与GNU make实用指南

Subversion与GNU make实用指南 1. Subversion管理工具概述 Subversion是一款强大的版本控制系统,拥有多种管理工具,用于不同方面的操作,如仓库管理、信息查看、远程访问等。下面将详细介绍这些工具及其用法。 2. 仓库管理工具svnadmin svnadmin是用于监控和修复Subversi…

作者头像 李华
网站建设 2026/2/20 13:11:05

Linux环境下的C语言编程(四十一)

一、队列时间复杂度分析1. 链队列时间复杂度// 链队列节点 typedef struct QueueNode {int data;struct QueueNode* next; // 额外指针开销 } QueueNode;// 链队列结构 typedef struct {QueueNode* front; // 队头指针QueueNode* rear; // 队尾指针 } LinkedQueue;入队操作…

作者头像 李华
网站建设 2026/2/20 3:35:10

java计算机毕业设计人事管理系统的设计与实现 基于SpringBoot的教职工综合信息管理平台 面向高校的人事与薪酬一体化服务系统

计算机毕业设计人事管理系统的设计与实现49zx59&#xff08;配套有源码 程序 mysql数据库 论文&#xff09; 本套源码可以在文本联xi,先看具体系统功能演示视频领取&#xff0c;可分享源码参考。 高校人事科每天都在“三线作战”&#xff1a;纸质档案翻查、Excel工资条拼接、微…

作者头像 李华
网站建设 2026/2/13 18:41:10

DNA和蛋白质序列分析

DNA和蛋白质序列分析DNA和蛋白质序列分析是生物学研究中关键的技术手段&#xff0c;涉及通过测序、比对和解析基因组DNA以及由基因编码的蛋白质序列&#xff0c;进而揭示生命体的遗传信息及其生物学功能。DNA序列分析主要用于解读基因组中携带的遗传信息&#xff0c;包括基因突…

作者头像 李华