spring-1-IOC、创建bean的方式、创建bean的过程

news/2024/11/12 19:46:40

1.背景

IOC(Inversion of Control,控制反转)

控制反转是一种设计原则,它将对象的创建和管理责任从应用代码中移交给容器。

在Spring中,IOC容器负责管理应用中的所有对象,包括它们的生命周期和相互之间的依赖关系。

IOC的主要目的是为了减少代码之间的耦合,使代码更加模块化和可测试。

这里的控制反转,控制的是谁,反转又是反转到哪里了?

  • 控制:对各种对象的控制
  • 反转:将控制权反转给Spring容器

嗯,就是将对象的管理交给Spring容器,不用你自己来 new Xx();

好,我们也先别管它是怎么创建对象的了,我们思考一个问题。

我们类中,是不是经常会存在。

public class Xx{private MyClass myclass;
}

现在问题就来了,既然Spring容器要帮我们创建这个Xx对象,那么其中依赖的其他对象,例如此处的MyClass怎么办?

这就又谈到了我们的依赖注入(Dependency Injection, DI)

依赖注入是指将对象的依赖(即它所需要的对象)在外部注入,而不是由对象自己创建或查找这些依赖。

你看,依赖注入只是帮助我们构建这个类,其根本目的还是为了实现咱们的控制反转。

好,在开始之前,我们先来大致了解几个点,留个印象。

  • IOC创建对象:通过反射完成
  • 依赖注入的几种方式
    • 构造器注入:通过构造函数来注入依赖对象。
    • Setter方法注入:通过Setter方法来注入依赖对象。
    • 接口注入:通过接口来注入依赖对象(在Spring中不常用)。

后续的内容,基于SpringBoot程序演示。

2.@SpringBootApplication注解

上面已经提到了,IOC容器是用来管理咱们的对象的,Bean就是Spring IoC容器管理的一个对象,称呼而已。

好,在此之前,我们先来了解下,SpringBoot默认加载哪些bean。

回到梦开始的地方。

image-20240610120615736

一切来自于咱们的这个@SpringBootApplication注解。

image-20240610115354154

前面4个,是咱们java自带的元注解。

image-20240610120049470

注解 作用
@Target({ElementType.TYPE}) 表明可以应用于类、接口或枚举类型
@Retention(RetentionPolicy.RUNTIME) 表明在运行时可用
@Documented 表明使用的元素应被 javadoc 或类似工具记录
@Inherited 表明可以被子类继承

后面3个,是咱们Spring的。

注解 作用
@SpringBootConfiguration 这是一个特殊的 @Configuration 注解,用于标记一个类作为 Spring 配置类。
它继承自 @Configuration,表明该类可以作为 Spring IoC 容器的配置类。
@EnableAutoConfiguration 这个注解启用 Spring Boot 的自动配置机制。
Spring Boot 会根据类路径中的依赖、定义的 Bean 和各种属性设置自动配置 Spring 应用程序的上下文。
它通过读取类路径中的 META-INF/spring.factories 文件,查找可以自动配置的类。
@ComponentScan 这个注解启用组件扫描,默认扫描 @SpringBootApplication 所在包及其子包中的所有组件(如带有 @Component、@Service、@Repository 和 @Controller`的类)。
excludeFilters属性用于排除特定类型的组件。例如:
- TypeExcludeFilter.class:一个自定义过滤器类型,用于排除特定类型的组件。
- AutoConfigurationExcludeFilter.class:用于排除自动配置类。

综上,使用了 @SpringBootApplication 注解:

  • ZaApplication类被标记为一个配置类,可用于定义 Bean。

  • Spring Boot 的自动配置机制被启用:Spring Boot 会根据类路径中的依赖自动配置应用程序上下文。

  • Spring Boot 会自动扫描cn.yang37.za包及其子包中的组件,并将它们注册为 Spring Bean。

3.查看SpringBoot中的Bean

3.1 Actuator

注意啊,这个东西权限很大,不懂的话,你别在生产环境乱用。

这玩意就是个Spring自带的监控工具,用起来很简单。

3.1.1 导入pom

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

3.1.2 声明配置

这里就是声明暴露的端点,包含一个beans。

server:port: 9595
# actuator相关
management:endpoints:web:exposure:include: beans

3.1.3 启动后访问

启动后访问web,可以看到相关信息。

http://localhost:9595/actuator/beans

image-20240610125156296

3.2 ApplicationContext

这个就比较简单了,直接注入了使用就行。

例如我注册一个bean,类型是MyConfig,名字是“6666”。

package cn.yang37.za.config;import org.springframework.stereotype.Component;@Component("6666")
public class MyConfig {}

然后,在测试类中获取它。

package cn.yang37.za.controller;import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.context.ApplicationContext;import javax.annotation.Resource;@Slf4j
@SpringBootTest
class BeanControllerTest {@Resourceprivate ApplicationContext applicationContext;@Testvoid name() {Object myConfig = applicationContext.getBean("6666");log.info("info : {}", myConfig);}
}

可以看到相关信息。

image-20240610125652769

image-20240610125942078

4.SpringBoot中创建bean的方式

4.1 @Componet + @ComponentScan

4.1.1 @Componet

Bean命名规则

  • 类名的首字母小写:Spring会将类名的首字母小写,其他部分保持不变,作为默认的Bean名称。比如此处的beanName是myConfig。

  • 保留原有大小写:如果类名的首字母和第二个字母都是大写(例如URLConverter),Spring会直接使用类名URLConverter作为Bean名称,而不会改变首字母的大小写。

最简单的方式,就是直接用上咱们的@Componet注解,标记这个类要放到容器中去。

@Component
// 也可以声明一个Bean名字@Component("MyConfig666")
public class MyConfig {}

使用的时候,直接使用@Autowired或者@Resource获取即可。

@RestController
@RequestMapping("/bean")
public class BeanController {@Resourceprivate MyConfig myConfig;@GetMapping("/info")public String info() {return myConfig.toString();}
}

image-20240610114020906

我们平时用到的@Controller等注解,都是它的子注解。

那我到底写@Component还是@Controller?要反过来说,@Controller这些注解都不合适的时候,再用@Component。

注解 作用 适用层次
@Component 标记为Spring管理的通用bean 通用组件
@Controller 标记为Spring MVC控制器,处理HTTP请求并返回视图 表现层(MVC控制器)
@Service 标记为服务层组件,表示业务逻辑 服务层
@Repository 标记为数据访问层组件,提供数据访问功能并处理数据库异常转换 数据访问层
@RestController 标记为RESTful Web服务控制器,默认返回JSON或XML格式的数据 表现层(REST控制器)
image-20240610114532047

4.1.2 @ComponentScan

该注解呢,用于扫描某个包,将所有声明了@Component注解的类,注入到Spring容器中。

哎,你肯定问,我都@Componet了?还scan个毛?@Componet不是已经可以加载进来了吗?

你忘啦,最开始说的,启动类上的@SpringBootApplication注解包含了@ComponentScan注解,但是它的默认扫描位置是:

  • 启动类(即@SpringBootApplication的类)所在的包
  • 子包

回到项目里,就是默认只扫描cn.yang37.za文件夹和其中的文件夹。

image-20240610120937492

我们在别的位置新建一个类,即便用上注解,它也是扫描不到的。

image-20240610121437434

image-20240610121425863

这个时候,咱们的@ComponentScan注解不就有用了吗?咱们直接在类中补上这个扫描的位置,它不就启动起来啦。

image-20240610121633954

啊,你别懵,十分正常,你只是平时写习惯了,不妨看下咱们类加载器的知识:Java-JVM-类加载器

idea启动的时候,自动把classpath路径给我们传了,这下面所有的类都能在JVM里面找到。

所以咱们JVM中是有这个Demo类的,在配置了@ComponentScan到这个cn.yang的包下后,就一切正常了。

image-20240610122038897

好,实际上不会这样搞对吧?咱们Java程序员向来喜欢搞各种xx包、xx类的概念,咱们根本就做不出来旁边新建个文件夹cn.yang的鬼操作对吧?

是滴,这里是在给你演示。

常见的方式是,某个组件没在你当前项目(比如此处的ZaApplication)代码里,比如某某jar包、比如跨模块,此时没加载到Spring容器中,不妨试试这个注解。

当然,正常情况下,这个jar包应该使用咱们2.5节spring.factories的方式。

反正,你发现没自动加载上,你就试试手动声明下扫描位置,不用这么纠结到底要在何时使用。

注意,@ComponentScan不要乱写在启动类上,用在启动类上会覆盖默认的扫描范围。

4.2 @Bean

Bean命名规则:默认情况下,其名字是方法名,也可以在注解中指明名字(注解的name属性)。

当然可以我们自己创建一个对象,把它放到我们的Spring容器中去。

哎,前面不是说,咱们把对象管理交给Spring去处理吗?什么玩意这么大的面子,还得我亲自创建?

先来段专业的话术。

@Bean 注解用于告诉 Spring 容器这个方法将返回一个需要注册为 Spring Bean 的对象,这样,我们可以自定义对象的创建逻辑,尤其是在对象创建需要复杂配置或依赖于其他组件时。

上例子,假设我们有个User对象,朴实无华。

@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class User {private String name;private int age;
}

假设我创建它的时候,我要先到excel中读取下年龄数据,再调用xx接口获取下名字。

哈哈,你也知道我在乱扯。肯本原因就是,Spring哪能啥都知道啊,它也只是个框架,像上面的例子中,你还能指望Spring知道你要读取excel来加载年龄?

正如咱们自己写代码的时候,场景就是千变万化的,@Bean的用处,是告诉你,当某些复杂场景搞不定的时候,你可以自己定义bean的注册逻辑。

所以,复杂点的场景,我要yang某活到1666岁,咱们可以自己放一个想要的Bean进去。

image-20240610131455815

image-20240610131618672

又比如你在用Redis时,经常看到的StringRedisTemplate,这不就是你不满意默认的StringRedisTemplate嘛,你要搞一个给它覆盖了。

    @Beanpublic StringRedisTemplate stringRedisTemplate(@Qualifier("redissonConnectionFactory") RedisConnectionFactory connectionFactory) {StringRedisTemplate stringRedisTemplate = new StringRedisTemplate();StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();stringRedisTemplate.setConnectionFactory(connectionFactory);stringRedisTemplate.setKeySerializer(stringRedisSerializer);stringRedisTemplate.setValueSerializer(stringRedisSerializer);stringRedisTemplate.afterPropertiesSet();return stringRedisTemplate;}

4.3 @Configuration

Bean命名规则:跟@Componen一样的。

这个玩意呢,打开里面还是@Component,适用于配置类的场景。

image-20240610132927692

所以我们可以知道,它也能注册bean,那既然有自己的名字,肯定做了些定制化的操作。

Configuration这个单词你总该认识吧?什么,这你都不认识?

image-20240610133141821

那看名字我们也能猜到了,它就是专门针对配置类的场景

配置类的话,它的关键点在哪里,在于单例,即咱们的配置项key=123,它不会成key=456,所以@Configuration针对这点做了优化。

不过呢,这个优化点咱们一般感受不到。

Spring 会对 @Configuration 类进行 CGLIB 动态代理增强,确保@Bean对应的方法只被调用一次,直接返回相同的实例(单例)。

有点抽象对不对,我们来看个例子。

@Component
public class MyConfig {@Beanpublic User user1() {return new User();}@Beanpublic User user2() {// 调用user1方法return user1();}}

这里我们用的@Component注解,关键点在于下方的user2,它是在调用user1()方法。

此时,@Component注解返回了两个不同的对象。

image-20240610140720240

那我们改成@Configuration

@Configuration
public class MyConfig {@Beanpublic User user1() {return new User();}@Beanpublic User user2() {return user1();}}

此时,@Component返回的对象是同一个。

image-20240610140822053

哎,有点奇怪了对吧。

注意啊,注意,这不是@Component多例@Configuration单例,是强调这两个类中的@Bean对应的方法执行时会不会被代理掉。

Bean的单例还是多例,取决于Bean它自己本身声明的是单例bean还是原型bean。

你别晕,我给你改造下,是@Component对吧,注册了一个user1的bean(默认为单例bean)。

@Component
public class MyConfig {@Beanpublic User user1() {return new User();}}

因为它是默认的单例Bean,不管你怎么调用它都还是同一个。

image-20240611165300981

你改成原型Bean。

@Component
public class MyConfig {@Bean@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)public User user1() {return new User();}
}

image-20240611165408107

你看,生成出来的Bean是单例还是多例,取决于它自身是单例Bean还是原型Bean。

回顾一下之前的User2这个Bean。

    @Beanpublic User user2() {return user1();}@Beanpublic User user2() {return new User();}

上方的根本逻辑是什么,是我们主动去触发user1()这个方法了,user1方法干嘛的,给你new个User出来。

@Configuration 中所有带@Bean注解的方法都会被动态代理,调用该方法首次执行后,总该有返回值了吧?后续呢,它不会再执行,直接返回这个实例(除非你又自己主动去强调Bean的类型是原型Bean)。

@Component,注意啊,这里的user2是个原型Bean,意味着每次都会返回新的对象,触发咱们的user1()方法。

image-20240611170336291

image-20240611170410978

改成@Configuration呢,会发现,user1()方法不会多次触发。

image-20240611170429777

image-20240611170452176

哎,你肯定奇怪,那我平时也没有跑去调那个@Bean下面的方法呀。

声明就声明,我没事我乱主动调用干嘛,我自己注册两个User对象,我闲的没事我跑去用user1()方法创建啊?

这应该就是针对这个场景的防范式编码了。

  • 首先呢,咱们不乱调用,Spring源码里面可能涉及到多次调用,是为了确保@Configuration它符合自己的特性。
  • 其次呢,也是为了避免你乱来,在咱们@Configuration类中写的玩意,哪怕你自己乱用,咱们的配置对象也不会混乱。

4.4 @Import

Bean命名规则:跟@Componen一样的。

@Configuration可以将某个类标记为配置类,当配置类比较多的时候呢,我们可以不用再在每个类上写@Configuration,而是统一在一个类中借用@Import引入。

比如这样:

MyConfig2、MyConfig3都上没有注解

public class MyConfig2 {@BeanUser user3(){return new User();}
}
public class MyConfig3 {@BeanUser user4() {return new User();}
}

咱们在AppConfig类中引入它们两个配置类。

@Configuration
@Import({MyConfig2.class, MyConfig3.class})
public class AppConfig {}

验证一下,两个类都能被成功导入。

image-20240611171828484

嗯,这是@Import的一个简单用法,这样我们只用在一个类中(例如AppConfig)去找都导入了哪些玩意,方便管理。

然后呢,还有个常用的用法就是动态导入配置,编写一个类来实现ImportSelector接口即可。

配置选择类

public class MyImportSelector implements ImportSelector {@Overridepublic String[] selectImports(AnnotationMetadata importingClassMetadata) {LinkedList<String> configList = new LinkedList<>();// 根据条件导入配置类if (true) {configList.add(MyConfig2.class.getName());configList.add(MyConfig3.class.getName());}return configList.toArray(new String[0]);}}

这里我list.toArray(new String[0])是个正确的写法,返回的就是对应的那个2个长度的数组。并且是建议这样写的,你可以去自己搜索下。

在@Import中使用配置选择类

@Configuration
@Import(MyImportSelector.class)
public class AppConfig {}

验证结果

image-20240611173026448

4.5 FactoryBean接口

FactoryBean是一个工厂Bean,可以生成指定类型的Bean实例。

实现FactoryBean接口,可以对Bean创建提供更高的灵活性和扩展性,例如动态创建不同类型的 Bean、复杂初始化逻辑、延迟初始化等场景。

好,简单来说,就是我们想要创建某个类型的Bean的时候,可以找这个FactoryBean要。

public interface FactoryBean<T> {String OBJECT_TYPE_ATTRIBUTE = "factoryBeanObjectType";@NullableT getObject() throws Exception;@NullableClass<?> getObjectType();default boolean isSingleton() {return true;}
}

比如上方的User,我们想要创建该类型的bean。

重写的getObject方法中,我们描述详细的创建逻辑。

  • sex为1时,age是18。
  • sex为2时,age是20。
@NoArgsConstructor
@AllArgsConstructor
public class UserFactoryBean implements FactoryBean<User> {private int sex;@Overridepublic User getObject() {User user = User.builder().name("yang").age(0).build();if (1 == sex) {user.setAge(18);}if (2 == sex) {user.setAge(20);}return user;}@Overridepublic Class<?> getObjectType() {return User.class;}}

注册这个UserFactoryBean。

  • userFactoryBean1:sex为1,其中创建的user的age是18。
  • userFactoryBean2:sex为2,其中创建的user的age是20。
@Slf4j
@Configuration
public class MyConfig {@Beanpublic UserFactoryBean userFactoryBean1() {return new UserFactoryBean(1);}@Beanpublic UserFactoryBean userFactoryBean2() {return new UserFactoryBean(2);}}

此时,咱们利用UserFactoryBean来获取User对象,并没有说去@Bean来一个user。

image-20240611190430845

哈,这里我拿的Bean名字不是userFactoryBean1吗,咱直接返回里面的User对象了?往后看。

简单来说,就是为了方便,我都开始写FactoryBean了,当然是关注于拿到实际的Bean,咱默认设计成给你直接返回对应Bean实例。

然后呢,你还可以这样用。image-20240611191112047

额,这里你可能看到我用了个&userFactoryBean1,这个&是何用意?

& 前缀在 Spring 中是专门用来获取FactoryBean实例本身的。

即通过 getBean方法获取 Bean 时,FactoryBean这个玩意,默认情况下返回的是它创建的对象。

  • 违背预期:大多数情况下,开发者希望获取的是由 FactoryBean 创建的对象,而不是 FactoryBean 本身。

    例如,当你配置了一个 UserFactoryBean,你希望得到的是 User 对象,而不是 UserFactoryBean的 实例。

  • 简化使用:通过 getBean 方法直接获取目标对象,简化了代码的使用,不需要开发者额外处理 FactoryBean 实例。

  • 避免混淆:明确区分 FactoryBean 和它创建的对象,有助于代码的可读性和可维护性。

那么在例子中,UserFactoryBean本身是不会直接注入的,默认注册的是 UserFactoryBean创建的User,为了注入 UserFactoryBean实例,得像我下面这样:

    @Autowired@Qualifier("&userFactoryBean1")private UserFactoryBean userFactoryBean1;

不用太纠结,有个印象即可,毕竟,报错了你会搜索,而且,也不是不给你拿,你加&指明下就好了。

有点懵是吧,上图。

image-20240611193658510

4.6 BeanDefinitionRegistryPostProcessor接口

BeanDefinitionRegistryPostProcessor与上面FactoryBean接口类似,实现这个接口,也能创建出Bean。

@Slf4j
@Configuration
public class UserBeanDefinitionRegistryPostProcessor implements BeanDefinitionRegistryPostProcessor {@Overridepublic void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry beanDefinitionRegistry) throws BeansException {log.info("=====> UserBeanDefinitionRegistryPostProcessor");// 创建User类的Bean定义BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder.genericBeanDefinition(User.class);beanDefinitionBuilder.addPropertyValue("name", "yang");beanDefinitionBuilder.addPropertyValue("age", 166);// 注册User类的Bean定义beanDefinitionRegistry.registerBeanDefinition("user", beanDefinitionBuilder.getBeanDefinition());}@Overridepublic void postProcessBeanFactory(ConfigurableListableBeanFactory configurableListableBeanFactory) throws BeansException {// 这里可以留空,或者用于进一步定制 BeanFactory}
}

image-20240611194935762

4.7 spring.factories

这个呢,主要用于三方包的场景。

前面提到了,咱们默认扫描的是启动类和其子包,那我写给别人的stater,我还发个聊天消息给别人说,你来配置下扫描我的xx包?

spring.factories呢无非就是一个固定的文件,咱们写在里面的类,会被自动注册到容器中。

原理呢是基于Java的SPI机制的。

它的位置固定为:

META-INF/spring.factories

好,不妨演示一下。

  • 旧项目cn.yang37
  • 新项目com.yang37

4.7.1 旧项目

我们直接把当前项目清空,你看,啥都没有,只有个简单的Controller和启动类。

image-20240611201444843

记录下当前项目的坐标。

    <groupId>cn.yang37</groupId><artifactId>yang-spring-za</artifactId><version>0.0.1-SNAPSHOT</version>

image-20240611201547931

4.7.2 新项目

然后呢,直接新开一个项目,用不同的包名总可以了吧,新项目用com.yang37-2,这差别够大了吧。

    <groupId>com.yang37-2</groupId><artifactId>yang-spring-za2</artifactId><version>0.0.1-SNAPSHOT</version>

它里面啥也没有,只是导入了spring-boot-starter,代码呢写了两个实体类和配置类,启动类都没有。

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

image-20240611201812661

两个实体类,朴实无华,也没用spring的注解。

package com.yang37.za.entity;import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class User2 {private String name;private int age;
}
package com.yang37.za.entity;import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class User3 {private String name;private int age;}

然后呢,配置类中,我们把它俩注册为Bean。

package com.yang37.za.config;import com.yang37.za.entity.User2;
import com.yang37.za.entity.User3;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;@Configuration
public class UserConfig {@Beanpublic User2 user2() {return User2.builder().name("user2").age(30).build();}@Beanpublic User3 user3() {return User3.builder().name("user3").age(30).build();}}

接着我们在META-INF\spring.factories声明需要加载UserConfig。

# META-INF/spring.factories
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\com.yang37.za.config.UserConfig

image-20240611202510674

最后我们将这个项目install。

image-20240611202551996

4.7.3 验证

现在,我们在旧工程中导入打包好的新项目。

        <dependency><groupId>com.yang37-2</groupId><artifactId>yang-spring-za2</artifactId><version>0.0.1-SNAPSHOT</version></dependency>

image-20240611202718928

你看,已经加载进来了。

image-20240611202818037

停,先回顾下。

  • 旧项目cn.yang37:啥也没有
  • 新项目com.yang37:啊,是个外部的玩意,里面记录了要加载哪些bean。

你看,我们在当前项目中,引入了这个带有spring.factories的三方包,我们旧项目自身啥也没有对吧?

验证下,你看,一切都是如期而至。

image-20240611203131426


哎,前面不是讲了,咱们用scan是不是也可以,我们把com.yang37里面的spring.factories删除了重新打包。

没了之后,我们不@ComponentScan直接报错。

image-20240611203448302

加下@ComponentScan,是不是正常啦。

image-20240611203606098

好咯,回顾下,条条大路通罗马,无非就是适用的场景不一样而已。

4.7.4 总结

上面的例子只是演示了一个类似于加载自定义配置类的玩意,实际上咱们有很多场景可以用到。

spring.factories 文件中常见配置项的总结。

配置项类型 说明
org.springframework.boot.autoconfigure.EnableAutoConfiguration 用于指定自动配置类。
org.springframework.context.ApplicationContextInitializer 用于指定初始化 Spring 上下文的类。
org.springframework.context.ApplicationListener 用于指定应用程序事件监听器。
org.springframework.boot.env.EnvironmentPostProcessor 用于在 Spring Boot 应用程序环境准备好后进行处理的类。
org.springframework.boot.SpringApplicationRunListener 用于在 SpringApplication 运行时的监听器。

demo:

# META-INF/spring.factories# Auto Configuration
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\com.yang37.za.config.UserConfig,\com.yang37.za.config.OtherConfig# Application Context Initializers
org.springframework.context.ApplicationContextInitializer=\com.yang37.za.config.SomeInitializer,\com.yang37.za.config.AnotherInitializer# Application Listeners
org.springframework.context.ApplicationListener=\com.yang37.za.config.SomeListener,\com.yang37.za.config.AnotherListener# Environment Post Processors
org.springframework.boot.env.EnvironmentPostProcessor=\com.yang37.za.config.SomeEnvironmentPostProcessor,\com.yang37.za.config.AnotherEnvironmentPostProcessor# Spring Application Run Listeners
org.springframework.boot.SpringApplicationRunListener=\com.yang37.za.config.SomeRunListener,\com.yang37.za.config.AnotherRunListener

5.SpringBoot如何创建Bean

5.1 过程

5.2 问题

5.2.1 三级缓存

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

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

相关文章

11-CSS定位

CSS定位01 CSS定位概念理解 01 标准流布局概念的理解02 position属性02 相对定位 依然在标准流中 应用场景: 在不影响其它元素的情况下,对当前元素进行微调 <!DOCTYPE html> <html lang="en"><head><meta charset="UTF-8"><met…

Combining Recurrent, Convolutional, and Continuous-time Models with Linear State-Space Layers

目录概符号说明LSSL和其它方法的联系代码Gu A., Johnson I., Goel K., Saab K., Dao T., Rudra A., and Re C. Combining recurrent, convolutional, and continuous-time models with linear state-space layers. NeurIPS, 2021.State space representaion-wiki.概 Mamba 系列…

堆基础知识

arenachunk通俗地说,一块由分配器分配的内存块叫做一个 chunk,包含了元数据和用户数据。具体一点,chunk 完整定义如下: struct malloc_chunk {INTERNAL_SIZE_T mchunk_prev_size; /* Size of previous chunk (if free). */INTERNAL_SIZE_T mchunk_size; …

【Azure Spring Apps】Spring App部署上云遇见 502 Bad Gateway nginx

问题描述 在部署Azure Spring App应用后,访问应用,遇见了502 Bad Gateway Nginx。问题解答 502 Bad Gateway, 并且由Nginx返回。而自己的应用中,并没有定义Nginx相关内容,所以需要查看问题是否出现在Azure Spring App服务的设置上。 根据Spring App的通信模型图判断,502的…

学生管理系统的CRUD

include using namespace std; typedef struct Studnet { //初始化结构体变量 int ID; double math_scores; double english_scores; double computer_scores; double total_scores;}Student; void Input_student_score(int size, Student* stu); //输入所有学生信息 void Out…

C语言中关于Base64编码的基础原理

Base64编码简述: 1.Base64是网络上最常见的用于传输8Bit字节码的编码方式之一,Base64就是一种基于64个可打印字符来表示二进制数据的方法。 2.Base64,就是包括小写字母a-z、大写字母A-Z、数字0-9、符号"+"、"/"一共64个字符的字符集,(任何符号都可以转…

09-盒子模型

盒子模型01 认识盒子模型02 盒子模型的四边03 盒子边框04 盒子内边距-padding 通常用于设置边框和内容之间的间距 <!DOCTYPE html> <html lang="en"> <head><meta charset="UTF-8"><meta http-equiv="X-UA-Compatible&quo…

试了下ocr

pdf能看了,拓展的驱动下,想着是否可以ORC呢,识别到文字内容更有帮助。 按网搜的顺序,开始是用pytesseract,pip安装顺利,但运行不了,提示找不到pytesseract,按网上的帮助下载win安装包,选上中文包,再试,可以运行了,就是中文基本识别不了,也不知哪里改善,只得作罢。…