手写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

相关推荐
FIN技术铺2 小时前
Redis集群模式之Redis Sentinel vs. Redis Cluster
数据库·redis·sentinel
内核程序员kevin3 小时前
在Linux环境下使用Docker打包和发布.NET程序并配合MySQL部署
linux·mysql·docker·.net
CodingBrother4 小时前
MySQL 中的 `IN`、`EXISTS` 区别与性能分析
数据库·mysql
kayotin4 小时前
Wordpress博客配置2024
linux·mysql·docker
代码小鑫4 小时前
A027-基于Spring Boot的农事管理系统
java·开发语言·数据库·spring boot·后端·毕业设计
小小不董5 小时前
Oracle OCP认证考试考点详解082系列16
linux·运维·服务器·数据库·oracle·dba
甄臻9245 小时前
Windows下mysql数据库备份策略
数据库·mysql
内蒙深海大鲨鱼5 小时前
qt之ui开发
数据库·qt·ui
杀神lwz5 小时前
Java 正则表达式
java·mysql·正则表达式
不爱学习的YY酱5 小时前
【计网不挂科】计算机网络第一章< 概述 >习题库(含答案)
java·数据库·计算机网络