SpringBoot系列:SpringBootApplication详解

软件环境

  • Spring Boot 2.0.4.RELEASE
  • JDK 1.8.0_181
  • Maven 3.5.4
  • IntelliJ IDEA 2018.1.6

疑问

快速入门中,我们只需要启动main方法,整个spring容器就可以正常运行,这是如何做到的呢?

查看SpringBootApplication这个注解源码,发现是一个复合注解,包括@ComponentScan,和@SpringBootConfiguration,@EnableAutoConfiguration



package org.springframework.boot.autoconfigure;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.boot.SpringBootConfiguration;
import org.springframework.boot.context.TypeExcludeFilter;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.FilterType;
import org.springframework.context.annotation.ComponentScan.Filter;
import org.springframework.core.annotation.AliasFor;

@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,
        attribute = "exclude"
    )
    Class<?>[] exclude() default {};

    @AliasFor(
        annotation = EnableAutoConfiguration.class,
        attribute = "excludeName"
    )
    String[] excludeName() default {};

    @AliasFor(
        annotation = ComponentScan.class,
        attribute = "basePackages"
    )
    String[] scanBasePackages() default {};

    @AliasFor(
        annotation = ComponentScan.class,
        attribute = "basePackageClasses"
    )
    Class<?>[] scanBasePackageClasses() default {};
}

@SpringBootConfiguration

说明

@SpringBootConfiguration继承自@Configuration,二者功能也一致,标注当前类是配置类,并会将当前类内声明的一个或多个以@Bean注解标记的方法的实例纳入到srping容器中,并且实例名就是方法名。


package com.itxiaoer.demo.config;

import org.springframework.boot.SpringBootConfiguration;
import org.springframework.context.annotation.Bean;

import java.util.HashMap;
import java.util.Map;

/**
 * @author : liuyk
 */
@SpringBootConfiguration
public class BootConfiguration {


    /**
     * 代表在spring容器中创建了一个名字为userMap的对象,值为该方法的返回值。
     *
     * @return map对象
     */
    @Bean
    public Map<String, String> userMap() {
        Map<String, String> map = new HashMap<>(2);
        map.put("id", "id");
        map.put("name", "name");
        return map;
    }
}


package com.itxiaoer.demo;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;

import java.util.Map;

/**
 * @author : liuyk
 */
@SpringBootApplication
public class DemoSpringBootSpringBootConfigurationApplication {

    public static void main(String[] args) {
        ConfigurableApplicationContext applicationContext = SpringApplication.run(DemoSpringBootSpringBootConfigurationApplication.class, args);

        Map<String, Object> userMap = (Map<String, Object>) applicationContext.getBean("userMap");
        userMap.forEach((k, v) -> System.out.println(k + "=" + v));

    }
}

@ComponentScan

  • 有以下目录结构程序
---- package1
-------- Package1.java
-------- Package1Application.java
---- package2
-------- Package2.java
-------- Package2Application.java
---- package3
-------- Package3.java
-------- Package3Application.java
---- Application.java
  • Application.java
package com.itxiaoer;

import com.itxiaoer.package1.Package1;
import com.itxiaoer.package2.Package2;
import org.springframework.boot.SpringApplication;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.ComponentScan;

/**
 * @author : liuyk
 */
@ComponentScan
public class Application {

    public static void main(String[] args) {
        ConfigurableApplicationContext context = SpringApplication.run(Application.class, args);

        Package2 package2 = context.getBean(Package2.class);
        System.out.println("package2 = " + package2);


        Package1 package1 = context.getBean(Package1.class);
        System.out.println("package1 = " + package1);

        // 执行结果,都有值说明都扫描到了
//        package2 = com.itxiaoer.package2.Package2@750fe12e
//        package1 = com.itxiaoer.package1.Package1@f8908f6
        
    }
}


  • Package1Application.java

package com.itxiaoer.package1;

import com.itxiaoer.package2.Package2;
import org.springframework.boot.SpringApplication;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.ComponentScan;

/**
 * @author : liuyk
 */
@ComponentScan
public class Package1Application {

    public static void main(String[] args) {
        ConfigurableApplicationContext context = SpringApplication.run(Package1Application.class, args);

        Package1 package1 = context.getBean(Package1.class);
        System.out.println("package1 = " + package1);

        Package2 package2 = context.getBean(Package2.class);
        System.out.println("package2 = " + package2);
        
        // 执行结果 package1对象扫描到了,package2没有扫描到
        // package1 = com.itxiaoer.package1.Package1@34cdeda2
        //  No qualifying bean of type 'com.itxiaoer.package2.Package2' available

    }

}

  • Package2Application.java

package com.itxiaoer.package2;

import com.itxiaoer.package1.Package1;
import org.springframework.boot.SpringApplication;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.ComponentScan;

/**
 * @author : liuyk
 */
@ComponentScan
public class Package2Application {

    public static void main(String[] args) {
        ConfigurableApplicationContext context = SpringApplication.run(Package2Application.class, args);

        Package2 package2 = context.getBean(Package2.class);
        System.out.println("package2 = " + package2);


        Package1 package1 = context.getBean(Package1.class);
        System.out.println("package1 = " + package1);

        // 执行结果 package1对象扫描到了,package2没有扫描到
        // package2 = com.itxiaoer.package2.Package2@305a0c5f
        //  No qualifying bean of type 'com.itxiaoer.package1.Package1' available

    }
}

  • Package3Application.java

package com.itxiaoer.package3;

import com.itxiaoer.package1.Package1;
import com.itxiaoer.package2.Package2;
import org.springframework.boot.SpringApplication;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.ComponentScan;

/**
 * @author : liuyk
 */
@ComponentScan("com.itxiaoer.package1,com.itxiaoer.package2")
public class Package3Application {

    public static void main(String[] args) {
        ConfigurableApplicationContext context = SpringApplication.run(Package3Application.class, args);

        Package1 package1 = context.getBean(Package1.class);
        System.out.println("package1 = " + package1);


        Package2 package2 = context.getBean(Package2.class);
        System.out.println("package2 = " + package2);


        Package3 package3 = context.getBean(Package3.class);
        System.out.println("package3 = " + package3);

        // 执行结果 指定扫描的包都扫描到了,没有指定的包无法扫描
//        package1 = com.itxiaoer.package1.Package1@1ecee32c
//        package2 = com.itxiaoer.package2.Package2@4535b6d5
//        No qualifying bean of type 'com.itxiaoer.package3.Package3' available

    }
}

总结

  • @ComponentScan默认只当前包以及子包下被@Component,@Controller,@RestController,@Service,@Repository等标记的类
  • @ComponentScan若想当前包以及子包以外的包,需要显示的指定
  • @ComponentScan和以前的<context:component-scan/>(以前使用在xml中使用的标签,用来扫描包配置的平行支持)
  • @ComponentScan若指定了包名,那么就只会扫描指定的包以及指定包的子包

@EnableAutoConfiguration