Spring-AOP分析
Spring分析-AOP
1.案例引入
在上一篇文章中,【Spring–IOC】【https://www.cnblogs.com/jackjavacpp/p/18829545】,我们了解到了IOC容器的创建过程,在文末也提到了AOP相关,但是没有作细致分析,这篇文章就结合示例,来详细分析一下Spring-AOP。
本文章示例代码见该仓库:【spring】中的“spring”模块。
仓库地址:https://gitee.com/quercus-sp204/sourcecode-and-demos
本节AOP示例代码如下:[ 在springaop 包下、然后测试类就是Main类里面的aop()方法 ]
@Component("dog")
public class Dog {public Dog() { }public void wangwang() {System.out.println("wangwang --- 狗");}
}public interface Life {void create();void wangwang();
}@Component(value = "lifeImpl")
public class LifeImpl implements Life {@Overridepublic void create() {System.out.println("[]--生命创建");}@Overridepublic void wangwang() {System.out.println("[]--生命 汪汪汪");}public void wangwang( String msg ) {System.out.println("=========== " + msg);create(); wangwang();System.out.println("===========");}
}
// advice.java
@Component
@Aspect
public class MyAdvice {private static final String dogExpression = "execution(* com.feng.springaop.*.wangwang*(..))";@Around(dogExpression)public Object aroundAdvice(ProceedingJoinPoint joinPoint) throws Throwable {System.out.println("##########【DOG-环绕通知中的前置通知】##########");Object returnVale = joinPoint.proceed();System.out.println("##########【DOG-环绕通知中的后置通知】##########");return returnVale;}
}
// 一个普通的,没有配置任何通知
@Component
public class Cat {public Cat() { }public void miaomiao() {System.out.println("猫 喵喵");}
}
//Main.java
@Configuration
// @ComponentScan("com.feng.springioc") // 循环依赖分析 && IOC分析
@ComponentScan("com.feng.springaop") // spring-aop分析
@EnableAspectJAutoProxy
//@EnableAspectJAutoProxy(proxyTargetClass = true) // cglib
public class Main {public static void main(String[] args) {// xunhuan();aop();}public static void aop() {ApplicationContext context = new AnnotationConfigApplicationContext(Main.class);Dog dog = context.getBean("dog", Dog.class);dog.wangwang();System.out.println(dog.getClass().getName());Life impl = context.getBean("lifeImpl", Life.class);impl.wangwang();System.out.println(impl.getClass().getName());Cat cat = context.getBean("cat", Cat.class);cat.miaomiao();System.out.println(cat.getClass().getName());}
}
上面的示例代码挺简单的,运行上面的测试代码,我们可以得到如下输出:
##########【DOG-环绕通知中的前置通知】##########
wangwang --- 狗
##########【DOG-环绕通知中的后置通知】##########
com.feng.springaop.Dog$$EnhancerBySpringCGLIB$$563f1145
##########【DOG-环绕通知中的前置通知】##########
[]--生命 汪汪汪
##########【DOG-环绕通知中的后置通知】##########
com.sun.proxy.$Proxy17
猫 喵喵
com.feng.springaop.Cat
从输出内容可以看到,dog
对象的bean是走的cglib的动态代理,由于lifeImpl
实现了接口,故其采用的是jdk动态代理,但是猫猫确实是一个实打实的我们的对象。
动态代理不知道的可以看这篇文章:【动态代理】:https://blog.csdn.net/okok__TXF/article/details/144191784
可以得知是创建了代理对象,然后执行就是将“通知”和实际“执行的逻辑”组合在一起了,那么我们就从SpringAOP 是如何创建代理对象、执行过程是什么样子这两个方面来分析一下其AOP。
2.代理对象的创建
在前一篇文章中,我们得知,代理对象的创建是在initializeBean(xx)
方法里面进行的,我们来验证一下:
首先debug到cat对象的初始化:如下图【在return的时候猫对象仍然是Cat类型的】
然后再看dog对象的初始化:如下图 【经过了图中绿色下划线之后,类型发生了变化CGLIB$$xxx的了】
在看一下lifeImpl的初始化:如下图 【经过了图中绿色下划线之后,类型发生了变化$Proxy的了】
经过上面的对比:我们可以得知,代理对象的生成是在wrappedBean = applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName);
里面的,此外如果实现了接口,那么就是jdk动态代理生成的代理对象、如果没有实现接口,那么就是走的CGLIB生成的代理对象。
目标很明确了,我们分析一下applyBeanPostProcessorsAfterInitialization
方法就可以了。
进入到这个方法里面,debug调试过后
发现dog
经过AnnotationAwareAspectJAutoProxyCreator.postProcessAfterInitialization(result, beanName)
方法之后,current就变成了 CGLIB的代理对象,说明这个方法大有奥秘!后面的lifeImpl
对象亦是如此,就不给出图片阐述了。那为什么cat前后还是cat呢,并没有发生变化呢?
// 实际上是到了下面的类的postProcessAfterInitialization方法
// AbstractAutoProxyCreator.java 实现了BeanPostProcessor接口
public abstract class AbstractAutoProxyCreator extends ProxyProcessorSupportimplements SmartInstantiationAwareBeanPostProcessor, BeanFactoryAware {@Overridepublic Object postProcessAfterInitialization(@Nullable Object bean, String beanName) {if (bean != null) {Object cacheKey = getCacheKey(bean.getClass(), beanName);// 检查该 Bean 是否已被提前代理(如循环依赖中的早期引用)if (this.earlyProxyReferences.remove(cacheKey) != bean) {return wrapIfNecessary(bean, beanName, cacheKey);}}return bean;}
}
postProcessAfterInitialization()
方法是 BeanPostProcessor
接口的实现,作用于 Bean 初始化之后(如 @PostConstruct
执行后),主要用于处理 Spring AOP 的代理逻辑。其核心目标是确保 Bean 在初始化完成后,根据需要生成代理对象,同时避免重复代理(尤其是在存在循环依赖时)。
此处的this.earlyProxyReferences.remove(cacheKey)
就是从早期代理引用里面取出并移除该key的早期代理引用对象,来进行比对。这个earlyProxyReferences
在哪里put呢?
在IOC那一章里面,在属性填充之前有这样一段代码,在三级缓存中添加了如下对象
// 缓存早期单例,以便能够解析循环引用
boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&isSingletonCurrentlyInCreation(beanName));
if (earlySingletonExposure) {....addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
}// 这个getEarlyBeanReference,最后是来到了
// AbstractAutoProxyCreator.java
@Override
public Object getEarlyBeanReference(Object bean, String beanName) {Object cacheKey = getCacheKey(bean.getClass(), beanName);this.earlyProxyReferences.put(cacheKey, bean); // 往早期代理引用放了一个对象--这里put的return wrapIfNecessary(bean, beanName, cacheKey);
}
// return的是wrapIfNecessary(bean, beanName, cacheKey)
此章是没有循环引用的,只有循环依赖的时候才会用到三级缓存里面的东西,也就是循环依赖的时候才会往earlyProxyReferences中put东西。为什么?见后续文章**【spring循环依赖的解决】**。
那么这里的earlyProxyReferences就肯定一直都是空的,故在AbstractAutoProxyCreator :: postProcessAfterInitialization()
方法里面会走
if (this.earlyProxyReferences.remove(cacheKey) != bean) {return wrapIfNecessary(bean, beanName, cacheKey); // 走这里
}
这个wrapIfNecessary
是什么,下面来看看
protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) {//1. 如果该 Bean 已经被手动指定 TargetSource(如通过自定义 Scope),直接返回原始 Beanif (StringUtils.hasLength(beanName) && this.targetSourcedBeans.contains(beanName)) return bean;// 2. 如果缓存中标记该 Bean 不需要代理,直接返回if (Boolean.FALSE.equals(this.advisedBeans.get(cacheKey))) return bean;// 3. 检查是否为基础设施类(如 Spring 内部类)或需要跳过代理的 Beanif (isInfrastructureClass(bean.getClass()) || shouldSkip(bean.getClass(), beanName)) {this.advisedBeans.put(cacheKey, Boolean.FALSE);return bean;}// 4. 获取适用于该 Bean 的增强器(Advisors/Advices)**Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, null);if (specificInterceptors != DO_NOT_PROXY) {// 标记该 Bean 需要代理this.advisedBeans.put(cacheKey, Boolean.TRUE);// 5.创建代理对象 **Object proxy = createProxy(bean.getClass(), beanName, specificInterceptors, new SingletonTargetSource(bean));this.proxyTypes.put(cacheKey, proxy.getClass());// 缓存代理类型,后续可通过 getBean 直接返回代理对象return proxy;}// 6. 无增强器,标记该 Bean 不需要代理this.advisedBeans.put(cacheKey, Boolean.FALSE);return bean;
}
**一、获取适用于该 Bean 的增强器:**getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, null);
// AbstractAdvisorAutoProxyCreator.java
protected Object[] getAdvicesAndAdvisorsForBean(Class<?> beanClass, String beanName, @Nullable TargetSource targetSource) {List<Advisor> advisors = findEligibleAdvisors(beanClass, beanName); // 进入if (advisors.isEmpty()) {return DO_NOT_PROXY;}return advisors.toArray();
}
// 只看比较重要的1 2两点
protected List<Advisor> findEligibleAdvisors(Class<?> beanClass, String beanName) {// 1.获取候选AdvisorList<Advisor> candidateAdvisors = findCandidateAdvisors();// 2.获取适用于该bean的Advisor: 例如Pointcut匹配List<Advisor> eligibleAdvisors = findAdvisorsThatCanApply(candidateAdvisors, beanClass, beanName);extendAdvisors(eligibleAdvisors); // 扩展if (!eligibleAdvisors.isEmpty()) {eligibleAdvisors = sortAdvisors(eligibleAdvisors); // 排序}return eligibleAdvisors;
}
//第1点:候选Advisor AnnotationAwareAspectJAutoProxyCreator.java
@Override
protected List<Advisor> findCandidateAdvisors() {// Add all the Spring advisors found according to superclass rules.List<Advisor> advisors = super.findCandidateAdvisors();// 为 Bean Factory 中的所有 AspectJ 方面构建 Advisor。if (this.aspectJAdvisorsBuilder != null) {advisors.addAll(this.aspectJAdvisorsBuilder.buildAspectJAdvisors()); // 进入}return advisors;
}
//BeanFactoryAspectJAdvisorsBuilder.java
public List<Advisor> buildAspectJAdvisors() {...if (this.advisorFactory.isAspect(beanType)) { // @AspectaspectNames.add(beanName);AspectMetadata amd = new AspectMetadata(beanType, beanName);if (amd.getAjType().getPerClause().getKind() == PerClauseKind.SINGLETON) {MetadataAwareAspectInstanceFactory factory =new BeanFactoryAspectInstanceFactory(this.beanFactory, beanName);List<Advisor> classAdvisors = this.advisorFactory.getAdvisors(factory);if (this.beanFactory.isSingleton(beanName)) {this.advisorsCache.put(beanName, classAdvisors);}else {this.aspectFactoryCache.put(beanName, factory);}advisors.addAll(classAdvisors);}else {。。。}}....
}
如图,找到了我们的MyAdvice.
// 第2点:获取适用于该bean的Advisor -- 自己debug吧。。。
// 我这就不给了,反正就是看candidateAdvisors匹不匹配嘛
protected List<Advisor> findAdvisorsThatCanApply(List<Advisor> candidateAdvisors, Class<?> beanClass, String beanName) {ProxyCreationContext.setCurrentProxiedBeanName(beanName);try {return AopUtils.findAdvisorsThatCanApply(candidateAdvisors, beanClass);}finally {ProxyCreationContext.setCurrentProxiedBeanName(null);}
}
在例子中,创建cat的时候,找不到合适的Advice,所以第二步就不会执行咯,故就不需要创建代理对象了
二、创建代理对象 : createProxy(xxx, xx, xxx, xxx) 【本文就以cglib创建代理对象为主,jdk动态代理创建就由读者自行调试分析了】
createProxy
是 Spring AOP 中 创建代理对象的核心方法,位于 AbstractAutoProxyCreator
类中。它负责根据 Bean 的配置和增强器(Advice/Advisors)生成 JDK 动态代理或 CGLIB 代理对象
// specificInterceptors就是第一步找到的advice中匹配该bean的东西,这里叫做拦截器
protected Object createProxy(Class<?> beanClass, @Nullable String beanName,@Nullable Object[] specificInterceptors, TargetSource targetSource) {...if (proxyFactory.isProxyTargetClass()) {// 强制使用 CGLIB 代理 -- if (Proxy.isProxyClass(beanClass) || ClassUtils.isLambdaClass(beanClass)) {// 处理已被 JDK 代理的类或 Lambda 表达式for (Class<?> ifc : beanClass.getInterfaces()) {proxyFactory.addInterface(ifc); // 添加接口(确保引入增强器生效)}}} else {// 根据默认规则选择代理类型if (shouldProxyTargetClass(beanClass, beanName)) {// 如果是true-强制使用 CGLIB,即使目标类实现了接口proxyFactory.setProxyTargetClass(true); // 强制 CGLIB} else {evaluateProxyInterfaces(beanClass, proxyFactory); // 检查接口决定代理类型}}...//构建并添加增强器(Advisors)//将 specificInterceptors(如 MethodInterceptor)转换为 Spring 的 Advisor 对象。Advisor[] advisors = buildAdvisors(beanName, specificInterceptors);proxyFactory.addAdvisors(advisors);...//选择类加载器并生成代理ClassLoader classLoader = getProxyClassLoader();if (classLoader instanceof SmartClassLoader && classLoader != beanClass.getClassLoader()) {classLoader = ((SmartClassLoader) classLoader).getOriginalClassLoader();}return proxyFactory.getProxy(classLoader);
}
上面最后是return proxyFactory.getProxy(classLoader);
— 下面以dog对象为例子
// CglibAopProxy.java
@Override
public Object getProxy(@Nullable ClassLoader classLoader) {try{....// 配置 CGLIB 的 Enhancer 对象Enhancer enhancer = createEnhancer();if (classLoader != null) {// 设置类加载器enhancer.setClassLoader(classLoader);if (classLoader instanceof SmartClassLoader &&((SmartClassLoader) classLoader).isClassReloadable(proxySuperClass)) {enhancer.setUseCache(false);}}// 设置代理类的父类enhancer.setSuperclass(proxySuperClass);// 设置代理类要实现的接口enhancer.setInterfaces(AopProxyUtils.completeProxiedInterfaces(this.advised));enhancer.setNamingPolicy(SpringNamingPolicy.INSTANCE);enhancer.setStrategy(new ClassLoaderAwareGeneratorStrategy(classLoader));// 获取回调函数数组---------【重点1】Callback[] callbacks = getCallbacks(rootClass);Class<?>[] types = new Class<?>[callbacks.length];for (int x = 0; x < types.length; x++) {types[x] = callbacks[x].getClass();}// 设置回调过滤器--------【重点2】enhancer.setCallbackFilter(new ProxyCallbackFilter(this.advised.getConfigurationOnlyCopy(), this.fixedInterceptorMap, this.fixedInterceptorOffset));enhancer.setCallbackTypes(types);// 生成代理类并创建代理实例 --------- 【重点3】return createProxyClassAndInstance(enhancer, callbacks);} ...
}
创建代理对象【重点1】getCallbacks(rootClass);
这个里面是什么呢?
private Callback[] getCallbacks(Class<?> rootClass) throws Exception {// Parameters used for optimization choices...boolean isFrozen = this.advised.isFrozen();boolean exposeProxy = this.advised.isExposeProxy();// 是否静态类,这里的静态并非指静态类,而是每次调用返回的实例都是否是不可变的// 如单例模式的bean就是静态,而多例模式下的bean就不是静态boolean isStatic = this.advised.getTargetSource().isStatic();// DynamicAdvisedInterceptor:用于处理包含 AOP 通知的方法调用,//它会根据配置的切面和通知逻辑来执行相应的增强操作。//内部通过 ReflectiveMethodInvocation 链式调用通知逻辑。Callback aopInterceptor = new DynamicAdvisedInterceptor(this.advised);....Callback[] mainCallbacks = new Callback[] {aopInterceptor, // for normal advicetargetInterceptor, // 目标方法直接调用拦截器new SerializableNoOp(), // no override for methods mapped to thistargetDispatcher, this.advisedDispatcher,new EqualsInterceptor(this.advised),// 处理代理对象的 equals 方法new HashCodeInterceptor(this.advised)// 处理代理对象的 hashCode 方法};Callback[] callbacks;// 如果类是静态 && 配置冻结。则准备做一些优化策略if (isStatic && isFrozen) {Method[] methods = rootClass.getMethods();Callback[] fixedCallbacks = new Callback[methods.length];this.fixedInterceptorMap = CollectionUtils.newHashMap(methods.length);// TODO: small memory optimization here (can skip creation for methods with no advice)for (int x = 0; x < methods.length; x++) {Method method = methods[x];List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, rootClass);fixedCallbacks[x] = new FixedChainStaticTargetInterceptor(chain, this.advised.getTargetSource().getTarget(), this.advised.getTargetClass());this.fixedInterceptorMap.put(method, x);}callbacks = new Callback[mainCallbacks.length + fixedCallbacks.length];System.arraycopy(mainCallbacks, 0, callbacks, 0, mainCallbacks.length);System.arraycopy(fixedCallbacks, 0, callbacks, mainCallbacks.length, fixedCallbacks.length);this.fixedInterceptorOffset = mainCallbacks.length;}else {callbacks = mainCallbacks; //}return callbacks;}
创建代理对象【重点2】ProxyCallbackFilter
回调过滤器
// Constants for CGLIB callback array indices
private static final int AOP_PROXY = 0;
private static final int INVOKE_TARGET = 1;
private static final int NO_OVERRIDE = 2;
private static final int DISPATCH_TARGET = 3;
private static final int DISPATCH_ADVISED = 4;
private static final int INVOKE_EQUALS = 5;
private static final int INVOKE_HASHCODE = 6;public ProxyCallbackFilter(AdvisedSupport advised, Map<Method, Integer> fixedInterceptorMap, int fixedInterceptorOffset) {this.advised = advised;this.fixedInterceptorMap = fixedInterceptorMap;this.fixedInterceptorOffset = fixedInterceptorOffset;
}@Override
public int accept(Method method) {// 1. 如果当前方法被 final 修饰,则不代理该方法//如果method 被 final 修饰,则无法代理if (AopUtils.isFinalizeMethod(method)) {...return NO_OVERRIDE;}if (!this.advised.isOpaque() && method.getDeclaringClass().isInterface() &&method.getDeclaringClass().isAssignableFrom(Advised.class)) {return DISPATCH_ADVISED;}// 3. equals 方法if (AopUtils.isEqualsMethod(method)) ...// 4.hashCodeif (AopUtils.isHashCodeMethod(method)) ...Class<?> targetClass = this.advised.getTargetClass();List<?> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);boolean haveAdvice = !chain.isEmpty();boolean isFrozen = this.advised.isFrozen();boolean exposeProxy = this.advised.isExposeProxy();boolean isStatic = this.advised.getTargetSource().isStatic();if (haveAdvice || !isFrozen) {if (exposeProxy) {...return AOP_PROXY;}// Check to see if we have fixed interceptor to serve this method.// Else use the AOP_PROXY.if (isStatic && isFrozen && this.fixedInterceptorMap.containsKey(method)) {...// We know that we are optimizing so we can use the FixedStaticChainInterceptors.int index = this.fixedInterceptorMap.get(method);return (index + this.fixedInterceptorOffset);}else {return AOP_PROXY;}}else {if (exposeProxy || !isStatic) return INVOKE_TARGET;Class<?> returnType = method.getReturnType();if (targetClass != null && returnType.isAssignableFrom(targetClass)) {...return INVOKE_TARGET;}else ..return DISPATCH_TARGET;}
}
创建代理对象【重点3】生成代理类并创建代理实例 createProxyClassAndInstance(enhancer, callbacks);
// ObjenesisCglibAopProxy.java
@Override
protected Object createProxyClassAndInstance(Enhancer enhancer, Callback[] callbacks) {Class<?> proxyClass = enhancer.createClass();Object proxyInstance = null;if (objenesis.isWorthTrying()) {try {//Objenesis 是一个专门用于绕过对象构造函数直接实例化对象的库。proxyInstance = objenesis.newInstance(proxyClass, enhancer.getUseCache());}.....}// 当 Objenesis 失败时,通过反射调用默认或指定参数的构造方法。if (proxyInstance == null) {// Regular instantiation via default constructor...try {Constructor<?> ctor = (this.constructorArgs != null ?proxyClass.getDeclaredConstructor(this.constructorArgTypes) :proxyClass.getDeclaredConstructor());ReflectionUtils.makeAccessible(ctor);proxyInstance = (this.constructorArgs != null ?ctor.newInstance(this.constructorArgs) : ctor.newInstance());}....}// 回调链决定了代理对象的方法拦截行为(如切面增强、直接调用目标方法等)。((Factory) proxyInstance).setCallbacks(callbacks);return proxyInstance;
}
3.执行过程
执行是怎么样的呢?
照样从案例的执行开始看起
public static void aop() {ApplicationContext context = new AnnotationConfigApplicationContext(Main.class);Dog dog = context.getBean("dog", Dog.class);dog.wangwang();System.out.println(dog.getClass().getName());Life impl = context.getBean("lifeImpl", Life.class);impl.wangwang();System.out.println(impl.getClass().getName());Cat cat = context.getBean("cat", Cat.class);cat.miaomiao();System.out.println(cat.getClass().getName());
}
先执行的dog的方法【cglib】、再执行impl的方法【jdk】,因为第一个没有实现接口,第二个实现了接口。
上一节我们可以知道是proxyFactory.getProxy(classLoader);
创建的代理对象,实际上getProxy是AopProxy接口的方法,那么在Spring中该接口的直接实现类只有两个:
以第一个例子开头,dog肯定是CglibAopProxy的代理对象。【代理不会的看这里】:https://www.cnblogs.com/jackjavacpp/p/18582124
熟悉cglib的都知道,通过cglib生成的代理对象,然后使用该对象执行目标方法,会走设置的方法拦截器,上一章最后创建代理对象那一节里面的getCallbacks方法,第一个就new了DynamicAdvisedInterceptor对象,它是用于处理包含 AOP 通知的方法调用。我们点开CglibAopProxy里面的DynamicAdvisedInterceptor
静态内部类看一下,肯定重写了intercept方法,不用想啊,这就是会代理的好处啊。
//CglibAopProxy.java的静态内部类 DynamicAdvisedInterceptor
//proxy:CGLIB 生成的代理对象。
//method:被调用的方法(目标方法的反射对象)
//args: 方法参数。
//methodProxy:CGLIB 的 MethodProxy 对象,用于直接调用目标方法(比反射高效)
public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {Object oldProxy = null;boolean setProxyContext = false;Object target = null;//targetSource:目标对象来源(如单例、原型或池化对象)TargetSource targetSource = this.advised.getTargetSource();try {if (this.advised.exposeProxy) { //暴露代理到当前线程上下文//允许目标对象内部方法通过 AopContext.currentProxy() 获取代理对象,//解决自调用(如 this.method())时 AOP 失效的问题。oldProxy = AopContext.setCurrentProxy(proxy);setProxyContext = true;} target = targetSource.getTarget(); //获取被代理的原始对象Class<?> targetClass = (target != null ? target.getClass() : null);List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);Object retVal;// 无拦截器,直接调用目标方法if (chain.isEmpty() && CglibMethodInvocation.isMethodProxyCompatible(method)) {Object[] argsToUse = AopProxyUtils.adaptArgumentsIfNecessary(method, args);retVal = invokeMethod(target, method, argsToUse, methodProxy);}else {// 有拦截器,创建方法调用链并执行//调用其 proceed() 方法,按顺序执行拦截器链。retVal = new CglibMethodInvocation(proxy, target, method, args, targetClass, chain, methodProxy).proceed();}retVal = processReturnType(proxy, target, method, retVal);return retVal;}finally {if (target != null && !targetSource.isStatic()) {targetSource.releaseTarget(target);}if (setProxyContext) {AopContext.setCurrentProxy(oldProxy); // 恢复原始代理上下文}}
}public boolean equals()...
public int hashCode()....
}
CglibMethodInvocation.java
private static class CglibMethodInvocation extends ReflectiveMethodInvocation {@Override@Nullablepublic Object proceed() throws Throwable {try {return super.proceed();}....}
}//ReflectiveMethodInvocation.java
@Override
@Nullable
public Object proceed() throws Throwable {//从索引-1开始if (this.currentInterceptorIndex == this.interceptorsAndDynamicMethodMatchers.size() - 1) {return invokeJoinpoint(); // 所有拦截器执行完毕,调用目标方法}// 获取下一个拦截器Object interceptorOrInterceptionAdvice =this.interceptorsAndDynamicMethodMatchers.get(++this.currentInterceptorIndex);//若获取的拦截器是 InterceptorAndDynamicMethodMatcher 类型,需进行动态方法匹配if (interceptorOrInterceptionAdvice instanceof InterceptorAndDynamicMethodMatcher) {// Evaluate dynamic method matcher here: static part will already have// been evaluated and found to match.InterceptorAndDynamicMethodMatcher dm =(InterceptorAndDynamicMethodMatcher) interceptorOrInterceptionAdvice;Class<?> targetClass = (this.targetClass != null ? this.targetClass : this.method.getDeclaringClass());//若匹配成功,调用拦截器的 invoke 方法,传入当前 ReflectiveMethodInvocation 对象。//匹配就是在拦截器链执行过程中动态判断当前拦截器是否需要应用于目标方法调用if (dm.methodMatcher.matches(this.method, targetClass, this.arguments)) {return dm.interceptor.invoke(this);}else {// 动态匹配失败,跳过当前拦截器,递归调用 proceed 方法执行下一个拦截器return proceed();}}else {// 不是动态方法匹配器,直接调用拦截器的 invoke 方法return ((MethodInterceptor) interceptorOrInterceptionAdvice).invoke(this);}
}
上面一个小小的方法就包含了
- 拦截器链模式:
通过责任链模式按顺序执行拦截器,支持灵活扩展(如事务、日志、安全等)。 - 性能优化:
- 无拦截器时直接调用目标方法。
- 使用
MethodProxy
代替反射调用。
- 上下文管理:
AopContext
解决自调用问题。TargetSource
管理目标对象的生命周期。
4.案例分析
下面就以@Transacional注解、自定义Advice相结合为例子,分析一下代理创建及其运行过程。
首先搭建一个项目,如下:
// 1.实体类对象
@Data
@AllArgsConstructor
@NoArgsConstructor
@TableName("user_tab")
public class User {@TableId(type = IdType.AUTO)private Integer id;private String name;private BigDecimal account;
}
// 2.Mapper接口
@Mapper
public interface UserMapper extends BaseMapper<User> {
}
// 3.service
@Service
public class UserService {@Resourceprivate UserMapper userMapper;@Transactionalpublic void insertUser() {userMapper.insert(new User(null, "张三", new BigDecimal(100)));System.out.println("================业务操作--执行插入");//throw new RuntimeException("插入用户失败");}
}
//4.配置AOP 和 数据源 、事务管理器
@Aspect
@Component
public class MyAdvice {private static final String userExpression = "execution(* com.feng.springCaseAnalysis.service.UserService.*(..))";@Before(userExpression)public void beforeAdvice() {System.out.println("Before通知 -- UserService 之前");}@Around(userExpression)public Object aroundAdvice(ProceedingJoinPoint joinPoint) throws Throwable {System.out.println("环绕通知 -- UserService 之前");Object result = joinPoint.proceed();System.out.println("环绕通知 -- UserService 之后");return result;}
}
// 配置类
@Configuration
@MapperScan("com.feng.springCaseAnalysis.mapper")
@EnableTransactionManagement
public class DataProjectConfig {@Beanpublic DataSource dataSource() {// 创建数据源DruidDataSource dataSource = new DruidDataSource();dataSource.setUsername("root");dataSource.setPassword("123456");dataSource.setUrl("jdbc:mysql://127.0.0.1:3306/spring_analysis?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai");dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");dataSource.setInitialSize(5);return dataSource;}@Beanpublic MybatisSqlSessionFactoryBean sqlSessionFactory(DataSource dataSource) {MybatisSqlSessionFactoryBean factoryBean = new MybatisSqlSessionFactoryBean();factoryBean.setDataSource(dataSource);return factoryBean;}// 事务管理器@Beanpublic DataSourceTransactionManager transactionManager(DataSource dataSource) {DataSourceTransactionManager transactionManager = new DataSourceTransactionManager();transactionManager.setDataSource(dataSource);return transactionManager;}
}//5.Main测试
@Configuration
@ComponentScan("com.feng.springCaseAnalysis") // spring-- Transactional 结合 自定义 Advice 分析
@EnableAspectJAutoProxy
public class Main {public static void main(String[] args) {AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(Main.class);UserService userService = context.getBean("userService", UserService.class);try {userService.insertUser();} catch ( Exception e ) {System.out.println("【Exception!!!】" + e.getMessage());}}
}
在上面的案例搭建好之后,我们仔细分析一下UserService的代理创建过程:
来到为UserService寻找advisor的这里,读者们还记得这在哪个阶段吗?然后进入里面就是List<Advisor> advisors = findEligibleAdvisors(beanClass, beanName);
。
如上图所示,在候选的Advisors中,除了我们自定义的两个Advisor,还找到了另外一个 BeanFactoryTransactionAttributeSourceAdvisor
,它也是一个PointCutAdvisor。在调用canApply( )
方法的时候,会解析UserService所有方法上面有没有@Transactional注解,并解析里面的属性,这里insertUser方法有@Transactional注解,故匹配上了。
@Override
public boolean matches(Method method, Class<?> targetClass) {/*method: com.feng.springCaseAnalysis.service.UserService.insertUsertargetClass: class com.feng.springCaseAnalysis.service.UserService*/// 这里的tas是AnnotationTransactionAttributeSourceTransactionAttributeSource tas = getTransactionAttributeSource();return (tas == null || tas.getTransactionAttribute(method, targetClass) != null);
}
所以elibibAdvisors都匹配上了,此时的elibibAdvisors里面有三个Advisor。然后创建代理对象,设置CallBack那些就同理了,如下图所示。
然后就返回代理对象咯。
可以看到经过applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName);
之后,WrapperBean变成了UserService$$EnhancerBySpringCGLIB$$a768552c@4109
代理对象了。
执行就很简单了:
递归调用嘛。
// TransactionInterceptor.java
@Override
@Nullable
public Object invoke(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 CoroutinesInvocationCallback() {@Override@Nullablepublic Object proceedWithInvocation() throws Throwable {return invocation.proceed();}@Overridepublic Object getTarget() {return invocation.getThis();}@Overridepublic Object[] getArguments() {return invocation.getArguments();}});
}
// invocation是一个函数式接口,proceedWithInvocation实际上就是上面的invocation.proceed(),这样就完成了递归调用了
protected Object invokeWithinTransaction(Method method, @Nullable Class<?> targetClass,final InvocationCallback invocation) throws Throwable {....PlatformTransactionManager ptm = asPlatformTransactionManager(tm);final String joinpointIdentification = methodIdentification(method, targetClass, txAttr);if (txAttr == null || !(ptm instanceof CallbackPreferringPlatformTransactionManager)) {TransactionInfo txInfo = createTransactionIfNecessary(ptm, txAttr, joinpointIdentification);Object retVal;try {// 调用下一层retVal = invocation.proceedWithInvocation();}catch (Throwable ex) { // 捕获到异常了// 在里面判断一些条件,然后事务回滚completeTransactionAfterThrowing(txInfo, ex);throw ex;}finally {cleanupTransactionInfo(txInfo);}if (retVal != null && vavrPresent && VavrDelegate.isVavrTry(retVal)) {// Set rollback-only in case of Vavr failure matching our rollback rules...TransactionStatus status = txInfo.getTransactionStatus();if (status != null && txAttr != null) {retVal = VavrDelegate.evaluateTryFailure(retVal, txAttr, status);}}commitTransactionAfterReturning(txInfo); // 提交事务return retVal;}
5.思考题: @Import注解
本章案例见importCase包:
为什么在第四章案例中,多了那么多和Transactional的bean?在配置类@EnableTransactionManagement这个是干嘛的?
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(TransactionManagementConfigurationSelector.class) // 有这个注解
public @interface EnableTransactionManagement {....
}
这个@Import
注解的作用:@Import只能用在类上 ,@Import通过快速导入的方式实现把实例加入spring的IOC容器中。
①主要用法:
1.直接填class数组: bean名称是该类的全类名
// 主测试类
ApplicationContext context = new AnnotationConfigApplicationContext(Main.class);
Student bean = context.getBean(Student.class);
for (String name : context.getBeanDefinitionNames()) {System.out.println(name);
}
System.out.println("========");
System.out.println(bean);
// 配置类
@Configuration
@Import({Student.class})
public class Config {
}
// Student并没有被Spring管理,
// 但是通过@Import导进去了
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Student {private String name = "asd";private Integer age = 10;
}
运行结果如下:
org.springframework.context.annotation.internalConfigurationAnnotationProcessor
org.springframework.context.annotation.internalAutowiredAnnotationProcessor
org.springframework.context.annotation.internalCommonAnnotationProcessor
org.springframework.context.event.internalEventListenerProcessor
org.springframework.context.event.internalEventListenerFactory
main
config
com.feng.importCase.Student //看这里!!!!
org.springframework.aop.config.internalAutoProxyCreator
========
Student(name=asd, age=10)
2.ImportSelector方式【SpringBoot中用的很多】:
@Configuration
//@Import({Student.class})
@Import({MyStudentImportSelector.class})
public class Config {
}//MyStudentImportSelector.java
public class MyStudentImportSelector implements ImportSelector {//参数: AnnotationMetadata表示当前被@Import注解给标注的所有注解信息@Overridepublic String[] selectImports(AnnotationMetadata importingClassMetadata) {return new String[]{"com.feng.importCase.Student"};}
}
配置类改成导入Selector,需要导入的类,在该Selector中给出来,这样就可以把我的Student类导进容器里面了。
3.ImportBeanDefinitionRegistrar方式
同样是一个接口,类似于第二种ImportSelector用法,只不过这种用法更加地自定义化注册
public class StudentImportRegistrar implements ImportBeanDefinitionRegistrar {@Overridepublic void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {RootBeanDefinition beanDefinition = new RootBeanDefinition(Student.class);registry.registerBeanDefinition("haha-student", beanDefinition);}
}@Configuration
//@Import({Student.class})
//@Import({MyStudentImportSelector.class})
@Import({StudentImportRegistrar.class})
public class Config {
}
②源码解析
@Import是在下面这一步起作用的。
refresh()里面往下 ---->
// Invoke factory processors registered as beans in the context.
invokeBeanFactoryPostProcessors(beanFactory); ---->
|
PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(beanFactory, getBeanFactoryPostProcessors());
|
invokeBeanDefinitionRegistryPostProcessors(currentRegistryProcessors, registry, beanFactory.getApplicationStartup());
待读者自行分析把。嘿嘿嘿
end.参考
- https://blog.csdn.net/qq_36882793/article/details/119823785 【cglib 的代理过程】
- https://www.cnblogs.com/yichunguo/p/12122598.html 【import】
相关文章:
Spring-AOP分析
Spring分析-AOP 1.案例引入 在上一篇文章中,【Spring–IOC】【https://www.cnblogs.com/jackjavacpp/p/18829545】,我们了解到了IOC容器的创建过程,在文末也提到了AOP相关,但是没有作细致分析,这篇文章就结合示例&am…...
opencv 对图片的操作
对图片的操作 1.图片镜像旋转(cv2.flip())2 图像的矫正 1.图片镜像旋转(cv2.flip()) 图像的旋转是围绕一个特定点进行的,而图像的镜像旋转则是围绕坐标轴进行的。图像的镜像旋转分为水平翻转、垂直翻转、水平垂直翻转…...
Python第一周作业
Python第一周作业 文章目录 Python第一周作业 如何在命令行中创建一个名为venv的虚拟环境?请写出具体命令编写一段代码,判断变量x是否为偶数,如果是则返回"Even",否则返回"Odd"编写代码,使用分支结…...
jinjia2将后端传至前端的字典变量转换为JS变量
后端 country_dict {AE: .amazon.ae, AU: .amazon.com.au} 前端 const country_list JSON.parse({{ country_list | tojson | safe }});...
[渗透测试]渗透测试靶场docker搭建 — —全集
[渗透测试]渗透测试靶场docker搭建 — —全集 对于初学者来说,仅仅了解漏洞原理是不够的,还需要进行实操。对于公网上的服务我们肯定不能轻易验证某些漏洞,否则可能触犯法律。这是就需要用到靶场。 本文主要给大家介绍几种常见漏洞对应的靶场…...
二分查找、分块查找、冒泡排序、选择排序、插入排序、快速排序
二分查找/折半查找 前提条件:数组中的数据必须是有序的 核心逻辑:每次排除一半的查找范围 优点:提高查找效率 代码 public static int binarySearch(int[] arr, int num) {int start 0;int end arr.length - 1;while (start < end) {…...
【AI】SpringAI 第三弹:接入通用大模型平台
1.添加依赖 <dependency><groupId>org.springframework.ai</groupId><artifactId>spring-ai-starter-model-openai</artifactId> </dependency> 2.设置 yml 配置文件 在 application.yml 中添加 DeepSeek 的配置信息: spr…...
C++常用函数合集
万能头文件:#include<bits/stdc.h> 1. 输入输出流(I/O)函数 1.1cin 用于从标准输入流读取数据。 1.2cout 用于向标准输出流写入数据。 // 输入输出流(I/O)函数 #include <iostream> using namespace…...
22. git show
基本概述 git show 的作用是:显示各种 Git 对象(如提交、标签、树对象、文件对象等)的详细信息 基本用法 1.基本语法 git show [选项] [对象]2.查看提交的详细信息 git show <commit-hash> # 示例 git show a1b2c3d # 显示某…...
使用blob文件流
1.后端 GetMapping(value "/static/**")public void view(HttpServletRequest request, HttpServletResponse response) {// ISO-8859-1 > UTF-8 进行编码转换String imgPath extractPathFromPattern(request);if(oConvertUtils.isEmpty(imgPath) || imgPath&q…...
操作指南:在vue-fastapi-admin上增加新的功能模块
近期在github上看到一个很不错的web框架,https://github.com/mizhexiaoxiao/vue-fastapi-admin。该项目基于 FastAPI Vue3 Naive UI 的现代化前后端分离开发平台,融合了 RBAC 权限管理、动态路由和 JWT 鉴权,可以助力中小型应用快速搭建&am…...
文字、语音、图片、视频四个模态两两之间(共16种转换方向)的生成技术及理论基础的详细说明及表格总结
以下是文字、语音、图片、视频四个模态两两之间(共16种转换方向)的生成技术及理论基础的详细说明及表格总结: 1. 技术与理论基础详解 (1) 文字与其他模态的转换 文字→文字 技术:GPT、BERT、LLaMA等语言模型。理论:T…...
FramePack:让视频生成更高效、更实用
想要掌握如何将大模型的力量发挥到极致吗?叶梓老师带您深入了解 Llama Factory —— 一款革命性的大模型微调工具(限时免费)。 1小时实战课程,您将学习到如何轻松上手并有效利用 Llama Factory 来微调您的模型,以发挥其…...
【大语言模型DeepSeek+ChatGPT+python】最新AI-Python机器学习与深度学习技术在植被参数反演中的核心技术应用
在全球气候变化与生态环境监测的重要需求下,植被参数遥感反演作为定量评估植被生理状态、结构特征及生态功能的核心技术,正面临数据复杂度提升、模型精度要求高、多源异构数据融合等挑战。人工智能(AI)技术的快速发展,…...
RSS 2025|苏黎世提出「LLM-MPC混合架构」增强自动驾驶,推理速度提升10.5倍!
论文题目:Enhancing Autonomous Driving Systems with On-Board Deployed Large Language Models 论文作者:Nicolas Baumann,Cheng Hu,Paviththiren Sivasothilingam,Haotong Qin,Lei Xie,Miche…...
Oracle expdp的 EXCLUDE 参数详解
Oracle expdp的 EXCLUDE 参数详解 EXCLUDE 是 Oracle Data Pump Export (expdp) 工具中的一个关键参数,用于指定在导出过程中要排除的对象或对象类型。 一、基本语法 expdp username/password DUMPFILEexport.dmp DIRECTORYdpump_dir EXCLUDEobject_type[:name_c…...
Git创建空分支并推送到远程仓库
new-empty-branch是新分支的名称 完全空提交(Git 2.23)【推荐】 git switch --orphan new-empty-branch git config user.email "youexample.com" git config user.name "Your Name" git commit --allow-empty -m "初始空提交…...
TDS电导率传感器详解(STM32)
目录 一、介绍 二、传感器原理 1.原理图 2.引脚描述 三、程序设计 main文件 tds.h文件 tds.c文件 四、实验效果 五、资料获取 项目分享 一、介绍 TDS电导率传感器介绍 : TDS(Total Dissolved Solid),中文名总溶解固…...
初识Redis · C++客户端list和hash
目录 前言: list lpush lrange rpush rpush llen rpop lpop blpop hash hset hget hmget hkeys hvals hexists hdel 前言: 在上一篇文章我们介绍了string的基本使用,并且发现几乎唯一的难点就是使用迭代器方面,并且我们…...
SpringBoot和微服务学习记录Day3
Hystrix 熔断器 在分布式架构中,很多服务因为网络或自身原因不可避免发生故障,如果某个服务出现问题往往会导致一系列的服务都发生故障,导致整个微服务架构瘫痪,称为服务雪崩,Hystrix就是为了解决这个问题的 服务熔…...
12个领域近120个典型案例:2024年“数据要素X”大赛典型案例集(附下载)
2024年10月25日,2024年“数据要素”大赛全国总决赛颁奖仪式在北京举行。这次大赛是首届“数据要素x”大赛,全国共有近2万支队伍踊跃参赛,10万参赛者用数据编织梦想,最终角逐出12个赛道120个典型案例。 根据国家数据局等相关公开资…...
如何在腾讯云Ubuntu服务器上部署Node.js项目
最近弄了一个Node.js项目,包含前端用户前台,管理后台和服务端API服务三个项目,本地搭建好了,于是在腾讯云上新建了个Ubuntu 24.04服务器,想要将本地的Node.js项目部署上去,包括环境配置和数据库搭建。 本文…...
【NLP 67、知识图谱】
你像即将到来的夏季一样鲜明, 以至于我这样寡淡的生命, 竟山崩般为你着迷 —— 25.4.18 一、信息 VS 知识 二、知识图谱 1.起源 于2012年5月17日被Google正式提出,初衷是为了提高搜索引擎的能力,增强用户的搜索质量以及搜索体验 …...
Java写数据结构:栈
1.概念: 一种特殊的线性表,其只允许在固定的一端进行插入和删除元素操作。进行数据插入和删除操作的一端称为栈顶,另一端称为栈底。栈中的数据元素遵守后进先出LIFO(Last In First Out)的原则。 压栈:栈的插…...
跨境电商行业新周期下的渠道突围策略
2024年初,跨境电商圈动荡不断,多家卖家平台股价大跌,引发行业舆论热议。而作为东南亚主战场的Shopee,仅仅几个月时间跌幅已达23%。在这一波冲击中,大多数卖家都在"止血",但有棵树却逆势而上&…...
Docker如何更换镜像源提高拉取速度
在国内,由于网络政策和限制,直接访问DockerHub速度很慢,尤其是在拉取大型镜像时。为了解决这个问题,常用的方法就是更换镜像源。本文将详细介绍如何更换Docker镜像源,并提供当前可用的镜像源。 换源方法 方法1&#x…...
平方根倒数快速算法
一、平方根倒数算法的由来 在制作3D游戏的时候,曲面是由许多平面构成的,要求出光线在物体表面反射后的效果,就需要知道平面的单位法向量,法向量的长度的平方R很容易求出,单位法向量 坐标值 / R的平方根。电脑每次都要…...
详解.vscode 下的json .vscode文件夹下各个文件的作用
1.背景 看一些开源项目的时候,总是看到vscode先有不同的json文件,再次做一下总结方便之后查看 settings.json肯定不用多说了 vscode 编辑器分为 全局用户配置 和 当前工作区配置 那么.vscode文件夹下的settings.json文件夹肯定就是当前工作区配置了 在此文件对单个的项目进行配…...
【消息队列RocketMQ】二、RocketMQ 消息发送与消费:原理与实践
一、RocketMQ 消息发送原理与模式 1.1 消息发送原理 RocketMQ 消息发送的核心流程围绕 Producer、NameServer 和 Broker 展开。Producer 启动时,会向 NameServer 请求获取 Topic 的路由信息,这些信息包括 Topic 对应的 Broker 列表以及 Broker 上的…...
WPF的发展历程
文章目录 WPF的发展历程引言起源与背景(2001-2006)从Avalon到WPF设计目标与创新理念 WPF核心技术特点与架构基础架构与渲染模型关键技术特点MVVM架构模式 WPF在现代Windows开发中的地位与前景当前市场定位与其他微软UI技术的关系未来发展前景 社区贡献与…...
新书速览|OpenCV计算机视觉开发实践:基于Qt C++
《OpenCV计算机视觉开发实践:基于Qt C》 本书内容 OpenCV是计算机视觉领域的开发者必须掌握的技术。《OpenCV计算机视觉开发实践:基于Qt C》基于 OpenCV 4.10与Qt C进行编写,全面系统地介绍OpenCV的使用及实战案例,并配套提供全书示例源码、PPT课件与作…...
本地搭建一个简易版本的 Web3 服务
一、环境搭建与工具准备 (一)安装 Node.js 和 npm Node.js 是一个基于 JavaScript 的运行时环境,npm 是其默认的包管理器。在 Web3 开发中,Node.js 和 npm 是必不可少的工具。 访问 Node.js 官网 并下载最新的 LTS 版本。 安装…...
电脑安装CentOS系统
前言 电脑是Windows10系统,安装CentOS之前要将硬盘格式化,这个操作会将Windows10系统以及电脑上所有资料抹除,操作前务必谨慎复查是否有重要资料需要备份。 准备工作 准备两个U盘,一台电脑。提前把镜像下载好。镜像在百度网盘里…...
【Linux专栏】zip 多个文件不带路径
Linux && Oracle相关文档,希望互相学习,共同进步 风123456789~-CSDN博客 1.背景 今天发现 Linux 解压缩的文件中,不光包含需要的文件,还保留了目录层级,不是想要的结果。因此,本文关于…...
邀请函 | 「软件定义汽车 同星定义软件」 TOSUN用户日2025·杭州站
参会邀请函 尊敬的客户及合作伙伴: 新能源汽车智能化浪潮席卷全球,杭州作为中国技术创新高地,正引领行业变革。为助力工程师伙伴应对行业挑战,解决工程难题,同星智能将于2025年5月9日(周五)在…...
start_response详解
start_response 是Python的WSGI(Web Server Gateway Interface)中的一个重要概念,它是一个可调用对象(通常是一个函数),在WSGI应用程序里发挥着关键作用,下面为你详细介绍。 作用 在WSGI规范里…...
记一次 .NET某旅行社酒店管理系统 卡死分析
一:背景 1. 讲故事 年初有位朋友找到我,说他们的管理系统不响应了,让我帮忙看下到底咋回事? 手上也有dump,那就来分析吧。 二:为什么没有响应 1. 线程池队列有积压吗? 朋友的系统是一个web系统&#…...
[预备知识]1. 线性代数基础
线性代数基础 线性代数是深度学习的重要基础,本章节将介绍深度学习中常用的线性代数概念和操作。 1. 标量、向量、矩阵与张量 1.1 标量(Scalar) 标量是单个数值,用 x ∈ R x \in \mathbb{R} x∈R 表示。在深度学习中常用于表…...
RESTful学习笔记(二)---简单网页前后端springboot项目搭建
新建项目: 项目结构 Pom.xml中添加依赖: 要有用于启动的父进程,有启动依赖,有lombok用于自动构建getter和setter方法等 <parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-…...
C++ AI模型部署优化实战:基于TensorRT的高效推理引擎开发
🧑 博主简介:CSDN博客专家、CSDN平台优质创作者,高级开发工程师,数学专业,10年以上C/C, C#, Java等多种编程语言开发经验,拥有高级工程师证书;擅长C/C、C#等开发语言,熟悉Java常用开…...
[特殊字符] Prompt如何驱动大模型对本地文件实现自主变更:Cline技术深度解析
在AI技术快速发展的今天,编程方式正在经历一场革命性的变革。从传统的"人写代码"到"AI辅助编程",再到"AI自主编程",开发效率得到了质的提升。Cline作为一款基于VSCode的AI编程助手,通过其独特的pro…...
DevOps功能详解
DevOps 详解 1. 什么是 DevOps? DevOps 是 Development(开发) 和 Operations(运维) 的组合词,代表一种通过 自动化工具、协作文化 和 流程优化 来加速软件开发与交付的 方法论。其核心目标是打破开发与运维…...
忽略 CS8616 警告在 Visual Studio 2022 中【C# 8.0 】
CS8616 警告是 C# 8.0 引入的可空引用类型(NRT)相关警告,表示"由于可空引用类型的特性,某个不可为 null 的字段可能未被初始化"。 编辑项目csproj,直接删除<Nullable>enable</Nullable> 或者修改为disable或者annota…...
[架构之美]一键服务管理大师:Ubuntu智能服务停止与清理脚本深度解析
[架构之美]一键服务管理大师:Ubuntu智能服务停止与清理脚本深度解析 服务展示: 运行脚本: 剩余服务: 一、脚本设计背景与核心价值 在Linux服务器运维中,服务管理是日常操作的重要环节。本文介绍的智能服务管理脚本&a…...
23种设计模式-结构型模式之外观模式(Java版本)
Java 外观模式(Facade Pattern)详解 🧭 什么是外观模式? 外观模式是结构型设计模式之一,为子系统中的一组接口提供一个统一的高层接口,使得子系统更易使用。 就像是酒店前台,帮你处理入住、叫…...
《数据结构之美--双向链表》
引言 之前我们学习了单链表这一数据结构,虽然单链表的功能比较多,但是也存在着一些局限性,因为在单链表中节点的指向都是单向的,因此我们想从某个节点找到它的上一个节点比较困难,来不及再迷恋单链表了,接…...
如何判断设备是否支持带电插拔——从原理到实操的全面解析
点击下面图片带您领略全新的嵌入式学习路线 🔥爆款热榜 88万阅读 1.6万收藏 一、带电插拔的核心原理 带电插拔(热插拔)的本质是通过电气隔离设计和顺序通断控制,避免电流突变对设备造成损害。 • 触点分级设计:支持热…...
Google Store 如何利用 glTF 3D 模型改变产品教育
Google 为全球广大用户提供种类繁多、持续改进的硬件产品。Google 的智能手机、智能手表、耳机、平板电脑、智能家居设备等产品均通过 Google Store(谷歌商店) 以及遍布全球的实体和数字第三方零售商销售。作为一个以在人工智能、智能家居和个人设备体验方面不断开拓创新而闻名…...
Flutter 状态管理 Riverpod
Android Studio版本 Flutter SDK 版本 将依赖项添加到您的应用 flutter pub add flutter_riverpod flutter pub add riverpod_annotation flutter pub add dev:riverpod_generator flutter pub add dev:build_runner flutter pub add dev:custom_lint flutter pub add dev:riv…...
flutter 专题 六十六 Flutter Dio包网络请求抓包解决方案
在Flutter中进行网络请求时,我们可以使用的库有3个,即Http请求库、HttpClient请求库和Dio请求库(详细介绍请参考:Flutter开发之Http网络请求),使用得最多的就是Dio请求库。因为相比Http请求库和HttpClient请…...