我们知道SpringBoot就是框架的框架,它解决了Spring在开发过程中繁琐的配置问题。例如在引入web、aop、data、cache等等场景,以往我们使用Spring时,会需要向容器中手动配置DispatchServlet、 AspectJAutoProxyingConfiguration等等配置类,而使用SpringBoot框架后,只需要引入spring-boot-starter-xxx的jar包,即可自动完成相关场景的配置。
这项技术SpringBoot是如何帮助我们实现的呢?本篇文章就来详细聊聊其中的技术细节。
首先我们通过一小段代码来了解一下使用SpringBoot完成自动装配的过程。
@SpringBootApplication
public class SpringbootAutoconfigureApplication {
public static void main(String[] args) {
SpringApplication.run(SpringbootAutoconfigureApplication.class, args);
}
}
<dependencies>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-devtoolsartifactId>
<scope>runtimescope>
<optional>trueoptional>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-configuration-processorartifactId>
<optional>trueoptional>
dependency>
<dependency>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
<optional>trueoptional>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-testartifactId>
<scope>testscope>
dependency>
dependencies>
我们创建一个SpringBoot项目,然后引入spring-boot-starter-web jar包,有关于web相关的配置类SpringBoot就会自动帮助我们配置好,我们通过启动类的代码来验证一下:
@SpringBootApplication
public class SpringbootAutoconfigureApplication {
public static void main(String[] args) {
ConfigurableApplicationContext context = SpringApplication.run(SpringbootAutoconfigureApplication.class, args);
String[] beanDefinitionNames = context.getBeanDefinitionNames();
for (String beanDefinitionName : beanDefinitionNames) {
System.out.println(beanDefinitionName);
}
}
}
...
// 请求处理的bean
dispatcherServlet
// 视图解析器
viewResolver
// 文件上传
multipartResolver
// 字符编码 防止乱码
characterEncodingFilter
...
// 还有很多配置的类,这里就略过了
通过上面一小段的demo,我们可以认知到自动装配就是无需我们在手动配置Bean到Spring容器中,这部分工作由框架给实现了。
如果要了解SpringBoot为我们提供了哪些自动装配,我们可以通过两种途径去了解:
了解有哪些自动装配后,也需要了解需要配置的相关信息,例如配置DataSource数据源时,虽然框架替我们配置Bean,但是Bean的一些必要信息也是需要我们提供的,配置哪些信息以及如何配置我们也有两种方式来了解:
SpringBoot提供的自动装配已经满足我们绝大部分业务场景,如果还需要一些自定义内容,我们可以自己定义自动装配jar包,然后在项目中引用,至于怎么操作,我们通过下面章节的学习后即可学会如何定义开发。
往Spring容器中添加组件的注解。
每个注解的共同作用都是向容器中注册Bean,但又区分不同意义的Bean,所以这些注解也有一个特殊的名字-模式注解。
与yml、properties文件的一些配置绑定的Bean
我们可以通过@ConfigurationProperties 与 @Component 组合达到配置绑定的目的;也可以通过@EnableConfigurationProperties与@ConfigurationProperties/进行配置绑定。
下面我们来看下demo:
@Data
@ToString
@Component
@ConfigurationProperties(prefix = "person")
public class Person {
private String username;
private String password;
}
@Configuration
@EnableConfigurationProperties(UserProperties.class)
public class AppConfiguration {
}
@ConfigurationProperties(prefix = "user")
@Data
@ToString
public class UserProperties {
private String username;
private String password;
}
我们通过spring-boot-autoconfigure jar包可以看到springboot帮助我们设置了了非常多的自动装配类,但是这些配置中并不是我们所有的都需要,只有在需要时才会被注册进Spring容器中。对于这部分的实现,SpringBoot是使用条件注解进行按需加载。例如AOP的自动配置类:
@AutoConfiguration
@ConditionalOnProperty(prefix = "spring.aop", name = "auto", havingValue = "true", matchIfMissing = true)
public class AopAutoConfiguration {
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(Advice.class)
static class AspectJAutoProxyingConfiguration {
@Configuration(proxyBeanMethods = false)
@EnableAspectJAutoProxy(proxyTargetClass = false)
@ConditionalOnProperty(prefix = "spring.aop", name = "proxy-target-class", havingValue = "false")
static class JdkDynamicAutoProxyConfiguration {
}
@Configuration(proxyBeanMethods = false)
@EnableAspectJAutoProxy(proxyTargetClass = true)
@ConditionalOnProperty(prefix = "spring.aop", name = "proxy-target-class", havingValue = "true",
matchIfMissing = true)
static class CglibAutoProxyConfiguration {
}
}
@Configuration(proxyBeanMethods = false)
@ConditionalOnMissingClass("org.aspectj.weaver.Advice")
@ConditionalOnProperty(prefix = "spring.aop", name = "proxy-target-class", havingValue = "true",
matchIfMissing = true)
static class ClassProxyingConfiguration {
@Bean
static BeanFactoryPostProcessor forceAutoProxyCreatorToUseClassProxying() {
return (beanFactory) -> {
if (beanFactory instanceof BeanDefinitionRegistry) {
BeanDefinitionRegistry registry = (BeanDefinitionRegistry) beanFactory;
AopConfigUtils.registerAutoProxyCreatorIfNecessary(registry);
AopConfigUtils.forceAutoProxyCreatorToUseClassProxying(registry);
}
};
}
}
}
下面介绍下出现在本配置类的注解以及没在本类出现其他常用条件注解:
注解 | 注解意义 |
---|---|
@AutoConfiguration | 它是一个组合注解,表明这是一个自动配置类,需要注册进IoC容器中,并在此基础上还可以指定在某个配置类注册前或者后进行注册。 |
@Configuration | 表明该类是一个配置类,它有一个参数proxyBeanMethods比较重要:当它为true时,它内部所有的bean注册方法在被任何地方调用的时候都是单例的;当它为false时,它内部的bean注册方法没被调用一次,都会产生一个新的对象。当明确该配置类的配置无依赖关系的时候,可以采用false,减少判断,加速容器的启动。 |
@ConditionalOnProperty | 条件注解,判断当spring的属性配置中有prefix配置,以及该配置中有name指定的配置,并且值为havingValue时,条件通过,当然也可以指定默认配置matchIfMissing |
@ConditionalOnExpression | 条件注解,符合SpEL表达式时条件通过 |
@ConditionalOnJava | 条件注解,比指定Java版本 高、相等、低 通过条件判断,高、相等、低也是通过配置 |
@ConditionalOnClass | 条件注解,当类路径中存在某个类文件时,条件通过 |
@ConditionalOnMissingClass | 条件注解,当类路径中不存在某个类文件时,条件通过 |
@Profile | 条件注解的一种,指定应用所处的环境可以注册当前Bean,例如dev、test、staging、prod |
@ConditionalOnWebApplication | 条件注解,指定Web应用为某一类型时进行注册,类型包括ANY、SERVLET、REACTIVE |
@ConditionalOnResource | 条件注解,指定类路径下有某一资源文件时,条件判断通过 |
@ConditionalOnMissingBean | 条件注解,指定容器中不包含有某一Bean实例时才会通过 |
@ConditionalOnBean | 条件注解,指定容器中包含某一Bean实例时才会通过 |
上述的注解对于我们了解自动装配的原理足够了。也正是通过上述的配置,SpringBoot实现了按需加载配置Bean的功能。
通过上面章节的叙述,我们大概知道自动装配的原理是通过spring-boot-autoconfigure进行的注册(框架帮助我我们配置)以及按需加载的原理是通过条件注解实现的。
下面我们也手动定义一个jar包,开发一些功能,然后接入SpringBoot,框架帮我们自动注册。
假设有这样一个需求,在web开发中,对某些接口进行白名单准入,我们可以通过自定义组件注册到容器当中对目标方法进行拦截,注册的动作通过SpringBoot进行自动装配而无需我们配置。
package com.markus.middleware.whitelist.annotation;
import java.lang.annotation.*;
/**
* @author: markus
* @date: 2023/4/9 2:59 PM
* @Description: 白名单注解
* @Blog: https://markuszhang.com
* It's my honor to share what I've learned with you!
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface WhiteList {
/**
* 白名单命中时,是否通过此方法,如果不准入,则返回responseJson 否则正常执行目标方法
*
* @return
*/
boolean passAfterHit() default true;
/**
* 不执行目标方法时返回的内容
*
* @return
*/
String responseJson() default "";
}
package com.markus.middleware.whitelist.aspect;
import com.markus.middleware.whitelist.annotation.WhiteList;
import com.markus.middleware.whitelist.config.WhiteListProperties;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.List;
/**
* @author: markus
* @date: 2023/4/9 3:03 PM
* @Description:
* @Blog: https://markuszhang.com
* It's my honor to share what I've learned with you!
*/
@Aspect
@Component
public class WhiteListJudgeAspect {
@Resource
private List<String> whitelist;
@Pointcut("@annotation(com.markus.middleware.whitelist.annotation.WhiteList)")
public void pointcut() {
}
// 逻辑写的非常简单,实际并不是这样
@Around(value = "pointcut()")
public Object proceed(ProceedingJoinPoint pjp) throws Throwable {
MethodSignature signature = (MethodSignature) pjp.getSignature();
Method method = pjp.getTarget().getClass().getMethod(signature.getName(), signature.getParameterTypes());
WhiteList whiteList = method.getAnnotation(WhiteList.class);
if (whiteList == null) {
return pjp.proceed();
}
Object[] args = pjp.getArgs();
if (args.length == 0) {
return pjp.proceed();
}
// 我们假定选择第一个参数就是用户名
if (whitelist.contains(args[0])) {
// 放行
return pjp.proceed();
} else {
return whiteList.responseJson();
}
}
}
也就是接入方需要在自己的项目中需要配置的内容:
package com.markus.middleware.whitelist.config;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import java.util.List;
/**
* @author: markus
* @date: 2023/4/9 3:08 PM
* @Description: 白名单配置绑定
* @Blog: https://markuszhang.com
* It's my honor to share what I've learned with you!
*/
@ConfigurationProperties(prefix = "whitelist")
@Data
public class WhiteListProperties {
private List<String> users;
}
package com.markus.middleware.whitelist.config;
import com.markus.middleware.whitelist.aspect.WhiteListJudgeAspect;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.List;
/**
* @author: markus
* @date: 2023/4/9 3:04 PM
* @Description: 白名单自动配置
* @Blog: https://markuszhang.com
* It's my honor to share what I've learned with you!
*/
@Configuration
@EnableConfigurationProperties(WhiteListProperties.class)
@ConditionalOnClass(WhiteListProperties.class)
public class WhiteListAutoConfig {
@Bean("whitelist")
@ConditionalOnMissingBean
public List<String> whitelist(WhiteListProperties whiteListProperties) {
return whiteListProperties.getUsers();
}
}
org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.markus.middleware.whitelist.config.WhiteListAutoConfig
<dependency>
<groupId>com.markusgroupId>
<artifactId>middleware-whitelistartifactId>
<version>0.0.1-SNAPSHOTversion>
dependency>
whitelist:
users:
- markuszhang
- luna
package com.example.springboot.autoconfigure.controller;
import com.example.springboot.autoconfigure.properties.Person;
import com.example.springboot.autoconfigure.properties.UserProperties;
import com.markus.middleware.whitelist.annotation.WhiteList;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
/**
* @author: markus
* @date: 2023/4/8 10:26 PM
* @Description:
* @Blog: https://markuszhang.com
* It's my honor to share what I've learned with you!
*/
@RestController
public class HelloController {
@RequestMapping("/properties")
public String properties() {
return "use @EnableConfigurationProperties " + userProperties + " and use @Component " + person;
}
@WhiteList(responseJson = "{\n" +
" \"code\":\"500\",\n" +
" \"msg\":\"非白名单可访问用户拦截\",\n" +
" \"data\":\"null\"\n" +
"}")
@RequestMapping("/whitelist/test")
public String whitelist(@RequestParam String userId) {
return "Hello SpringBoot User " + userId;
}
}
通过本文的叙述,我们掌握了如下内容: