springboot 获取bean_SpringBoot教程,SpringBoot高级之原理分析

一、SpringBoot自动配置--注解说明

1.1、Condition条件判断

1.1.1、创建Condition模块

Condition(条件):Condition是在Spring4.0增加的条件判断功能,通过这个可以功能可以实现选择性的创建Bean操作。

思考:

SpringBoot是如何知道要创建哪个Bean的?比如SpringBoot是如何知道要创建RedisTemplate的?

创建一个模块,springboot-condition:

@SpringBootApplicationpublic class SpringbootConditionApplication {public static void main(String[] args) {//启动springBoot的应用,返回spring的IOC容器ConfigurableApplicationContext context =SpringApplication.run(SpringbootConditionApplication.class, args);//获取一个Bean,RedisTemplateObject redis = context.getBean("redisTemplate");System.out.println(redis);}}
f0b132f32d184ba1893c87b87d320b7c

没有引入坐标,所以没有redisTemplate的Bean。

增加redis坐标

   org.springframework.boot   spring-boot-starter-data-redis

redisTemplate的Bean已经引入

837973d2a8fd45ccadfca6a7e980ef59

1.1.2、Condition案例1

需求:

在 Spring 的 IOC 容器中有一个 User 的 Bean,现要求:

1. 导入Jedis坐标后,加载该Bean,没导入,则不加载。

2. 将类的判断定义为动态的。判断哪个字节码文件存在可以动态指定。

第一步:创建User实体类

package com.itheima.condition.domain;public class User {}

第二步:创建User配置类

package com.itheima.condition.config;import com.itheima.condition.domain.User;import org.springframework.context.annotation.Bean;importorg.springframework.context.annotation.Configuration;@Configurationpublic class UserConfig {   @Bean   public User user(){       return new User();  }}

第三步:修改启动类

SpringbootConditionApplication 中增加user的Bean获取

Object user = context.getBean("user");System.out.println(user);
66280396b8d34ca382b4f99eeef3eee5

现在是任何情况下都能加载User这个类。

第四步:实现Condition

新建一个ClassCondition类,实现Condition接口里的matches方法来控制类的加载

新建一个类实现Condition接口

public class ClassCondition implements Condition {   @Override   public boolean matches(ConditionContextconditionContext, AnnotatedTypeMetadataannotatedTypeMetadata) {       return false;  }}

修改condition.config的UserConfig类:

@Bean@Conditional(ClassCondition.class)public User user(){   return new User();}

将user对象加入Bean里的时候增加一个@Conditional注解

测试:当matches返回false时,不加载User类,返回true时,加载User类

第五步:导入Jedis坐标

   redis.clients   jedis

第六步:修改matches方法

public boolean matches(ConditionContextconditionContext, AnnotatedTypeMetadataannotatedTypeMetadata) {   boolean flag = true;   try {       Class> aClass =Class.forName("redis.clients.jedis.Jedis");  } catch (ClassNotFoundException e) {       flag = false;  }   return flag;}

当Jedis坐标导入后,可以加载到 redis.clients.jedis.Jedis 这个类,未导入时,加载不到redis.clients.jedis.Jedis这个类,所以通过异常捕获可以返回是否加载。

1.1.3、Condition案例2

现在的 Class.forName("redis.clients.jedis.Jedis") 这个是写死的,是否可以动态的加载呢?

第一步:新建注解类

新建ConditionOnClass注解类,增加@Conditional注解

@Target({ElementType.TYPE, ElementType.METHOD})@Retention(RetentionPolicy.RUNTIME)@Documented@Conditional(ClassCondition.class)public @interface ConditionOnClass {   String[] value();}

注解类中增加value变量。

第二步:修改UserConfig

@Bean//@Conditional(ClassCondition.class)@ConditionOnClass("redis.clients.jedis.Jedis")public User user(){   return new User();}

现在新建的注解@ConditionOnClass和原注解@Conditional作用一致

第三步:修改matches方法

/*** @param conditionContext 上下文对象,用于获取类加载,Bean工厂等信息* @param annotatedTypeMetadata 注解的元对象,可以用于获取注解定义的属性值* @return*/@Overridepublic boolean matches(ConditionContextconditionContext, AnnotatedTypeMetadataannotatedTypeMetadata) {   Map map =annotatedTypeMetadata.getAnnotationAttributes(ConditionOnClass.class.getName());   //System.out.println(map);   String[] strings = (String[]) map.get("value");   boolean flag = true;   try {       for (String className : strings) {           Class> aClass = Class.forName(className);      }  } catch (ClassNotFoundException e) {       flag = false;  }   return flag;}

此时,通过UserConfig注解注入的类存在就加载User类,如果注入的类不存在就不加载User类测试,引入fastjson坐标,加载User类

第四步:查看源代码jar包

org.springframework.boot.autoconfigure.condition.ConditionalOnClass

org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration

第五步:判断配置文件

@Bean@ConditionalOnProperty(name = "itcast", havingValue ="itheima")public User user2(){   return new User();}

修改启动类

Object user = context.getBean("user2");System.out.println(user);
  • 在UserConfig类中新增一个方法,同样加载User类
  • 使用@ConditionalOnProperty注解,name是itcast,value是itheima
  • 修改配置文件application.properties,增加itcast=itheima
  • 测试加载User类

1.1.4、Condition小结

User实体类,UserConfig配置类将User放入Bean工厂,ClassCondition类重写matches方法,ConditionOnClass注解类增加注解。

自定义条件:

自定义条件类:自定义类实现Condition接口,重写 matches 方法,在 matches 方法中进行逻辑判断,返回boolean值 。matches 方法两个参数:

  • context:上下文对象,可以获取属性值,获取类加载器,获取FactoryBean等。
  • metadata:元数据对象,用于获取注解属性。

判断条件:在初始化Bean时,使用@Conditional(条件类.class)注解。

SpringBoot常用条件注解:

  • ConditionalOnProperty:判断配置文件中是否有对应的属性和值才初始化Bean
  • ConditionalOnClass:判断环境中是否有对应的字节码文件才初始化Bean
  • ConditionalOnMissingBean:判断环境中是否有对应的Bean才初始化Bean。

1.2、SpringBoot切换内置服务器

1.2.1、启用内置web服务器

引入starter-web坐标之后,服务器内置tomcat启动了

   org.springframework.boot   spring-boot-starter-web

1.2.2、查看一下源jar包

org.springframework.boot.autoconfigure.web.embedded.TomcatWebServerFactoryCustomizer

修改坐标:

   org.springframework.boot   spring-boot-starter-web                   spring-boot-starter-tomcat           org.springframework.boot             spring-boot-starter-jetty   org.springframework.boot
c0f4c38a753541ddafade6434a564964

Jetty服务器启动

1.3、@Enable*注解

SpringBoot中提供了很多Enable开头的注解,这些注解都是用于动态启用某

些功能的。而其底层原理是使用@Import注解导入一些配置类,实现Bean的动

态加载。

问题:

SpringBoot 工程是否可以直接获取jar包中定义的Bean?

1.3.1、创建两个模块

一个是springboot-enable,一个是springboot-enable-other

1.3.2、创建实体类与配置类

package com.itheima.domain;public class User {}@Configurationpublic class UserConfig {   @Bean   public User user(){       return new User();  }}

1.3.3、引入enable-other坐标

   com.itheima   springboot-enable-other   0.0.1-SNAPSHOT

1.3.4、修改enable启动类

public static void main(String[] args) {   ConfigurableApplicationContext context =SpringApplication.run(SpringbootEnableApplication.class,args);   Object user = context.getBean("user");   System.out.println(user);}

Bean里没有User这个类

0da6d888a50047398cd83f6e7281dc09

@ComponentScan扫描范围:当前引导类所在包以其子包

com.itheima.enable

com.itheima.config.UserConfig

两个包明显是平级的

第一种:增加扫描包的范围

@SpringBootApplication@ComponentScan("com.itheima.config")public class SpringbootEnableApplication {}

第二种:使用@Import注解

使用@Import注解,都会被Spring创建,放入IOC容器

@SpringBootApplication@Import(UserConfig.class)public class SpringbootEnableApplication {}

第三种:对@Import进行封装

创建EnableUser注解类,使用@Import注解

@Target({ElementType.TYPE})@Retention(RetentionPolicy.RUNTIME)@Documented@Import(UserConfig.class)public @interface EnableUser {}

此时,查看@SpringBootApplication注解时发现,里面有@EnableAutoConfiguration注解,而这个注解中使用了@Import({AutoConfigurationImportSelector.class})注解,加入了一个类。

1.4、@Import注解

@Enable*底层依赖于@Import注解导入一些类,使用@Import导入的类会被Spring加载到IOC容器中。而@Import提供4中用法:

  • 导入Bean
  • 导入配置类
  • 导入ImportSelector实现类,一般用于加载配置文件中的类
  • 导入ImportBeanDefinitionRegistrar实现类

1.4.1、导入Bean

@SpringBootApplication@Import(User.class)public class SpringbootEnableApplication {}
84250401d6a34539b22e6bbd47f80ff0

这样导入不了的原因在于,我们是通过Bean的名称"user"来获取对象,而导入的这个User.class不一定叫"user"这个名字,所以需要修改启动类,通过类的类型来获取。

public static void main(String[] args) {   ConfigurableApplicationContext context =SpringApplication.run(SpringbootEnableApplication.class,args);   /*Object user = context.getBean("user");System.out.println(user);*/   User user = context.getBean(User.class);   System.out.println(user);}

如果想要获取Bean的名称,那么可以使用context.getBeansOfType

Map map =context.getBeansOfType(User.class);System.out.println(map);
acd4fb9b73e64f16b211fc40a76854d6

所以自动创建的Bean名称为com.itheima.domain.User

Object user1 =context.getBean("com.itheima.domain.User");System.out.println(user1);

那么通过这个名字就可以获取这个类

2283e0cfaed34183bc7a34f1ff1dd727

1.4.2、导入配置类

配置类指的是前面创建好的UserConfig,那么现在再创建一个实体类

package com.itheima.domain;public class Role {}

修改UserConfig配置类,将Role这个类也加入到Bean里

@Beanpublic Role role(){   return new Role();}

修改启动类,增加Role的类加载

Role role = context.getBean(Role.class);System.out.println(role);
cd21250b571b45b592d090643bd92765

此时,两个类都可以被加载。

1.4.3、实现ImportSelector接口

创建一个MyImportSelector类,实现ImportSelector接口,重写里面的selectImports方法

@SpringBootApplication@Import(MyImportSelector.class)public class SpringbootEnableApplication {}

修改启动类

@SpringBootApplication@Import(MyImportSelector.class)public class SpringbootEnableApplication {}
9ba4830a7bcd4a529a7acaeaf3f942eb

此时,两个类都可以被加载。

1.4.4、实现ImportBeanDefifinitionRegistrar接口

创建一个MyImportBeanDefifinitionRegistrar类,实现ImportBeanDefifinitionRegistrar接口,重写registerBeanDefifinitions方法

AbstractBeanDefinition beanDefinition =BeanDefinitionBuilder.rootBeanDefinition(User.class).getBeanDefinition();registry.registerBeanDefinition("user", beanDefinition);

修改启动类

@SpringBootApplication@Import(MyImportBeanDefinitionRegistrar.class)public class SpringbootEnableApplication {}
399a2d2428174e53b1b4ccd7c88fe45d

此时,User类被加载,而Role没有被加载,想要加载Role类,再次修改registerBeanDefinitions方法

@Overridepublic void registerBeanDefinitions(AnnotationMetadataimportingClassMetadata, BeanDefinitionRegistry registry){   //注册User类   AbstractBeanDefinition beanDefinition =BeanDefinitionBuilder.rootBeanDefinition(User.class).getBeanDefinition();   registry.registerBeanDefinition("user",beanDefinition);   //注册Role类   beanDefinition =BeanDefinitionBuilder.rootBeanDefinition(Role.class).getBeanDefinition();   registry.registerBeanDefinition("role",beanDefinition);}

而通过名称也可以将User类加入到Bean中,只需要修改启动类用名称获取Bean即可。

1.5、@EnableAutoConfiguration

@SpringBootApplication中的@EnableAutoConfiguration注解,也是通过@Import({AutoConfigurationImportSelector.class})来实现类的加载,那么说明Springboot中也是使用第三种方法导入类。

44210c1e1c7f4fac9d739edbf9acb845
eec2ed06b1894925b9aaa49a93fcbe97
4f90c0af76954051bfe144e0f1f6d24b
f0eac3e6ed6a4a6cbdf9bccadea93a4d

配置文件位置:META-INF/spring.factories,该配置文件中定义了大量的配置类,当 SpringBoot 应用启动时,会自动加载这些配置类,初始化Bean。

并不是所有的Bean都会被初始化,在配置类中使用Condition来加载满足条件的Bean。

二、SpringBoot自动配置--自定义Starter

2.1、分析MyBatis加载过程

引入坐标

   org.mybatis.spring.boot   mybatis-spring-boot-starter   1.3.2

加载过程

86a852435de64f60bd1c3c58e0bf59ad

2.2、自定义Starter需求

自定义redis-starter。要求当导入redis坐标时,SpringBoot自动创建Jedis的Bean。

步骤:

  • 创建 redis-spring-boot-autoconfigure 模块
  • 创建 redis-spring-boot-starter 模块,依赖 redis-spring-boot-autoconfigure的模块
  • 在 redis-spring-boot-autoconfigure 模块中初始化 Jedis 的 Bean。并定义META-INF/spring.factories 文件
  • 在测试模块中引入自定义的 redis-starter 依赖,测试获取 Jedis 的Bean,操作 redis。

2.3、创建两个模块

redis-spring-boot-autoconfigure

redis-spring-boot-starter

在starter的坐标中加入autoconfigure

   com.itheima   redis-spring-boot-autoconfigure   0.0.1-SNAPSHOT

在autoconfigure的坐标中加入Jedis

   redis.clients   jedis

2.4、创建RedisAutoConfigure配置类

在autoconfigure模块中创建RedisAutoConfigure配置类

@Configuration@EnableConfigurationProperties(RedisProperties.class)public class RedisAutoConfigure {   /**    * 提供Jedis的Bean    */   @Bean   public Jedis jedis(RedisProperties redisProperties){       return new Jedis(redisProperties.getHost(),redisProperties.getPort());  }}

在autoconfigure模块中创建RedisProperties配置类

@ConfigurationProperties(prefix = "redis")public class RedisProperties {   private String host = "localhost";   private int port = 6379;   public String getHost() {       return host;  }   public void setHost(String host) {       this.host = host;  }   public int getPort() {       return port;  }   public void setPort(int port) {       this.port = port;  }}

2.5、创建META-INF/spring.factories

在resources下新建META-INF/spring.factories

13e4238e07f94298a51eb7e56d34959a
org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.itheima.redis.RedisAutoConfigure

2.6、修改启动类

修改springboot-enable启动类

@SpringBootApplicationpublic class SpringbootEnableApplication {public static void main(String[] args) {ConfigurableApplicationContext context =SpringApplication.run(SpringbootEnableApplication.class,args);       Jedis jedis = context.getBean(Jedis.class);System.out.println(jedis);}}
9cf534d4cada45c8bac718347d3e968f

此时,Jedis可以加载到。

2.7、使用Jedis

jedis.set("name", "itcast");String value = jedis.get("name");System.out.println(value);

2.8、使用配置文件

修改enable里的application.properties

70b6f29ff0a44cce8b98788d1d9c4883

启动时报错,连接不到本地的localhost:6666,证明redis配置文件已经起作用了。

2.9、优化RedisAutoConfigure

在RedisAutoConfigure类上增加注解@ConditionalOnClass(Jedis.class)加载的时候判断Jedis类存在的时候才加载Bean

@Configuration@EnableConfigurationProperties(RedisProperties.class)@ConditionalOnClass(Jedis.class)public class RedisAutoConfigure {   /**    * 提供Jedis的Bean    */   @Bean   @ConditionalOnMissingBean(name = "jedis")   public Jedis jedis(RedisProperties redisProperties){       System.out.println("RedisAutoConfigure....");       return new Jedis(redisProperties.getHost(),redisProperties.getPort());  }}

在jedis方法上增加@ConditionalOnMissingBean注解,当jedis没有被创建时加载这个类,并写入Bean

测试:

在启动类中创建一个Jedis

@Beanpublic Jedis jedis(){   return new Jedis();}

启动的时候可以看到当Jedis存在时则不再加载。

2.10、查看redis源jar包

72e2b05773824d919a0adb37a40eba32

三、SpringBoot监听机制

3.1、JAVA的监听机制

SpringBoot 的监听机制,其实是对Java提供的事件监听机制的封装。

Java中的事件监听机制定义了以下几个角色:

①事件:Event,继承 java.util.EventObject 类的对象

②事件源:Source ,任意对象Object

③监听器:Listener,实现 java.util.EventListener 接口的对象

3.2、Springboot监听器

SpringBoot 在项目启动时,会对几个监听器进行回调,我们可以实现这些监听器接口,在项目启动时完成一些操作。

一共有四种实现方法:

  • ApplicationContextInitializer
  • SpringApplicationRunListener
  • CommandLineRunner
  • ApplicationRunner

3.3、创建Listener模块

3.3.1、创建MyApplicationContextInitializer

创建MyApplicationContextInitializer,实现ApplicationContextInitializer接口

@Componentpublic class MyApplicationContextInitializer implementsApplicationContextInitializer {   @Override   public voidinitialize(ConfigurableApplicationContextconfigurableApplicationContext) {      System.out.println("ApplicationContextInitializer...initialize");  }}

3.3.2、创建MySpringApplicationRunListener

创建MySpringApplicationRunListener,实现SpringApplicationRunListener接口

@Componentpublic class MySpringApplicationRunListener implementsSpringApplicationRunListener {   @Override   public void starting() {      System.out.println("SpringApplicationRunListener...正在启动");  }   @Override   public voidenvironmentPrepared(ConfigurableEnvironment environment){      System.out.println("SpringApplicationRunListener...环境准备中");  }   @Override   public voidcontextPrepared(ConfigurableApplicationContext context){      System.out.println("SpringApplicationRunListener...上下文准备");  }   @Override   public voidcontextLoaded(ConfigurableApplicationContext context) {      System.out.println("SpringApplicationRunListener...上下文加载");  }   @Override   public void started(ConfigurableApplicationContextcontext) {      System.out.println("SpringApplicationRunListener...已经启动");  }   @Override   public void running(ConfigurableApplicationContextcontext) {      System.out.println("SpringApplicationRunListener...正在启动中");  }   @Override   public void failed(ConfigurableApplicationContextcontext, Throwable exception) {      System.out.println("SpringApplicationRunListener...启动失败");  }}

3.3.3、创建MyCommandLineRunner

创建MyCommandLineRunner,实现CommandLineRunner接口

@Componentpublic class MyCommandLineRunner implementsCommandLineRunner {@Overridepublic void run(String... args) throws Exception {System.out.println("CommandLineRunner...run");}}

3.3.4、创建MyApplicationRunner

创建MyApplicationRunner,实现ApplicationRunner接口

@Componentpublic class MyCommandLineRunner implementsCommandLineRunner {   @Override   public void run(String... args) throws Exception {       System.out.println("CommandLineRunner...run");  }}

3.3.5、运行启动类

只有MyApplicationRunner和MyCommandLineRunner运行了监听。

@Componentpublic class MyApplicationRunner implementsApplicationRunner {   @Override   public void run(ApplicationArguments args) throwsException {       System.out.println("ApplicationRunner...run");  }}

3.3.6、修改监听类

修改MyApplicationRunner和MyCommandLineRunner打印args

MyApplicationRunner:

@Overridepublic void run(ApplicationArguments args) throwsException {   System.out.println("ApplicationRunner...run"); System.out.println(Arrays.asList(args.getSourceArgs()));}

MyCommandLineRunner:

@Overridepublic void run(String... args) throws Exception {   System.out.println("CommandLineRunner...run");   System.out.println(Arrays.asList(args));}

在运行时可以将配置信息加载进来

cf4df03461eb435bae1072ebac6bf4d6
2e6cae9fe68b45f58abde4902230ecfa

所以,这两个监听ApplicationRunner和CommandLineRunner是一样的。

3.4、配置MyApplicationContextInitializer

在模块中增加 META-INF/spring.factories 配置文件

org.springframework.context.ApplicationContextInitializer=com.itheima.listener.listener.MyApplicationContextInitializer
a1d7ddf06e5443b4894a5aadc0a1507c

3.5、配置MySpringApplicationRunListener

修改 META-INF/spring.factories 配置文件

MyCommandLineRunner:@Overridepublic void run(String... args) throws Exception {   System.out.println("CommandLineRunner...run");   System.out.println(Arrays.asList(args));}
b8fad1a9dc0d4fb69a70b919d2b33bac

运行提示:没有MySpringApplicationRunListener的init方法

所以需要查看SpringApplicationRunListener这个接口的实现类

43cd36ea8dd44eb09704bd6175444aaf

需要SpringApplication和String[]两个参数进行构造,因此修改MySpringApplicationRunListener实现类,增加构造方法

public MySpringApplicationRunListener(SpringApplicationapplication, String[] args) {}

并且去掉@Component注解

27f8dc8ef10c485896c4e6142e5531e8

此时启动正常。

4a51cdf8e7d44558bd38836554fec098

ApplicationStartedEvent继承自SpringApplicationEvent

SpringApplicationEvent继承自ApplicationEvent

ApplicationEvent继承自EventObject

证明:Springboot里的监听事件是对java监听事件的一个封装

3.6、SpringBoot运行流程分析

c4f4d72daec94180bfcba93346fce977

四、SpringBoot监控

SpringBoot自带监控功能Actuator,可以帮助实现对程序内部运行情况监控,比如监控状况、Bean加载情况、配置属性、日志信息等。

4.1、创建模块

需要勾选web和ops下的SpringBootActuator

坐标自动引入

   org.springframework.boot   spring-boot-starter-actuator   org.springframework.boot   spring-boot-starter-web
1b2359f8746e4b2eb1ba38796313a80c

4.2、查看info

info获取的是配置文件以info开头的信息:

修改properties

info.name=zhangsaninfo.age=20
223f853ced224dc9ae40d56b277e6d82

4.3、查看所有信息

未开启所有明细状态:

07bf2a67ab464d5cba70e2e0490ab2d3

修改properties

management.endpoint.health.show-details=always

开启所有明细状态:

d5cfdf6162544865a393d7c1c047dff0

4.4、暴露所有的信息

修改properties:

management.endpoints.web.exposure.include=*
e1ae2a6167204e43938d3f99d1d3a8c7
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值