SpringBoot原理深入点滴 SpringBoot注解详解 (持续更新中)

文章目录

  • springboot内嵌服务器
  • 微服务的思想与挑战
  • SpringBoot注解大全
    • @Configuration
    • @Bean
    • @Import
    • @Conditional
    • @ConditionalOnBean
    • @ConditionalOnMissingBean
    • @ConfigurationProperties
    • @SpringBootApplication
    • @SpringBootConfiguration
    • @ComponentScan
    • @EnableAutoConfiguration
    • @AutoConfigurationPackage
    • @ConditionalOnClass(A.class)
    • @PathVariable
    • @RequestHeader
    • @ModelAttribute
    • @RequestParam
    • @MatrixVariable
    • @RequestBody
    • @CookieValue
  • SpringBoot核心源码
    • 兼容不规范的文件上传解析器名称
    • 自动配置执行流程小结
      • 举例:修改默认的启动图片
      • 举例:修改默认的静态资源路径
    • 静态资源访问底层原理
    • Rest映射及源码解析
    • 请求映射的原理
  • Controller的那些特殊入参 Session 、 ServletRequest等
    • Servlet API
    • 复杂参数
    • 自定义对象参数

servlet编程 reactor响应式编程

springboot内嵌服务器

springboot内嵌web服务器undertow,完全采用java语言开发,支持阻塞和非阻塞io

对于高并发系统,undertow的性能表现更优

不需要tomcat

简化配置、简化部署(打jar,打war 只需要maven clean package即可)

微服务的思想与挑战

微服务 去中心化 一个大应用拆成很多小服务 各自单独运行

微服务的困难:远程调用、服务发现、负载均衡、服务容错、配置管理、服务监控、链路追踪、日志管理、任务调度

云原生

上云的困难:服务自愈、弹性伸缩、服务隔离、自动化部署、灰度发布、流量治理

springboot依赖里有很多依赖 名称为XXX starter

这种starter 就能把相关的依赖全部导入

springboot版本仲裁 我们在写依赖时可以不写版本号。但也不是全部,引入非版本仲裁的依赖,必须写版本号

默认主程序所在的包及其所在包的子包都可以被扫到。

各种配置拥有默认值:

​ 默认配置最终都是映射到MultipartProperties

​ 配置文件的值最终会绑定到每个类上,这个类会在容器中创建对象

SpringBoot注解大全

@Configuration

以前配置需要在bean.xml中手动配置,而有了@Configuration,只需要在类上注明,+@Bean注解,即可实现以前bean.xml和的效果。方法名就是bean的id 当然@Bean(“name”)也可以

如果@Configuration(proxyBeanMethods = true)代表对象调用方法,SpringBoot总会检查这个组件是否在容器中已经有了,如果有就不新创,保证组件单实例。

而proxyBeanMethods为false时,SpringBoot则不会去代理创建对象,而是只要被调用,就直接创建,此时组件就不是单实例了。

false用来解决组件依赖的情形。

@Configuration(proxyBeanMethods = true) 称为自动配置的全模式

@Configuration(proxyBeanMethods = false) 称为自动配置的轻量级模式 不会去检查容器中是否有重复对象,运行也会更快

两种模式的区分主要是取决于组件是否相互依赖

@Bean

@Bean默认也是单实例的 配置类本身也是组件

外部无论从容器中获取多少次同一个bean,获取到的都是同一个bean

@Import

@Import(A.class)同样的作用,导入A组件

@Conditional

@Conditional 条件装配 当满足指定条件时,才进行组件注入

@ConditionalOnBean

@ConditionalOnBean(name = “fill”) 当容器中有名称为fill的组件时,才注入

@ConditionalOnMissingBean

@ConditionalOnMissingBean(name = “fill”) 当容器中没有名称为fill的组件时,才注入

@ConfigurationProperties

@ConfigurationProperties(prefix=“xxx”)
SpringBoot配置文件application.yml或者application.properties中前缀为xxx的配置将会被自动注入给该配置类中名称相同的对象
SpringBoot原理深入点滴 SpringBoot注解详解 (持续更新中)_第1张图片
SpringBoot原理深入点滴 SpringBoot注解详解 (持续更新中)_第2张图片
SpringBoot原理深入点滴 SpringBoot注解详解 (持续更新中)_第3张图片

@SpringBootApplication

包含:
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan

@SpringBootConfiguration

内部为@Configuration 代表是一个配置类

@ComponentScan

指定扫描哪些包

@EnableAutoConfiguration

内部包含@AutoConfigurationPackage

@AutoConfigurationPackage

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import({Registrar.class})
public @interface AutoConfigurationPackage {

主要是一个@Import({Registrar.class})
利用Registrar给容器中导入一系列组件

static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {
        Registrar() {
        }

        public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
            AutoConfigurationPackages.register(registry, (String[])(new AutoConfigurationPackages.PackageImports(metadata)).getPackageNames().toArray(new String[0]));
        }

        public Set<Object> determineImports(AnnotationMetadata metadata) {
            return Collections.singleton(new AutoConfigurationPackages.PackageImports(metadata));
        }
    }

spring-boot-autoconfigure-2.3.4.RELEASE.jar/META-INF/spring.factories
此文件写死了SpringBoot启动时要加载的全部配置类

其实就是导入指定包下的所有组件。如果不指定就是Application所在包及其子包下的所有组件。
虽然127个场景的所有自动配置启动的时候默认全部加载,但最终其实并不是全部加载,而是按需配置。
如何实现的呢?得益于@ConditionalOnClass(A.class)

@ConditionalOnClass(A.class)

当可以检测到类A时,才装配。
比如我没有类A,(pom文件没有引入),那么此时标着该注解的组件,其实是不会加载的。

@PathVariable

注解源码:

@Target({ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface PathVariable {
    @AliasFor("name")
    String value() default "";

    @AliasFor("value")
    String name() default "";

    boolean required() default true;
}

@RequestHeader

使用举例:

当从请求头中取单个元素时,在注解上写明元素名称,如果不写,则使用Map默认接受全部请求头信息。

注解源码:

@Target({ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RequestHeader {
    @AliasFor("name")
    String value() default "";

    @AliasFor("value")
    String name() default "";

    boolean required() default true;

    String defaultValue() default "\n\t\t\n\t\t\n\ue000\ue001\ue002\n\t\t\t\t\n";
}

@ModelAttribute

@Target({ElementType.PARAMETER, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ModelAttribute {
    @AliasFor("name")
    String value() default "";

    @AliasFor("value")
    String name() default "";

    boolean binding() default true;
}

@RequestParam

使用: 入参前面注释做好映射名即可
比如:@RequestParam(“id”)

@Target({ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RequestParam {
    @AliasFor("name")
    String value() default "";

    @AliasFor("value")
    String name() default "";

    boolean required() default true;

    String defaultValue() default "\n\t\t\n\t\t\n\ue000\ue001\ue002\n\t\t\t\t\n";
}

@MatrixVariable

矩阵入参

@Target({ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MatrixVariable {
    @AliasFor("name")
    String value() default "";

    @AliasFor("value")
    String name() default "";

    String pathVar() default "\n\t\t\n\t\t\n\ue000\ue001\ue002\n\t\t\t\t\n";

    boolean required() default true;

    String defaultValue()
      default "\n\t\t\n\t\t\n\ue000\ue001\ue002\n\t\t\t\t\n";
}

@RequestBody

直接加载入参前即可
注:加在包装类前,且请求的json串的变量名与包装类的变量名一一对应
才能传参成功

@Target({ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RequestBody {
    boolean required() default true;
}

@CookieValue

使用举例:

当从web页面发送请求时,才有cookie

cookie是浏览器缓存,一般是存在session里的。

源码:

@Target({ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface CookieValue {
    @AliasFor("name")
    String value() default "";

    @AliasFor("value")
    String name() default "";

    boolean required() default true;

    String defaultValue() default "\n\t\t\n\t\t\n\ue000\ue001\ue002\n\t\t\t\t\n";
}

SpringBoot核心源码

兼容不规范的文件上传解析器名称

        @Bean
        @ConditionalOnBean({MultipartResolver.class})
        @ConditionalOnMissingBean(
            name = {"multipartResolver"}
        )
        public MultipartResolver multipartResolver(MultipartResolver resolver) {
            return resolver;
        }

当容器中有MultipartResolver这个类型的类,但是却没有multipartResolver这个名称的组件时,触发。
意思其实就是,用户注入的MultipartResolver,名称不是multipartResolver,即命名没有按照SpringBoot默认的组件命名的规范。但此时容器仍然能识别,仍然能把正确的组件返回。(会返回类型为MultipartResolver的组件)

自动配置执行流程小结

SpringBoot自动配置加载顺序:

  1. SpringBoot先加载所有的配置类 xxxAutoConfiguration
  2. 每个自动配置类按照条件进行生效 默认都会绑定配置文件指定的值,xxxProperties里面取,xxxProperties也和配置文件进行了绑定
  3. 生效的配置类就会给容器中装配很多组件
  4. 只要容器中有了这些组件,相当于这些功能就有了
  5. 只要用户有自己配置的,就以用户的优先。(可以自己直接用@Bean替换底层组件,也可以去看这个组件是获取的配置文件什么值,就去配置文件修改) 配置文件也就是application.yml application.properties

xxxxAutoConfiguration —> 组件 -----> xxxProperties里面拿值 -------> application.properties

举例:修改默认的启动图片

SpringBoot原理深入点滴 SpringBoot注解详解 (持续更新中)_第4张图片

举例:修改默认的静态资源路径

SpringBoot原理深入点滴 SpringBoot注解详解 (持续更新中)_第5张图片
默认的静态资源路径是在:resources下的static包下

修改网页启动的标签栏左侧小图标:
SpringBoot原理深入点滴 SpringBoot注解详解 (持续更新中)_第6张图片

静态资源访问底层原理

在此类中
org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration.WebMvcAutoConfigurationAdapter#addResourceHandlers
配置静态资源的访问规则

public void addResourceHandlers(ResourceHandlerRegistry registry) {
            if (!this.resourceProperties.isAddMappings()) {
                logger.debug("Default resource handling disabled");
            } else {
                Duration cachePeriod = this.resourceProperties.getCache().getPeriod();
                CacheControl cacheControl = this.resourceProperties.getCache().getCachecontrol().toHttpCacheControl();
                if (!registry.hasMappingForPattern("/webjars/**")) {
                    this.customizeResourceHandlerRegistration(registry.addResourceHandler(new String[]{"/webjars/**"}).addResourceLocations(new String[]{"classpath:/META-INF/resources/webjars/"}).setCachePeriod(this.getSeconds(cachePeriod)).setCacheControl(cacheControl));
                }

                String staticPathPattern = this.mvcProperties.getStaticPathPattern();
                if (!registry.hasMappingForPattern(staticPathPattern)) {
                    this.customizeResourceHandlerRegistration(registry.addResourceHandler(new String[]{staticPathPattern}).addResourceLocations(WebMvcAutoConfiguration.getResourceLocations(this.resourceProperties.getStaticLocations())).setCachePeriod(this.getSeconds(cachePeriod)).setCacheControl(cacheControl));
                }

            }
        }


自动配置有开关控制:resourceProperties.isAddMappings()
在ResourceProperties配置类中可以看到,这个值是读取的spring.resources.addMappings的值
所以选择是否开启,只需要在application.yml中配置spring.resources.addMappings的布尔值即可

Rest映射及源码解析

所有请求都会请求到DispatcherServlet ,这是和SpringMVC开发一脉相承的
下图是DispatcherServlet 的继承树
SpringBoot原理深入点滴 SpringBoot注解详解 (持续更新中)_第7张图片
调用时序图:

可以看到最终是调到doDispatch()这个方法
org.springframework.web.servlet.DispatcherServlet#doDispatch
源码:

   protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
        HttpServletRequest processedRequest = request;
        HandlerExecutionChain mappedHandler = null;
        boolean multipartRequestParsed = false;
        WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);

        try {
            try {
                ModelAndView mv = null;
                Object dispatchException = null;

                try {
                    processedRequest = this.checkMultipart(request);
                    multipartRequestParsed = processedRequest != request;
                    mappedHandler = this.getHandler(processedRequest);
                    if (mappedHandler == null) {
                        this.noHandlerFound(processedRequest, response);
                        return;
                    }

                    HandlerAdapter ha = this.getHandlerAdapter(mappedHandler.getHandler());
                    String method = request.getMethod();
                    boolean isGet = "GET".equals(method);
                    if (isGet || "HEAD".equals(method)) {
                        long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
                        if ((new ServletWebRequest(request, response)).checkNotModified(lastModified) && isGet) {
                            return;
                        }
                    }

                    if (!mappedHandler.applyPreHandle(processedRequest, response)) {
                        return;
                    }

                    mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
                    if (asyncManager.isConcurrentHandlingStarted()) {
                        return;
                    }

                    this.applyDefaultViewName(processedRequest, mv);
                    mappedHandler.applyPostHandle(processedRequest, response, mv);
                } catch (Exception var20) {
                    dispatchException = var20;
                } catch (Throwable var21) {
                    dispatchException = new NestedServletException("Handler dispatch failed", var21);
                }

                this.processDispatchResult(processedRequest, response, mappedHandler, mv, (Exception)dispatchException);
            } catch (Exception var22) {
                this.triggerAfterCompletion(processedRequest, response, mappedHandler, var22);
            } catch (Throwable var23) {
                this.triggerAfterCompletion(processedRequest, response, mappedHandler, new NestedServletException("Handler processing failed", var23));
            }

        } finally {
            if (asyncManager.isConcurrentHandlingStarted()) {
                if (mappedHandler != null) {
                    mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
                }
            } else if (multipartRequestParsed) {
                this.cleanupMultipart(processedRequest);
            }

        }
    }

那么到底/xxx请求进来,doDispatch是怎么知道把请求转发给哪个方法处理的呢?

请求映射的原理

SpringBoot能够对注解中的参数进行解析,是得益于底层的五个handlerMapping:

  • RequestMappingHandlerMapping
  • WelcomePageHandlerMapping
  • BeanNameUrlHandlerMapping
  • RouterFunctionMapping
  • SimpleUrlHandlerMapping

RequestMappingHandlerMapping,底层保存了所有@RequestMapping和handler的映射规则。

  • SpringBoot自动配置欢迎页的WelcomePageHandlerMapping,访问/能访问到index.html页面
  • SpringBoot自动配置了默认的RequestMappingHandlerMapping
  • 请求进来,挨个尝试所有的HandlerMapping看是否有请求信息
    • 如果有就找到这个请求对应的handler
    • 如果没有就是下一个HandlerMapping
  • 我们需要一些自定义的映射处理,我们也可以自己给容器中放HandlerMapping。自定义HandlerMapping

Controller的那些特殊入参 Session 、 ServletRequest等

Servlet API

以下入参可以直接写到controller的入参列表 无需单独传参

WebRequest、ServletRequest、MultipartRequest、HttpSession、javax.servlet.http.PushBuilder、Principal、InputStream、Reader、HttpMethod、Locale、TimeZone、ZoneId

比如常用的HttpSession 的源码:

import java.util.Enumeration;
import javax.servlet.ServletContext;

public interface HttpSession {
    long getCreationTime();

    String getId();

    long getLastAccessedTime();

    ServletContext getServletContext();

    void setMaxInactiveInterval(int var1);

    int getMaxInactiveInterval();

    /** @deprecated */
    @Deprecated
    HttpSessionContext getSessionContext();

    Object getAttribute(String var1);

    /** @deprecated */
    @Deprecated
    Object getValue(String var1);

    Enumeration<String> getAttributeNames();

    /** @deprecated */
    @Deprecated
    String[] getValueNames();

    void setAttribute(String var1, Object var2);

    /** @deprecated */
    @Deprecated
    void putValue(String var1, Object var2);

    void removeAttribute(String var1);

    /** @deprecated */
    @Deprecated
    void removeValue(String var1);

    void invalidate();

    boolean isNew();
}

复杂参数

Map、Errors/BindingResult、Model、RedirectAttributes、ServletResponse、SessionStatus、UriComponentsBuilder、ServletUriComponentsBuilder

自定义对象参数

可以使用@RequestBody自动类型抓换与格式化,可以级联封装

你可能感兴趣的:(SpringBoot,spring,boot,java,spring,注解,程序员)