mini-spring|基于JDK和Cglib动态代理,实现AOP核心功能

news2025/1/18 19:04:12

AOP 的核心技术实现主要是动态代理的使用
那么我们就需要先来实现一个可以代理方法的 Proxy,其实代理方法主要是使用到方法拦截器类处理方法的调用 MethodInterceptor#invoke,而不是直接使用 invoke 方法中的入参 Method method 进行 method.invoke(targetObj, args) 这块是整个使用时的差异。
除了以上的核心功能实现,还需要使用到 org.aspectj.weaver.tools.PointcutParser 处理拦截表达式 “execution(* cn.bugstack.springframework.test.bean.IUserService.*(…))”,有了方法代理和处理拦截,我们就可以完成设计出一个 AOP 的雏形了。

工程结构

src
├── main
│ └── java
│ └── cn.bugstack.springframework
│ ├── aop
│ │ ├── aspectj
│ │ │ ├──AspectJExpressionPointcut.java

切点表达式实现了 Pointcut、ClassFilter、MethodMatcher,三个接口定义方法,同时这个类主要是对 aspectj 包提供的表达式校验方法使用

│ │ │ ├──AspectJExpressionPointcutAdvisor.java
│ │ ├── framework
│ │ │ ├──adapter
│ │ │ │ └──MethodBeforeAdviceInterceptor.java
│ │ │ ├── autoproxy
│ │ │ │ └── MethodBeforeAdviceInterceptor.java
│ │ │ ├── AopProxy.java
│ │ │ ├── Cglib2AopProxy.java
│ │ │ ├──JdkDynamicAopProxy.java
│ │ │ ├──ProxyFactory.java
│ │ │ ├── ReflectiveMethodInvocation.java
│ │ ├── AdvisedSupport.java

AdvisedSupport,主要是用于把代理、拦截、匹配的各项属性包装到一个类中,方便在 Proxy 实现类进行使用。这和你的业务开发中包装入参是一个道理

│ │ ├── Advisor.java
│ │ ├── BeforeAdvice.java
│ │ ├── ClassFilter.java

定义类匹配类,用于切点找到给定的接口和目标类。

│ │ ├── MethodBeforeAdvice.java
│ │ ├── MethodMatcher.java

方法匹配,找到表达式范围内匹配下的目标类和方法。在上文的案例中有所体现:methodMatcher.matches(method, targetObj.getClass())

│ │ ├── Pointcut.java

切入点接口,定义用于获取 ClassFilter、MethodMatcher 的两个类

│ │ ├── PointcutAdvisor.java
│ │ ├── TargetSource.java
│ ├── beans
│ │ ├── factory
│ │ │ ├── factory
│ │ │ │ ├── AutowireCapableBeanFactory.java
│ │ │ │ ├── BeanDefinition.java(实体类)

初始化和销毁

│ │ │ │ ├── BeanFactoryPostProcessor.java
│ │ │ │ ├── BeanPostProcessor.java
│ │ │ │ ├── BeanReference.java
│ │ │ │ ├── ConfigurableBeanFactory.java(接口) 定义了 destroySingletons 销毁方法
│ │ │ │ └── SingletonBeanRegistry.java
│ │ │ ├── support
│ │ │ │ ├── AbstractAutowireCapableBeanFactory.java(抽象类)

主要作用:
继承关系:继承AbstractBeanFactory
实现AutowireCapableBeanFactory接口
主要方法:
CreateBean():创建Bean 调用registerDisposableBeanIfNecessary
initializeBean():初始化Bean,调用PostProcessor Before 处理,执行初始化方法invokeInitMethods,执行 BeanPostProcessor After 处理

│ │ │ │ ├── AbstractBeanDefinitionReader.java
│ │ │ │ ├── AbstractBeanFactory.java
│ │ │ │ ├── BeanDefinitionReader.java
│ │ │ │ ├── BeanDefinitionRegistry.java
│ │ │ │ ├── CglibSubclassingInstantiationStrategy.java
│ │ │ │ ├── DefaultListableBeanFactory.java
│ │ │ │ ├── DefaultSingletonBeanRegistry.java

实现destroySingletons 销毁方法( AbstractBeanFactory.java的父类)

│ │ │ │ ├── DisposableBeanAdapter.java

描述:销毁方法适配器
继承关系: 实现DisposableBean接口

│ │ │ │ ├── FactoryBeanRegistrySupport.java(继承 DefaultSingletonBeanRegistry)

作用:实现一个 FactoryBean 注册服务
维护一个存放FactoryBean对象的缓存 factoryBeanObjectCache
处理的就是关于 FactoryBean 此类对象的注册操作
GetObjectFromFactoryBean() 从FactoryBean通过getObject()获取对象,先判断缓存中是否有 如果有直接获取,没有则加入缓存

│ │ │ │ ├── InstantiationStrategy.java
│ │ │ │ └── SimpleInstantiationStrategy.java
│ │ │ ├── support
│ │ │ │ └── XmlBeanDefinitionReader.java
│ │ │ ├── Aware.java(接口)
│ │ │ ├── BeanClassLoaderAware.java(实现Aware接口)
│ │ │ ├── BeanFactory.java
│ │ │ ├──BeanFactoryAware.java(实现Aware接口)
│ │ │ ├── BeanNameAware.java
│ │ │ ├── ConfigurableListableBeanFactory.java
│ │ │ ├── DisposableBean.java
│ │ │ ├──FactoryBean.java(实现FactoryBean)

主要方法:
getObject()获取对象
getObjectType()对象类型
isSingleton()是否是单例对象 如果是单例对象会被放到内存中

│ │ │ ├── HierarchicalBeanFactory.java
│ │ │ ├── InitializingBean.java(接口) 定义初始化方法
│ │ │ └── ListableBeanFactory.java
│ │ ├── BeansException.java
│ │ ├── PropertyValue.java
│ │ └── PropertyValues.java
│ ├── context
│ │ ├── support
│ │ │ ├── AbstractApplicationEventMulticaster.java

继承 ApplicationEventMulticaster的抽象类
维护一个事件监听器的Set,一个BeanFactory的属性
addApplicationListener() removeApplicationListener 添加和删除事件监听器
getApplicationListeners 摘取符合广播事件中的监听处理器 使用supportEvent判断
supportsEvent(applicationListener, ApplicationEvent event)

│ │ │ ├── ApplicationContextEvent.java (继承Application Event)

定义事件的抽象类

│ │ │ ├── ApplicationEventMulticaster.java

作用:添加监听和删除监听
接口

│ │ │ ├── ContextClosedEvent.java

定义关闭事件的抽象类(对象是ApplicationContent)

│ │ │ ├── ContextRefreshedEvent.java

定义刷新事件的抽象类(对象是ApplicationContent)

│ │ │ ├── SimpleApplicationEventMulticaster.java
│ │ ├── support
│ │ │ ├── AbstractApplicationContext.java(抽象类)

继承关系:实现 ConfigurableApplicationContext接口 继承DefaultResourceLoader类

│ │ │ ├── AbstractRefreshableApplicationContext.java
│ │ │ ├── AbstractXmlApplicationContext.java
│ │ │ ├── ApplicationContextAwareProcessor.java(实现BeanPostProcessor接口)
│ │ │ └── ClassPathXmlApplicationContext.java
│ │ ├── ApplicationContext.java
│ │ ├──ApplicationContextAware.java
│ │ ├──ApplicationEvent.java

定义事件的抽象类
包含事件源对象

│ │ ├── ApplicationEventPublisher.java

事件发布接口

│ │ ├── ApplicationListener.java
│ │ └── ConfigurableApplicationContext.java(接口)

主要描述:虚拟机关闭钩子注册调用销毁,定义刷新容器,关闭应用上下文
继承关系:继承ApplicationContext
主要方法:
refresh():
registerShutdownHook():注册虚拟机钩子的方法
close():手动执行关闭虚拟机钩子的方法

│ ├── core.io
│ │ ├── ClassPathResource.java
│ │ ├── DefaultResourceLoader.java(实体类)

作用:资源处理器

│ │ ├── FileSystemResource.java
│ │ ├── Resource.java
│ │ ├── ResourceLoader.java
│ │ └── UrlResource.java
│ └── utils
│ └── ClassUtils.java
└── test
结构关系
在这里插入图片描述
1.整个类关系图就是 AOP 实现核心逻辑的地方,上面部分是关于方法的匹配实现,下面从 AopProxy 开始是关于方法的代理操作。
2.AspectJExpressionPointcut 的核心功能主要依赖于 aspectj 组件并处理 Pointcut、ClassFilter,、MethodMatcher 接口实现,专门用于处理类和方法的匹配过滤操作。
3.AopProxy 是代理的抽象对象,它的实现主要是基于 JDK 的代理和 Cglib 代理。

实现

代理方法案例
@Test
public void test_proxy_method() {
    // 目标对象(可以替换成任何的目标对象)
    Object targetObj = new UserService();
    // AOP 代理->代理对象
    IUserService proxy = (IUserService) Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(), targetObj.getClass().getInterfaces(), new InvocationHandler() {
        // 方法匹配器
        MethodMatcher methodMatcher = new AspectJExpressionPointcut("execution(* cn.bugstack.springframework.test.bean.IUserService.*(..))");
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            if (methodMatcher.matches(method, targetObj.getClass())) {//方法匹配
                // 方法拦截器->加入操作
                MethodInterceptor methodInterceptor = invocation -> {
                    long start = System.currentTimeMillis();
                    try {
                        return invocation.proceed();
                    } finally {
                        System.out.println("监控 - Begin By AOP");
                        System.out.println("方法名称:" + invocation.getMethod().getName());
                        System.out.println("方法耗时:" + (System.currentTimeMillis() - start) + "ms");
                        System.out.println("监控 - End\r\n");
                    }
                };
                // 反射调用
                return methodInterceptor.invoke(new ReflectiveMethodInvocation(targetObj, method, args));
            }
            return method.invoke(targetObj, args);
        }
    });
    String result = proxy.queryUserInfo();
    System.out.println("测试结果:" + result);
}

**目标:**整个案例的目标是给一个 UserService 当成目标对象,对类中的所有方法进行拦截添加监控信息打印处理。

切点表达式

Pointcut

public interface Pointcut {

    /**
     * Return the ClassFilter for this pointcut.
     * @return the ClassFilter (never <code>null</code>)
     */
    ClassFilter getClassFilter();

    /**
     * Return the MethodMatcher for this pointcut.
     * @return the MethodMatcher (never <code>null</code>)
     */
    MethodMatcher getMethodMatcher();

}

切入点接口,定义用于获取 ClassFilter、MethodMatcher 的两个类,这两个接口获取都是切点表达式提供的内容。

ClassFilter

public interface ClassFilter {
    boolean matches(Class<?> clazz);
}

定义类匹配类,用于切点找到给定的接口和目标类。
MethodMatcher

public interface MethodMatcher {

    /**
     * Perform static checking whether the given method matches. If this
     * @return whether or not this method matches statically
     */
    boolean matches(Method method, Class<?> targetClass);
    
}

方法匹配,找到表达式范围内匹配下的目标类和方法。在上文的案例中有所体现:methodMatcher.matches(method, targetObj.getClass())
实现切点表达式类

public class AspectJExpressionPointcut implements Pointcut, ClassFilter, MethodMatcher {

    private static final Set<PointcutPrimitive> SUPPORTED_PRIMITIVES = new HashSet<PointcutPrimitive>();

    static {
    //EXECUTION是常用的一种切点函数
        SUPPORTED_PRIMITIVES.add(PointcutPrimitive.EXECUTION);
    }

    private final PointcutExpression pointcutExpression;

    public AspectJExpressionPointcut(String expression) {
        PointcutParser pointcutParser = PointcutParser.getPointcutParserSupportingSpecifiedPrimitivesAndUsingSpecifiedClassLoaderForResolution(SUPPORTED_PRIMITIVES, this.getClass().getClassLoader());
        pointcutExpression = pointcutParser.parsePointcutExpression(expression);
    }

    @Override
    public boolean matches(Class<?> clazz) {
        return pointcutExpression.couldMatchJoinPointsInType(clazz);
    }

    @Override
    public boolean matches(Method method, Class<?> targetClass) {
        return pointcutExpression.matchesMethodExecution(method).alwaysMatches();
    }

    @Override
    public ClassFilter getClassFilter() {
        return this;
    }

    @Override
    public MethodMatcher getMethodMatcher() {
        return this;
    }

}

切点表达式实现了 Pointcut、ClassFilter、MethodMatcher,三个接口定义方法,同时这个类主要是对 aspectj 包提供的表达式校验方法使用
包装切面通知信息
AdvisedSupport

public class AdvisedSupport {

    // 被代理的目标对象
    private TargetSource targetSource;
    // 方法拦截器
    private MethodInterceptor methodInterceptor;
    // 方法匹配器(检查目标方法是否符合通知条件)
    private MethodMatcher methodMatcher;
    
    // ...get/set
}

AdvisedSupport,主要是用于把代理、拦截、匹配的各项属性包装到一个类中,方便在 Proxy 实现类进行使用。这和你的业务开发中包装入参是一个道理
TargetSource,是一个目标对象,在目标对象类中提供 Object 入参属性,以及获取目标类 TargetClass 信息。
MethodInterceptor,是一个具体拦截方法实现类,由用户自己实现 MethodInterceptor#invoke 方法,做具体的处理。像我们本文的案例中是做方法监控处理
MethodMatcher,是一个匹配方法的操作,这个对象由 AspectJExpressionPointcut 提供服务。

代理抽象实现(JDK&Cglib)
public interface AopProxy {

    Object getProxy();

}

获取代理类的接口,接口包括JDK和Cglib类型
在invoke方法中定义拦截器操作完成代理
JdkDynamicAopProxy

public class JdkDynamicAopProxy implements AopProxy, InvocationHandler {

    private final AdvisedSupport advised;

    public JdkDynamicAopProxy(AdvisedSupport advised) {
        this.advised = advised;
    }

    @Override
    public Object getProxy() {
        return Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(), advised.getTargetSource().getTargetClass(), this);
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        if (advised.getMethodMatcher().matches(method, advised.getTargetSource().getTarget().getClass())) {
            MethodInterceptor methodInterceptor = advised.getMethodInterceptor();
            return methodInterceptor.invoke(new ReflectiveMethodInvocation(advised.getTargetSource().getTarget(), method, args));
        }
        return method.invoke(advised.getTargetSource().getTarget(), args);
    }

}

基于 JDK 实现的代理类,需要实现接口 AopProxy、InvocationHandler,这样就可以把代理对象 getProxy 和反射调用方法 invoke 分开处理了。
getProxy 方法中的是代理一个对象的操作,需要提供入参 ClassLoader、AdvisedSupport、和当前这个类 this,因为这个类提供了 invoke 方法。
invoke 方法中主要处理匹配的方法后,使用用户自己提供的方法拦截实现,做反射调用 methodInterceptor.invoke 。methodInterceptor.invoke 是什么
methodInterceptor是方法对象,方法对象通过调用invoke方法来调用对应方法

这里还有一个 ReflectiveMethodInvocation(着重再看),其他它就是一个入参的包装信息,提供了入参对象:目标对象、方法、入参。
Cglib2AopProxy

public class Cglib2AopProxy implements AopProxy {

    private final AdvisedSupport advised;

    public Cglib2AopProxy(AdvisedSupport advised) {
        this.advised = advised;
    }

    @Override
    public Object getProxy() {
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(advised.getTargetSource().getTarget().getClass());
        enhancer.setInterfaces(advised.getTargetSource().getTargetClass());
        enhancer.setCallback(new DynamicAdvisedInterceptor(advised));
        return enhancer.create();
    }

    private static class DynamicAdvisedInterceptor implements MethodInterceptor {

        @Override
        public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
            CglibMethodInvocation methodInvocation = new CglibMethodInvocation(advised.getTargetSource().getTarget(), method, objects, methodProxy);
            //if else的判断逻辑是为了什么
            if (advised.getMethodMatcher().matches(method, advised.getTargetSource().getTarget().getClass())) {
                return advised.getMethodInterceptor().invoke(methodInvocation);
            }
            return methodInvocation.proceed();
        }
    }

    private static class CglibMethodInvocation extends ReflectiveMethodInvocation {

        @Override
        public Object proceed() throws Throwable {
            return this.methodProxy.invoke(this.target, this.arguments);
        }

    }

}

基于 Cglib 使用 Enhancer 代理的类可以在运行期间为接口使用底层 ASM 字节码增强技术处理对象的代理对象生成,因此被代理类不需要实现任何接口。
关于扩展进去的用户拦截方法,主要是在 Enhancer#setCallback 中处理,用户自己的新增的拦截处理。这里可以看到 DynamicAdvisedInterceptor#intercept 匹配方法后做了相应的反射操作。

测试

public class UserService implements IUserService {

    public String queryUserInfo() {
        try {
            Thread.sleep(new Random(1).nextInt(100));
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return "小傅哥,100001,深圳";
    }

    public String register(String userName) {
        try {
            Thread.sleep(new Random(1).nextInt(100));
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return "注册用户:" + userName + " success!";
    }

}

自定义拦截方法

public class UserServiceInterceptor implements MethodInterceptor {

    @Override
    public Object invoke(MethodInvocation invocation) throws Throwable {
        long start = System.currentTimeMillis();
        try {
            return invocation.proceed();
        } finally {
            System.out.println("监控 - Begin By AOP");
            System.out.println("方法名称:" + invocation.getMethod());
            System.out.println("方法耗时:" + (System.currentTimeMillis() - start) + "ms");
            System.out.println("监控 - End\r\n");
        }
    }

}

用户自定义的拦截方法需要实现 MethodInterceptor 接口的 invoke 方法,使用方式与 Spring AOP 非常相似,也是包装 invocation.proceed() 调用被拦截的方法,并在 finally 中添加监控信息。

单元测试

public void test_dynamic() {
    // 目标对象
    IUserService userService = new UserService();     

    // 组装代理信息
    AdvisedSupport advisedSupport = new AdvisedSupport();
    advisedSupport.setTargetSource(new TargetSource(userService));
    advisedSupport.setMethodInterceptor(new UserServiceInterceptor());
    advisedSupport.setMethodMatcher(new AspectJExpressionPointcut("execution(* cn.bugstack.springframework.test.bean.IUserService.*(..))"));
    
    // 代理对象(JdkDynamicAopProxy)
    IUserService proxy_jdk = (IUserService) new JdkDynamicAopProxy(advisedSupport).getProxy();
    // 测试调用
    System.out.println("测试结果:" + proxy_jdk.queryUserInfo());
    
    // 代理对象(Cglib2AopProxy)
    IUserService proxy_cglib = (IUserService) new Cglib2AopProxy(advisedSupport).getProxy();
    // 测试调用
    System.out.println("测试结果:" + proxy_cglib.register("花花"));
}

相关面试题

1.AOP的基础概念

Aop把业务和日志隔离开
相关概念
切面(Aspect):包含多个切入点表达式和通知的类
连接点(joinpoint):程序中可能被织入增强的点
增强(Advice):定义了在连接点执行的具体逻辑
切点(pointcut):定义了增强逻辑要发生的地方,通过其确定哪些连接点被织入增强
切面表达式:需要拦截的方法(使用execution)

2.JDK和Cglib的实现原理与不同

详见博客
JDK动态代理
JDK的使用流程
jdk动态代理机制使用proxy的newProxyInstance() 方法生成代理对象
该方法有三个参数:
1)loader :目标类的类加载器
2)interfaces : 代理类需要实现的一些接口;
3)h : 实现了 InvocationHandler 接口的类对象(xx implements InvocationHandler 属性包含目标对象);
InvocationHandler(方法对象)的invoke()方法实现自定义的处理逻辑,使用InvocationHandler.invoke()进行调用
invoke()参数:
proxy :动态生成的代理类
method : 与被代理类对象调用的方法相对应
args : 当前 method 方法的参数
注: 注意InvocationHandler的invoke与方法对象method的invoke的不同
方法对象method的invoke方法调用被代理对象的方法
InvocationHandler的invoke方法包含加入其他操作的逻辑

当执行方法时,会调用InvocationHandler的invoke方法
JDK为什么只能代理实现了接口的类
当你使用Proxy类创建代理对象时,你需要指定一个接口列表来表示代理对象所应该实现的接口,这些接口就成为代理对象的类型。由于代理对象的类型是由接口列表决定的,因此只有实现了接口的类才能被代理
CGLIB 动态代理
自定义 MethodInterceptor 并重写 intercept 方法,intercept 用于拦截增强,当代理类调用方法的时候,实际调用的是 MethodInterceptor 中的 intercept 方法。
1.)接口MethodInterceptor继承被代理类并定义intercept方法
2)实现 MethodInterceptor接口 并重写 intercept 方法,intercept方法参数:被代理对象,被拦截的方法,用于调用原始方法的MethodProxy(用spuerinvoke调用)
3)获取代理类:
参数:被代理对象类加载器,被代理类,自定义方法拦截器

3.利用JDK和Cglib的实现AOP的过程

1).实现切点表达式类AspectJExpressionPointcut,用于方法和类的筛选
2).AdvisedSupport包装切面通知信息 目标对象,方法拦截器,方法匹配器
3).动态代理实现
JDK
JdkDynamicAopProxy继承InvocationHandler,属性是切面信息
如果切面信息可以处理目标方法
就调用methodInterceptor的invoke方法,后调用目标方法。否则只调用目标方法
Cglib
Cglib2AopProxy属性是切面信息
同样的判断逻辑

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1496361.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

CleanMyMac X4.14.7永久免费Mac电脑清理和优化软件

CleanMyMac X 是一款功能强大的 Mac 清理和优化软件&#xff0c;适合以下几类人群使用&#xff1a; 需要定期清理和优化 Mac 的用户&#xff1a;随着时间的推移&#xff0c;Mac 设备上可能会积累大量的无用文件、缓存和垃圾&#xff0c;导致系统运行缓慢。CleanMyMac X 的智能扫…

记录第一次使用QT

今晚和舍友准备搞一个QT网盘的项目&#xff0c;我之前也没有用过QT。在舍友的指导下&#xff0c;我安装了QT creator&#xff0c;然后完成了第一次的QT的编译运行&#xff0c;记录一下这激动的感觉&#xff08;2024-03-07)。 使用qmake进行的编译。qDebug进行输出调试hello qt…

github一定要把github-recovery-codes.txt保存好,多备份

之前github让必须2FA&#xff0c;使用了Authy Desktop Setup 2.4.2.exe&#xff0c;但是现在authy不能用了&#xff0c;中国的手机号收不到验证码&#xff0c;急的忙了一天没想到好办法&#xff0c;然后给github发过去消息&#xff0c;反馈的信息如下&#xff1a; 还好没换电脑…

论文笔记 - 基于振动信号的减速器故障诊断方法

1.论文摘要 基于振动信号的减速器故障诊断方法, 沈晴,《起重运输机械》,2018 原作者联系方式: shenqing@zmpc.com 这篇文章包含了一个从工程到数据处理和故障定位的完整过程。是一篇综述文档。它介绍了机械设备常见的三类故障(轴,齿轮、轴承)的故障特征,并在一个故障追…

获取别人店铺的所有商品API接口

使用淘宝淘口令接口的步骤通常包括&#xff1a; 注册成为淘宝开放平台的开发者&#xff1a;在淘宝开放平台网站上注册账号并完成认证。 创建应用以获取API密钥&#xff1a;在您的开发者控制台中创建一个应用&#xff0c;并获取用于API调用的密钥&#xff0c;如Client ID和Clie…

Flink:Temporal Table 的两种实现方式 Temporal Table DDL 和 Temporal Table Function

博主历时三年精心创作的《大数据平台架构与原型实现&#xff1a;数据中台建设实战》一书现已由知名IT图书品牌电子工业出版社博文视点出版发行&#xff0c;点击《重磅推荐&#xff1a;建大数据平台太难了&#xff01;给我发个工程原型吧&#xff01;》了解图书详情&#xff0c;…

直播预告|小白开箱: 云数据库在五朵云上的评测

3 月 7 日&#xff0c;周四晚上 19:00-20:30 由明说三人行组织&#xff0c;邀请了 NineData 国际总经理(GM) Ni Demai、云猿生数据 CTO &#xff06; 联合创始人子嘉&#xff0c;和《明说三人行》创始人 &主持人明叔&#xff0c;共同围绕《小白开箱: 云数据库在五朵云上的评…

vim 中 命令模式下 常见指令

首先&#xff0c;Vim 是一款常用的文本编辑器&#xff0c;具有强大的功能和灵活的操作方式。 vim 分为 三种模式&#xff1a; 命令模式、底行模式、插入模式。 在使用 VIM 编译器 进入文件时&#xff0c;初始默认是命令模式&#xff0c;三种模式转换如下图: 下列使用 Vim 编译…

《探索自动驾驶技术的前景与挑战》

自动驾驶技术,作为现代科技的一大突破,正逐渐改变着我们的交通方式、生活方式以及整个社会结构。本文将围绕自动驾驶技术的现状、优势、局限性以及未来发展趋势展开探讨。 自动驾驶技术的现状概述 自动驾驶技术作为当今科技领域的一项前沿技术,已经取得了巨大的进展并在不同…

扭蛋机小程序开发,企业提升利润的“神器”

扭蛋机在当下消费市场也是比较常见的&#xff0c;价格较低&#xff0c;性价比高&#xff0c;并且具有非常高的收藏价值&#xff0c;能够吸引到各个年龄层的消费者。 扭蛋机与盲盒一样&#xff0c;具有未知性和神秘性&#xff0c;刺激着消费者的购买欲望。此外&#xff0c;扭蛋…

Spring之Bean详解

Spring之Bean详解 什么是Bean&#xff1f; 在Spring中&#xff0c;Bean是指由Spring容器管理的对象&#xff0c;这些对象是由Spring IoC容器负责创建、组装和管理的。Bean可以是Java类的实例&#xff0c;也可以是其他Spring管理的组件&#xff0c;例如数据源、事务管理器等。…

深空通信DTN总结

这里写自定义目录标题 A novel Federated Computation approach for Artificial Intelligence applications in Delay and Disruption Tolerant NetworksabstractintroductionDELAY AND DISRUPTION TOLERANT NETWORKS联邦计算用于容忍延迟和干扰的网络的联合学习框架DTN-ML Orc…

MySQL 学习笔记(基础篇 Day2)

「写在前面」 本文为黑马程序员 MySQL 教程的学习笔记。本着自己学习、分享他人的态度&#xff0c;分享学习笔记&#xff0c;希望能对大家有所帮助。推荐先按顺序阅读往期内容&#xff1a; 1. MySQL 学习笔记&#xff08;基础篇 Day1&#xff09; 目录 3 函数 3.1 字符串函数 3…

关于springboot一个接口请求后,主动取消后,后端是否还在跑

1、最近在思考一个问题&#xff0c;如果一个springboot的请求的接口比较耗时&#xff0c;中途中断该请求后&#xff0c;则后端服务是否会终止该线程的处理&#xff0c;于是写了一个demo RequestMapping(value "/test", method RequestMethod.GET)public BasicResul…

响应人大代表王旭的提议:996程序员也要每天一节体育课

哈喽&#xff0c;我是熊子峰&#xff0c;38岁程序员&#xff0c;正在结合AI写作进行自我成长&#xff0c;穿越程序员的中年危机&#xff0c;这是第 69 篇日更文章。 每天一节体育课 今天&#xff0c;看到一条新闻&#xff0c;人大代表王旭提议中小学生每天应该有一节体育课&am…

docker安装和使用kafka

1. 启动zookeeper Kafka依赖zookeeper, 首先安装zookeeper -p&#xff1a;设置映射端口&#xff08;默认2181&#xff09; docker run --name zookeeper \--network app-tier \-e ALLOW_ANONYMOUS_LOGINyes \--restartalways \-d bitnami/zookeeper:latest2. 启动kafka docker…

Vscode 使用SSH远程连接树莓派的教程(解决卡在Downloading with wget)

配置Vscode Remote SSH 安装OpenSSH 打开Windows开始页面&#xff0c;直接进行搜索PowerShell&#xff0c;打开第一个Windows PowerShell&#xff0c;点击以管理员身份运行 输入指令 Get-WindowsCapability -Online | ? Name -like OpenSSH* 我是已经安装好了&#xff0c;…

基于springboot的车辆充电桩管理系统(系统+数据库+文档)

** &#x1f345;点赞收藏关注 → 私信领取本源代码、数据库&#x1f345; 本人在Java毕业设计领域有多年的经验&#xff0c;陆续会更新更多优质的Java实战项目&#xff0c;希望你能有所收获&#xff0c;少走一些弯路。&#x1f345;关注我不迷路&#x1f345;** 一、研究背景…

彻底搞清楚CUDA和cuDNN版本问题

彻底搞清楚CUDA和cuDNN版本问题 1. 缘起 我的机器上以下三条指令输出的版本不相同。 nvcc -V # 这个输出11.7 nvidia-smi # 右上角显示12.3 import torch; torch.version.cuda # 这个输出12.1我想以此为契机&#xff0c;彻底搞清楚CUDA、cuDNN和torch之间的关系。 环境&a…

Wireshark——获取捕获流量的前N个数据包

1、问题 使用Wireshark捕获了大量的消息&#xff0c;但是只想要前面一部分。 2、方法 使用Wireshark捕获了近18w条消息&#xff0c;但只需要前5w条。 选择文件&#xff0c;导出特定分组。 输入需要保存的消息范围。如&#xff1a;1-50000。 保存即可。