Spring手写模拟源码篇(你值得拥有)

news2024/11/16 6:44:26

概念篇

下面是本文章关于Spring底层原理的章节

Bean的创建的生命周期

类-》推断构造方法-》根据构造方法创建普通对象-》依赖注入(@Autowired等进行属性注入)-》初始化前(@PostConstruct)->初始化(InitializingBean)-》初始化后(AOP)-》代理对象(没有开启AOP就会把普通对象放入单例池)-》放入单例池-》Bean对象

依赖注入

在Spring容器中创建了一个普通对象后,如果这个对象有类似于@Autowired注解的属性,如何给这个属性赋值呢?这里利用的是反射的机制,在创建完一个普通对象后,利用反射机制看有没有@Autowird注解的属性,如果依赖注入的bean为单例,首先从单例池中寻找,找到就赋值注入,找不到就创建然后注入属性,如果这个bean为多例,就会直接new 一个对象出来然后赋值。这个具体可以看下面的模拟代码进行深入理解。

推断构造方法

在Spring容器中使用构造方法创建对象的时候默认采用无参构造方法。在Spring容器中创建对象是通过反射根据构造方法进行创建的,至于具体根据哪个构造方法进行创建对象,内容如下:

1.只有一个构造方法,那么实例化就只能使用这个构造方法了。有参的话(前提是根据参数类型或者名字可以找到唯一的bean。
2.有多个构造方法,不管构造方法参数是一个还是多个,那么Spring会去找默认的无参的构造方法,找不到则报错。
3.多个构造方法,并且开发者指定了想使用的构造方法,那么就用这个构造方法
通过@Autowired注解,@Autowired注解可以写在构造方法上,所以哪个构造方法上写了@Autowired注解,表示开发者想使用哪个构造方法。通过@Autowired注解的方式,需要Spring通过byType+byName的方式去找到符合条件的bean作为构造方法的参数值,当然找不到是要报错的。通过byType找如果只有一个就使用该Bean对象,如果有多个再根据名字去找,Spring容器在寻找过程中是根据参数名作为名字去寻找的,找不到则报错。这个类似于@Autowired注解,一开始根据类型去寻找,如果有多个,再根据属性名去找对应的是该名字的Bean对象。

@PostConstruct

如果想要在对象初始化之前执行该对象中的一些方法,可以在该对象方法上加上@PostConstruct注解。在Spring容器中初始化之前执行有该注解的方法。

初始化

Spring容器中对于对象的初始化可以通过继承 InitializingBean 接口重写 afterPropertiesSet() 方法,在此方法里面执行自己的初始化的业务逻辑。有关代码如下:

@Component("test")
public class Test implements InitializingBean {
    public  void hello(){
        System.out.println("执行方法");
    }

    @PostConstruct
    public void go(){
        System.out.println("初始化之前");
    }

    @Override
    public void afterPropertiesSet() throws Exception {
        System.out.println("初始化");
    }
}

执行结果如下图:

AOP

AOP简介

这里先对AOP进行简单的介绍

AOP (Aspect Orient Programming),直译过来就是 面向切面编程,AOP 是一种编程思想,是面向对象编程(OOP)的一种补充。面向切面编程,实现在不修改源代码的情况下给程序动态统一添加额外功能的一种技术。AOP可以拦截指定的方法并且对方法增强,而且无需侵入到业务代码中,使业务与非业务处理逻辑分离,比如Spring的事务,通过事务的注解配置,Spring会自动在业务方法中开启、提交业务,并且在业务处理失败时,执行相应的回滚策略。AOP可以拦截指定的方法并且对方法增强,而且无需侵入到业务代码中,使业务与非业务处理逻辑分离,比如Spring的事务,通过事务的注解配置,Spring会自动在业务方法中开启、提交业务,并且在业务处理失败时,执行相应的回滚策略。

这里采用SpringBoot整合AOP实例代码如下:

导入依赖

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-aop</artifactId>
</dependency>

导入配置类

@Configuration
@ComponentScan("com.example.demo.test")
@EnableAspectJAutoProxy
public class AspectConfiguration {

}

导入切面类

@Aspect
@Component
public class MyAspect {

    @Pointcut("execution(* com.example.demo.test.*.*(..))")
    public void myPointCut(){

    }
    //前置通知
    @Before("myPointCut()")
    public void before(JoinPoint joinPoint){
        System.out.println("前置通知");
        System.out.println("目标类对象"+joinPoint.getTarget()+"被增强的方法"+joinPoint.getSignature().getName());
    }
    //后置返回通知
    @AfterReturning("myPointCut()")
    public void afterreturn(JoinPoint joinPoint){
        System.out.println("后置返回通知");
        System.out.println("目标类对象"+joinPoint.getTarget()+"被增强的方法"+joinPoint.getSignature().getName());
    }

      //环绕通知,返回值类型为Object,必须有一参数是ProceedingJoinPoint
    @Around("myPointCut()")
    public Object aroud(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        System.out.println("环绕开始,模拟开启事务");
       //执行当前方法
        Object proceed = proceedingJoinPoint.proceed();
        System.out.println("环绕结束,模拟关闭事务");
        return proceed;
    }

    //异常通知
    @AfterThrowing(value = "myPointCut()",throwing = "e")
    public void except(Throwable e){
        System.out.println("异常通知"+e.getMessage());
    }

    //后置最终通知
    @After("myPointCut()")
    public void after(JoinPoint joinPoint){
        System.out.println("最终通知");
    }



}

执行代码

public class hello {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext annotationConfigApplicationContext=new AnnotationConfigApplicationContext(AspectConfiguration.class);
        Test test = (Test) annotationConfigApplicationContext.getBean("test");
        test.hello();
    }
}

结果如下图所示

 这里对于Spring5通知的执行顺序进行简单的总结,注意Spring4通知的执行顺序和Spring5不一样。

程序执行正常:

1、环绕通知前
2、@Before通知
3、程序逻辑
4、@AfterReturning通知
5、@After通知
6、环绕通知后

程序执行异常:

1、环绕通知前
2、@Before通知
3、@AfterThrowing异常通知
4、@After通知
异常日志

AOP原理

对于上面的代码我们在执行test.hello的时候我们的对象是Test对象吗?还是代理对象?经过debug我们来看一下。如下图证实我们拿到的对象是代理对象。

 注意在Spring容器中假如真实对象中有类似@Autowired注解进行依赖注入的时候,我们在这里debug拿到的代理对象关于这样的属性实际上是空的,但是直接运行的时候实际上又会获得依赖注入对象,这是什么原因呢?

在Spring容器中代理类其实是真实类的子类,通过extends继承,既然代理类是真实类的子对象,那么他们之间是怎么实现的呢?实现方法之一如下:

public class My extends Test {
    @Override
    public void hello() {
        //执行切面逻辑
        super.hello();
    }
}

这样可以正确的实现吗?其实是不行,假入Test类中有依赖注入的属性,然后My代理类执行父类的时候,在执行方法中的有依赖注入的属性其实是空的,因为父类创建了一个对象并为这个属性赋值,它的子类并不会获得该属性的值的。那解决办法呢?那就是在My类中创建一个Test类对象的属性Target,并把真实类赋值给Target属性,然后在执行方法中执行Target.hello方法就可以了。

Spring的事务

Spring声明式事务管理是通过AOP技术实现的事务管理,其本质是对方法前后进行拦截,在目标方法开始之前创建一个事务,在目标方法执行之后,根据执行情况提交或回滚事务。

事务在逻辑上是一组操作,要么执行,要不都不执行。主要是针对数据库而言的,为了保证事务是正确可靠的,在数据库进行写入或者更新操作时,就必须得表现出 ACID 的 4 个重要特性:
原子性(Atomicity):一个事务中的所有操作,要么全部完成,要么全部不完成,不会结束在中间某个环节。事务在执行过程中发生错误,会被回滚(Rollback)到事务开始前的状态,就像这个事务从来没有执行过一样。
一致性(Consistency):在事务开始之前和事务结束以后,数据库的完整性没有被破坏。
事务隔离(Isolation):数据库允许多个并发事务同时对其数据进行读写和修改,隔离性可以防止多个事务并发执行时由于交叉执行而导致数据的不一致。
持久性(Durability):事务处理结束后,对数据的修改就是永久的,即便系统故障也不会丢失。

Spring事务其实就是在上一个AOP内容中切面逻辑中实现的,在开启事务后,Spring容器会用事务管理器新建一个数据库连接,并且一开始设置autocommint=false,然后执行普通对象中的相关方法如果没有抛异常就会提交,否则就会回滚。然后下面我们来分析个案例代码如下

@Component()
public class Test{


    @Transactional
    public  void hello(){
        System.out.println("good");
        a();
    }

    @Transactional(propagation = Propagation.NEVER)
    public void a(){
        System.out.println("a");
    }
}

@SpringBootApplication
@MapperScan("com.example.demo.mybatisplus")
@EnableTransactionManagement
public class DemoApplication {


    public static void main(String[] args) {
        ConfigurableApplicationContext run = SpringApplication.run(DemoApplication.class, args);
        Test bean = run.getBean(Test.class);
        bean.hello();
    }

}

PROPAGATION_NEVER该传播机制不支持外层事务,即如果外层有事务就抛出异常,当我们获取Test Bean对象的时候,然后执行hello方法的时候他会抛出异常吗?实际上是不会的,那到底是什么原因呢?我们通过上面的内容我们知道了Spring事务是在代理类中的相关方法的代理逻辑中执行的,在执行方法切面逻辑的时候是由我们的代理对象执行事务管理器相关操作的,而a方法实际的执行的是普通对象并不是代理对象,普通对象在执行a方法的时候是不会有事务的操作的,注解仅仅就成为了一个摆设。如果有兴趣的小伙伴可以尝试一下。那么怎么解决问题呢?当有@Transactional的类就会在Spring容器中生成代理对象放到单例池当中,那么可以在这个对象中使用@Autowired注解依赖注入代理对象,把a方法放在这个代理对象对应的普通类中,然后通过这个依赖注入的代理对象调用a方法,就可以正常的解决。还有一个办法就是如下面代码也可以正常的解决。

@Transactional
public  void hello(){
    System.out.println("good");
    Test o = (Test) AopContext.currentProxy();
    o.a();
}

循环依赖

这里引用

被 Spring 管理的对象叫做 Bean 。Bean的生成步骤如下:

Spring 扫描 class 得到 BeanDefinition;
根据得到的 BeanDefinition 去生成 bean;
首先根据 class 推断构造方法;
根据推断出来的构造方法,反射,得到一个对象(暂时叫做原始对象);
填充原始对象中的属性(依赖注入);
如果原始对象中的某个方法被 AOP 了,那么则需要根据原始对象生成一个代理对象;
把最终生成的代理对象放入单例池(源码中叫做 singletonObjects)中,下次 getBean 时就直接从单例池拿即可;
对于 Spring 中的 Bean 的生成过程,步骤还是很多的,并且不仅仅只有上面的7步,还有很多很多,这里不详细说了。

我们可以发现,在得到一个原始对象后,Spring 需要给对象中的属性进行依赖注入,那么这个注入过程是怎样的?

比如上文说的 A 类,A 类中存在一个 B 类的 b 属性,所以,当 A 类生成了一个原始对象之后,就会去给 b 属性去赋值,此时就会根据 b 属性的类型和属性名去 BeanFactory 中去获取 B 类所对应的单例bean。

1. 如果此时 BeanFactory 中存在 B 对应的 Bean,那么直接拿来赋值给 b 属性;
2. 如果此时 BeanFactory 中不存在 B 对应的 Bean,则需要生成一个 B 对应的 Bean,然后赋值给 b属性。

问题就出现在「第二种」情况,如果此时 B 类在 BeanFactory 中还没有生成对应的 Bean,那么就需要去生成,就会经过 B 的 Bean 的生命周期。

那么在创建 B 类的 Bean 的过程中,如果 B 类中存在一个 A 类的 a 属性,那么在创建 B 的 Bean 的过程中就需要 A 类对应的 Bean,但是,触发 B 类 Bean 的创建的条件是 A 类 Bean 在创建过程中的依赖注入,所以这里就出现了循环依赖:

A Bean创建–>依赖了 B 属性–>触发 B Bean创建—>B 依赖了 A 属性—>需要 A Bean(但A Bean还在创建过程中)

从而导致 A Bean 创建不出来,B Bean 也创建不出来。

这里就用到三级缓存了,这里设置两个类Aservice,Bservice。Aservice中有Bservice属性的注入,Bservice中有Aservice属性的注入。那么三级缓存是如何解决问题的呢?这里先对三级缓存进行简单的描述。

「singletonObjects」:缓存某个 beanName 对应的经过了完整生命周期的bean也就是我们的单例池;
「earlySingletonObjects」:缓存提前拿原始对象进行了 AOP 之后得到的代理对象,原始对象还没有进行属性注入和后续的 BeanPostProcesso r等生命周期;
「singletonFactories」:缓存的是一个 ObjectFactory ,主要用来去生成原始对象进行了 AOP之后得到的「代理对象」,在每个 Bean 的生成过程中,都会提前暴露一个工厂,这个工厂可能用到,也可能用不到,如果没有出现循环依赖依赖本 bean,那么这个工厂无用,本 bean 按照自己的生命周期执行,执行完后直接把本 bean 放入 singletonObjects 中即可,如果出现了循环依赖依赖了本 bean,则另外那个 bean 执行 ObjectFactory 提交得到一个 AOP 之后的代理对象(如果有 AOP 的话,如果无需 AOP ,则直接得到一个原始对象)。

那么如何打破循环依赖呢?

摘用网上的图片

Aservice  在Spring容器创建过程中,在实例化后把Aservice普通对象放在缓存中,然后进行Bservice属性的依赖注入,首先从单例池中寻找Bservice,如果找到就会赋值,找不到就会创建Bservice,在进行Aservice注入的时候从单例池寻找,找不到然后从缓存中寻找进行属性的注入。此时循环依赖问题得以解决。因为在整个过程中AService都是单例的 , 所以即使从缓存中拿到的AService的原始对象也没有关系 , 因为在后续的Bean生命周期中 ,AService在堆内存中没有发生变化。这种情况当Aservice对象没有AOP的时候这种情况是没有问题的,如果Aservice类有AOP,从上文可知那么单例池中的该对象是代理对象,而我们在Bservice中依赖注入的Aservice是普通对象,这显而易见就有问题了。

所以就需要二级缓存了,在Bservice进行Aservice属性注入的时候,要进行提前AOP,而上面的缓存就相当于三级缓存存储原始对象,出现循环依赖后从二级缓存earlySingletonObjects中获取如果获取不到对应的对象,然后就会从三级缓存中获取原始对象,如果是AOP就生成代理对象,不是就是普通对象然后放在二级缓存中,此时这个对象还不能放入单例池中,为什么呢?假如这里是个代理对象,代理对象的Target原始对象还没有完成生命周期属性还没有完全注入完成,如果在这里放入单例池,在多线程环境下在这时从单例池中获取这个bean对象就会发生不可预期的错误。当Bservice Bean对象创建完成后然后在Aservice中填充完所有属性后,就可以从二级缓存中获取该对象然后放到单例池中了。

手写源码篇

通过手写模拟,了解Spring的底层源码启动过程

通过手写模拟,了解扫描逻辑和依赖注入等底层源码工作流程

通过手写模拟,了解初始化机制工作流程

通过手写模拟,了解BeanDefinition、BeanPostProcessor的概念

通过手写模拟,了解Spring AOP的底层源码工作流程

目录结构

启动代码

public class test {
    public static void main(String[] args) throws NoSuchMethodException, InstantiationException, IllegalAccessException, InvocationTargetException {
        ApplicationContext applicationContext = new ApplicationContext(AppConfig.class);
        UserInterface userservice = (UserInterface) applicationContext.getBean("userservice");
        userservice.test();

    }
}

运行代码如下图

 

Component注解

这里其余的注解代码省略

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Component {
    String value() default "";
}

ApplicationContext类

这个类主要是扫描@ComponentScan指定的包路径生成其中类含有@Componet注解的Bean对象。

public class ApplicationContext {


    private Class configClass;
    private ConcurrentHashMap<String,Object> singletonObjects=new ConcurrentHashMap<>();//单列池
    private ConcurrentHashMap<String,BeanDefinition> beanDefinitionConcurrentHashMap=new ConcurrentHashMap<>();
    private List<BeanPostProcessor> beanPostProcessorList=new ArrayList<>();

    public ApplicationContext(Class configClass) throws InvocationTargetException, NoSuchMethodException, InstantiationException, IllegalAccessException {
        this.configClass = configClass;
        scan(configClass);
        for (Map.Entry<String, BeanDefinition> stringBeanDefinitionEntry : beanDefinitionConcurrentHashMap.entrySet()) {
            String beanName = stringBeanDefinitionEntry.getKey();
            BeanDefinition beanDefinition = stringBeanDefinitionEntry.getValue();
            if(beanDefinition.getScope().equals("singleton")){
                Object bean = createBean(beanDefinition);
                singletonObjects.put(beanName,bean);
            }
        }
    }

    //创造Bean对象
    public Object createBean(BeanDefinition beanDefinition){
        Class clazz = beanDefinition.getClazz();
        Object o = null;
        try {
            o = clazz.getDeclaredConstructor().newInstance();
            for (Field declaredField : clazz.getDeclaredFields()) {
                if(declaredField.isAnnotationPresent(Autowired.class)){
                    Object bean = getBean(declaredField.getName());
                    declaredField.setAccessible(true);
                    declaredField.set(o,bean);//此处有bug,当singleton时注入失败为null,当注入的属性对象bean在单例池排序靠前可以成功。
                }
            }//getFields为获得所有public的属性,这里为所有
            //后置处理器,这里没有对beanName进行设计
            for (BeanPostProcessor beanPostProcessor : beanPostProcessorList) {
                beanPostProcessor.postProcessBeforeInitialization(o,null);
            }

            //下面为初始化机制
            if (o instanceof InitializingBean){
                try {
                    ((InitializingBean) o).afterPropertiesSet();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }

            for (BeanPostProcessor beanPostProcessor : beanPostProcessorList) {
               o = beanPostProcessor.postProcessAfterInitialization(o, null);
            }

            return o;
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        }
        return null;
    }

   //扫描包路径
    public void scan(Class configClass) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
        ComponentScan componentScan = (ComponentScan) configClass.getDeclaredAnnotation(ComponentScan.class);
        String value = componentScan.value();
//        System.out.println(value);
        String replace = value.replace('.', '/');
        ClassLoader classLoader = ApplicationContext.class.getClassLoader();
        URL resource = classLoader.getResource(replace);//根据相对路径 com.example.service
//        System.out.println(resource);
        File file=new File(resource.getFile());
        if(file.isDirectory()){
            File[] files=file.listFiles();
            for (File file1 : files) {
                String absolutePath = file1.getAbsolutePath();
                if (absolutePath.endsWith(".class")) {
//                System.out.println(absolutePath);
                    String com = absolutePath.substring(absolutePath.indexOf("com"), absolutePath.indexOf(".class"));
                    String replace1 = com.replace("\\", ".");//注意\是转义字符,而/不是,com.example.service.xx
//                System.out.println(replace1);
                    Class<?> aClass = null;
                    try {
                        aClass = classLoader.loadClass(replace1);
                        if (aClass.isAnnotationPresent(Component.class)) {
                            if(BeanPostProcessor.class.isAssignableFrom(aClass)){//该class对象是否实现了BeanPostProcessor接口,不能用instanceof
                               BeanPostProcessor beanPostProcessor = (BeanPostProcessor) aClass.getDeclaredConstructor().newInstance();
                                beanPostProcessorList.add(beanPostProcessor);
                            }


                            Component declaredAnnotation = aClass.getDeclaredAnnotation(Component.class);
                            String beanName = declaredAnnotation.value();
                            BeanDefinition beanDefinition = new BeanDefinition();
                            beanDefinition.setClazz(aClass);
                            if (aClass.isAnnotationPresent(Scope.class)) {
                                Scope scope = aClass.getDeclaredAnnotation(Scope.class);
                                beanDefinition.setScope(scope.value());
                            } else {
                                beanDefinition.setScope("singleton");
                            }
                            beanDefinitionConcurrentHashMap.put(beanName,beanDefinition);

                        }
                    } catch (ClassNotFoundException e) {
                        e.printStackTrace();
                    }


                }
            }
        }
    }


   //从Spring容器中获取Bean对象
    public Object getBean(String beanName){
      if(beanDefinitionConcurrentHashMap.containsKey(beanName)){
          BeanDefinition beanDefinition = beanDefinitionConcurrentHashMap.get(beanName);
          if(beanDefinition.getScope().equals("singleton")){
              Object o = singletonObjects.get(beanName);
              return o;
          }
          else {
              Object bean = createBean(beanDefinition);
              return bean;
          }
      }else {
        throw new NullPointerException();
      }
    }
}

BeanDefinition类

这个类主要包含Bean对象的class类型和scope。

public class BeanDefinition {
    private Class clazz;
    private String scope;

    public BeanDefinition(Class clazz, String scope) {
        this.clazz = clazz;
        this.scope = scope;
    }

    public BeanDefinition() {
    }

    public Class getClazz() {
        return clazz;
    }

    public void setClazz(Class clazz) {
        this.clazz = clazz;
    }

    public String getScope() {
        return scope;
    }

    public void setScope(String scope) {
        this.scope = scope;
    }
}

BeanPostProcessor接口实现

继承BeanPostProcessor接口主要是实现初始化前和初始化后的操作。

@Component
public class MyBeanPostProcessor implements BeanPostProcessor {
    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) {
        if(bean instanceof UserInterface)
        System.out.println("初始化前");
        return null;
    }

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) {
        //AOP模拟
        if(bean instanceof UserInterface){
            Object proxyInstance = Proxy.newProxyInstance(MyBeanPostProcessor.class.getClassLoader(), bean.getClass().getInterfaces(), new InvocationHandler() {
                @Override
                public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                    System.out.println("代理前");
                    Object invoke = method.invoke(bean, args);
                    System.out.println("代理后");
                    System.out.println("初始化后");
                    return invoke;
                }
            });
            return proxyInstance;
        }

        return bean;
    }
}

相关代码可以在我的文件资源下载https://download.csdn.net/download/qq_43649937/87506483?spm=1001.2014.3001.5503

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

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

相关文章

【Feign扩展】OpenFeign日志打印Http请求参数和响应数据

SpringBoot使用log4j2 在Spring Boot中所有的starter 都是基于spring-boot-starter-logging的&#xff0c;默认使用Logback。使用Log4j2的话&#xff0c;你需要排除 spring-boot-starter-logging 的依赖&#xff0c;并添加 spring-boot-starter-log4j2的依赖。 配置依赖 <…

transformer 网络概述

1. RNN存在的问题 RNN对并行计算并不友好&#xff0c;下一输出依赖于上一输入&#xff0c;难以实现并行高效计算RNN相比较与self-attension模块&#xff0c;缺少对部分变量权重的预估&#xff0c;输出的数据默认拥有一致的权重 2. self-attension self-attension是干嘛的&am…

Shell编程规范与变量使用(再也回不到故事开始的第一章了)

一、Shell编程概述 1.Shell脚本的概念 将要执行的命令按顺序保存到一个文本文件&#xff0c;给该文件可执行权限&#xff0c;可结合各种shell控制语句以完成更复杂的操作。 2.Shell脚本的应用场景 重复性操作 交互性任务 批量事务处理 服务运行状态监控 定时任务执行 … 3…

【MySQL高级】——SQL执行流程

一、MySQL 中的 SQL执行流程 1. 查询缓存 Server 如果在查询缓存中发现了这条 SQL 语句&#xff0c;就会直接将结果返回给客户端&#xff1b;如果没 有&#xff0c;就进入到解析器阶段。需要说明的是&#xff0c;因为查询缓存往往效率不高&#xff0c;所以在 MySQL8.0 之后就抛…

设计模式 -- 组合模式

前言 月是一轮明镜,晶莹剔透,代表着一张白纸(啥也不懂) 央是一片海洋,海乃百川,代表着一块海绵(吸纳万物) 泽是一柄利剑,千锤百炼,代表着千百锤炼(输入输出) 月央泽,学习的一种过程,从白纸->吸收各种知识->不断输入输出变成自己的内容 希望大家一起坚持这个过程,也同…

CKA证书题库-总结

CKA真题&#xff08;考题总结&#xff09; 文章目录 CKA真题&#xff08;考题总结&#xff09;证书个人考试总结申诉结果 CKA题目参考博主重点介绍 CKA模拟题库 注意事项考试概要考试注意事项&#xff1a; CKA题目答案设置自动补全方法一方法二 第⼀题&#xff1a;权限控制RBAC…

C语言编程技巧 --- C语言中左移右移与乘除法的比较

C语言中右移与除法的比较 最近在做项目的时候&#xff0c;遇到了一个有趣的现象。那就是&#xff0c;对于除2的整数次幂的操作而言&#xff0c;为了加快计算速度&#xff0c;一般情况下&#xff0c;会用右移&#xff08;>>&#xff09;来替代除法&#xff08;/&#xff0…

SparkSql(RDD、DataFrame、DataSet详解)idea实例+jdbc读取数据库并保存至数据库或本地

DataFrame 是什么 DataFrame 是一种以 RDD 为基础的分布式数据集&#xff0c;类似于传统数据库中 的二维表格。DataFrame 与 RDD 的主要区别在于&#xff0c;前者带有 schema 元信息&#xff0c;即 DataFrame 所表示的二维表数据集的每一列都带有名称和类型。这使得 Spark SQL …

QT Data Visualization 模块概述(数据三维显示的模块)

Data Visualization 是 Qt 提供的用于数据三维显示的模块。在 Ot 5.7 以前只有商业版才有此模块&#xff0c;而从Qt5.7 开始此模块在社区版本里也可以免费使用了。Data Visualization 用于数据的三维显示&#xff0c;包括三维柱状图、三维空间散点、三维曲面等。Data Visualiza…

KeepChatGPT插件-提效神器,解决ChatGPT报错!

KeepChatGPT插件-提效神器&#xff0c;解决ChatGPT报错&#xff01; 一、错误提示 最近⼏天&#xff0c;相信不少人在使用OpenAI的ChatGPT时都发现一个问题&#xff0c;就是官⽹报错越来越频繁了。 当你需⽤ChatGPT来处理⼀些⽐较琐碎的任务时&#xff0c;⼀旦你离开⻚⾯时间…

Java多线程基础-7:wait() 和 notify() 用法解析

线程之间是抢占式执行的&#xff0c;线程调度是无序的、随机的&#xff0c;因此线程之间执行的先后顺序是难以预知的。但是&#xff0c;实际开发中&#xff0c;有时我们希望合理地协调多个线程间执行的先后顺序。 虽然 join() 算是一种控制顺序的方式&#xff0c;但它毕竟“功…

4月第3周榜单丨飞瓜数据B站UP主排行榜(哔哩哔哩平台)发布!

飞瓜轻数发布2023年4月17日-4月23日飞瓜数据UP主排行榜&#xff08;B站平台&#xff09;&#xff0c;通过充电数、涨粉数、成长指数三个维度来体现UP主账号成长的情况&#xff0c;为用户提供B站号综合价值的数据参考&#xff0c;根据UP主成长情况用户能够快速找到运营能力强的B…

【华为机考】模拟题:Words、Vowel、计算字符串重新排列数

前言 刷题之路任重而道远&#xff0c;革命尚未成功&#xff0c;同志仍需努力。由于刷惯了 LeetCode&#xff0c;虽然知道华为机考是需要自己输入输出&#xff0c;也稍稍练了一下&#xff0c;结果真做模拟题的时候&#xff0c;一下子忘了怎么获取字符串了&#xff0c;直接搞了个…

数据降维算法 | Matlab基于局部费歇尔判别(LFDA)的分类数据降维可视化

文章目录 效果一览文章概述部分源码参考资料效果一览 文章概述 数据降维算法 | Matlab 基于局部费歇尔判别(LFDA)的分类数据降维可视化 部分源码 %--------------------

了解网卡的类型

网卡&#xff0c;即网络接口卡&#xff0c;也叫NIC卡&#xff0c;是一种允许网络连接的计算机硬件设备。网卡应用广泛&#xff0c;市场上有许多不同种类&#xff0c;如PCle网卡&#xff0c;服务器网卡。本文将对网卡的基础&#xff0c;功能&#xff0c;元件与类型进行全方位讲解…

用友自主研发企业商用版TimensionDB时序数据库重磅发布!

2023年4月19日&#xff0c;用友BIP技术大会上&#xff0c;用友自主研发专用企业服务能力的商用版时序数据库——TimensionDB重磅发布&#xff01;源于物联网、工业4.0等场景实践&#xff0c;打造自主、安全、可控的专业服务工业企业的轻量级、高性能、易使用的时序数据管理引擎…

linux-02-软件安装-centos7配置jdk、tomcat、lrzsz、项目部署(Git、Maven)

文章目录 Linux-Day02课程内容1. 软件安装1.1 软件安装方式1.2 安装JDKshell脚本里写 cd命令不生效 1.3 安装Tomcat1.3.1 Tomcat安装好多方便的自定义命令:1.3.2 Tomcat进程查看1.3.3 防火墙操作1.3.4 停止Tomcat 1.4 安装MySQL1.4.1 MySQL安装1.4.2 MySQL启动1.4.3 MySQL登录1…

PYQT5学习笔记04——QObject对象和属性名称设置API以及对应案例

一、QObject对象和属性名称设置API 简单介绍一下PyQt5最上层父类QObject的一些属性方法&#xff0c;并给出具体的使用过程示例。 1、setObjectName(“唯一名称”)和objectName() setObjectName方法的作用是给Qt对象设置一个名称&#xff0c;一般这个名称是唯一的&#xff0c;这…

Excel技能之实用技巧,高手私藏

今天来讲一下Excel技巧&#xff0c;工作常用&#xff0c;高手私藏。能帮到你是我最大的荣幸。 与其加班熬夜赶进度&#xff0c;不如下班学习提效率。能力有成长&#xff0c;效率提上去&#xff0c;自然不用加班。 消化吸收&#xff0c;工作中立马使用&#xff0c;感觉真不错。…

【Java】Eclipse如何创建java项目并运行

前面Eclipse、JDK的安装与JDK环境变量配置好了之后&#xff0c;开始进行基本的使用 一、创建java项目并运行 1、先打开Eclipse IDE 2、创建项目 点击左上角的File—>New—>Project&#xff0c;这样就可以新建一个Java的项目了。也就是说&#xff0c;在Eclipse中&#…