SpringBoot自动装配原理学习与实战运用

一、本文概览

我们知道SpringBoot就是框架的框架,它解决了Spring在开发过程中繁琐的配置问题。例如在引入web、aop、data、cache等等场景,以往我们使用Spring时,会需要向容器中手动配置DispatchServlet、 AspectJAutoProxyingConfiguration等等配置类,而使用SpringBoot框架后,只需要引入spring-boot-starter-xxx的jar包,即可自动完成相关场景的配置。
这项技术SpringBoot是如何帮助我们实现的呢?本篇文章就来详细聊聊其中的技术细节。

二、了解自动装配

1、什么是自动装配

首先我们通过一小段代码来了解一下使用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容器中,这部分工作由框架给实现了。

2、SpringBoot提供的自动装配

如果要了解SpringBoot为我们提供了哪些自动装配,我们可以通过两种途径去了解:

  • 查看spring-boot-autoconfigure jar包下的模块
  • 通过官网了解:https://docs.spring.io/spring-boot/docs/2.7.10/reference/html/using.html#using.build-systems.starters

SpringBoot自动装配原理学习与实战运用_第1张图片SpringBoot自动装配原理学习与实战运用_第2张图片
了解有哪些自动装配后,也需要了解需要配置的相关信息,例如配置DataSource数据源时,虽然框架替我们配置Bean,但是Bean的一些必要信息也是需要我们提供的,配置哪些信息以及如何配置我们也有两种方式来了解:

  • 通过spring-boot-starter-autoconfigure的xxAutoConfig了解
  • 通过官网了解 https://docs.spring.io/spring-boot/docs/2.7.10/reference/html/application-properties.html#appendix.application-properties

3、定制化配置

SpringBoot提供的自动装配已经满足我们绝大部分业务场景,如果还需要一些自定义内容,我们可以自己定义自动装配jar包,然后在项目中引用,至于怎么操作,我们通过下面章节的学习后即可学会如何定义开发。

三、底层注解以及API

1、底层注解

在了解自动装配的原理前,我们需要了解学习一些底层的注解。
SpringBoot自动装配原理学习与实战运用_第3张图片

2、添加组件

往Spring容器中添加组件的注解。

  • @Bean 标注在方法上
  • @Configuration 标注在类上,表明该类是一个配置类
  • @Component 标注在类上,表明该类是一个普通组件
  • @Controller 标注在类上,表明该类是控制层的Bean
  • @Server 标注在类上,表明该类是服务层的Bean
  • @Repository 标注在类上,表明该类是DAO层的Bean

每个注解的共同作用都是向容器中注册Bean,但又区分不同意义的Bean,所以这些注解也有一个特殊的名字-模式注解。

3、配置绑定

与yml、properties文件的一些配置绑定的Bean

我们可以通过@ConfigurationProperties 与 @Component 组合达到配置绑定的目的;也可以通过@EnableConfigurationProperties与@ConfigurationProperties/进行配置绑定。
下面我们来看下demo:

  • 通过@ConfigurationProperties 与 @Component
@Data
@ToString
@Component
@ConfigurationProperties(prefix = "person")
public class Person {
    private String username;
    private String password;
}
  • @EnableConfigurationProperties
@Configuration
@EnableConfigurationProperties(UserProperties.class)
public class AppConfiguration {
}
@ConfigurationProperties(prefix = "user")
@Data
@ToString
public class UserProperties {
    private String username;
    private String password;
}

测试一下:
image.png

4、按需加载

我们通过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进行自动装配而无需我们配置。

第一步:新建一个工程,用来编写自定义组件。

目录结构如下图所示:
SpringBoot自动装配原理学习与实战运用_第4张图片

  • WhiteList代码
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 "";
}
  • WhiteListJudgeAspect
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;
}

第三步:实现自动装配

  • 配置类实现-WhiteListAutoConfig
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();
    }
}
  • 在类路径下配置META-INF/spring.factories,这样SpringBoot框架就能扫描到我们自定义的配置类
org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.markus.middleware.whitelist.config.WhiteListAutoConfig

第四步:业务方接入

  • 引入jar包文件
<dependency>
    <groupId>com.markusgroupId>
    <artifactId>middleware-whitelistartifactId>
    <version>0.0.1-SNAPSHOTversion>
dependency>
  • 配置白名单信息
whitelist:
  users:
    - markuszhang
    - luna
  • 在业务类上加入@WhiteList注解
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;
    }

}

第五步:测试验证

属于白名单配置的用户:
image.png
image.png
不属于白名单配置的用户:
image.png

五、本文总结

通过本文的叙述,我们掌握了如下内容:

  • 什么是自动装配
  • SpringBoot已经提供的自动装配类
  • 底层的注解和API内容学习
  • 以及掌握上述内容后,如何自定义开发一套接入SpringBoot的组件实现自动装配

你可能感兴趣的:(《spring,源码分析》,spring,boot,学习,java)