SpringBoot高级 1

本文涉及的产品
Redis 开源版,标准版 2GB
推荐场景:
搭建游戏排行榜
简介: SpringBoot高级

1. SpringBoot 原理分析

1.1 Condition

Condition 是在Spring 4.0 增加的条件判断功能,通过这个可以功能可以实现选择性的创建 Bean 操作。

使用时需要实现此接口进行匹配.通过注解Conditional加以判断.

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

案例:需求

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

  1. 导入Jedis坐标后,加载该Bean,没导入,则不加载
  2. 新建maven工程,导入依赖
 <?xml version="1.0" encoding="UTF-8"?>
 <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
          xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
     <modelVersion>4.0.0</modelVersion>
     <parent>
         <groupId>org.springframework.boot</groupId>
         <artifactId>spring-boot-starter-parent</artifactId>
         <version>2.3.3.RELEASE</version>
         <relativePath/> <!-- lookup parent from repository -->
     </parent>
     <groupId>com.example</groupId>
     <artifactId>springboot-day02</artifactId>
     <version>0.0.1-SNAPSHOT</version>
     <name>springboot-day02</name>
     <description>Demo project for Spring Boot</description>
     <properties>
         <java.version>1.8</java.version>
     </properties>
     <dependencies>
         <dependency>
             <groupId>org.springframework.boot</groupId>
             <artifactId>spring-boot-starter-web</artifactId>
         </dependency>
         <dependency>
             <groupId>org.springframework.boot</groupId>
             <artifactId>spring-boot-starter-data-redis</artifactId>
         </dependency>
         <dependency>
             <groupId>redis.clients</groupId>
             <artifactId>jedis</artifactId>
         </dependency>
    <dependency>
             <groupId>com.alibaba</groupId>
             <artifactId>fastjson</artifactId>
             <version>1.2.4</version>
         </dependency>
         <dependency>
             <groupId>org.springframework.boot</groupId>
             <artifactId>spring-boot-starter-test</artifactId>
             <scope>test</scope>
             <exclusions>
                 <exclusion>
                     <groupId>org.junit.vintage</groupId>
                     <artifactId>junit-vintage-engine</artifactId>
                 </exclusion>
             </exclusions>
         </dependency>
     </dependencies>
     <build>
         <plugins>
             <plugin>
                 <groupId>org.springframework.boot</groupId>
                 <artifactId>spring-boot-maven-plugin</artifactId>
             </plugin>
         </plugins>
     </build>
 </project>

编写SpringApplication

 package com.example.springbootday02;
 import com.example.springbootday02.domain.User;
 import org.springframework.boot.SpringApplication;
 import org.springframework.boot.autoconfigure.SpringBootApplication;
 import org.springframework.context.ConfigurableApplicationContext;
 import org.springframework.data.redis.core.RedisTemplate;
 @SpringBootApplication
 public class SpringbootDay02Application {
     public static void main(String[] args) {
         ConfigurableApplicationContext run = SpringApplication.run(SpringbootDay02Application.class, args);
 //        RedisTemplate redisTemplate = (RedisTemplate) run.getBean("redisTemplate");
 //        System.out.println(redisTemplate);
         User user = (User) run.getBean("user");
         System.out.println(user);
     }
 }
  1. 编写ClassCondition
 package com.example.springbootday02.condition;
 import org.springframework.context.annotation.Condition;
 import org.springframework.context.annotation.ConditionContext;
 import org.springframework.core.type.AnnotatedTypeMetadata;
 import redis.clients.jedis.Jedis;
 public class ClassCondition implements Condition {
     @Override
     public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata annotatedTypeMetadata) {
         try {
             Class.forName("redis.clients.jedis.Jedis");
         } catch (ClassNotFoundException e) {
             e.printStackTrace();
             return false;
         }
         return true;
     }
 }
  1. 编写UserConfig类和User类
   package com.example.springbootday02.domain;
   public class User {
   }

@Conditional的使用

作用:根据条件,决定类是否加载到Spring Ioc容器中,在SpringBoot中有大量的运用

应用场景:在一些需要条件满足才是实例化的类中,使用此注解,我曾经在项目中需要根据不同的场景使用不同的mq中间件的时候使用过,在mq的实例化bean上,加上此注解,根据配置文件的不同,来决定这个bean是否加载至ioc容器中。

使用方法

实现Conditional接口, 实现matches方法,看类是否被加载.

package com.example.springbootday02.config;
import com.example.springbootday02.condition.ClassCondition;
import com.example.springbootday02.domain.User;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration;
@Configuration
public class UserConfig {
    @Bean("user")
    @Conditional(ClassCondition.class)
    public User getUser(){
        return new User();
    }
}

案例:需求

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

  1. 导入Jedis坐标后,加载该Bean,没导入,则不加载
  2. 将类的判断定义为动态的。判断哪个字节码文件存在可以动态指定。
  3. 定义注解MyConditionOnClass
   package com.example.springbootday02.condition;
   import org.springframework.context.annotation.Conditional;
import java.lang.annotation.*;
   @Target({ElementType.TYPE, ElementType.METHOD})
   @Retention(RetentionPolicy.RUNTIME)
   @Documented
   @Conditional(ClassCondition.class)
   public @interface MyConditionOnClass {
       String[] value();
   }
   package com.example.springbootday02.condition;
   import org.springframework.context.annotation.Condition;
   import org.springframework.context.annotation.ConditionContext;
   import org.springframework.core.type.AnnotatedTypeMetadata;
   import org.springframework.util.MultiValueMap;
   import java.util.Map;
   public class ClassCondition implements Condition {
       @Override
       public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata annotatedTypeMetadata) {
   //        try {
   //            Class.forName("redis.clients.jedis.Jedis");
   //        } catch (ClassNotFoundException e) {
   //            e.printStackTrace();
   //            return false;
   //        }
   //        return true;
           Map<String, Object> myConditionOnClass = annotatedTypeMetadata.getAnnotationAttributes(MyConditionOnClass.class.getName());
           try {
               String[] value = (String[]) myConditionOnClass.get("value");
               for (String classes : value){
                   Class.forName(classes);
               }
           } catch (ClassNotFoundException e) {
               e.printStackTrace();
               return false;
           }
           return true;
       }
   }
  1. UserConfig 使用 MyConditionOnClass注解
      package com.example.springbootday02.config;
      import com.example.springbootday02.condition.ClassCondition;
      import com.example.springbootday02.condition.MyConditionOnClass;
      import com.example.springbootday02.domain.User;
      import org.springframework.context.annotation.Bean;
      import org.springframework.context.annotation.Conditional;
      import org.springframework.context.annotation.Configuration;
      @Configuration
      public class UserConfig {
          @Bean("user")
      //    @Conditional(ClassCondition.class)
          @MyConditionOnClass("redis.clients.jedis.Jedis")
          public User getUser(){
              return new User();
          }
      }
  使用springboot提供的注解 实现条件判断
   @Bean("user2")
      @ConditionalOnProperty(name = "itoldlu",havingValue = "oldlu")
      public User getUser2(){
          return new User();
      }
   itoldlu=oldlu

1.2 Condition 小结

  1. 自定义条件:
  1. 定义条件类:自定义类实现Condition接口,重写 matches 方法,在 matches 方法中进行逻辑判断,返回 boolean值 。 matches 方法两个参数:
  1. context:上下文对象,可以获取属性值,获取类加载器,获取BeanFactory等。
  2. metadata:元数据对象,用于获取注解属性。
  1. 判断条件: 在初始化Bean时,使用 @Conditional(条件类.class)注解

SpringBoot 提供的常用条件注解:

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

1.3 切换内置web服务器

SpringBoot的web环境中默认使用tomcat作为内置服务器,其实SpringBoot提供了4中内置服务器供我们选择,我们可 以很方便的进行切换。

<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
            <exclusions>
                <exclusion>
                    <artifactId>spring-boot-starter-tomcat</artifactId>
                    <groupId>org.springframework.boot</groupId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jetty</artifactId>
        </dependency>

1.4 @Enable*注解

SpringBoot中提供了很多Enable开头的注解,这些注解都是用于动态启用某些功能的。而其底层原理是使用@Import注 解导入一些配置类,实现Bean的动态加载。

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

package com.example.springbootday02enable;
import com.example.springbootday02enableother.config.EnableUser;
import com.example.springbootday02enableother.config.UserConfig;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Import;
import org.springframework.stereotype.Component;
/**
 * @ComponentScan 扫描范围:当前引导类所在包及其子包
 *
 * com.itoldlu.springbootenable
 * com.itoldlu.config
 * //1.使用@ComponentScan扫描com.itoldlu.config包
 * //2.可以使用@Import注解,加载类。这些类都会被Spring创建,并放入IOC容器
 * //3.可以对Import注解进行封装。
 */
@SpringBootApplication
//@ComponentScan(basePackages="com.example.springbootday02enableother")
//@Import(UserConfig.class)
@EnableUser
public class SpringbootDay02EnableApplication {
    public static void main(String[] args) {
        ConfigurableApplicationContext run = SpringApplication.run(SpringbootDay02EnableApplication.class, args);
        Object user = run.getBean("user");
        System.out.println(user);
    }
}
package com.example.springbootday02enableother.config;
import org.springframework.context.annotation.Import;
import java.lang.annotation.*;
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(UserConfig.class)
public @interface EnableUser {
}
package com.example.springbootday02enableother.config;
import com.example.springbootday02enableother.domain.User;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class UserConfig {
    @Bean("user")
    public User getUser(){
        return new User();
    }
}
package com.example.springbootday02enableother.domain;
public class User {
}

1.5 @Import注解

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

  1. 导入Bean
  2. 导入配置类
  3. 导入 ImportSelector 实现类。一般用于加载配置文件中的类
  4. 导入 ImportBeanDefinitionRegistrar 实现类。
package com.example.springbootday02enable;
import com.example.springbootday02enableother.config.EnableUser;
import com.example.springbootday02enableother.config.MyImportBeanDefinitionRegistrar;
import com.example.springbootday02enableother.config.MyImportSelector;
import com.example.springbootday02enableother.config.UserConfig;
import com.example.springbootday02enableother.domain.User;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Import;
import org.springframework.stereotype.Component;
/**
 * @ComponentScan 扫描范围:当前引导类所在包及其子包
 *
 * com.itoldlu.springbootenable
 * com.itoldlu.config
 * //1.使用@ComponentScan扫描com.itoldlu.config包
 * //2.可以使用@Import注解,加载类。这些类都会被Spring创建,并放入IOC容器
 * //3.可以对Import注解进行封装。
 */
/**
 * Import4中用法:
 *  1. 导入Bean
 *  2. 导入配置类
 *  3. 导入ImportSelector的实现类。
 *  4. 导入ImportBeanDefinitionRegistrar实现类
 */
@SpringBootApplication
//@ComponentScan(basePackages="com.example.springbootday02enableother")
//@Import(UserConfig.class)
//@EnableUser
//@Import(User.class)
//@Import(UserConfig.class)
//@Import(MyImportSelector.class)
@Import(MyImportBeanDefinitionRegistrar.class)
public class SpringbootDay02EnableApplication {
    public static void main(String[] args) {
        ConfigurableApplicationContext run = SpringApplication.run(SpringbootDay02EnableApplication.class, args);
        Object user = run.getBean(User.class);
        System.out.println(user);
        Object user1 = run.getBean("user");
        System.out.println(user1);
    }
}
package com.example.springbootday02enableother.config;
import org.springframework.context.annotation.ImportSelector;
import org.springframework.core.type.AnnotationMetadata;
public class MyImportSelector implements ImportSelector {
    @Override
    public String[] selectImports(AnnotationMetadata annotationMetadata) {
        return new String[]{"com.example.springbootday02enableother.domain.User"};
    }
}
    package com.example.springbootday02enableother.config;
    import com.example.springbootday02enableother.domain.User;
    import org.springframework.beans.factory.support.AbstractBeanDefinition;
    import org.springframework.beans.factory.support.BeanDefinitionBuilder;
    import org.springframework.beans.factory.support.BeanDefinitionRegistry;
    import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
    import org.springframework.core.type.AnnotationMetadata;
    public class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
        @Override
        public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
            AbstractBeanDefinition beanDefinition = BeanDefinitionBuilder.rootBeanDefinition(User.class).getBeanDefinition();
            registry.registerBeanDefinition("user",beanDefinition);
        }
    }

1.6 面试题:@EnableAutoConfiguration注解

1.@EnableAutoConfiguration 注解内部使用 @Import(AutoConfigurationImportSelector.class)来加载配置类。(AutoConfigurationImportSelector实现类的目的就是扫描下面的配置文件)

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

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


目录
相关文章
|
消息中间件 缓存 监控
spring boot 高级篇
spring boot 高级篇
636 1
|
Java 容器 Spring
SpringBoot高级 3
SpringBoot高级
117 1
|
NoSQL Java Redis
SpringBoot高级 2
SpringBoot高级
189 1
|
Java 数据安全/隐私保护
SpringBoot - 优雅的实现【参数分组校验】高级进阶
SpringBoot - 优雅的实现【参数分组校验】高级进阶
460 0
|
Java API
SpringBoot【集成ElasticSearch 01】2种方式的高级客户端 RestHighLevelClient 使用(依赖+配置+客户端API测试源码)
SpringBoot【集成ElasticSearch 01】2种方式的高级客户端 RestHighLevelClient 使用(依赖+配置+客户端API测试源码)
592 1
|
安全 Java 容器
SpringBoot - 优雅的实现【业务校验】高级进阶
SpringBoot - 优雅的实现【业务校验】高级进阶
406 0
|
Java Spring
SpringBoot - 优雅的实现【自定义参数校验】高级进阶
SpringBoot - 优雅的实现【自定义参数校验】高级进阶
320 0
|
JSON 前端开发 Java
SpringBoot - 优雅的实现【参数校验】高级进阶
SpringBoot - 优雅的实现【参数校验】高级进阶
292 0
|
前端开发 Java 测试技术
SpringBoot测试——高级配置
SpringBoot测试——高级配置
208 0
|
消息中间件 SQL Java
spring boot Rabbit高级教程(三)
spring boot Rabbit高级教程
192 0