概述

剖析@SpringBootApplication注解
首先分析springboot的启动注解@SpringBootApplication
@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
@Target(ElementType.TYPE) 注解的目标位置:接口、类、枚举。
@Retention(RetentionPolicy.RUNTIME) 注解会在class字节码文件中存在,在运行时可以通过反射获取到。
@Documented 用于生成javadoc,默认情况下,javadoc是不包括注解的. 但如果声明注解时指定了 @Documented, 则它会被 javadoc 之类的工具处理,所以注解类型信息也会被包括在生成的文档中。
@Inherited 作用:在类继承关系中,如果子类要继承父类的注解,那么要该注解必须被@Inherited修饰的注解。
除了以上常规的几个注解,剩下几个就是springboot的核心注解了。
@SpringBootApplication就是一个复合注解,包括@ComponentScan,和@SpringBootConfiguration,@EnableAutoConfiguration。
@SpringBootApplication注解原理
通过了解@SpringBootApplication,明白了它是一个复合注解。
通过在springboot项目中删除@SpringBootApplication,用下面三个代替,然后启动springboot:
@ComponentScan
@SpringBootConfiguration
@EnableAutoConfiguration
那么以上所有注解就只干一件事:把bean注册到spring ioc容器。
@SpringBootApplication就只干了一件事通过3种方式来实现:
1. @SpringBootConfiguration 通过@Configuration 与@Bean结合,注册到Spring ioc 容器。
2. @ComponentScan 通过范围扫描的方式,扫描特定注解类,将其注册到Spring ioc 容器。
3. @EnableAutoConfiguration 通过spring.factories的配置,来实现bean的注册到Spring ioc 容器。
1. 剖析@SpringBootConfiguration
从以上源码可以看出@SpringBootConfiguration其实就是一个@Configuration,说明了标注当前类是配置类。
(1) 什么是@Configuration注解,它有什么作用?
从Spring3.0开始,@Configuration用于定义配置类,可替换xml配置文件,被注解的类内部包含有一个或多个被@Bean注解的方法,这些方法将会被AnnotationConfigApplicationContext或AnnotationConfigWebApplicationContext类进行扫描,并用于构建bean定义,初始化Spring容器。
(2) 用@Configuration配置spring并加载spring容器
@Configuration标注在类上,@Configuation等价于spring的xml配置文件中的<Beans></Beans>
步骤1:先加入spring的依赖包
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.1.9.RELEASE</version>
</dependency>
步骤2:创建一个Configuration类
@Configuration
public class MyConfiguration {
public MyConfiguration() {
System.out.println("MyConfiguration容器启动初始化。。。");
}
}
以上代码,等价于以下xml配置文件中的<Beans></Beans>
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context" xmlns:jdbc="http://www.springframework.org/schema/jdbc"
xmlns:jee="http://www.springframework.org/schema/jee" xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:util="http://www.springframework.org/schema/util" xmlns:task="http://www.springframework.org/schema/task" xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd
http://www.springframework.org/schema/jdbc http://www.springframework.org/schema/jdbc/spring-jdbc-4.0.xsd
http://www.springframework.org/schema/jee http://www.springframework.org/schema/jee/spring-jee-4.0.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.0.xsd
http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-4.0.xsd
http://www.springframework.org/schema/task http://www.springframework.org/schema/task/spring-task-4.0.xsd" default-lazy-init="false">
</beans>
步骤3:加一个测试类
public class Main {
public static void main(String[] args) {
ApplicationContext context = new AnnotationConfigApplicationContext(MyConfiguration.class);
}
}
结果:
MyConfiguration容器启动初始化。。。
Process finished with exit code 0
(3)如何把一个对象,注册到Spring IoC 容器中
要把一个对象注册到Spring IoC 容器中,一般是用@Bean 注解来实现:
@Bean的作用:带有 @Bean 的注解方法将返回一个对象,该对象应该被注册为在Spring IoC 容器中。
步骤1:创建一个bean
public class UserBean {
private String username;
private String password;
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
@Override
public String toString() {
return "UserBean{" +
"username='" + username + '\'' +
", password='" + password + '\'' +
'}';
}
}
步骤2:把bean注解在ioc容器里面
@Configuration
public class MyConfiguration {
public MyConfiguration() {
System.out.println("MyConfiguration容器启动初始化。。。");
}
/**
* @Bean注解在返回实例的方法上,如果未通过@Bean指定bean的名称,则默认的Bean对象名与标注的方法名相同;
* 以下创建的对象名,和方法名一样,即userBean
*/
@Bean
public UserBean userBean(){
UserBean userBean= new UserBean();
userBean.setUsername("test");
userBean.setPassword("123456");
return userBean;
}
}
上面的代码将等同于下面的 XML 配置:
<beans>
<bean id="userBean" class="com.test.boot.annotation.bean.UserBean" />
</beans>
步骤3:加一个体验类
创建的bean对象,可以通过AnnotationConfigApplicationContext加载进spring ioc 容器中。
public static void main(String[] args) {
ApplicationContext context = new AnnotationConfigApplicationContext(MyConfiguration.class);
UserBean userBean=(UserBean)context.getBean("userBean");
System.out.println(userBean.toString());
}
结果:
MyConfiguration容器启动初始化。。。
UserBean{username='test', password='123456'}
Process finished with exit code 0
2. 剖析@ComponentScan注解
@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class), @Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
excludeFilters:过滤不需要扫描的类型。
@Filter 过滤注解
FilterType.CUSTOM 过滤类型为自定义规则,即指定特定的class
classes :过滤指定的class,即剔除了TypeExcludeFilter.class、AutoConfigurationExcludeFilter.class
从以上源码可以知道:
- @SpringBootApplication的源码包含了@ComponentScan。
因此,只要@SpringBootApplication注解的所在的包及其下级包,都会将class扫描到并装入spring ioc容器。 - 如果你自定义的定义一个Spring bean,不在@SpringBootApplication注解的所在的包及其下级包,
都必须手动加上@ComponentScan注解并指定那个bean所在的包。
(1)为什么要用@ComponentScan?它解決什么问题?
1. 为什么要用@ComponentScan ?
定义一个Spring bean 一般是在类上加上注解 @Service 或@Controller 或 @Component就可以,
但是,spring怎么知道有你这个bean的存在呢?
因此,我们必须告诉spring去哪里找这个bean类。
@ComponentScan就是用来告诉spring去哪里找bean类。
2. @componentscan的作用
作用:告诉Spring去扫描@componentscan指定包下所有的注解类,然后将扫描到的类装入spring bean容器。
例如:@ComponentScan("com.test.boot.scan"),就只能扫描com.test.boot.scan包下的注解类。
如果不写?就像@SpringBootApplication的@ComponentScan没有指定路径名?它去哪里找?
@SpringBootApplication注解的所在的包及其下级包,都会讲class扫描到并装入spring ioc容器。
(2)案例实战:体验@ComponentScan的作用
步骤1:在包名为com.test.boot.scan,新建一个ComponentScan测试类
package com.test.boot.scan;
import org.springframework.stereotype.Service;
@Service
public class TestComponentScan {
}
步骤2:在com.test.boot.app下面建个启动类
package com.test.boot.app;
import com.test.boot.scan.TestComponentScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.ComponentScan;
@SpringBootApplication
public class Application {
public static void main(String[] args) {
ConfigurableApplicationContext run = SpringApplication.run(Application.class, args);
TestComponentScan componentScan = run.getBean(TestComponentScan.class);
System.out.println(componentScan.toString());
}
}
启动报错:
seconds (JVM running for 3.941)
Exception in thread "main" org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'com.test.boot.scan.TestComponentScan' available
at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBean(DefaultListableBeanFactory.java:346)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBean(DefaultListableBeanFactory.java:337)
at org.springframework.context.support.AbstractApplicationContext.getBean(AbstractApplicationContext.java:1123)
at com.test.boot.app.Application.main(Application.java:14)
以上报错的意思是:找不到com.test.boot.scan.TestComponentScan这个bean,那怎么办呢?
这要加这行代码重新运行即可
- 手工指定包路径
@ComponentScan("com.test.boot.scan")
整体如下:
@SpringBootApplication
@ComponentScan("com.test.boot.scan")
public class Application {
public static void main(String[] args) {
ConfigurableApplicationContext run = SpringApplication.run(Application.class, args);
TestComponentScan componentScan = run.getBean(TestComponentScan.class);
System.out.println(componentScan.toString());
}
}
以上启动正常
3.剖析@EnableAutoConfiguration
@EnableAutoConfiguration是@SpringBootApplication中3大核心注解最重要的一个。
源码如下:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";
/**
* Exclude specific auto-configuration classes such that they will never be applied.
* @return the classes to exclude
*/
Class<?>[] exclude() default {};
/**
* Exclude specific auto-configuration class names such that they will never be
* applied.
* @return the class names to exclude
* @since 1.3.0
*/
String[] excludeName() default {};
}
其中最关键的是@Import(AutoConfigurationImportSelector.class),我们先来讲解@Import
(1)@Import有什么作用?
@Import作用: 将指定的类实例注入到spring IOC容器中。
(2)编码实现@Import例子
步骤1:创建一个bean
创建这个bean的目的是把它注入springioc容器中
public class UserBean {
}
步骤2:新建一个service
采用@Import来,将UserBean注入到spring ioc 容器中
@Component
@Import({UserBean.class})
public class UserService {
}
步骤3:启动类
public class Main {
public static void main(String[] args) {
ApplicationContext context = new AnnotationConfigApplicationContext(UserService.class);
UserService userService = context.getBean(UserService.class);
UserBean userBean=context.getBean(UserBean.class);
System.out.println(userService.toString());
System.out.println(userBean.toString());
}
}
结果:
com.test.boot.ioc.imports.UserService@52525845
com.test.boot.ioc.imports.UserBean@3b94d659
(3)spring的ImportSelector接口有什么作用?
从AutoConfigurationImportSelector源码,进入后,发现了6个核心接口,如下:
public class AutoConfigurationImportSelector implements DeferredImportSelector, BeanClassLoaderAware,ResourceLoaderAware, BeanFactoryAware, EnvironmentAware, Ordered
但是最核心的是DeferredImportSelector接口。最核心的!!!
从DeferredImportSelector接口的源码中,看出了它继承了ImportSelector,源码如下:
public interface DeferredImportSelector extends ImportSelector {
再看ImportSelector的源码
public interface ImportSelector {
/**
* Select and return the names of which class(es) should be imported based on
* the {@link AnnotationMetadata} of the importing @{@link Configuration} class.
*/
String[] selectImports(AnnotationMetadata importingClassMetadata);
}
从以上源码可以看出:
ImportSelector接口值定义了一个selectImports方法,它的作用收集需要将class注册到spring ioc容器里面。
ImportSelector接口一般和@Import一起使用,一般用@Import会引入ImportSelector实现类后,会把实现类中得返回class数组都注入到spring ioc 容器中。
(4)案例实战: 模仿@EnableAutoConfiguration注解,写一个@Enable*的开关注解
很多开关注解类,例如:@EnableAsync 、@EnableSwagger2、@EnableAutoConfiguration
@Enable代表的意思就是开启一项功能,起到了开关的作用。
这些开关注解类的原理是什么?
底层是用ImportSelector接口来实现的。
步骤1: 新建2个bean
``
public class UserBean {
}
public class RoleBean {
}
``
步骤2:自定义一个ImportSelector类,记得实现ImportSelector接口
通过ImportSelector的selectImports方法,返回2个calass
"com.test.boot.ioc.selector.UserBean"
"com.test.boot.ioc.selector.RoleBean"
目的:将收集到的2个class注册到spring ioc容器里面
public class UserImportSelector implements ImportSelector {
@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
return new String[]{"com.test.boot.ioc.selector.UserBean",
"com.test.boot.ioc.selector.RoleBean"};
}
}
步骤3:自定义一个开关类
一般采用@Import会引入ImportSelector实现类(UserImportSelector.class)后,
会把实现类中得返回class数组
new String[]{"com.test.boot.ioc.selector.UserBean",
"com.test.boot.ioc.selector.RoleBean"};
都注入到spring ioc 容器中。
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Target(ElementType.TYPE)
@Import(UserImportSelector.class)
public @interface EnableUserConfig {
}
步骤4:增加一个配置类,用于设置加入@EnableUserConfig
@EnableUserConfig
public class UserConfig {
}
步骤5:体验测试类
public class Main {
public static void main(String[] args) {
ApplicationContext context = new AnnotationConfigApplicationContext(UserConfig.class);
RoleBean roleBean = context.getBean(RoleBean.class);
UserBean userBean = context.getBean(UserBean.class);
System.out.println(userBean.toString());
System.out.println(roleBean.toString());
}
}
执行结果:
com.test.boot.ioc.selector.UserBean@6e2c9341
com.test.boot.ioc.selector.RoleBean@32464a14
网友评论