SpringBoot的相关知识

一、SpringBoot内置Tomcat的启动原理:

<dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-web</artifactId>
   <version>2.1.6.RELEASE</version>
</dependency>
@SpringBootApplication
public class MySpringbootTomcatStarter{
    public static void main(String[] args) {
        Long time=System.currentTimeMillis();
        SpringApplication.run(MySpringbootTomcatStarter.class);
        System.out.println("===应用启动耗时:"+(System.currentTimeMillis()-time)+"===");
    }
}

这里是main函数入口,SpringBootApplication注解和SpringApplication.run()方法是关键。

源码如下:

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration

@EnableAutoConfiguration

@ComponentScan(
    excludeFilters = {@Filter(
    type = FilterType.CUSTOM,
    classes = {TypeExcludeFilter.class}
), @Filter(
    type = FilterType.CUSTOM,
    classes = {AutoConfigurationExcludeFilter.class}
)}
)
public @interface SpringBootApplication {
    @AliasFor(
        annotation = EnableAutoConfiguration.class
    )
    Class<?>[] exclude() default {};
    @AliasFor(
        annotation = EnableAutoConfiguration.class
    )
    String[] excludeName() default {};
    @AliasFor(
        annotation = ComponentScan.class,
        attribute = "basePackages"
    )
    String[] scanBasePackages() default {};
    @AliasFor(
        annotation = ComponentScan.class,
        attribute = "basePackageClasses"
    )
    Class<?>[] scanBasePackageClasses() default {};
}

可以通过源码看到,在SpringBootApplication类上有一个重要的注解@EnableAutoConfiguration,它就是实现自动化配置的核心。当SpringBoot项目启动的时候,就会调用@EnableAutoConfiguration来进一步加载系统所需的一些配置信息,完成自动化配置。

@EnableAutoConfiguration的源码如下:

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage

@Import({AutoConfigurationImportSelector.class})

public @interface EnableAutoConfiguration {
    String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";
    Class<?>[] exclude() default {};
    String[] excludeName() default {};
}

可以看出,在EnableAutoConfiguration类中,它使用@Import注解来导入配置类AutoConfigurationImportSelector。

而且通过@Import注解可以导入三种类型配置类:
(1)直接导入配置类:@Import({xxxConfiguration.class})

(2)依据条件选择配置类:@Import({xxxSelector.class})

(3)动态注册 Bean:@Import({xxxRegistrar.class})

一般情况下,springboot提供的一些JAR包里面会带有文件META-INF/spring.factories,然后在Springboot启动的时候,根据启动阶段不同的需求,框架就会多次调用SpringFactoriesLoader加载相应的工厂配置信息。

使用了注解@EnableAutoConfiguration时,就会触发对SpringFactoriesLoader.loadFactoryNames()的调用。

SpringFactoriesLoader类是spring框架自己使用的内部工具类,本身被声明为 final,表示不可以被其他类继承。

SpringFactoriesLoader类的源码如下:

// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//
package org.springframework.core.io.support;
import java.io.IOException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Enumeration;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Map.Entry;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.core.annotation.AnnotationAwareOrderComparator;
import org.springframework.core.io.UrlResource;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.ConcurrentReferenceHashMap;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.util.ReflectionUtils;
import org.springframework.util.StringUtils;
/**
 * SpringFactoriesLoader#loadFactories设计用于加载和实例化指定类型的工厂,这些工厂类型的定义
 * 来自classpath中多个JAR包内常量FACTORIES_RESOURCE_LOCATION所指定的那些spring.factories文件。
 * spring.factories文件的格式必须是属性文件格式,每条属性的key必须是接口或者抽象类的全限定名,
 * 而属性值value是一个逗号分割的实现类的名称。
*/
public final class SpringFactoriesLoader {
    /*
    * 要加载的资源路径,该常量定义了该工具类要从每个jar包中提取的工厂类定义属性文件的相对路径
    * 在classpath中的多个JAR中,要扫描的工厂配置文件的在本JAR包中的路径。
    * 实际上,Springboot的每个 autoconfigure包都包含spring.factories这个配置文件。
    */
    public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";
    //日志
    private static final Log logger = LogFactory.getLog(SpringFactoriesLoader.class);
    private static final Map<ClassLoader, MultiValueMap<String, String>> cache = new ConcurrentReferenceHashMap();
    private SpringFactoriesLoader() {
    }
    /**
    * @param factoryClass 工厂所属接口/抽象类全限定名称
    * @param classLoader 所要使用的类加载器
    *                    
    * 该方法会读取classpath上所有的jar包中的所有  META-INF/spring.factories 属性文件,找出其中定义的匹配类型 factoryClass 的工厂类,
    * 然后创建每个工厂类的对象/实例,并返回这些工厂类对象/实例的列表
    */
    public static <T> List<T> loadFactories(Class<T> factoryClass, @Nullable ClassLoader classLoader) {
        Assert.notNull(factoryClass, "'factoryClass' must not be null");
        ClassLoader classLoaderToUse = classLoader;
        if (classLoader == null) {
            classLoaderToUse = SpringFactoriesLoader.class.getClassLoader();
        }
        //加载类型为factoryClass的工厂的名称,其实是一个个的全限定类名,使用指定的classloader:classLoaderToUse
        List<String> factoryNames = loadFactoryNames(factoryClass, classLoaderToUse);
        if (logger.isTraceEnabled()) {
            logger.trace("Loaded [" + factoryClass.getName() + "] names: " + factoryNames);
        }
        List<T> result = new ArrayList(factoryNames.size());
        Iterator var5 = factoryNames.iterator();
        // 实例化所加载的每个工厂类
        while(var5.hasNext()) {
            String factoryName = (String)var5.next();
            result.add(instantiateFactory(factoryName, factoryClass, classLoaderToUse));
        }
        //对工厂类进行排序
        AnnotationAwareOrderComparator.sort(result);
        return result;
    }
    /**
    * 
    * @param factoryClass  工厂所属接口/抽象类全限定名称
    * @param classLoader 类加载器
    * @return 
    * 
    * 该方法会读取classpath上所有的jar包中的所有  META-INF/spring.factories 属性文件,找出其中定义的匹配类型 factoryClass 的工厂类,
    * 然后并返回这些工厂类的名字列表,注意是包含包名的全限定名。
    */
    public static List<String> loadFactoryNames(Class<?> factoryClass, @Nullable ClassLoader classLoader) {
        String factoryClassName = factoryClass.getName();
        // 1. 使用指定的classloader扫描classpath上所有的JAR包中的文件META-INF/spring.factories,加载其中的多值工厂属性定义,使用多值Map的形式返回,
        // 2. 返回多值Map中key为factoryClassName的工厂名称列表,如果没有相应的entry,返回空列表而不是返回null
        return (List)loadSpringFactories(classLoader).getOrDefault(factoryClassName, Collections.emptyList());
    }
    /**
    * @param classLoader 类加载器
    *
    * 使用指定的classloader扫描classpath上所有的JAR包中的文件META-INF/spring.factories,加载其中的多值
    * 工厂属性定义,使用多值Map的形式返回
    **/
    private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
        MultiValueMap<String, String> result = (MultiValueMap)cache.get(classLoader);
        if (result != null) {
            return result;
        } else {
            try {
                // 扫描classpath上所有JAR中的文件META-INF/spring.factories
                Enumeration<URL> urls = classLoader != null ? classLoader.getResources("META-INF/spring.factories") : ClassLoader.getSystemResources("META-INF/spring.factories");
                LinkedMultiValueMap result = new LinkedMultiValueMap();
                while(urls.hasMoreElements()) {
                    // 找到的每个META-INF/spring.factories文件都是一个Properties文件,将其内容
                    // 加载到一个 Properties 对象然后处理其中的每个属性
                    URL url = (URL)urls.nextElement();
                    UrlResource resource = new UrlResource(url);
                    Properties properties = PropertiesLoaderUtils.loadProperties(resource);
                    Iterator var6 = properties.entrySet().iterator();
                    while(var6.hasNext()) {
                        Entry<?, ?> entry = (Entry)var6.next();
                        // 获取工厂类名称(接口或者抽象类的全限定名)
                        String factoryClassName = ((String)entry.getKey()).trim();
                        String[] var9 = StringUtils.commaDelimitedListToStringArray((String)entry.getValue());
                        int var10 = var9.length;
                        for(int var11 = 0; var11 < var10; ++var11) {
                            String factoryName = var9[var11];
                            result.add(factoryClassName, factoryName.trim());
                        }
                    }
                }
                cache.put(classLoader, result);
                return result;
            } catch (IOException var13) {
                throw new IllegalArgumentException("Unable to load factories from location [META-INF/spring.factories]", var13);
            }
        }
    }
    /**
    * @param instanceClassName 工厂实现类全限定名称
    * @param factoryClass 工厂所属接口/抽象类全限定名称
    * @param classLoader 所要使用的类加载器
    **/
    private static <T> T instantiateFactory(String instanceClassName, Class<T> factoryClass, ClassLoader classLoader) {
        try {
            Class<?> instanceClass = ClassUtils.forName(instanceClassName, classLoader);
            if (!factoryClass.isAssignableFrom(instanceClass)) {
                throw new IllegalArgumentException("Class [" + instanceClassName + "] is not assignable to [" + factoryClass.getName() + "]");
            } else {
                return ReflectionUtils.accessibleConstructor(instanceClass, new Class[0]).newInstance();
            }
        } catch (Throwable var4) {
            throw new IllegalArgumentException("Unable to instantiate factory class: " + factoryClass.getName(), var4);
        }
    }
}

根据源码分析,再结合实际中SpringBoot的应用,我们可以发现其优点:

      1.去除了大量的xml配置文件

      2.简化复杂的依赖管理

      3.配合各种starter使用,基本上可以做到自动化配置

      4.快速启动容器

      5. 配合Maven或Gradle等构件工具打成Jar包后,Java -jar 进行部署运行还是蛮简单的

二、springBoot中外部或本地jar包的导入:

实际项目中,可能存在,在maven项目中,由于种种原因如在maven中心仓库不存在所需的jar包、公司内部的jar包等,在这种情况下咱们需要引入第三方的jar包,这时有两种方法来处理:
一)、将jar包直接放到项目中:
也就是在resources中新建lib包,然后将jar包放入其中,同时在pom.xml文件中做如下配置,

<!--dependencies部分-->
<dependencies>
    <!--按如下方式引入每一个第三方的jar包,其中${project.basedir}指当前项目的根目录-->
    <dependency>
        <groupId>com.test</groupId>
        <artifactId>test</artifactId>
        <scope>system</scope>
        <version>1.0</version>
        <systemPath>${project.basedir}/src/main/resources/lib/test.jar</systemPath>
    </dependency>
</dependencies>
 
<!--如果是打jar包,则需在build的plugins中添加如下配置-->
<plugin>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-maven-plugin</artifactId>
    <configuration>
        <!--值为true是指打包时包含scope为system的第三方Jar包-->
        <includeSystemScope>true</includeSystemScope>
    </configuration>
</plugin>
 
<!--如果是打war包,则需在build的plugins中设置maven-war-plugin插件,否则外部依赖无法打进war包 -->
<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-war-plugin</artifactId>
    <configuration>
        <webResources>
            <resource>
                <directory>src/main/resources/lib</directory>
                <targetPath>WEB-INF/lib/</targetPath>
                <includes>
                    <include>**/*.jar</include>
                </includes>
            </resource>
        </webResources>
    </configuration>
</plugin>

二)、将jar包引入本地maven仓库
也就是将第三方jar包放入公司(若是公司的maven仓库,需在maven的setting.xml中配置相应参数)或个人的本地maven仓库中,然后在pom.xml文件中引用

<dependency>
    <groupId>com.test</groupId>
    <artifactId>test</artifactId>
    <version>1.0</version>
</dependency>

如果是用Idea将本地的jar包导入SpringBoot,请参照博客:详解

三、SpringBoot的几个核心注解:

Spring Boot为不同的Spring模块提供了许多依赖项。一些最常用的是:

spring-boot-starter-data-jpa
spring-boot-starter-security
spring-boot-starter-test
spring-boot-starter-web
spring-boot-starter-thymeleaf

一)、Spring相关6个注解

Spring Boot的有些注解也需要与Spring的注解搭配使用,在项目中与Spring Boot注解配合最为紧密的6个Spring基础框架的注解。分别是:

1、@Configuration

从Spring3.0,@Configuration用于定义配置类,可替换xml配置文件,被注解的类内部包含有一个或多个被@Bean注解的方法,这些方法将会被AnnotationConfigApplicationContext或AnnotationConfigWebApplicationContext类进行扫描,并用于构建bean定义,初始化Spring容器。

@Configuration
public class TaskAutoConfiguration {

    @Bean
    @Profile("biz-electrfence-controller")
    public BizElectrfenceControllerJob bizElectrfenceControllerJob() {
        return new BizElectrfenceControllerJob();
    }

    @Bean
    @Profile("biz-consume-1-datasync")
    public BizBikeElectrFenceTradeSyncJob bizBikeElectrFenceTradeSyncJob() {
        return new BizBikeElectrFenceTradeSyncJob();
    }
}

2、@ComponentScan

web开发一定都有用过@Controller,@Service,@Repository注解,查看其源码,他们中有一个共同的注解@Component,没错@ComponentScan注解默认就会装配标识了@Controller,@Service,@Repository,@Component注解的类到spring容器中。

@ComponentScan(value = "com.abacus.check.api")
public class CheckApiApplication {
    public static void main(String[] args) {
        SpringApplication.run(CheckApiApplication.class, args);
    }
}

@SpringBootApplication注解也包含了@ComponentScan注解,所以在使用中我们也可以通过@SpringBootApplication注解的scanBasePackages属性进行配置。

@SpringBootApplication(scanBasePackages = {"com.abacus.check.api", "com.abacus.check.service"})
public class CheckApiApplication {
    public static void main(String[] args) {
        SpringApplication.run(CheckApiApplication.class, args);
    }
}

3、@Conditional

@Conditional是Spring4新提供的注解,通过@Conditional注解可以根据代码中设置的条件装载不同的bean,在设置条件注解之前,先要把装载的bean类去实现Condition接口,然后对该实现接口的类设置是否装载的条件。Spring Boot注解中的@ConditionalOnProperty、@ConditionalOnBean等以@Conditional*开头的注解,都是通过集成了@Conditional来实现相应功能的。

4、@Import

通过导入的方式实现把实例加入springIOC容器中。可以在需要时将没有被Spring容器管理的类导入至Spring容器中。

//类定义
public class Square {}

public class Circular {}

//导入
@Import({Square.class,Circular.class})
@Configuration
public class MainConfig{}

5、@ImportResource

和@Import类似,区别就是@ImportResource导入的是配置文件。

@ImportResource("classpath:spring-redis.xml")      //导入xml配置

public class CheckApiApplication {
    public static void main(String[] args) {
        SpringApplication.run(CheckApiApplication.class, args);
    }
}

6、@Component

@Component是一个元注解,意思是可以注解其他类注解,如@Controller @Service @Repository。
带此注解的类被看作组件,当使用基于注解的配置和类路径扫描的时候,这些类就会被实例化。
其他类级别的注解也可以被认定为是一种特殊类型的组件,比如@Controller 控制器(注入服务)、@Service服务(注入dao)、@Repository dao(实现dao访问)。@Component泛指组件,当组件不好归类的时候,我们可以使用这个注解进行标注,作用就相当于 XML配置,<bean id="" class=""/>

二)、Spring Boot提供的核心注解
1、@SpringBootApplication

这个注解是Spring Boot最核心的注解,用在 Spring Boot的主类上,标识这是一个 Spring Boot 应用,用来开启 Spring Boot 的各项能力。实际上这个注解是
@Configuration,
@EnableAutoConfiguration,
@ComponentScan
三个注解的组合。由于这些注解一般都是一起使用,所以Spring Boot提供了一个统一的注解@SpringBootApplication。

@SpringBootApplication(exclude = {
        MongoAutoConfiguration.class,
        MongoDataAutoConfiguration.class,
        DataSourceAutoConfiguration.class,
        ValidationAutoConfiguration.class,
        MybatisAutoConfiguration.class,
        MailSenderAutoConfiguration.class,
})
public class API {
    public static void main(String[] args) {
        SpringApplication.run(API.class, args);
    }
}

2、@EnableAutoConfiguration

允许 Spring Boot 自动配置注解,开启这个注解之后,Spring Boot 就能根据当前类路径下的包或者类来配置 Spring Bean。

如:当前类路径下有 Mybatis 这个 JAR 包,MybatisAutoConfiguration 注解就能根据相关参数来配置 Mybatis 的各个 Spring Bean。

@EnableAutoConfiguration实现的关键在于引入了AutoConfigurationImportSelector,其核心逻辑为selectImports方法,逻辑大致如下:

从配置文件META-INF/spring.factories加载所有可能用到的自动配置类;

去重,并将exclude和excludeName属性携带的类排除;

过滤,将满足条件(@Conditional)的自动配置类返回;

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage//导入AutoConfigurationImportSelector的子类@Import({EnableAutoConfigurationImportSelector.class})
public @interface EnableAutoConfiguration {
    String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";

    Class<?>[] exclude() default {};

    String[] excludeName() default {};
}

3、@SpringBootConfiguration

这个注解就是 @Configuration 注解的变体,只是用来修饰是 Spring Boot 配置而已,或者可利于 Spring Boot 后续的扩展。

4、@ConditionalOnBean

@ConditionalOnBean(A.class)仅仅在当前上下文中存在A对象时,才会实例化一个Bean,也就是说只有当A.class 在spring的applicationContext中存在时,这个当前的bean才能够创建。

@Bean
//当前环境上下文存在DefaultMQProducer实例时,才能创建RocketMQProducerLifecycle这个Bean
@ConditionalOnBean(DefaultMQProducer.class)
public RocketMQProducerLifecycle rocketMQLifecycle() {
     return new RocketMQProducerLifecycle();
}

5、@ConditionalOnMissingBean

组合@Conditional注解,和@ConditionalOnBean注解相反,仅仅在当前上下文中不存在A对象时,才会实例化一个Bean。

@Bean
  //仅当当前环境上下文缺失RocketMQProducer对象时,才允许创建RocketMQProducer Bean对象
  @ConditionalOnMissingBean(RocketMQProducer.class)
  public RocketMQProducer mqProducer() {
      return new RocketMQProducer();
  }

6、@ConditionalOnClass

组合 @Conditional 注解,可以仅当某些类存在于classpath上时候才创建某个Bean。

@Bean
  //当classpath中存在类HealthIndicator时,才创建HealthIndicator Bean对象
  @ConditionalOnClass(HealthIndicator.class)
  public HealthIndicator rocketMQProducerHealthIndicator(Map<String, DefaultMQProducer> producers) {
      if (producers.size() == 1) {
          return new RocketMQProducerHealthIndicator(producers.values().iterator().next());
      }
  }

7、@ConditionalOnMissingClass

组合@Conditional注解,和@ConditionalOnMissingClass注解相反,当classpath中没有指定的 Class才开启配置。

8、@ConditionalOnWebApplication

组合@Conditional 注解,当前项目类型是 WEB 项目才开启配置。当前项目有以下 3 种类型:ANY(任何Web项目都匹配)、SERVLET(仅但基础的Servelet项目才会匹配)、REACTIVE(只有基于响应的web应用程序才匹配)。

9、@ConditionalOnNotWebApplication

组合@Conditional注解,和@ConditionalOnWebApplication 注解相反,当前项目类型不是 WEB 项目才开启配置。

10、@ConditionalOnProperty

组合 @Conditional 注解,当指定的属性有指定的值时才开启配置。具体操作是通过其两个属性name以及havingValue来实现的,其中name用来从application.properties中读取某个属性值,如果该值为空,则返回false;如果值不为空,则将该值与havingValue指定的值进行比较,如果一样则返回true;否则返回false。如果返回值为false,则该configuration不生效;为true则生效。

@Bean
 //匹配属性rocketmq.producer.enabled值是否为true
 @ConditionalOnProperty(value = "rocketmq.producer.enabled", havingValue = "true", matchIfMissing = true)
 public RocketMQProducer mqProducer() {
     return new RocketMQProducer();
 }

基本上常用的一些SpringBoot注解就像上述的这几样,今天就总结到这里,谢谢各位认真阅读。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值