【Spring高级】AOP和动态代理

目录

  • AspectJ实现AOP
  • Java Agent实现AOP
  • Proxy(代理)模式实现AOP
    • JDK代理
    • CGLIB代理
  • AOP的底层实现
    • 切点
    • Aspect与Advisor切面
    • AOP底层的实现演示
  • Spring中的代理选择

在Java中,AOP(面向切面编程)的实现可以通过以下几种方法:

  1. Spring AOP:Spring框架提供了强大的AOP支持。Spring AOP主要基于代理模式实现,可以应用于方法执行的前后,异常处理等场景。Spring AOP可以通过XML配置或注解的方式实现。Spring AOP默认使用JDK动态代理,如果目标对象实现了接口,则使用JDK动态代理,否则使用CGLIB代理。
  2. AspectJ:AspectJ是一个基于Java语言的AOP框架,它扩展了Java语言,提供了更丰富的AOP特性。AspectJ使用编译时织入,而不是运行时织入,因此它可以应用于任何Java应用程序,而不仅仅是Spring应用程序。AspectJ提供了更强大的功能,如环绕通知(around advice)、前置通知(before advice)、后置通知(after advice)和异常通知(after throwing advice)等。
  3. Java Agent:它是一种特殊的工具,可以在 JVM 启动时或运行时加载,以修改类的字节码,从而实现 AOP 效果。

AspectJ实现AOP

一般我们说Spring的AOP都是代理增强,但是一种是利用插件AspectJ直接修改class文件来实现增强。

准备 service

package com.cys.spring.chapter08;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;

@Service
public class MyService {

    private static final Logger log = LoggerFactory.getLogger(MyService.class);

    public static void foo() {
        log.debug("foo()");
    }
}

这里要注意的是,这个aspectj类是没有交给Spring容器进行管理的

创建切面类

package com.cys.spring.chapter08;

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;


@Aspect // 注意此切面并未被 Spring 管理
public class MyAspect {

    private static final Logger log = LoggerFactory.getLogger(MyAspect.class);

    @Before("execution(* com.itheima.service.MyService.foo())")
    public void before() {
        log.debug("before()");
    }
}

测试类:

package com.cys.spring.chapter08;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;


@SpringBootApplication
public class TestAspectj {

    private static final Logger log = LoggerFactory.getLogger(TestAspectj.class);

    public static void main(String[] args) {
        ConfigurableApplicationContext context = SpringApplication.run(TestAspectj.class, args);
        MyService service = context.getBean(MyService.class);
        log.debug("service class: {}", service.getClass());
        service.foo();
        context.close();

    }
}

运行结果发现,我们的service class并不是代理类,但是却实现了增强,打印了before()

这是因为我们使用的是aspectj插件进行的实现,因此无需容器也可以进行增强。

aspectj插件进行的实现,可以直接修改的是.class文件的字节码,因此静态类或不用容器直接调用对象的方法也可以执行。

Java Agent实现AOP

准备工作

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
 
@Service
public class MyService {
 
    private static final Logger log = LoggerFactory.getLogger(MyService.class);
 
    final public void foo() {
        log.debug("foo()");
        this.bar();
    }
 
    public void bar() {
        log.debug("bar()");
    }
}

切面类

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
 
@Aspect // 注意此切面并未被 Spring 管理
public class MyAspect {
 
    private static final Logger log = LoggerFactory.getLogger(MyAspect.class);
     //MyService类中的所有方法都被增强
    @Before("execution(* com.itheima.service.MyService.*())")
    public void before() {
        log.debug("before()");
    }
}

测试

import com.itheima.service.MyService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;
 
@SpringBootApplication
public class A10 {
 
    private static final Logger log = LoggerFactory.getLogger(A10.class);
 
    public static void main(String[] args) {
        ConfigurableApplicationContext context = SpringApplication.run(A10.class, args);
        MyService service = context.getBean(MyService.class);
 
        log.debug("service class: {}", service.getClass());
        service.foo();
 
      
    }
}

运行得到结果,并没有得到增强。

因为我们 第一没有用aop的增强 第二没有加入插件 aspectj的插件 所以没有进行增强。

下面使用agent增强 :

运行时需要在 VM options 里加入 -javaagent:C:/Users/manyh/.m2/repository/org/aspectj/aspectjweaver/1.9.7/aspectjweaver-1.9.7.jar
把其中 C:/Users/manyh/.m2/repository 改为你自己 maven 仓库起始地址

Proxy(代理)模式实现AOP

这也是最重要的实现方式。上面两种了解即可。

JDK代理

JDK代理是JDK自带的技术,他只能对接口进行代理。在生成代理类时,JDK实际上是在运行时动态地创建字节码。这个过程是透明的,你不需要直接处理字节码。

以下是JDK动态代理的基本步骤:

  1. 定义接口:首先,我们需要定义一个接口,该接口将声明目标对象需要实现的方法。
  2. 实现目标对象:接着,我们需要实现这个接口,定义目标对象的具体行为。
  3. 实现InvocationHandler:我们需要创建一个类来实现InvocationHandler接口,并重写其invoke方法。这个方法会在代理对象上的方法被调用时被调用,我们可以在这里添加额外的逻辑,可以使用Lambda表达式。
  4. 创建代理对象:最后,我们使用Proxy类的静态方法newProxyInstance来创建一个代理对象。这个方法需要三个参数:类加载器、代理接口数组和InvocationHandler对象。

示例:

package com.cys.spring.chapter08;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;


public class JdkProxyDemo {

    interface Foo {
        void foo();
    }

    static class Target implements Foo {

        @Override
        public void foo() {
            System.out.println("target foo");
        }
    }

    public static void main(String[] args) {
        // 原对象
        Target target = new Target();

        // 获取classLoader,用来加载在运行期间动态生成字节码
        ClassLoader classLoader = JdkProxyDemo.class.getClassLoader();
        Foo proxyInstance = (Foo) Proxy.newProxyInstance(classLoader, new Class[] {Foo.class}, new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                System.out.println("before...");
                Object res = method.invoke(target, args);
                System.out.println("after...");
                return res;
            }
        });

        proxyInstance.foo();
    }
}

运行结果如下:

before...
target foo
after...

这样,我们就成功地在运行时动态地为目标对象的方法前后都添加了额外的逻辑。

特点:

  1. 代理对象和原对象是兄弟关系,他们都实现了接口Foo
  2. 目标对象可以是final的,后面讲的CGLIB代理就不可以

CGLIB代理

CGLIB(Code Generation Library)是一个第三方库,它扩展了JDK的动态代理,允许对没有实现接口的类进行代理。CGLIB通过继承目标类来创建代理对象,而不是像JDK动态代理那样实现接口。因此,CGLIB代理的对象必须是可继承的。

CGLIB代理的工作原理大致如下:

  1. 生成子类:CGLIB在运行时动态生成一个目标类的子类。这个子类继承了目标类,并覆盖了目标类中的方法。
  2. 方法拦截:CGLIB通过修改字节码来在子类中插入额外的逻辑。具体来说,它会修改方法调用指令,使得当这些方法在代理对象上被调用时,它们会先调用一个拦截器(Interceptor)对象的方法。
  3. 拦截器实现:你需要提供一个实现了MethodInterceptor接口的类作为拦截器。这个类需要实现intercept方法,该方法会在代理对象的方法被调用时被调用。你可以在这个方法中添加额外的逻辑,并决定是否调用目标对象的方法。
  4. 创建代理对象:使用Enhancer类来创建代理对象。Enhancer是CGLIB库中的一个核心类,它提供了创建代理对象的方法。你需要设置拦截器,并指定要代理的目标类。
  5. 方法调用:当通过代理对象调用一个方法时,CGLIB会先调用拦截器的intercept方法。在这个方法中,你可以添加任何你想要的额外逻辑,然后决定是否调用目标对象的方法。如果决定调用目标对象的方法,你可以使用MethodProxy对象来调用它。
  6. 字节码操作:为了实现上述功能,CGLIB使用ASM库来在运行时动态地修改字节码。ASM库允许你读取、修改和写入Java类的字节码。CGLIB通过ASM库修改目标类的字节码,以在方法调用前插入拦截器的逻辑。

示例:

package com.cys.spring.chapter08;

import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.InvocationHandler;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;

/**
 * @author Ethan
 * @date 2024/3/6
 * @description
 */
public class CglibProxyDemo {

    static class Target {
        public void foo() {
            System.out.println("target foo");
        }
    }


    public static void main(String[] args) {
        Target target = new Target();
        Target proxy = (Target) Enhancer.create(Target.class, new MethodInterceptor() {
            @Override
            public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
                System.out.println("before...");
                Object res = method.invoke(target, args);
                System.out.println("after...");
                return res;
            }
        });
        proxy.foo();
    }

}

运行结果一样。

另外我们发现,上面的invoke方法还有一个参数MethodProxy,使用他也可以完成代理,并且避免使用反射,他有两种方式使用:

// 1. 使用MethodProxy。不再使用反射
Object res = methodProxy.invoke(target, args);
// 2. 连原对象target都不需要了,需要代理对象
Object res = methodProxy.invokeSuper(o, args);  

特点:

  1. 原对象与代理对象是父子关系,即代理对象继承了原对象;
  2. 因此原对象和原对象的方法都不能是final修饰。

AOP的底层实现

切点

在Spring中其提供了PointCut接口来表示切点,其源码如下:

package org.springframework.aop;


public interface Pointcut {

   /**
    * 返回类过滤器
    */
   ClassFilter getClassFilter();

   /**
    * 返回方法过滤器
    */
   MethodMatcher getMethodMatcher();


   /**
    * Canonical Pointcut instance that always matches.
    */
   Pointcut TRUE = TruePointcut.INSTANCE;

}

其中最重要的两个方法是获取类过滤器和方法过滤器的方法。

在AspectJ中,有一个实现类AspectJExpressionPointcut,其源码如下:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

使用 AspectJExpressionPointcut,可以定义复杂的切入点表达式,包括基于方法名、参数类型、返回类型、执行者(target)、参数值等的匹配规则。

Aspect与Advisor切面

在Sping底层,有两个切面的概念,一个是aspect,一个是advisor

Aspect(切面)

Aspect 是一个模块化的组件,它封装了横切关注点(cross-cutting concerns)。横切关注点通常指的是那些跨越多个应用程序模块或对象的关注点,如日志记录、事务管理、安全检查等。一个切面可以包含一个或多个通知(advice),这些通知定义了何时以及如何执行横切逻辑。

在Spring AOP中,切面通常使用AspectJ的注解(如@Aspect@Before@After@Around等)来定义。一个切面可能包含多个通知,每个通知都对应一个特定的连接点(join point),如方法执行、异常处理等。

Advisor(通知器)

Advisor 是Spring AOP中的另一个核心概念。它封装了一个通知(advice)和一个切入点表达式(pointcut expression),用于确定何时应用通知。切入点表达式定义了哪些连接点(如方法调用)将触发通知的执行。

在Spring AOP中,Advisor 通常以编程方式创建,而不是通过AspectJ注解。Advisor 的一个常见实现是DefaultPointcutAdvisor,它接受一个Pointcut和一个Advice作为参数。

区别与联系

  • 区别:
    • Aspect 是一个更高级的概念,它封装了多个横切关注点,并通常使用AspectJ注解来定义。
    • Advisor 是一个更底层的概念,它封装了一个通知和一个切入点表达式,用于确定何时应用通知。
  • 联系:
    • 在Spring AOP中,Aspect 可以被自动注册为Advisor。当Spring容器检测到带有AspectJ注解的类时,它会将这些类注册为Aspect,并将它们转换为Advisor
    • 你可以手动创建和配置Advisor,但这通常不是必需的,因为Spring可以自动为你处理AspectAdvisor的转换。

AOP底层的实现演示

为了更方便我们演示底层细节,这里我们选择AspectJExpressionPointcut切点和Advisor切面。

看如下案例:

package com.cys.spring.chapter08;

import org.aopalliance.intercept.MethodInterceptor;
import org.springframework.aop.aspectj.AspectJExpressionPointcut;
import org.springframework.aop.framework.ProxyFactory;
import org.springframework.aop.support.DefaultPointcutAdvisor;


/**
 * @author Ethan
 * @date 2024/3/6
 * @description
 */
public class TestSpringProxy {

    public static void main(String[] args) {
        // 1.创建切点
        AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();
        // 添加切点表达式
        pointcut.setExpression("execution(* foo())");

        // 2.创建通知
        MethodInterceptor advice = invocation -> {
            System.out.println("before...");
            Object res = invocation.proceed();  // 调用目标
            System.out.println("after...");
            return res;
        };

        // 3.创建切面
        DefaultPointcutAdvisor advisor = new DefaultPointcutAdvisor(pointcut, advice);

        // 创建代理,使用Spring提供的工厂方法
        Target1 target1 = new Target1();
        ProxyFactory proxyFactory = new ProxyFactory();
        proxyFactory.setTarget(target1);
        proxyFactory.addAdvisor(advisor);

        I1 proxy = (I1) proxyFactory.getProxy();
        System.out.println(proxy.getClass());
        proxy.foo();
        proxy.bar();

    }

    interface I1 {
        void foo();

        void bar();
    }

    static class Target1 implements I1 {
        @Override
        public void foo() {
            System.out.println("target1 foo");
        }

        @Override
        public void bar() {
            System.out.println("target1 bar");
        }
    }

    static class Target2 implements I1 {
        @Override
        public void foo() {
            System.out.println("target2 foo");
        }

        @Override
        public void bar() {
            System.out.println("target2 bar");
        }
    }

}

上面代码中,我们创建两个类,分别都实现了接口,然后我们在main方法中自己创建了切点、切面、通知,以及使用代理工厂创建了代理,然后分别调用foo方法和bar方法。

运行结果如下:

class com.cys.spring.chapter08.TestSpringProxy$Target1$$EnhancerBySpringCGLIB$$8416bf5d
before...
target1 foo
after...
target1 bar

注意,如果发生以下错误:
Caused by: java.lang.ClassNotFoundException: org.aspectj.weaver.tools.PointcutDesignatorHandler
则是缺少依赖包:

<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
</dependency>

通过上面结果发现以下两点:

  1. 我们的代理对象类型为$$EnhancerBySpringCGLIB$$8416bf5d,可以看到他使用了CGLIB代理
  2. foo方法符合切点表达式,被增强了,bar方法没有

下面再看下哪些情况会使用JDK代理。

Spring中的代理选择

在Spring中,也是选择代码来实现AOP功能,而且上面说的JDK代理和CGLIB代理他都会使用。

那在Spring中什么时候使用JDK代理和什么时候使用CGLIB代理呢?

先说结论:

  1. 如果目标对象实现了接口,默认情况下会采用JDK的动态代理实现
  2. 如果目标对象实现了接口,可以强制使用CGLIB实现AOP(proxyTargetClass=true)
  3. 如果目标对象没有实现了接口,必须采用CGLIB库

在上面的案例中,我们的Target1已经实现了接口,为什么还是使用的CGLIB呢?那是因为Spring自己并不知道Target1到底实没实现接口,需要我们明确告诉他,代码如下:

proxyFactory.setInterfaces(target1.getClass().getInterfaces());

再次测试下:

package com.cys.spring.chapter08;

import org.aopalliance.intercept.MethodInterceptor;
import org.springframework.aop.aspectj.AspectJExpressionPointcut;
import org.springframework.aop.framework.ProxyFactory;
import org.springframework.aop.support.DefaultPointcutAdvisor;


public class TestSpringProxy {

    public static void main(String[] args) {
        // 1.创建切点
        AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();
        // 添加切点表达式
        pointcut.setExpression("execution(* foo())");

        // 2.创建通知
        MethodInterceptor advice = invocation -> {
            System.out.println("before...");
            Object res = invocation.proceed();  // 调用目标
            System.out.println("after...");
            return res;
        };

        // 3.创建切面
        DefaultPointcutAdvisor advisor = new DefaultPointcutAdvisor(pointcut, advice);

        // 创建代理,使用Spring提供的工厂方法
        Target1 target1 = new Target1();
        ProxyFactory proxyFactory = new ProxyFactory();
        proxyFactory.setTarget(target1);
        proxyFactory.addAdvisor(advisor);
        proxyFactory.setInterfaces(target1.getClass().getInterfaces());

        I1 proxy = (I1) proxyFactory.getProxy();
        System.out.println(proxy.getClass());
        proxy.foo();
        proxy.bar();

    }

    interface I1 {
        void foo();

        void bar();
    }

    static class Target1 implements I1 {
        @Override
        public void foo() {
            System.out.println("target1 foo");
        }

        @Override
        public void bar() {
            System.out.println("target1 bar");
        }
    }

    static class Target2 implements I1 {
        @Override
        public void foo() {
            System.out.println("target2 foo");
        }

        @Override
        public void bar() {
            System.out.println("target2 bar");
        }
    }

}

运行结果如下:

class com.cys.spring.chapter08.$Proxy0
before...
target1 foo
after...
target1 bar

可以看到,这时候代理类就变成JDK代理实现的了。

但是当我们设置proxyTargetClass=true时,总是使用CGLIB代理实现

package com.cys.spring.chapter08;

import org.aopalliance.intercept.MethodInterceptor;
import org.springframework.aop.aspectj.AspectJExpressionPointcut;
import org.springframework.aop.framework.ProxyFactory;
import org.springframework.aop.support.DefaultPointcutAdvisor;


public class TestSpringProxy {

    public static void main(String[] args) {
        // 1.创建切点
        AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();
        // 添加切点表达式
        pointcut.setExpression("execution(* foo())");

        // 2.创建通知
        MethodInterceptor advice = invocation -> {
            System.out.println("before...");
            Object res = invocation.proceed();  // 调用目标
            System.out.println("after...");
            return res;
        };

        // 3.创建切面
        DefaultPointcutAdvisor advisor = new DefaultPointcutAdvisor(pointcut, advice);

        // 创建代理,使用Spring提供的工厂方法
        Target1 target1 = new Target1();
        ProxyFactory proxyFactory = new ProxyFactory();
        proxyFactory.setTarget(target1);
        proxyFactory.addAdvisor(advisor);
        proxyFactory.setInterfaces(target1.getClass().getInterfaces());
        proxyFactory.setProxyTargetClass(true);

        I1 proxy = (I1) proxyFactory.getProxy();
        System.out.println(proxy.getClass());
        proxy.foo();
        proxy.bar();

    }

    interface I1 {
        void foo();

        void bar();
    }

    static class Target1 implements I1 {
        @Override
        public void foo() {
            System.out.println("target1 foo");
        }

        @Override
        public void bar() {
            System.out.println("target1 bar");
        }
    }

    static class Target2 implements I1 {
        @Override
        public void foo() {
            System.out.println("target2 foo");
        }

        @Override
        public void bar() {
            System.out.println("target2 bar");
        }
    }

}

运行后如下:

class com.cys.spring.chapter08.TestSpringProxy$Target1$$EnhancerBySpringCGLIB$$f9f7352c
before...
target1 foo
after...
target1 bar

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

MySQL的日志:undo log、redo log、binlog有什么作用

目录 从一个update语句说起 undo log 为什么需要undo log undo log 版本链 undo log 是如何持久化到磁盘? redo log 为什么需要redo log redo的组成 redo Log的刷盘策略 redo Log循环写 crash-safe能力 binlog 为什么需要 binlog &#xff1f; binlog与redo lo…

淘宝API接口开发系列——淘宝详情数据采集

淘宝详情数据采集涉及多种技术和方法&#xff0c;下面列举几种常见的方式&#xff1a; 请求示例&#xff0c;API接口接入Anzexi58 爬虫技术&#xff1a;使用编程语言&#xff08;如Python&#xff09;编写网络爬虫程序&#xff0c;通过模拟浏览器行为访问淘宝网站&#xff0c;…

XMind:让思维可视化,提升工作效率的利器

XMind是一款全球领先的开源思维导图和头脑风暴软件&#xff0c;它应用全球最先进的Eclipse RCP软件架构&#xff0c;拥有优秀的用户体验&#xff0c;凭借简单易用、功能强大的特点&#xff0c;在2013年被著名互联网媒体Lifehacker评选为全球最受欢迎的思维导图软件。目前&#…

PyQt上手指南

文章目录 前言PyQt的好处从一个最简单的例子入手PyQt5基础组件体系源码结构 Qt Designer基础布局高级界面Web控件 多线程列表图形绘制PyQt5.QtGuiPyQtGraphmatplotlib和PyQt结合和mplfinance结合 工具使用打包链接 前言 用户界面开发&#xff0c;我搞过visual C MFC、Delphi V…

【链表】Leetcode 142. 环形链表 II【中等】

环形链表 II 给定一个链表的头节点 head &#xff0c;返回链表开始入环的第一个节点。 如果链表无环&#xff0c;则返回 null。 如果链表中有某个节点&#xff0c;可以通过连续跟踪 next 指针再次到达&#xff0c;则链表中存在环。 为了表示给定链表中的环&#xff0c;评测系…

ruoyi-activiti添加用车申请流程(二)

实体类Car中必须要有String userId属性。 设置自定义表单为system/car/deptleadercheck&#xff1a; 然后在CarController中编写system/car/deptleadercheck对应的函数&#xff1a; GetMapping("/deptleadercheck")public String deptleadercheck(String taskid, M…

学习总结!

最近主要学习了java&#xff0c;题目的话就写了两道。 这道题目运用三维的bfs&#xff0c;第一次做时无从下手&#xff0c;原来可以利用三维数组&#xff08;第一次用三维数组&#xff09;可以解决这类问题&#xff0c;然后套bfs模板即可。 #include<iostream> #include…

算法体系-11 第十一节:二叉树基本算法(上)

一 两链表相交 1.1 题目描述 给定两个可能有环也可能无环的单链表&#xff0c;头节点head1和head2。请实现一个函数&#xff0c;如果两个链表相交&#xff0c;请返回相交的 第一个节点。如果不相交&#xff0c;返回null 【要求】 如果两个链表长度之和为N&#xff0c;时间复杂…

瑞_Redis_短信登录_基于Session实现登录流程

文章目录 项目介绍1 短信登录1.1 项目准备1.2 基于Session实现登录流程1.2.1 功能流程介绍1.2.1.1 发送短信验证码1.2.1.2 短信验证码登录、注册1.2.1.3 校验登录状态 1.2.2 实现发送短信验证码功能1.2.2.1 页面流程1.2.2.2 代码实现1.2.2.3 测试 1.2.3 实现短信验证码登录、注…

SLAM 算法综述

LiDAR SLAM 其主要思想是通过两个算法&#xff1a;一个高频激光里程计进行低精度的运动估计&#xff0c;即使用激光雷达做里程计计算两次扫描之间的位姿变换&#xff1b;另一个是执行低频但是高精度的建图与校正里程计&#xff0c;利用多次扫描的结果构建地图&#xff0c;细化位…

今日早报 每日精选15条新闻简报 每天一分钟 知晓天下事 3月20日,星期三

每天一分钟&#xff0c;知晓天下事&#xff01; 2024年3月20日 星期三 农历二月十一 春分 1、 教育部发布新增24种本科专业 涉及智能视觉工程、足球运动等。 2、 香港特区立法会全票通过《维护国家安全条例》&#xff0c;将于23日刊宪。 3、 国际乒联更新世界排名&#xff0c…

高效数据处理:亚信安慧AntDB在行动

亚信安慧AntDB数据库在运营商自主可控替换项目中的成功应用&#xff0c;具有极其重要的意义。该数据库的落地&#xff0c;不仅为这一项目注入了强大的支持力量&#xff0c;还在更大程度上提升了整体的运营效能。作为一种高效可靠的数据库解决方案&#xff0c;AntDB引入了先进的…

【项目】YOLOv5+PaddleOCR实现艺术字验证码识别

YOLOv5PaddleOCR实现艺术字类验证码识别 一、引言1.1 实现目标1.2 人手动点选验证码逻辑1.3 计算机点选逻辑 二、计算机验证方法2.1 PaddleOCR下方文字识别方法2.2 YOLOv5目标检测方法2.3 艺术字分类方法2.4 返回结果 三、代码获取 一、引言 1.1 实现目标 要识别的验证码类型…

浏览器的渲染原理

浏览器的渲染原理 来总结一下最近理解的浏览器渲染原理和流程 首先浏览器是多进程的&#xff0c;分为渲染进程、插件进程、主进程、网络进程以及GPU进程 而我们打包出来的js html css文件&#xff0c;经过浏览器的渲染进程&#xff0c;就会展示出看到的页面。下面主要来了解一…

mysql索引实现

什么是索引失效 在MySQL中&#xff0c;索引失效指的是查询语句无法有效地使用索引&#xff0c;而必须进行全表扫描。索引失效可能会导致查询性能下降&#xff0c;特别是在处理大量数据时。 索引失效的原因 1.索引列进行了运算或函数操作 如果对索引列进行了运算或使用了函数…

第十届电气工程、控制和机器人技术国际会议(ICRAS 2024)即将召开!

2024年第十届电气工程、控制和机器人技术国际会议&#xff08;ICRAS 2024&#xff09;将于2024年6月21日至23日在日本东京举行。本次会议由中国地质大学&#xff08;武汉&#xff09;和北京控制机器人与智能技术研究所主办。ICRAS 2024旨在聚集来自世界各地的教授、研究人员、学…

遇到大量的照片需要尺寸调整怎么办?跟着小编往下看 轻松帮你解决照片尺寸修改的烦恼

在日常的摄影后期处理中&#xff0c;我们可能会遇到需要将大量照片上传至社交媒体、制作相册、或者进行打印等需求。不同的平台或用途对照片的尺寸有不同的要求&#xff0c;因此我们需要对照片的尺寸进行调整以满足这些要求。此外&#xff0c;随着手机、相机等设备的普及&#…

开源问卷调查系统

Java Vue 开源问卷调查系统 附项目地址 Astar问卷调查系统 基于SpringBootVue前后端分离的问卷调查系统 平台简介 本项目旨在提供一个简单易用的问卷调查平台&#xff0c;帮助用户创建、分享问卷&#xff0c;并收集、分析调查数据。我们希望能够为各行各业的调查需求提供一种…

【python】python结合js逆向,让有道翻译成为你的翻译官,实现本地免费实时翻译

✨✨ 欢迎大家来到景天科技苑✨✨ 🎈🎈 养成好习惯,先赞后看哦~🎈🎈 🏆 作者简介:景天科技苑 🏆《头衔》:大厂架构师,华为云开发者社区专家博主,阿里云开发者社区专家博主,CSDN新星创作者等等。 🏆《博客》:Python全栈,前后端开发,人工智能,js逆向,A…

理清大数据技术与架构

大数据并不是一个系统软件&#xff0c;更不是一个单一的软件&#xff0c;它实际上是一种技术体系、一种数据处理方法&#xff0c;甚至可以说是一个服务平台。在这个技术体系中&#xff0c;涵盖了许多不同的部件&#xff0c;比如Hadoop服务平台。这一服务平台可以根据具体情况自…