精华内容
下载资源
问答
  • SpringBoot工作原理

    千次阅读 2019-09-19 21:02:03
    SpringBoot工作原理 Spring Boot是由Pivotal团队提供的全新框架,其设计目的是用来简化新Spring应用的初始搭建以及开发过程。该框架使用了特定的方式来进行配置,从而使开发人员不再需要定义样板化的配置。通过这种...

    SpringBoot工作原理

    Spring Boot是由Pivotal团队提供的全新框架,其设计目的是用来简化新Spring应用的初始搭建以及开发过程。该框架使用了特定的方式来进行配置,从而使开发人员不再需要定义样板化的配置。通过这种方式,Spring Boot致力于在蓬勃发展的快速应用开发领域(rapid application development)成为领导者。

    Spring框架是Java平台上的一种开源应用框架,提供具有控制反转特性的容器。尽管Spring框架自身对编程模型没有限制,但其在Java应用中的频繁使用让它备受青睐,以至于后来让它作为EJB(EnterpriseJavaBeans)模型的补充,甚至是替补。Spring框架为开发提供了一系列的解决方案,比如利用控制反转的核心特性,并通过依赖注入实现控制反转来实现管理对象生命周期容器化,利用面向切面编程进行声明式的事务管理,整合多种持久化技术管理数据访问,提供大量优秀的Web框架方便开发等等。Spring框架具有控制反转(IOC)特性,IOC旨在方便项目维护和测试,它提供了一种通过Java的反射机制对Java对象进行统一的配置和管理的方法。Spring框架利用容器管理对象的生命周期,容器可以通过扫描XML文件或类上特定Java注解来配置对象,开发者可以通过依赖查找或依赖注入来获得对象。Spring框架具有面向切面编程(AOP)框架,SpringAOP框架基于代理模式,同时运行时可配置;AOP框架主要针对模块之间的交叉关注点进行模块化。Spring框架的AOP框架仅提供基本的AOP特性,虽无法与AspectJ框架相比,但通过与AspectJ的集成,也可以满足基本需求。Spring框架下的事务管理、远程访问等功能均可以通过使用SpringAOP技术实现。Spring的事务管理框架为Java平台带来了一种抽象机制,使本地和全局事务以及嵌套事务能够与保存点一起工作,并且几乎可以在Java平台的任何环境中工作。Spring集成多种事务模板,系统可以通过事务模板、XML或Java注解进行事务配置,并且事务框架集成了消息传递和缓存等功能。Spring的数据访问框架解决了开发人员在应用程序中使用数据库时遇到的常见困难。它不仅对Java:JDBC、iBATS/MyBATIs、Hibernate、Java数据对象(JDO)、ApacheOJB和ApacheCayne等所有流行的数据访问框架中提供支持,同时还可以与Spring的事务管理一起使用,为数据访问提供了灵活的抽象。Spring框架最初是没有打算构建一个自己的WebMVC框架,其开发人员在开发过程中认为现有的StrutsWeb框架的呈现层和请求处理层之间以及请求处理层和模型之间的分离不够,于是创建了SpringMVC。

    特点

    • 可以创建独立的Spring应用程序,并且基于其Maven或Gradle插件,可以创建可执行的JARs和WARs;
    • 内嵌Tomcat或Jetty等Servlet容器;
    • 提供自动配置的“starter”项目对象模型(POMS)以简化Maven配置;
    • 尽可能自动配置Spring容器;
    • 提供准备好的特性,如指标、健康检查和外部化配置;
    • 绝对没有代码生成,不需要XML配置。

    重要策略

    SpringBoot框架中还有两个非常重要的策略:开箱即用和约定优于配置

    开箱即用,Outofbox,是指在开发过程中,通过在Maven项目的pom文件中添加相关依赖包,然后使用对应注解来代替繁琐的XML配置文件以管理对象的生命周期。这个特点使得开发人员摆脱了复杂的配置工作以及依赖的管理工作,更加专注于业务逻辑。

    约定优于配置,Convention over configuration,是一种由SpringBoot本身来配置目标结构,由开发者在结构中添加信息的软件设计范式。这一特点虽降低了部分灵活性,增加了BUG定位的复杂性,但减少了开发人员需要做出决定的数量,同时减少了大量的XML配置,并且可以将代码编译、测试和打包等工作自动化。

    SpringBoot应用系统开发模板的基本架构设计从前端到后台进行说明:前端常使用模板引擎,主要有FreeMarker和Thymeleaf,它们都是用Java语言编写的,渲染模板并输出相应文本,使得界面的设计与应用的逻辑分离,同时前端开发还会使用到Bootstrap、AngularJS、JQuery等;在浏览器的数据传输格式上采用Json,非xml,同时提供RESTfulAPI;SpringMVC框架用于数据到达服务器后处理请求;到数据访问层主要有Hibernate、MyBatis、JPA等持久层框架;数据库常用MySQL;开发工具推荐IntelliJIDEA。

    工作原理

    在这里插入图片描述

    • @configuration(相当于xml中的 配置spring并启动spring容器) :任何一个标注了@configuration的Java类定义都是一个JavaConfig配置类
    • @bean (相当于xml中的) :任何一个标注了@bean的方法,其返回值将作为一个bean定义注册到spring的ioc容器,方法名将默认成为该bean定义的id
    • @ComponentScan (相当于xml中context:component-scan): @ComponentScan用于配合一些元信息Java annotation,比如@component和@repository等,将标注了这些元信息annotation的bean定义类批量采集到spring的ioc容器中。
      我们可以通过basePackages等属性来细粒度地定制@ComponentScan自动扫描的范围,如果不指定,则默认spring框架实现会从声明@ComponentScan所在类的package进行扫描
    • @PropertySource (相当于xml中的context:property-placeholder) : @PropertySource用于从某些地方加载*.properties文件内容,并将其中的属性加载到ioc容器中,便于填充一些bean定义属性的占位符
    • @Impor (@Import(AddressAutoConfig.class)): @Import负责引入JavaConfig形式(class)定义的ioc容器配置
    • @ImportResource (相当于): 有一些遗留的配置或者遗留系统需要以xml的形式来配置(比如dubbo框架),我们依然可以通过@ImportResource将它们一起合并到当前JavaConfig配置的容器中
    • @MapperScan (相当于xml文件中MapperScannerConfigurer下的basePackage): mapper接口扫描注解
    • application.properties中的mybatis.mapperLocations (相当于xml文件下Mybatis SqlSessionFactoryBean下的mapperLocations):主要用来指明mapper xml文件的位置
    • springboot+mybatis需要@MapperScan与mybatis.mapperLocations均指明,才能生成mapper接口实现类并注入spring容器中

    执行流程

    1)如果我们使用的是 SpringApplication 的静态 run 方法,那么,这个方法里面首先需要创建一个 SpringApplication 对象实例,然后调用这个创建好的 SpringApplication 的实例 run方 法。在 SpringApplication 实例初始化的时候,它会提前做几件事情:

    • 根据 classpath 里面是否存在某个特征类(org.springframework.web.context.ConfigurableWebApplicationContext)来决定是否应该创建一个为 Web 应用使用的 ApplicationContext 类型,还是应该创建一个标准 Standalone 应用使用的 ApplicationContext 类型。
    • 使用 SpringFactoriesLoader 在应用的 classpath 中查找并加载所有可用的 ApplicationContextInitializer。
    • 使用 SpringFactoriesLoader 在应用的 classpath 中查找并加载所有可用的 ApplicationListener。
    • 推断并设置 main 方法的定义类。

    2)SpringApplication 实例初始化完成并且完成设置后,就开始执行 run 方法的逻辑了,方法执行伊始,首先遍历执行所有通过 SpringFactoriesLoader 可以查找到并加载的 SpringApplicationRunListener,调用它们的 started() 方法,告诉这些 SpringApplicationRunListener,“嘿,SpringBoot 应用要开始执行咯!”。

    3)创建并配置当前 SpringBoot 应用将要使用的 Environment(包括配置要使用的 PropertySource 以及 Profile)。

    4)遍历调用所有 SpringApplicationRunListener 的 environmentPrepared()的方法,告诉它们:“当前 SpringBoot 应用使用的 Environment 准备好咯!”。

    5)如果 SpringApplication的showBanner 属性被设置为 true,则打印 banner(SpringBoot 1.3.x版本,这里应该是基于 Banner.Mode 决定 banner 的打印行为)。这一步的逻辑其实可以不关心,我认为唯一的用途就是“好玩”(Just For Fun)。

    6)根据用户是否明确设置了applicationContextClass 类型以及初始化阶段的推断结果,决定该为当前 SpringBoot 应用创建什么类型的 ApplicationContext 并创建完成,然后根据条件决定是否添加 ShutdownHook,决定是否使用自定义的 BeanNameGenerator,决定是否使用自定义的 ResourceLoader,当然,最重要的,将之前准备好的 Environment 设置给创建好的 ApplicationContext 使用。

    7)ApplicationContext 创建好之后,SpringApplication 会再次借助 Spring-FactoriesLoader,查找并加载 classpath 中所有可用的 ApplicationContext-Initializer,然后遍历调用这些 ApplicationContextInitializer 的 initialize(applicationContext)方法来对已经创建好的 ApplicationContext 进行进一步的处理。

    8)遍历调用所有 SpringApplicationRunListener 的 contextPrepared()方法,通知它们:“SpringBoot 应用使用的 ApplicationContext 准备好啦!”

    9)最核心的一步,将之前通过 @EnableAutoConfiguration 获取的所有配置以及其他形式的 IoC 容器配置加载到已经准备完毕的 ApplicationContext。

    10)遍历调用所有 SpringApplicationRunListener 的 contextLoaded() 方法,告知所有 SpringApplicationRunListener,ApplicationContext “装填完毕”!

    11)调用 ApplicationContext 的 refresh() 方法,完成 IoC 容器可用的最后一道工序。

    12)查找当前 ApplicationContext 中是否注册有 CommandLineRunner,如果有,则遍历执行它们。

    13)正常情况下,遍历执行 SpringApplicationRunListener 的 finished() 方法,告知它们:“搞定!”。(如果整个过程出现异常,则依然调用所有 SpringApplicationRunListener 的 finished() 方法,只不过这种情况下会将异常信息一并传入处理)。

    至此,一个完整的 SpringBoot 应用启动完毕!

    SpringBoot应用启动步骤简要示意图
    在这里插入图片描述

    参考文章:
    https://baike.baidu.com/item/Spring%20Boot/20249767?fr=aladdin
    https://www.jianshu.com/p/431b53bc38cc
    http://c.biancheng.net/view/4632.html

    展开全文
  • Springboot工作原理

    2019-09-09 18:00:31
    本文通过图解的方式,介绍Springboot工作原理、执行流程,对Filter、Interceptor、ControllerAdvice、Handler等组件的用途执行流程做了详细的介绍,并附有代码示例。

    图解Springboot工作原理

    在这里插入图片描述
    SpringMVC执行流程:

    1. 用户发送请求,请求经过 SpringFramworkFilterChain,UserDefinedFilterChain、HttpServlet、SpringFrameworkServlet 至前端控制器 DispatcherServlet;
    2. DispatcherServlet 收到请求调用处理器映射器 HandlerMapping;
    3. HandlerMapping 根据请求 url 找到具体的处理器,生成处理器执行链 HandlerExecutionChain(包括处理器拦截器和处理器对象),返回给 DispatcherServlet;
    4. DispatcherServlet 根据处理器 Handler 获取处理器适配器 HandlerAdapter 并执行一系列的操作,如:参数封装,数据格式转换,数据验证等;
    5. 执行处理器 Handler(Controller,也叫页面控制器),执行完成返回 ModelAndView;
    6. HandlerAdapter 将 Handler 执行结果 ModelAndView 返回到 DispatcherServlet;
    7. DispatcherServlet 将 ModelAndView 传给视图解析器 ViewReslover,ViewReslover 解析后返回具体视图 View 给 DispatcherServlet;
    8. DispatcherServlet 对 View 进行渲染,将模型数据 model 填充至视图中。
    9. DispatcherServlet 返回 view,响应用户请求;
    10. 用户收到视图响应。

    可以在Controller上打断点,跟踪Springboot的执行流程,如下图
    在这里插入图片描述

    Filter Interceptor ControllerAdvice Aspect And Controller

    在这里插入图片描述

    1. 过滤器 Filter 是 java 过滤器,和框架无关,是所有过滤组件中最外层的,可以控制最初的http请求,粒度最大;
    2. 拦截器 Interceptor 是 Spring 框架的拦截器,可以拦截Controller外层的调用,可以控制请求的请求处理器和方法,但控制不了请求方法里的参数;
    3. ControllerAdvice 是controller的增强,和 ExceptionHandler 一起用来做全局统一异常处理,使用方式参考:《Springboot统一异常处理》
    4. 切面 Aspect,通过切面可以自定义要切入的类甚至再细的方法,粒度最小;
    5. Controller 请求处理器,和具体的业务逻辑相关。

    Filter 和 Interceptor的区别

    1. Filter 是基于函数回调(doFilter()方法)的,而 Interceptor 则是基于 Java 反射的(AOP思想)。
    2. Filter 依赖于 Servlet 容器,而 Interceptor 不依赖于 Servlet 容器。
    3. Filter 对几乎所有的请求起作用,而 Interceptor 只能对 Handler 请求起作用。
    4. Interceptor 可以访问 Handler 的上下文,值栈里的对象,而Filter不能。
    5. 在 Handler 的生命周期里,Interceptor 可以被多次调用,而 Filter 只能在容器初始化时调用一次。
    6. Filter 只能对 request 和 response 进行操作,而 interceptor 可以对 request、response、handler、modelAndView、exception 进行操作。
    Filter 和 Interceptor用法

    过滤器(Filter)和拦截器(Interceptor)的代码示例,参见《Springboot Filter Interceptor》
    在这里插入图片描述
    Filter主要用法

    1. Filter主要用于对用户请求进行预处理,同时也可以进行通用逻辑判断,如响应时间统计、字符编解码,数据流转换、请求拦截等。
    2. Filter 依赖于 Servlet 容器,随Web应用一起启动,系统会自动调用 init(FilterConfig) 方法初始。
    3. Filter的 destroy() 方法,在Filter销毁时执行。
    4. Filter 的核心流程在 doFilter() 方法中定义,以chain.doFilter() 为分界线,Filter 链中 chain.doFilter() 前面的代码总是先执行,chain.doFilter() 后面的代码总是在最后执行,执行流程如下:
      在这里插入图片描述

    在这里插入图片描述
    Interceptor主要用法

    1. Interceptor 只针对 Controller 请求进行处理,拦截用户请求,如判断用户登录情况、权限验证等。
    2. Interceptor 通常通过三种方式定义,
      1)一种是对会话的拦截,实现 HandlerInterceptor 接口或继承 HandlerInterceptorAdapter,并注册到 MVC 的拦截队列中;
      2)另一种是实现WebRequestInterceptor接口,或继承WebRequestInterceptor接口的实现类来定义;
      3)第三种是对方法的拦截,需要使用@Aspect注解,在每次调用指定方法的前、后进行拦截。
    3. Interceptor主要方法用途
      1) preHandle() 在调用 Handler 之前进行拦截,其返回值表示是否中断后续操作,当其返回值为true时,表示继续向下执行;
      2)postHandle() 在控制器方法调用之后、视图渲染之前调用,可以通过此方法对请求域中的模型和视图做出进一步的修改;
      3)afterCompletion() 在会在视图渲染结束之后、返回响应之前执行,可以通过此方法实现一些资源清理、记录日志信息等工作。
      拦截器的执行流程如下:

    在这里插入图片描述

    Filter 和 Interceptor 执行时序

    在这里插入图片描述

    DispatcherServlet

    前端控制器(DispatcherServlet),相当于 MVC 模式中的 C,是整个流程控制的中心,由它调用其它组件处理用户的请求。DispatcherServlet 的存在降低了组件之间的耦合性,提高了系统的可扩展性。

    核心代码

    /**
    	 * Process the actual dispatching to the handler.
    	 * <p>The handler will be obtained by applying the servlet's HandlerMappings in order.
    	 * The HandlerAdapter will be obtained by querying the servlet's installed HandlerAdapters
    	 * to find the first that supports the handler class.
    	 * <p>All HTTP methods are handled by this method. It's up to HandlerAdapters or handlers
    	 * themselves to decide which methods are acceptable.
    	 * @param request current HTTP request
    	 * @param response current HTTP response
    	 * @throws Exception in case of any kind of processing failure
    	 */
    	protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
    		HttpServletRequest processedRequest = request;
    		HandlerExecutionChain mappedHandler = null;
    		boolean multipartRequestParsed = false;
    
    		WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
    
    		try {
    			ModelAndView mv = null;
    			Exception dispatchException = null;
    
    			try {
    				processedRequest = checkMultipart(request);
    				multipartRequestParsed = (processedRequest != request);
    
    				// Determine handler for the current request.
    				mappedHandler = getHandler(processedRequest);
    				if (mappedHandler == null) {
    					noHandlerFound(processedRequest, response);
    					return;
    				}
    
    				// Determine handler adapter for the current request.
    				HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
    
    				// Process last-modified header, if supported by the handler.
    				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;
    				}
    
    				// Actually invoke the handler.
    				mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
    
    				if (asyncManager.isConcurrentHandlingStarted()) {
    					return;
    				}
    
    				applyDefaultViewName(processedRequest, mv);
    				mappedHandler.applyPostHandle(processedRequest, response, mv);
    			}
    			catch (Exception ex) {
    				dispatchException = ex;
    			}
    			catch (Throwable err) {
    				// As of 4.3, we're processing Errors thrown from handler methods as well,
    				// making them available for @ExceptionHandler methods and other scenarios.
    				dispatchException = new NestedServletException("Handler dispatch failed", err);
    			}
    			processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
    		}
    		catch (Exception ex) {
    			triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
    		}
    		catch (Throwable err) {
    			triggerAfterCompletion(processedRequest, response, mappedHandler,
    					new NestedServletException("Handler processing failed", err));
    		}
    		finally {
    			if (asyncManager.isConcurrentHandlingStarted()) {
    				// Instead of postHandle and afterCompletion
    				if (mappedHandler != null) {
    					mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
    				}
    			}
    			else {
    				// Clean up any resources used by a multipart request.
    				if (multipartRequestParsed) {
    					cleanupMultipart(processedRequest);
    				}
    			}
    		}
    	}
    

    HandlerMapping

    处理器映射器(HandlerMapping),负责根据用户请求的 url 找到 请求处理器(Handler),SpringMVC提供了不同的映射器来实现不同的映射方式,根据一定的规则去查找,例如:xml配置方式,接口方式,注解方式等。

    Handler

    请求处理器(Handler),是继 DispatcherServlet 的后端控制器,在 DispatcherServlet 的控制下 Handler 对具体的用户请求进行处理。由于 Handler 涉及到具体的用户业务请求,所以一般需要程序员根据业务需求开发Handler(Controller)。

    HandlerAdapter

    处理器适配器(HandlAdapter),负责调用请求处理器执行业务逻辑,通过扩展适配器可以执行更多类型的请求处理器,这是适配器模式的应用。

    ModelAndView

    ModelAndView 是 SpringMVC 的封装对象,它将 Model 和 View 封装在一起。

    ViewResolver

    视图解析器(ViewResolver),负责将处理结果生成 View 视图,视图解析器首先根据逻辑视图名解析成物理视图名,即具体的页面地址,再生成 View 视图对象,最后对 View 进行渲染将处理结果通过页面展示给用户。

    Model

    模型数据(Model),是用于填充视图的数据模型,一般需要程序员根据业务需求开发。

    View

    视图(View),是springmvc的封装对象,是一个接口,SpringMVC 框架提供了很多的 View 视图类型,包括:jspview,pdfview, jstlView、freemarkerView、pdfView、ThemleafView 等。一般情况下需要通过页面标签或页面模版技术,将模型数据通过页面展示给用户,需要由程序员根据业务需求开发具体的页面。

    参考文献
    SpringMVC执行流程及工作原理
    Spring中Filter和Interceptor的区别

    展开全文
  • springboot工作原理

    千次阅读 2019-06-21 09:17:48
    SpringBoot为我们做的自动配置,确实方便快捷,但一直搞不明白它的内部启动原理,这次就来一步步解开SpringBoot的神秘面纱,让它不再神秘。 @SpringBootApplication public class Application { public static ...

    SpringBoot为我们做的自动配置,确实方便快捷,但一直搞不明白它的内部启动原理,这次就来一步步解开SpringBoot的神秘面纱,让它不再神秘。

    @SpringBootApplication
    public class Application {
        public static void main(String[] args) {
            SpringApplication.run(Application.class, args);
        }
    }
    

    从上面代码可以看出,Annotation定义(@SpringBootApplication)和类定义(SpringApplication.run)最为耀眼,所以要揭开SpringBoot的神秘面纱,我们要从这两位开始就可以了。

    SpringBootApplication背后的秘密

    @Target(ElementType.TYPE)            // 注解的适用范围,其中TYPE用于描述类、接口(包括包注解类型)或enum声明
    @Retention(RetentionPolicy.RUNTIME)  // 注解的生命周期,保留到class文件中(三个生命周期)
    @Documented                          // 表明这个注解应该被javadoc记录
    @Inherited                           // 子类可以继承该注解
    @SpringBootConfiguration             // 继承了Configuration,表示当前是注解类
    @EnableAutoConfiguration             // 开启springboot的注解功能,springboot的四大神器之一,其借助@import的帮助
    @ComponentScan(excludeFilters = {    // 扫描路径设置(具体使用待确认)
            @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
            @Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
    public @interface SpringBootApplication {
    ...
    }
    

    虽然定义使用了多个Annotation进行了原信息标注,但实际上重要的只有三个Annotation:

    @Configuration(@SpringBootConfiguration点开查看发现里面还是应用了@Configuration)
    @EnableAutoConfiguration
    @ComponentScan

    所以,如果我们使用如下的SpringBoot启动类,整个SpringBoot应用依然可以与之前的启动类功能对等:

    @Configuration
    @EnableAutoConfiguration
    @ComponentScan
    public class Application {
        public static void main(String[] args) {
            SpringApplication.run(Application.class, args);
        }
    }
    

    每次写这3个比较累,所以写一个@SpringBootApplication方便点。接下来分别介绍这3个Annotation。

    @Configuration

    这里的@Configuration对我们来说不陌生,它就是JavaConfig形式的Spring Ioc容器的配置类使用的那个@Configuration,SpringBoot社区推荐使用基于JavaConfig的配置形式,所以,这里的启动类标注了@Configuration之后,本身其实也是一个IoC容器的配置类。
    举几个简单例子回顾下,XML跟config配置方式的区别:

    表达形式层面
    基于XML配置的方式是这样:

    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd"
           default-lazy-init="true">
        <!--bean定义-->
    </beans>
    

    而基于JavaConfig的配置方式是这样:

    @Configuration
    public class MockConfiguration{
        //bean定义
    }
    

    任何一个标注了@Configuration的Java类定义都是一个JavaConfig配置类。

    注册bean定义层面
    基于XML的配置形式是这样:

    <bean id="mockService" class="..MockServiceImpl">
        ...
    </bean>
    

    而基于JavaConfig的配置形式是这样的:

    @Configuration
    public class MockConfiguration{
        @Bean
        public MockService mockService(){
            return new MockServiceImpl();
        }
    }
    

    任何一个标注了@Bean的方法,其返回值将作为一个bean定义注册到Spring的IoC容器,方法名将默认成该bean定义的id。

    表达依赖注入关系层面
    为了表达bean与bean之间的依赖关系,在XML形式中一般是这样:

    <bean id="mockService" class="..MockServiceImpl">
        <propery name ="dependencyService" ref="dependencyService" />
    </bean>
    
    <bean id="dependencyService" class="DependencyServiceImpl"></bean>
    

    而基于JavaConfig的配置形式是这样的:

    @Configuration
    public class MockConfiguration{
        @Bean
        public MockService mockService(){
            return new MockServiceImpl(dependencyService());
        }
        
        @Bean
        public DependencyService dependencyService(){
            return new DependencyServiceImpl();
        }
    }
    

    如果一个bean的定义依赖其他bean,则直接调用对应的JavaConfig类中依赖bean的创建方法就可以了。

    @ComponentScan

    @ComponentScan这个注解在Spring中很重要,它对应XML配置中的元素,@ComponentScan的功能其实就是自动扫描并加载符合条件的组件(比如@Component和@Repository等)或者bean定义,最终将这些bean定义加载到IoC容器中。

    我们可以通过basePackages等属性来细粒度的定制@ComponentScan自动扫描的范围,如果不指定,则默认Spring框架实现会从声明@ComponentScan所在类的package进行扫描。

    注:所以SpringBoot的启动类最好是放在root package下,因为默认不指定basePackages。

    @EnableAutoConfiguration

    个人感觉@EnableAutoConfiguration这个Annotation最为重要,所以放在最后来解读,大家是否还记得Spring框架提供的各种名字为@Enable开头的Annotation定义?比如@EnableScheduling、@EnableCaching、@EnableMBeanExport等,@EnableAutoConfiguration的理念和做事方式其实一脉相承,简单概括一下就是,借助@Import的支持,收集和注册特定场景相关的bean定义。

    @EnableScheduling是通过@Import将Spring调度框架相关的bean定义都加载到IoC容器。
    @EnableMBeanExport是通过@Import将JMX相关的bean定义加载到IoC容器。
    @EnableAutoConfiguration也是借助@Import的帮助,将所有符合自动配置条件的bean定义加载到IoC容器,仅此而已!

    @EnableAutoConfiguration作为一个复合Annotation,其自身定义关键信息如下:

    @SuppressWarnings("deprecation")
    @Target(ElementType.TYPE)
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    @Inherited
    @AutoConfigurationPackage
    @Import(EnableAutoConfigurationImportSelector.class)
    public @interface EnableAutoConfiguration {
        ...
    }
    

    两个比较重要的注解:

    @AutoConfigurationPackage:自动配置包

    @Import: 导入自动配置的组件

    AutoConfigurationPackage注解:

    static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {
    
            @Override
            public void registerBeanDefinitions(AnnotationMetadata metadata,
                    BeanDefinitionRegistry registry) {
                register(registry, new PackageImport(metadata).getPackageName());
            }
    

    它其实是注册了一个Bean的定义。

    new PackageImport(metadata).getPackageName(),它其实返回了当前主程序类的 同级以及子级 的包组件。

    以上图为例,DemoApplication是和demo包同级,但是demo2这个类是DemoApplication的父级,和example包同级

    也就是说,DemoApplication启动加载的Bean中,并不会加载demo2,这也就是为什么,我们要把DemoApplication放在项目的最高级中。

    Import(AutoConfigurationImportSelector.class)注解:

    可以从图中看出 AutoConfigurationImportSelector 继承了 DeferredImportSelector 继承了 ImportSelector

    ImportSelector有一个方法为:selectImports。

    @Override
        public String[] selectImports(AnnotationMetadata annotationMetadata) {
            if (!isEnabled(annotationMetadata)) {
                return NO_IMPORTS;
            }
            AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader
                    .loadMetadata(this.beanClassLoader);
            AnnotationAttributes attributes = getAttributes(annotationMetadata);
            List<String> configurations = getCandidateConfigurations(annotationMetadata,
                    attributes);
            configurations = removeDuplicates(configurations);
            Set<String> exclusions = getExclusions(annotationMetadata, attributes);
            checkExcludedClasses(configurations, exclusions);
            configurations.removeAll(exclusions);
            configurations = filter(configurations, autoConfigurationMetadata);
            fireAutoConfigurationImportEvents(configurations, exclusions);
            return StringUtils.toStringArray(configurations);
        }
    

    可以看到第九行,它其实是去加载 public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";外部文件。这个外部文件,有很多自动配置的类。如下:

    image

    其中,最关键的要属@Import(EnableAutoConfigurationImportSelector.class),借助EnableAutoConfigurationImportSelector@EnableAutoConfiguration可以帮助SpringBoot应用将所有符合条件的@Configuration配置都加载到当前SpringBoot创建并使用的IoC容器。就像一只“八爪鱼”一样。

    自动配置幕后英雄:SpringFactoriesLoader详解

    借助于Spring框架原有的一个工具类:SpringFactoriesLoader的支持,@EnableAutoConfiguration可以智能的自动配置功效才得以大功告成!

    SpringFactoriesLoader属于Spring框架私有的一种扩展方案,其主要功能就是从指定的配置文件META-INF/spring.factories加载配置。

    public abstract class SpringFactoriesLoader {
        //...
        public static <T> List<T> loadFactories(Class<T> factoryClass, ClassLoader classLoader) {
            ...
        }
    
    
        public static List<String> loadFactoryNames(Class<?> factoryClass, ClassLoader classLoader) {
            ....
        }
    }
    

    配合@EnableAutoConfiguration使用的话,它更多是提供一种配置查找的功能支持,即根据@EnableAutoConfiguration的完整类名org.springframework.boot.autoconfigure.EnableAutoConfiguration作为查找的Key,获取对应的一组@Configuration

    上图就是从SpringBoot的autoconfigure依赖包中的META-INF/spring.factories配置文件中摘录的一段内容,可以很好地说明问题。

    所以,@EnableAutoConfiguration自动配置的魔法骑士就变成了:从classpath中搜寻所有的META-INF/spring.factories配置文件,并将其中org.springframework.boot.autoconfigure.EnableutoConfiguration对应的配置项通过反射(Java Refletion)实例化为对应的标注了@Configuration的JavaConfig形式的IoC容器配置类,然后汇总为一个并加载到IoC容器。

    SpringBoot原理图

    展开全文
  • 深入springboot工作原理! 提示:写完文章后,目录可以自动生成,如何生成可参考右边的帮助文档 文章目录深入springboot工作原理!前言一、springboot能帮助我们做些什么?二、注入依赖在springboot中我们只需要引入...

    深入springboot工作原理!

    提示:写完文章后,目录可以自动生成,如何生成可参考右边的帮助文档


    前言

    使用过springboot的同学应该已经知道,springboot通过默认配置了很多框架的使用方式帮我们大大简化了项目初始搭建以及开发过程。本文的目的就是一步步分析springboot的启动过程,分析springboot是如何帮我们简化这个过程的。

    一、springboot能帮助我们做些什么?

    通常搭建一个基于spring的web应用,我们需要做以下工作:

    1、pom文件中引入相关jar包,包括spring、springmvc、redis、mybaits、log4j、mysql-connector-java 等等相关jar …

    2、配置web.xml,Listener配置、Filter配置、Servlet配置、log4j配置、error配置 …

    3、配置数据库连接、配置spring事务

    4、配置视图解析器

    5、开启注解、自动扫描功能

    6、配置完成后部署tomcat、启动调试

    搭个初始项目不一会就一个小时甚至半天过去了。而用springboot后,一切都变得很简便快速。下来我们来一步步分析springboot的起步依赖与自动配置这两个核心原理。

    二、注入依赖

    在springboot中我们只需要引入下面简单的几步就可以完成一个ssm后台项目的初始搭建。

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.0.4.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <!--mybatis 开发包-->
    <dependency>
        <groupId>org.mybatis.spring.boot</groupId>
        <artifactId>mybatis-spring-boot-starter</artifactId>
        <version>1.3.2</version>
    </dependency>
    <!--springboot web模块支持-->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <scope>runtime</scope>
    </dependency>
    <!--druid 的数据源-->
    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>druid</artifactId>
        <version>1.0.31</version>
    </dependency>
    
    

    spring-boot-starter-web包自动帮我们引入了web模块开发需要的相关jar包,

    mybatis-spring-boot-starter帮我们引入了dao开发相关的jar包。

    spring-boot-starter-xxx是官方提供的starter,xxx-spring-boot-starter是第三方提供的starter。
    如下截图:
    在这里插入图片描述

    可以看出在这个mybatis-spring-boot-starter 中,并没有任何源码,只有一个pom文件,它的作用就是帮我们引入了相关jar包。
    在这里插入图片描述

    2.配置数据源(yml方式)

    spring:
      datasource:
         url: jdbc:mysql://127.0.0.1:3306/mybatis_test
         username: root
         password: root
         driver-class-name: com.mysql.jdbc.Driver
         type: com.alibaba.druid.pool.DruidDataSource
         dbcp2:
           min-idle: 5
           initial-size: 5
           max-total: 5
           max-wait-millis: 200
    

    stater机制帮我们完成了项目起步所需要的的相关jar包。那问题又来了,传统的spring应用中不是要在application.xml中配置很多bean的吗,比如dataSource的配置,transactionManager的配置 … springboot是如何帮我们完成这些bean的配置的?下面我们来分析这个过程
    代码如下(示例):

    3.自动配置

    以mybatis为例,在上面的截图中,我们发下mybatis-spring-boot-starter这个包帮我们引入了mybatis-spring-boot-autoconfigure这个包,如下图:
    在这里插入图片描述
    里面有MybatisAutoConfiguration这个类,打开这个类看看有什么东西。
    在这里插入图片描述

    熟悉@Configuration&、@Bean这两个bean的同学或许已经知道了。这两个注解一起使用就可以创建一个基于java代码的配置类,可以用来替代相应的xml配置文件。
    @Configuration注解的类可以看作是能生产让Spring IoC容器管理的Bean实例的工厂。
    @Bean注解告诉Spring,一个带有@Bean的注解方法将返回一个对象,该对象应该被注册到spring容器中。

    传统的基于xml的bean配置方法如下:

    <beans>  
        <bean id = "car" class="com.itpsc.Car">  
            <property name="wheel" ref = "wheel"></property>  
        </bean>  
        <bean id = "wheel" class="com.itpsc.Wheel"></bean>  
    </beans>
    

    相当于用基于java代码的配置方式:

    @Configuration  
    public class Conf {  
        @Bean  
        public Car car() {  
            Car car = new Car();  
            car.setWheel(wheel());  
            return car;  
        }  
        @Bean   
        public Wheel wheel() {  
            return new Wheel();  
        }  
    }
    

    所以上面的MybatisAutoConfiguration这个类,自动帮我们生成了SqlSessionFactory这些Mybatis的重要实例并交给spring容器管理,从而完成bean的自动注册。

    beand的发现

    springboot默认扫描启动类所在的包下的主类与子类的所有组件,但并没有包括依赖包的中的类,那么依赖包中的bean是如何被发现和加载的?

    我们通常在启动类中加@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 {
    ...
    }
    

    实际上重要的只有三个Annotation:
    @Configuration(@SpringBootConfiguration里面还是应用了@Configuration)
    @EnableAutoConfiguration
    @ComponentScan

    @Configuration的作用上面我们已经知道了,被注解的类将成为一个bean配置类。
    @ComponentScan的作用就是自动扫描并加载符合条件的组件,比如@Component和@Repository等,最终将这些bean定义加载到spring容器中。
    @EnableAutoConfiguration 这个注解的功能很重要,借助@Import的支持,收集和注册依赖包中相关的bean定义。

    展开全文
  • springboot工作原理 springboot是什么? 首先我们还是来看一看百度百科,对springboot的介绍。 Spring Boot是由Pivotal团队提供的全新框架,其设计目的是用来简化新Spring应用的初始搭建以及开发过程。该框架

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 945
精华内容 378
关键字:

springboot工作原理

spring 订阅