手写Spring源码——实现一个简单的spring framework

这篇文章主要带大家实现一个简单的Spring框架,包含单例、多例bean的获取,依赖注入、懒加载等功能。为了能让大家更好的理解Spring各个功能的实现,将版本由易到难贴出代码,文章内容会持续更新,感兴趣的小伙伴可以持续关注一下。

目录

一、创建Java项目

二、开始实现Spring

1、BeanFactory接口

2、ApplicationContext接口

3、ApplicationContext接口的实现类(Spring容器)

4、Spring容器代码初步实现

创建配置类

创建自定义的组件注解

@Component

@ComponentScan

创建bean的定义

实现Spring的组件扫描功能

创建一个单例bean------UserService

组件扫描功能测试

5、懒加载和bean的作用域

创建自定义注解

@Lazy

@Scope

创建一个非单例bean------UserMapper

根据bean名称获取bean功能测试

InitializingBean

[@Configuration + @Bean](#@Configuration + @Bean)

6、使用Spring获取bean对象

7、未完待续


一、创建Java项目

首先,需要创建一个Java工程,名字就叫spring。

创建完成后,如下图,再依次创建三级包

二、开始实现Spring

Spring中最重要也是最基础的类就是Spring容器,Spring容器用于创建管理对象,为了方便实现类型转换功能,给接口设置一个参数化类型(泛型)。

1、BeanFactory接口

BeanFactory是spring容器的顶级接口,创建该接口,在该接口中定义三个重载的获取bean的方法。

java 复制代码
package com.example.spring;

/**
 * @author heyunlin
 * @version 1.0
 */
public interface BeanFactory<T> {
    
    Object getBean(String beanName);
    
    T getBean(Class<T> type);
    
    T getBean(String beanName, Class<T> type);
}

2、ApplicationContext接口

ApplicationContext接口扩展自BeanFactory接口

java 复制代码
package com.example.spring;

/**
 * @author heyunlin
 * @version 1.0
 */
public interface ApplicationContext<T> extends BeanFactory<T> {
    
}

3、ApplicationContext接口的实现类(Spring容器)

创建一个ApplicationContext接口的实现类,实现接口中定义的所有抽象方法,这就是我们要直接用的Spring容器。

java 复制代码
package com.example.spring;

/**
 * @author heyunlin
 * @version 1.0
 */
public class AnnotationConfigApplicationContext<T> implements ApplicationContext<T> {

    @Override
    public Object getBean(String beanName) {
        return null;
    }

    @Override
    public T getBean(Class<T> type) {
        return null;
    }

    @Override
    public T getBean(String beanName, Class<T> type) {
        return null;
    }

}

4、Spring容器代码初步实现

首先,组件扫描需要一个扫描路径,可以通过配置类上的@ComponentScan注解指定,如果不指定,则默认为配置类所在的包。

创建配置类

在当前包下创建一个类,配置包扫描路径。

java 复制代码
package com.example.spring;

/**
 * @author heyunlin
 * @version 1.0
 */
@ComponentScan("com.example.spring")
public class SpringConfig {
    
}

创建自定义的组件注解

@Component

java 复制代码
package com.example.spring;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * @author heyunlin
 * @version 1.0
 */
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Component {

    String value() default "";
}

@ComponentScan

java 复制代码
package com.example.spring;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * @author heyunlin
 * @version 1.0
 */
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface ComponentScan {
    
    String value() default "";
}

创建bean的定义

创建一个BeanDefinition用来保存bean的基本信息,按alt + insert键创建getter/setter和toString方法。

java 复制代码
package com.example.spring;

/**
 * @author heyunlin
 * @version 1.0
 */
public class BeanDefinition {

    /**
     * bean的类型
     */
    private Class type;

    /**
     * bean的作用域
     */
    private String scope;

    /**
     * 是否懒加载
     */
    private boolean lazy;

    public Class getType() {
        return type;
    }

    public void setType(Class type) {
        this.type = type;
    }

    public String getScope() {
        return scope;
    }

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

    public boolean isLazy() {
        return lazy;
    }

    public void setLazy(boolean lazy) {
        this.lazy = lazy;
    }

    @Override
    public String toString() {
        return "BeanDefinition{" +
                "type=" + type +
                ", scope='" + scope + '\'' +
                ", lazy=" + lazy +
                '}';
    }

}

实现Spring的组件扫描功能

以下代码只是完成了Spring的简单bean扫描功能,可以根据自己的见解进行代码实现。唯一复杂一点的地方是递归的过程,对递归不是很了解的需要去提前学习一下。

修改AnnotationConfigApplicationContext:

1、添加一个属性clazz,用于保存实例化时传递的配置类对象参数;

2、Spring容器中创建用于保存bean的定义BeanDefinition的map;

3、在初始化spring容器时,从指定的路径下开始扫描,地柜扫描当前目录及其子目录,把@Component注解标注的类加载出来,以bean名称为key,bean的信息封装成的BeanDefinition为value保存到一个map中;

  • 在这里只专注于扫描组件 ,只需要@Component和@ComponentScan两个注解;
  • 没有非常复杂的逻辑,暂时不考虑是否懒加载的单例和作用域,默认设置为false和单例;
java 复制代码
package com.example.spring;

import java.io.File;
import java.net.URL;
import java.util.HashMap;
import java.util.Map;

/**
 * @author heyunlin
 * @version 1.0
 */
public class AnnotationConfigApplicationContext<T> implements ApplicationContext<T> {

    private final Map<String, BeanDefinition> beanDefinitionMap = new HashMap<>();

    public final Class<T> clazz;

    public AnnotationConfigApplicationContext(Class<T> clazz) throws ClassNotFoundException {
        this.clazz = clazz;

        // 扫描组件
        componentScan(clazz);
    }

    public Map<String, BeanDefinition> getBeanDefinitionMap() {
        return beanDefinitionMap;
    }

    /**
     * 扫描组件
     * @param clazz 配置类的类对象
     * @throws ClassNotFoundException 类找不到
     */
    private void componentScan(Class<T> clazz) throws ClassNotFoundException {
        // 如果类上使用了@ComponentScan注解
        if (clazz.isAnnotationPresent(ComponentScan.class)) {
            ComponentScan componentScan = clazz.getAnnotation(ComponentScan.class);
            String value = componentScan.value();

            if (!"".equals(value)) {
                String path = value;
                path = path.replace(".", "/");

                URL resource = clazz.getClassLoader().getResource(path);
                File file = new File(resource.getFile());

                loopFor(file);
            }
        }
    }

    /**
     * 递归遍历指定文件/文件夹
     * @param file 文件/文件夹
     * @throws ClassNotFoundException 类找不到
     */
    private void loopFor(File file) throws ClassNotFoundException {
        if (file.isDirectory()) {
            for (File listFile : file.listFiles()) {
                if (listFile.isDirectory()) {
                    loopFor(listFile);

                    continue;
                }

                toBeanDefinitionMap(listFile);
            }
        } else if (file.isFile()) {
            toBeanDefinitionMap(file);
        }
    }

    /**
     * 解析bean,并保存到Map<String, BeanDefinition>
     * @param file 解析的class文件
     * @throws ClassNotFoundException 类找不到
     */
    private void toBeanDefinitionMap(File file) throws ClassNotFoundException {
        // 获取类的绝对路径
        String absolutePath = file.getAbsolutePath();
        // 处理得到类的全限定名
        absolutePath = absolutePath.substring(absolutePath.indexOf("com"), absolutePath.indexOf(".class"));
        absolutePath = absolutePath.replace("\\", ".");

        // 通过类加载器加载
        Class<?> loadClass = clazz.getClassLoader().loadClass(absolutePath);

        String beanName;

        if (loadClass.isAnnotationPresent(Component.class)) {
            // 获取@Component注解上配置的组件名
            Component component = loadClass.getAnnotation(Component.class);
            beanName = component.value();

            // 是否懒加载
            boolean lazy = false;
            // 作用域
            String scope = "singleton";

            // 保存bean的定义
            BeanDefinition beanDefinition = new BeanDefinition();

            beanDefinition.setType(loadClass);
            beanDefinition.setLazy(lazy);
            beanDefinition.setScope(scope);

            beanDefinitionMap.put(beanName, beanDefinition);
        }
    }

    @Override
    public Object getBean(String beanName) {
        return null;
    }

    @Override
    public T getBean(Class<T> type) {
        return null;
    }

    @Override
    public T getBean(String beanName, Class<T> type) {
        return null;
    }

}

创建一个单例bean------UserService

java 复制代码
package com.example.spring;

/**
 * @author heyunlin
 * @version 1.0
 */
@Component("userService")
public class UserService {
    
}

组件扫描功能测试

为了方便获取已经加载到的bean的定义,在AnnotationConfigApplicationContext中临时添加getter方法

复制代码
public Map<String, BeanDefinition> getBeanDefinitionMap() {
    return beanDefinitionMap;
}

测试代码:SpringExample

java 复制代码
package com.example.spring;

import java.util.Map;

/**
 * @author heyunlin
 * @version 1.0
 */
public class SpringExample {

    public static void main(String[] args) throws ClassNotFoundException {
        AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(SpringConfig.class);
        Map<String, BeanDefinition> beanDefinitionMap = applicationContext.getBeanDefinitionMap();

        for (Map.Entry<String, BeanDefinition> entry : beanDefinitionMap.entrySet()) {
            System.out.println(entry.getKey() + ":" + entry.getValue());
        }
    }

}

运行SpringExample的main方法,发现成功扫描到了bean

5、懒加载和bean的作用域

上面一节内容已经实现了了一个简单的组件扫描功能,这节内容主要是实现单例bean的懒加载和非懒加载,以及根据bean的作用域singleton和prototype来决定bean的创建、获取方式。

创建自定义注解

@Lazy

java 复制代码
package com.example.spring;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * @author heyunlin
 * @version 1.0
 */
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Lazy {
    
}

@Scope

java 复制代码
package com.example.spring;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * @author heyunlin
 * @version 1.0
 */
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Scope {

    String value() default "singleton";
}

当bean上面使用了@Lazy注解时,并且bean的作用域是单例,将执行懒加载的流程,第一次使用bean的时候才创建bean。

那么,如何保证单例bean是"单例"的呢?

其实这个问题很简单,通过一个map缓存单例的bean,当调用getBean()方法获取bean时,判断bean的作用域如果是单例,就从单例池中获取,不用再次创建。

因此,需要先创建一个map保存单例的bean,以bean的名称为key,单例bean对象为value存储起来。

复制代码
/**
 * 单例对象池
 */
private final Map<String, Object> singletonObjects = new HashMap<>();

在Spring容器初始化过程中,把非懒加载单例的单例bean缓存到singletonObjects中,而懒加载的单例bean是在第一次获取bean的时候创建bean,然后保存到singletonObjects。

java 复制代码
package com.example.spring;

import java.io.File;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.net.URL;
import java.util.HashMap;
import java.util.Map;

/**
 * @author heyunlin
 * @version 1.0
 */
public class AnnotationConfigApplicationContext<T> implements ApplicationContext<T> {

    /**
     * 配置类的类对象
     */
    public final Class<T> clazz;

    /**
     * 单例对象池
     */
    private final Map<String, Object> singletonObjects = new HashMap<>();

    private final Map<String, BeanDefinition> beanDefinitionMap = new HashMap<>();

    public AnnotationConfigApplicationContext(Class<T> clazz) throws ClassNotFoundException {
        this.clazz = clazz;

        // 扫描组件
        componentScan(clazz);
    }

    public Map<String, BeanDefinition> getBeanDefinitionMap() {
        return beanDefinitionMap;
    }

    /**
     * 扫描组件
     * @param clazz 配置类的类对象
     * @throws ClassNotFoundException 类找不到
     */
    private void componentScan(Class<T> clazz) throws ClassNotFoundException {
        // 如果类上使用了@ComponentScan注解
        if (clazz.isAnnotationPresent(ComponentScan.class)) {
            ComponentScan componentScan = clazz.getAnnotation(ComponentScan.class);
            String value = componentScan.value();

            if (!"".equals(value)) {
                String path = value;
                path = path.replace(".", "/");

                URL resource = clazz.getClassLoader().getResource(path);
                File file = new File(resource.getFile());

                loopFor(file);
            }
        }
    }

    /**
     * 递归遍历指定文件/文件夹
     * @param file 文件/文件夹
     * @throws ClassNotFoundException 类找不到
     */
    private void loopFor(File file) throws ClassNotFoundException {
        if (file.isDirectory()) {
            for (File listFile : file.listFiles()) {
                if (listFile.isDirectory()) {
                    loopFor(listFile);

                    continue;
                }

                toBeanDefinitionMap(listFile);
            }
        } else if (file.isFile()) {
            toBeanDefinitionMap(file);
        }
    }

    /**
     * 解析bean,并保存到Map<String, BeanDefinition>
     * @param file 解析的class文件
     * @throws ClassNotFoundException 类找不到
     */
    private void toBeanDefinitionMap(File file) throws ClassNotFoundException {
        // 获取类的绝对路径
        String absolutePath = file.getAbsolutePath();
        // 处理得到类的全限定名
        absolutePath = absolutePath.substring(absolutePath.indexOf("com"), absolutePath.indexOf(".class"));
        absolutePath = absolutePath.replace("\\", ".");

        // 通过类加载器加载
        Class<?> loadClass = clazz.getClassLoader().loadClass(absolutePath);

        String beanName;

        if (loadClass.isAnnotationPresent(Component.class)) {
            // 获取@Component注解上配置的组件名
            Component component = loadClass.getAnnotation(Component.class);
            beanName = component.value();

            // 是否懒加载
            boolean lazy = false;
            // 作用域
            String scope = "singleton";

            // 新增的代码-------------------start
            // 类上使用了@Scope注解
            if (loadClass.isAnnotationPresent(Scope.class)) {
                // 获取@Scope注解
                Scope annotation = loadClass.getAnnotation(Scope.class);

                // 单例bean
                if (isSingleton(annotation.value())) {
                    lazy = loadClass.isAnnotationPresent(Lazy.class);
                } else {
                    // 非单例
                    scope = annotation.value();
                }
            } else {
                // 类上没有使用@Scope注解,默认是单例的
                lazy = loadClass.isAnnotationPresent(Lazy.class);
            }
            // 新增的代码-------------------end

            // 保存bean的定义
            BeanDefinition beanDefinition = new BeanDefinition();

            beanDefinition.setType(loadClass);
            beanDefinition.setLazy(lazy);
            beanDefinition.setScope(scope);

            beanDefinitionMap.put(beanName, beanDefinition);
        }
    }

    // 新增的代码-------------------start
    /**
     * 判断作用域是否单例
     * @param scope bean的作用域
     * @return boolean 如果是单例,返回true,否则返回false
     */
    private boolean isSingleton(String scope) {
        return "singleton".equals(scope);
    }
    // 新增的代码-------------------end

    @Override
    public Object getBean(String beanName) {
        // 修改的代码-------------------start
        BeanDefinition beanDefinition = beanDefinitionMap.get(beanName);

        //if (!beanDefinitionMap.containsKey(beanName)) {
        if (beanDefinition == null) {
            throw new NullPointerException();
        }

        return getBean(beanName, beanDefinition);
        // 修改的代码-------------------end
    }

    @Override
    public T getBean(Class<T> type) {
        return null;
    }

    @Override
    public T getBean(String beanName, Class<T> type) {
        return null;
    }

    // 新增的代码-------------------start
    /**
     * 统一获取bean的方法
     * @param beanName bean名称
     * @param beanDefinition BeanDefinition
     * @return Object 符合条件的bean对象
     */
    private Object getBean(String beanName, BeanDefinition beanDefinition) {
        String scope = beanDefinition.getScope();

        // bean的作用域是单例
        if (isSingleton(scope)) {
            Object object = singletonObjects.get(beanName);

            // 懒加载的单例bean
            if (object == null) {
                Object bean = createBean(beanDefinition);

                singletonObjects.put(beanName, bean);
            }

            return singletonObjects.get(beanName);
        }

        return createBean(beanDefinition);
    }

    /**
     * 创建bean对象
     * @param beanDefinition bean的定义
     * @return Object 创建好的bean对象
     */
    private Object createBean(BeanDefinition beanDefinition) {
        Object bean = null;
        Class beanType = beanDefinition.getType();

        try {
            // 调用无参构造方法初始化
            Constructor constructor = beanType.getConstructor();

            bean = constructor.newInstance();
        } catch (InstantiationException | IllegalAccessException | InvocationTargetException | NoSuchMethodException e) {
            e.printStackTrace();
        }

        return bean;
    }
    // 新增的代码-------------------end

}

如上,已经实现了通过bean名称获取bean的方法,接下来简单测试一下

创建一个非单例bean------UserMapper

java 复制代码
package com.example.spring;

/**
 * @author heyunlin
 * @version 1.0
 */
@Component("userMapper")
@Scope("prototype")
public class UserMapper {

}

根据bean名称获取bean功能测试

java 复制代码
package com.example.spring;

/**
 * @author heyunlin
 * @version 1.0
 */
public class SpringExample {

    public static void main(String[] args) throws ClassNotFoundException {
        AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(SpringConfig.class);

        Object userService1 = applicationContext.getBean("userService");
        Object userService2 = applicationContext.getBean("userService");

        System.out.println(userService1);
        System.out.println(userService2);

        System.out.println("----------------------------------------------------");

        Object userMapper3 = applicationContext.getBean("userMapper");
        Object userMapper4 = applicationContext.getBean("userMapper");

        System.out.println(userMapper3);
        System.out.println(userMapper4);
    }

}

InitializingBean

InitializingBean是Spring bean生命周期的重要钩子方法之一,在Spring初始化完成后调用。

我们创建一个InitializingBean的实现类,同时使用@Component将该实现类声明为bean。

创建InitializingBean接口

java 复制代码
package com.example.spring;

/**
 * @author heyunlin
 * @version 1.0
 */
public interface InitializingBean {

    void afterPropertiesSet();
}

创建InitializingBean接口的实现类

java 复制代码
package com.example.spring;

import com.example.spring.annotation.Component;

/**
 * @author heyunlin
 * @version 1.0
 */
@Component
public class InitializingBeanImpl implements InitializingBean {

    @Override
    public void afterPropertiesSet() {
        System.out.println("执行InitializingBean.afterPropertiesSet()");
    }

}

在创建bean的时候判断该bean是否InitializingBean接口的实现类,如果是,调用其afterPropertiesSet()方法,代码很简单。

java 复制代码
    /**
     * 创建bean对象
     * @param beanDefinition bean的定义
     * @return Object 创建好的bean对象
     */
    private Object createBean(BeanDefinition beanDefinition) {
        Object bean = null;
        Class beanType = beanDefinition.getType();

        // 获取所有构造方法
        Constructor[] constructors = beanType.getConstructors();

        try {
            /**
             * 推断构造方法
             * 1、没有提供构造方法:调用默认的无参构造
             * 2、提供了构造方法:
             *   - 构造方法个数为1
             *     - 构造方法参数个数为0:无参构造
             *     - 构造方法参数个数不为0:传入多个为空的参数
             *   - 构造方法个数 > 1:推断失败,抛出异常
             */
            if (isEmpty(constructors)) {
                // 无参构造方法
                Constructor constructor = beanType.getConstructor();

                bean = constructor.newInstance();
            } else if (constructors.length == 1) {
                Constructor constructor = constructors[0];
                // 得到构造方法参数个数
                int parameterCount = constructor.getParameterCount();

                if (parameterCount == 0) {
                    // 无参构造方法
                    bean = constructor.newInstance();
                } else {
                    // 多个参数的构造方法
                    Object[] array = new Object[parameterCount];

                    bean = constructor.newInstance(array);
                }
            } else {
                throw new IllegalStateException();
            }

            // 获取bean的所有自定义属性
            Field[] fields = beanType.getDeclaredFields();

            // 处理字段注入
            for (Field field : fields) {
                if (field.isAnnotationPresent(Autowired.class)) {
                    // 获取bean,并设置到@Autowired注入的属性中
                    Object autowiredBean = getBean(field.getName());

                    field.setAccessible(true);
                    field.set(bean, autowiredBean);
                }
            }

            // 新增代码:调用InitializingBean的afterPropertiesSet()方法
            if (bean instanceof InitializingBean) {
                Method afterPropertiesSet = beanType.getDeclaredMethod("afterPropertiesSet");

                afterPropertiesSet.invoke(bean);
            }
        } catch (InstantiationException | IllegalAccessException | InvocationTargetException | NoSuchMethodException e) {
            e.printStackTrace();
        }

        return bean;
    }

@Configuration + @Bean

上面的代码完成了@Component注解的隐式声明的bean的扫描,接下来完成@Bean注解配置的bean扫描。(@Configuration注解标注的类中,@Bean注解标注方法的返回值是bean的类型,方法名就是bean的名称)

创建@Configuration注解

java 复制代码
package com.example.spring;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * @author heyunlin
 * @version 1.0
 */
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Component
public @interface Configuration {
    
}

创建@Bean注解

java 复制代码
package com.example.spring;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * @author heyunlin
 * @version 1.0
 */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Bean {

    String value() default "";
}

6、使用Spring获取bean对象

在main方法里测试获取单例和非单例bean,其中通过上面三种方法获取到的UserService都是同一个,下半部分获取到的UserMapper都是不一样的(因为它是prototype)。

java 复制代码
package com.example.spring;

/**
 * @author heyunlin
 * @version 1.0
 */
public class SpringExample {

    public static void main(String[] args) throws ClassNotFoundException {
        AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(SpringConfig.class);


        Object userService1 = applicationContext.getBean(UserService.class);
        Object userService2 = applicationContext.getBean(UserService.class);
        Object userService3 = applicationContext.getBean("userService");
        Object userService4 = applicationContext.getBean("userService");
        Object userService5 = applicationContext.getBean("userService", UserService.class);
        Object userService6 = applicationContext.getBean("userService", UserService.class);

        System.out.println(userService1);
        System.out.println(userService2);
        System.out.println(userService3);
        System.out.println(userService4);
        System.out.println(userService5);
        System.out.println(userService6);

        System.out.println("----------------------------------------------------");

        Object userMapper1 = applicationContext.getBean(UserMapper.class);
        Object userMapper2 = applicationContext.getBean(UserMapper.class);
        Object userMapper3 = applicationContext.getBean("userMapper");
        Object userMapper4 = applicationContext.getBean("userMapper");
        Object userMapper5 = applicationContext.getBean("userMapper", UserMapper.class);
        Object userMapper6 = applicationContext.getBean("userMapper", UserMapper.class);

        System.out.println(userMapper1);
        System.out.println(userMapper2);
        System.out.println(userMapper3);
        System.out.println(userMapper4);
        System.out.println(userMapper5);
        System.out.println(userMapper6);
    }

}

7、未完待续

文章和代码持续更新中,敬请期待~

手写Spring Framework源码https://gitee.com/he-yunlin/spring.git

相关推荐
hrrrrb5 分钟前
【Spring Security】Spring Security 概念
java·数据库·spring
小信丶5 分钟前
Spring 中解决 “Could not autowire. There is more than one bean of type“ 错误
java·spring
心止水j9 分钟前
spark
javascript·数据库·spark
xujiangyan_1 小时前
Redis详解
数据库·redis·缓存
Y编程小白4 小时前
PostgreSQL在Linux中的部署和安装教程
数据库·postgresql
TiAmo zhang6 小时前
SQL Server 2019实验 │ 数据库和表的创建、修改与删除
数据库·oracle
闲人编程7 小时前
从多个数据源(CSV, Excel, SQL)自动整合数据
python·mysql·数据分析·csv·存储·数据源·codecapsule
disanleya7 小时前
MySQL默认密码不安全?如何首次登录并强化?
数据库·mysql·安全
花开富贵贼富贵7 小时前
MySQL 核心高级特性
运维·数据库·mysql
hello 早上好7 小时前
深入 Spring 依赖注入底层原理
数据库·sql·spring