前言
我们在用SpringBoot的时候,只需要依赖一个starter项目,无需配置,就能使用这个starter项目的Bean。使用过早期Spring的人都知道,我们要想使用一个Spring bean,必须在xml配置文件里定义这个Bean。后来有了注解,只需在类上加注解,然后配置Spring扫描包的范围,就能够创建这些Bean。那么SpringBoot是如何做到自动创建starter项目里的Bean的呢?下面来一探究竟。
第一个starter项目
新建一个普通的maven项目,注意,不是springboot项目。在这个项目里我们自定义一个springmvc的拦截器,模拟web接口鉴权。pom.xml配置如下:
<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>
<groupId>com.starter.demo</groupId>
<artifactId>auth-interceptor-starter</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>auth-interceptor</name>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-autoconfigure</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<scope>provided</scope>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>2.2.7.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>5.2.6.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.2.6.RELEASE</version>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>4.0.1</version>
<scope>provided</scope>
</dependency>
</dependencies>
</dependencyManagement>
</project>
然后创建一个拦截器类,代码如下:
package com.auth.interceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
public class AuthInterceptor extends HandlerInterceptorAdapter {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
System.out.println("----------拦截器鉴权-------------");
return true;
}
}
编写拦截器配置类:
package com.auth.interceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
@ComponentScan({"com.auth.interceptor"})
public class InterceptorConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(authInterceptor())
.addPathPatterns("/**");
}
@Bean
public AuthInterceptor authInterceptor() {
return new AuthInterceptor();
}
}
这个配置类除了配置拦截器之外,还有一个重要作用,就是定义spring扫描包的范围,所以加上了这个@ComponentScan注解。到此,starter项目就编写完了,其实还差一步,先卖个关子,后面再说。最后,给大家看下包结构:
创建一个用于测试的SpringBoot项目
就是一个常规的spring-boot-starter-web项目,pom.xml添加依赖:
<?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.2.7.RELEASE</version>
<relativePath/>
</parent>
<groupId>com.example</groupId>
<artifactId>starter-demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>starter-demo</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>com.starter.demo</groupId>
<artifactId>auth-interceptor-starter</artifactId>
<version>0.0.1-SNAPSHOT</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>
这里依赖了我们刚才编写的starter项目。然后编写一个controller用于测试,代码如下:
package com.example.demo.controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/test")
public class TestController {
@GetMapping("/1")
public String test() {
return "OK";
}
}
最后web项目的包结构如下:
启动web项目,请求接口,发现starter项目里的拦截器根本没起作用。这很正常,因为启动类在com.example.demo目录下面,所以springboot只会扫描com.example.demo及其子包,而拦截器在com.auth.interceptor这个包下面,所以根本就不会扫描它啊。我们在开发的时候,自己定义的包名跟第三方starter也是不一样的,那怎么让springboot扫描到starter项目里的bean啊?
扫描starter项目的入口
在starter项目的类加载路径下面添加META-INF/spring.factories配置文件。对应maven项目的这个路径:
spring.factories配置文件的内容如下:
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\com.auth.interceptor.InterceptorConfig
springboot在启动时会扫描并解析所有jar包中的META-INF/spring.factories.这里我们配置InterceptorConfig作为入口,而InterceptorConfig中加了@ComponentScan注解,这样,springboot就知道该扫描starter项目的哪些包了。
重新编译,再次运行,发现控制台打印了如下内容:
----------拦截器鉴权-------------
说明拦截器生效了!
Conditional控制bean的创建
spring提供了一个更通用的基于条件去创建bean的机制——使用@Conditional注解。@Conditional
根据满足的某一个特定条件创建一个特定 bean.比方说,当某一 JAR 包在一个类路径下
的时候,会自动配置一个或多个 bean ;或者只有当某个 bean 被创建后才会创建另外的bean.
总的来说,就是根据特定条件来控制 bean 的创建行为,这样我们可以利用这个特性进行一些
自动的配置。当然, @Conditional 注解有非常多的使用方式,这里我们仅仅示范@ConditionalOnProperty.在InterceptorConfig类上加上@ConditionalOnProperty注解:
该注解的意思是当配置属性auth.enable=true时才会创建InterceptorConfig bean.重新编译,测试发现拦截器没起作用。然后,我们在application.yml中加上如下配置:
再次测试,发现拦截器又生效了。