spring5源码系列--循环依赖 之 手写代码模拟spring循环依赖 (下)

本文涉及的产品
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
云解析 DNS,旗舰版 1个月
全局流量管理 GTM,标准版 1个月
简介: spring5源码系列--循环依赖 之 手写代码模拟spring循环依赖 (下)

6. 增加三级缓存



三级缓存有什么作用呢? 这个问题众说纷纭, 有说代理, 有说AOP. 其实AOP的问题可以用二级缓存来解决. 下面就来看看AOP如何用二级缓存解决.


创建AOP动态代理 (不是耦合的, 采用解耦的, 通过BeanPostProcessor bean的后置处理器来创建). 之前讲过, 如下图


在初始化之后, 调用Bean的后置处理器去创建的AOP的动态代理

1187916-20201106103849714-2078147201.png


如上图. 我们在创建bean 的时候, 会有很多Bean的后置处理器BeanPostProcessor. 如果有AOP, 会在什么时候创建呢? 在初始化以后, 调用BeanPostProcessor创建动态代理.

结合上面的代码, 我们想一想, 其实在初始化以后创建动态代理就晚了. 为什么呢? 因为, 如果有循环依赖, 在初始化之后才调用, 那就不是动态代理. 其实我们这时候应该在实例化之后, 放入到二级缓存之前调用


面试题: 在创建bean的时候, 在哪里创建的动态代理, 这个应该怎么回答呢?

很多人会说在初始化之后, 或者在实例化之后.


其实更严谨的说,有两种情况: 第一种是在初始化之后调用 . 第二种是出现了循环依赖, 会在实例化之后调用


我们上面说的就是第二种情况. 也就是说,正常情况下是在初始化之后调用的, 但是如果有循环依赖, 就要在实例化之后调用了.

 

下面来看看如何在二级缓存加动态代理.

首先, 我们这里有循环依赖, 所以将动态代理放在实例化之后,

/**
     * 获取bean, 根据beanName获取
     */
    public static Object getBean(String beanName) throws Exception {
        // 增加一个出口. 判断实体类是否已经被加载过了
        Object singleton = getSingleton(beanName);
        if (singleton != null) {
            return singleton;
        }
        /**
         * 第一步: 实例化
         * 我们这里是模拟, 采用反射的方式进行实例化. 调用的也是最简单的无参构造函数
         */
        RootBeanDefinition beanDefinition = (RootBeanDefinition) beanDefinitionMap.get(beanName);
        Class<?> beanClass = beanDefinition.getBeanClass();
        // 调用无参的构造函数进行实例化
        Object instanceBean = beanClass.newInstance();
        /**
         * 创建AOP动态代理 (不是耦合的, 采用解耦的, 通过BeanPostProcessor bean的后置处理器得来的.  之前讲过,
         * 在初始化之后, 调用Bean的后置处理器去创建的AOP的动态代理 )
         */
        instanceBean = new JdkProxyBeanPostProcessor().getEarlyBeanReference(instanceBean, "instanceA");
        /**
         * 第二步: 放入到二级缓存
         */
        earlySingletonObjects.put(beanName, instanceBean);
        /**
         *  第三步: 属性赋值
         *  instanceA这类类里面有一个属性, InstanceB. 所以, 先拿到 instanceB, 然后在判断属性头上有没有Autowired注解.
         *  注意: 这里我们只是判断有没有Autowired注解. spring中还会判断有没有@Resource注解. @Resource注解还有两种方式, 一种是name, 一种是type
          */
        Field[] declaredFields = beanClass.getDeclaredFields();
        for (Field declaredField: declaredFields) {
            // 判断每一个属性是否有@Autowired注解
            Autowired annotation = declaredField.getAnnotation(Autowired.class);
            if (annotation != null) {
                // 设置这个属性是可访问的
                declaredField.setAccessible(true);
                // 那么这个时候还要构建这个属性的bean.
                /*
                 * 获取属性的名字
                 * 真实情况, spring这里会判断, 是根据名字, 还是类型, 还是构造函数来获取类.
                 * 我们这里模拟, 所以简单一些, 直接根据名字获取.
                 */
                String name = declaredField.getName();
                /**
                 * 这样, 在这里我们就拿到了 instanceB 的 bean
                 */
                Object fileObject = getBean(name);
                // 为属性设置类型
                declaredField.set(instanceBean, fileObject);
            }
        }
        /**
         * 第四步: 初始化
         * 初始化就是设置类的init-method.这个可以设置也可以不设置. 我们这里就不设置了
         */
      //   正常动态代理创建的时机
        /**
         * 第五步: 放入到一级缓存
         */
        singletonObjects.put(beanName, instanceBean);
        return instanceBean;
    }

这里只是简单模拟了动态代理.


我们知道动态代理有两个地方. 如果是普通类动态代理在初始化之后执行, 如果是循环依赖, 那么动态代理是在实例化之后.

 

上面在实例化之后创建proxy的代码不完整, 为什么不完整呢, 因为没有判断是否是循环依赖.

 

我们简单模拟一个动态代理的实现.


public class JdkProxyBeanPostProcessor implements SmartInstantiationAwareBeanPostProcessor {
    /**
     * 假设A被切点命中 需要创建代理  @PointCut("execution(* *..InstanceA.*(..))")
     * @param bean the raw bean instance
     * @param beanName the name of the bean
     * @return
     * @throws BeansException
     */
    @Override
    public Object getEarlyBeanReference(Object bean, String beanName) throws BeansException {
        // 假设A被切点命中 需要创建代理  @PointCut("execution(* *..InstanceA.*(..))")
        /**
         * 这里, 我们简单直接判断bean是不是InstanceA实例, 如果是, 就创建动态代理.
         * 这里没有去解析切点, 解析切点是AspectJ做的事.
         */
        if (bean instanceof InstanceA) {
            JdkDynimcProxy jdkDynimcProxy = new JdkDynimcProxy(bean);
            return jdkDynimcProxy.getProxy();
        }
        return bean;
    }
}

这里直接判断, 如果bean是InstanceA的实例, 那么就调用bean的动态代理.  动态代理的简单逻辑就是: 解析切面, 然后创建类, 如果类不存在就新增, 如果存在则不在创建, 直接取出来返回.

 

在来看看动态代理,放在实例化之后. 创建AOP, 但是, 在这里创建AOP动态代理的条件是循环依赖.


问题1: 那么如何判断是循环依赖呢?

二级缓存中bean不是null.

如果一个类在创建的过程中, 会放入到二级缓存, 如果完全创建完了, 会放入到一级缓存, 然后删除二级缓存. 所以, 如果二级缓存中的bean只要存在, 就说明这个类是创建中, 出现了循环依赖.


问题2: 什么时候判断呢?

应该在getSingleton()判断是否是循环依赖的时候判断. 因为这时候我们刚好判断了二级缓存中bean是否为空.


/**
     * 判断是否是循环引用的出口.
     * @param beanName
     * @return
     */
    private static Object getSingleton(String beanName) {
        // 先去一级缓存里拿,如果一级缓存没有拿到,去二级缓存里拿
        if (singletonObjects.containsKey(beanName)) {
            return singletonObjects.get(beanName);
        } else if (earlySingletonObjects.containsKey(beanName)){
            /**
             * 第一次创建bean是正常的instanceBean. 他并不是循环依赖. 第二次进来判断, 这个bean已经存在了, 就说明是循环依赖了
             * 这时候通过动态代理创建bean. 然后将这个bean在放入到二级缓存中覆盖原来的instanceBean.
             */
            Object obj = new JdkProxyBeanPostProcessor()
                    .getEarlyBeanReference(earlySingletonObjects.get(beanName), beanName);
            earlySingletonObjects.put(beanName, obj);
            return earlySingletonObjects.get(beanName);
        } else {
            return null;
        }
    }

这样我们在循环依赖的时候就完成了AOP的创建. 这是在二级缓存里创建的AOP,

问题3: 那这是不是说就不需要三级缓存了呢?

那么,来找问题.  这里有两个问题:

问题1: 我们发现在创建动态代理的时候, 我们使用的bean的后置处理器JdkProxyBeanPostProcessor.这有点不太符合规则,

     因为, spring在getBean()的时候并没有使用Bean的后置处理器, 而是在createBean()的时候才去使用的bean的后置处理器.

问题2: 如果A是AOP, 他一直都是, 最开始创建的时候也应该是. 使用这种方法, 结果是第一次创建出来的bean不是AOP动态代理.

 

对于第一个问题: 我们希望在实例化的时候创建AOP, 但是具体判断是在getSingleton()方法里判断. 这里通过三级缓存来实现. 三级缓存里面放的是一个接口定义的钩子方法. 方法的执行在后面调用的时候执行.

 

对于第二个问题: 我们的二级缓存就不能直接保存instanceBean实例了, 增加一个参数, 用来标记当前这个类是一个正在创建中的类. 这样来判断循环依赖.

 

下面先来看看创建的三个缓存和一个标识


// 一级缓存
    private static Map<String, Object> singletonObjects = new ConcurrentHashMap<>();
    // 二级缓存: 为了将成熟的bean和纯净的bean分离. 避免读取到不完整的bean.
    private static Map<String, Object> earlySingletonObjects = new ConcurrentHashMap<>();
    // 三级缓存:
    private static Map<String, ObjectFactory> singletonFactories = new ConcurrentHashMap<>();
    // 循环依赖的标识---当前正在创建的实例bean
    private static Set<String> singletonsCurrectlyInCreation = new HashSet<>();

然后在来看看循环依赖的出口

/**
     * 判断是否是循环引用的出口.
     * @param beanName
     * @return
     */
    private static Object getSingleton(String beanName) {
        //先去一级缓存里拿
        Object bean = singletonObjects.get(beanName);
        // 一级缓存中没有, 但是正在创建的bean标识中有, 说明是循环依赖
        if (bean == null && singletonsCurrectlyInCreation.contains(beanName)) {
            bean = earlySingletonObjects.get(beanName);
            // 如果二级缓存中没有, 就从三级缓存中拿
            if (bean == null) {
                // 从三级缓存中取
                ObjectFactory objectFactory = singletonFactories.get(beanName);
                if (objectFactory != null) {
                    // 这里是真正创建动态代理的地方.
                    Object obj = objectFactory.getObject();
                    // 然后将其放入到二级缓存中. 因为如果有多次依赖, 就去二级缓存中判断. 已经有了就不在再次创建了
                    earlySingletonObjects.put(beanName, obj);
                }
            }
        }
        return bean;
    }

这里的逻辑是, 先去一级缓存中拿, 一级缓存放的是成熟的bean, 也就是他已经完成了属性赋值和初始化. 如果一级缓存没有, 而正在创建中的类标识是true, 就说明这个类正在创建中, 这是一个循环依赖. 这个时候就去二级缓存中取数据, 二级缓存中的数据是何时放进去的呢, 是后面从三级缓存中创建动态代理后放进去的. 如果二级缓存为空, 说明没有创建过动态代理, 这时候在去三级缓存中拿, 然后创建动态代理. 创建完以后放入二级缓存中, 后面就不用再创建.

 

完成的代码如下:

package com.lxl.www.circulardependencies;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.Autowire;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.RootBeanDefinition;
import java.lang.reflect.Field;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
public class MainStart {
    private static Map<String, BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<>();
    // 一级缓存
    private static Map<String, Object> singletonObjects = new ConcurrentHashMap<>();
    // 二级缓存: 为了将成熟的bean和纯净的bean分离. 避免读取到不完整的bean.
    private static Map<String, Object> earlySingletonObjects = new ConcurrentHashMap<>();
    // 三级缓存:
    private static Map<String, ObjectFactory> singletonFactories = new ConcurrentHashMap<>();
    // 循环依赖的标识---当前正在创建的实例bean
    private static Set<String> singletonsCurrectlyInCreation = new HashSet<>();
    /**
     * 读取bean定义, 当然在spring中肯定是根据配置 动态扫描注册的
     *
     * InstanceA和InstanceB都有注解@Component, 所以, 在spring扫描读取配置类的时候, 会把他们两个扫描到BeanDefinitionMap中.
     * 这里, 我们省略这一步, 直接将instanceA和instanceB放到BeanDefinitionMap中.
     */
    public static void loadBeanDefinitions(){
        RootBeanDefinition aBeanDefinition = new RootBeanDefinition(InstanceA.class);
        RootBeanDefinition bBeanDefinition = new RootBeanDefinition(InstanceB.class);
        beanDefinitionMap.put("instanceA", aBeanDefinition);
        beanDefinitionMap.put("instanceB", bBeanDefinition);
    }
    public static void main(String[] args) throws Exception {
        // 第一步: 扫描配置类, 读取bean定义
        loadBeanDefinitions();
        // 第二步: 循环创建bean
        for (String key: beanDefinitionMap.keySet()) {
            // 第一次: key是instanceA, 所以先创建A类
            getBean(key);
        }
        // 测试: 看是否能执行成功
        InstanceA instanceA = (InstanceA) getBean("instanceA");
        instanceA.say();
    }
    /**
     * 获取bean, 根据beanName获取
     */
    public static Object getBean(String beanName) throws Exception {
        // 增加一个出口. 判断实体类是否已经被加载过了
        Object singleton = getSingleton(beanName);
        if (singleton != null) {
            return singleton;
        }
        // 标记bean正在创建
        if (!singletonsCurrectlyInCreation.contains(beanName)) {
            singletonsCurrectlyInCreation.add(beanName);
        }
        /**
         * 第一步: 实例化
         * 我们这里是模拟, 采用反射的方式进行实例化. 调用的也是最简单的无参构造函数
         */
        RootBeanDefinition beanDefinition = (RootBeanDefinition) beanDefinitionMap.get(beanName);
        Class<?> beanClass = beanDefinition.getBeanClass();
        // 调用无参的构造函数进行实例化
        Object instanceBean = beanClass.newInstance();
        /**
         * 第二步: 放入到三级缓存
         * 每一次createBean都会将其放入到三级缓存中. getObject是一个钩子方法. 在这里不会被调用.
         * 什么时候被调用呢?
         * 在getSingleton()从三级缓存中取数据, 调用创建动态代理的时候
         */
        singletonFactories.put(beanName, new ObjectFactory() {
            @Override
            public Object getObject() throws BeansException {
                return new JdkProxyBeanPostProcessor().getEarlyBeanReference(earlySingletonObjects.get(beanName), beanName);
            }
        });
        //earlySingletonObjects.put(beanName, instanceBean);
        /**
         *  第三步: 属性赋值
         *  instanceA这类类里面有一个属性, InstanceB. 所以, 先拿到 instanceB, 然后在判断属性头上有没有Autowired注解.
         *  注意: 这里我们只是判断有没有Autowired注解. spring中还会判断有没有@Resource注解. @Resource注解还有两种方式, 一种是name, 一种是type
         */
        Field[] declaredFields = beanClass.getDeclaredFields();
        for (Field declaredField: declaredFields) {
            // 判断每一个属性是否有@Autowired注解
            Autowired annotation = declaredField.getAnnotation(Autowired.class);
            if (annotation != null) {
                // 设置这个属性是可访问的
                declaredField.setAccessible(true);
                // 那么这个时候还要构建这个属性的bean.
                /*
                 * 获取属性的名字
                 * 真实情况, spring这里会判断, 是根据名字, 还是类型, 还是构造函数来获取类.
                 * 我们这里模拟, 所以简单一些, 直接根据名字获取.
                 */
                String name = declaredField.getName();
                /**
                 * 这样, 在这里我们就拿到了 instanceB 的 bean
                 */
                Object fileObject = getBean(name);
                // 为属性设置类型
                declaredField.set(instanceBean, fileObject);
            }
        }
        /**
         * 第四步: 初始化
         * 初始化就是设置类的init-method.这个可以设置也可以不设置. 我们这里就不设置了
         */
        /**
         * 第五步: 放入到一级缓存
         *
         * 在这里二级缓存存的是动态代理, 那么一级缓存肯定也要存动态代理的实例.
         * 从二级缓存中取出实例, 放入到一级缓存中
         */
        if (earlySingletonObjects.containsKey(beanName)) {
            instanceBean = earlySingletonObjects.get(beanName);
        }
        singletonObjects.put(beanName, instanceBean);
        return instanceBean;
    }
    /**
     * 判断是否是循环引用的出口.
     * @param beanName
     * @return
     */
    private static Object getSingleton(String beanName) {
        //先去一级缓存里拿,
        Object bean = singletonObjects.get(beanName);
        // 一级缓存中没有, 但是正在创建的bean标识中有, 说明是循环依赖
        if (bean == null && singletonsCurrectlyInCreation.contains(beanName)) {
            bean = earlySingletonObjects.get(beanName);
            // 如果二级缓存中没有, 就从三级缓存中拿
            if (bean == null) {
                // 从三级缓存中取
                ObjectFactory objectFactory = singletonFactories.get(beanName);
                if (objectFactory != null) {
                    // 这里是真正创建动态代理的地方.
                    Object obj = objectFactory.getObject();
                    // 然后将其放入到二级缓存中. 因为如果有多次依赖, 就去二级缓存中判断. 已经有了就不在再次创建了
                    earlySingletonObjects.put(beanName, obj);
                }
            }
        }
        return bean;
    }
}

1187916-20201108073933562-1940526219.png


下面就我们的代码分析一下:


第一种情况: 没有循环依赖

第二种情况: 有循环依赖

第三种情况: 有多次循环依赖

我们模拟一个循环依赖的场景, 覆盖这三种情况.

1187916-20201108074451802-402797875.png

用代码表示

类A

package com.lxl.www.circulardependencies;
import org.springframework.beans.factory.annotation.Autowired;
public class A {
    @Autowired
    private B b;
    @Autowired
    private C c;
}

类B

package com.lxl.www.circulardependencies;
import org.springframework.beans.factory.annotation.Autowired;
public class B {
    @Autowired
    private A a;
    @Autowired
    private B b;
}

类C

package com.lxl.www.circulardependencies;
import org.springframework.beans.factory.annotation.Autowired;
public class C {
    @Autowired
    private A a;
}


其中类A刚好匹配AOP的切面@PointCut("execution(* *..A.*(..))")

 

下面分析他们的循环依赖关系.

此时beanDefinitionMap中有三个bean定义. 分别是A, B, C

1. 先解析类A, 根据上面的流程.

  1) 首先调用getSingleton, 此时一级缓存, 二级缓存都没有, 正在创建标志也是null. 所以, 返回的是null

  2) 标记当前类正在创建中

  3) 实例化

  4) 将A放入到三级缓存, 并定义动态代理的钩子方法

  5) 属性赋值. A有两个属性, 分别是B和C. 都带有@Autowired注解, 先解析B.

  6) A暂停, 解析B

2. 解析A类的属性类B

  1) 首先调用getSingleton, 此时一级缓存, 二级缓存都没有, 正在创建标志也是null. 所以, 返回的是null  

  2) 标记当前类正在创建中

  3) 实例化

  4) 将B放入到三级缓存, 并定义动态代理的钩子方法

  5) 属性赋值. B有两个属性, 分别是A和C. 都带有@Autowired注解, 先解析A. 在解析C

  6) B暂停, 解析A

3. 解析B类的属性A 

  1) 首先调用getSingleton, 此时一级缓存中这个属性为null, 正在创建中标志位true, 二级缓存为空, 从三级缓存中创建动态代理, 然后判断是否符合动态代理切面要求, A符合. 所以通过动态代理创建A的代理bean放入到二级缓存.返回实例bean.


  2) A此时已经存在了, 所以, 直接返回

4. 解析B类的属性C

  1) 首先调用getSingleton, 此时一级缓存, 二级缓存都没有, 正在创建标志也是null. 所以, 返回的是null  

  2) 标记当前类C正在创建中

  3) 实例化

  4) 将C放入到三级缓存, 并定义动态代理的钩子方法

  5) 属性赋值. C有一个属性, 是A. 带有@Autowired注解, 先解析A

  6) C暂停, 解析A

5. 解析C中的属性A

  1) 首先调用getSingleton()方法, 此时一级缓存中没有, 标志位为true, 二级缓存中已经有A的动态代理实例了, 所以,直接返回.

  2) A此时已经在存在, 直接返回

6. 继续解析B类的属性C

  1) 接着第4步往下走

  2) 初始化类C

  3) 将类C放入到一级缓存中. 放之前去二级缓存中取, 二级缓存中没有. 所以, 这里存的是C通过反射构建的instanceBean

7. 继续解析A类的属性类B

  1) 接着第2步往下走

  2) 初始化类B

  3) 将类B放入到一级缓存中. 放之前去二级缓存中取.二级缓存中没有, 所以, 这里存的是B通过反射构建的instanceBean

  4) 构建结束,返回

8. 解析A类的属性类C

  1) 首先调用getSingleton()方法, 此时一级缓存中已经有了类C, 所以直接返回

9. 继续解析A类

  1) 接着第1步往下走

  2) 初始化类A

  3) 将A放入到一级缓存中. 放之前判断二级缓存中有没有实例bean, 我们发现有, 所以, 取出来放入到A的一级缓存中.

  4) 构建bean结束, 返回

10. 接下来构建beanDefinitionMap中的类B

  1) 首先调用getSingleton()方法, 此时一级缓存中已经有了类B, 所以直接返回

11. 接下来构建beanDefinitionMap中的类C

  1) 首先调用getSingleton()方法, 此时一级缓存中已经有了类C, 所以直接返回

至此整个构建过程结束.

 

总结:



再来感受一下三级缓存的作用:

一级缓存: 用来存放成熟的bean. 这个bean如果是切入点, 则是一个动态代理的bean,如果不是切入点, 则是一个普通的类

二级缓存: 用来存放循环依赖过程中创建的动态代理bean.

三级缓存: 用来存放动态代理的钩子方法. 用来在需要构建动态代理类的时候使用.


相关文章
|
3月前
|
监控 Java 应用服务中间件
高级java面试---spring.factories文件的解析源码API机制
【11月更文挑战第20天】Spring Boot是一个用于快速构建基于Spring框架的应用程序的开源框架。它通过自动配置、起步依赖和内嵌服务器等特性,极大地简化了Spring应用的开发和部署过程。本文将深入探讨Spring Boot的背景历史、业务场景、功能点以及底层原理,并通过Java代码手写模拟Spring Boot的启动过程,特别是spring.factories文件的解析源码API机制。
129 2
|
3月前
|
数据采集 监控 前端开发
二级公立医院绩效考核系统源码,B/S架构,前后端分别基于Spring Boot和Avue框架
医院绩效管理系统通过与HIS系统的无缝对接,实现数据网络化采集、评价结果透明化管理及奖金分配自动化生成。系统涵盖科室和个人绩效考核、医疗质量考核、数据采集、绩效工资核算、收支核算、工作量统计、单项奖惩等功能,提升绩效评估的全面性、准确性和公正性。技术栈采用B/S架构,前后端分别基于Spring Boot和Avue框架。
152 5
|
1月前
|
监控 JavaScript 数据可视化
建筑施工一体化信息管理平台源码,支持微服务架构,采用Java、Spring Cloud、Vue等技术开发。
智慧工地云平台是专为建筑施工领域打造的一体化信息管理平台,利用大数据、云计算、物联网等技术,实现施工区域各系统数据汇总与可视化管理。平台涵盖人员、设备、物料、环境等关键因素的实时监控与数据分析,提供远程指挥、决策支持等功能,提升工作效率,促进产业信息化发展。系统由PC端、APP移动端及项目、监管、数据屏三大平台组成,支持微服务架构,采用Java、Spring Cloud、Vue等技术开发。
|
2月前
|
存储 缓存 Java
Spring面试必问:手写Spring IoC 循环依赖底层源码剖析
在Spring框架中,IoC(Inversion of Control,控制反转)是一个核心概念,它允许容器管理对象的生命周期和依赖关系。然而,在实际应用中,我们可能会遇到对象间的循环依赖问题。本文将深入探讨Spring如何解决IoC中的循环依赖问题,并通过手写源码的方式,让你对其底层原理有一个全新的认识。
74 2
|
3月前
|
前端开发 Java 开发者
Spring生态学习路径与源码深度探讨
【11月更文挑战第13天】Spring框架作为Java企业级开发中的核心框架,其丰富的生态系统和强大的功能吸引了无数开发者的关注。学习Spring生态不仅仅是掌握Spring Framework本身,更需要深入理解其周边组件和工具,以及源码的底层实现逻辑。本文将从Spring生态的学习路径入手,详细探讨如何系统地学习Spring,并深入解析各个重点的底层实现逻辑。
93 9
|
3月前
|
缓存 监控 Java
|
24天前
|
XML Java 应用服务中间件
Spring Boot 两种部署到服务器的方式
本文介绍了Spring Boot项目的两种部署方式:jar包和war包。Jar包方式使用内置Tomcat,只需配置JDK 1.8及以上环境,通过`nohup java -jar`命令后台运行,并开放服务器端口即可访问。War包则需将项目打包后放入外部Tomcat的webapps目录,修改启动类继承`SpringBootServletInitializer`并调整pom.xml中的打包类型为war,最后启动Tomcat访问应用。两者各有优劣,jar包更简单便捷,而war包适合传统部署场景。需要注意的是,war包部署时,内置Tomcat的端口配置不会生效。
188 17
Spring Boot 两种部署到服务器的方式
|
24天前
|
Dart 前端开发 JavaScript
springboot自动配置原理
Spring Boot 自动配置原理:通过 `@EnableAutoConfiguration` 开启自动配置,扫描 `META-INF/spring.factories` 下的配置类,省去手动编写配置文件。使用 `@ConditionalXXX` 注解判断配置类是否生效,导入对应的 starter 后自动配置生效。通过 `@EnableConfigurationProperties` 加载配置属性,默认值与配置文件中的值结合使用。总结来说,Spring Boot 通过这些机制简化了开发配置流程,提升了开发效率。
58 17
springboot自动配置原理
|
29天前
|
XML JavaScript Java
SpringBoot集成Shiro权限+Jwt认证
本文主要描述如何快速基于SpringBoot 2.5.X版本集成Shiro+JWT框架,让大家快速实现无状态登陆和接口权限认证主体框架,具体业务细节未实现,大家按照实际项目补充。
78 11
|
1月前
|
缓存 安全 Java
Spring Boot 3 集成 Spring Security + JWT
本文详细介绍了如何使用Spring Boot 3和Spring Security集成JWT,实现前后端分离的安全认证概述了从入门到引入数据库,再到使用JWT的完整流程。列举了项目中用到的关键依赖,如MyBatis-Plus、Hutool等。简要提及了系统配置表、部门表、字典表等表结构。使用Hutool-jwt工具类进行JWT校验。配置忽略路径、禁用CSRF、添加JWT校验过滤器等。实现登录接口,返回token等信息。
317 12