Spring註解驅動開發(六)

[源碼]-Spring容器創建-BeanFactory預準備

Spring容器的refresh()【創建刷新】;
1prepareRefresh()刷新前的預處理;
	1)、initPropertySources()初始化一些屬性設置;子類自定義個性化的屬性設置方法;
	2)、getEnvironment().validateRequiredProperties();檢驗屬性的合法等
	3)、earlyApplicationEvents= new LinkedHashSet<ApplicationEvent>();保存容器中的一些早期的事件;
2obtainFreshBeanFactory();獲取BeanFactory;
	1)、refreshBeanFactory();刷新【創建】BeanFactory;
			創建了一個this.beanFactory = new DefaultListableBeanFactory();
			設置id;
	2)、getBeanFactory();返回剛纔GenericApplicationContext創建的BeanFactory對象;
	3)、將創建的BeanFactory【DefaultListableBeanFactory】返回;
3prepareBeanFactory(beanFactory);BeanFactory的預準備工作(BeanFactory進行一些設置);
	1)、設置BeanFactory的類加載器、支持表達式解析器...
	2)、添加部分BeanPostProcessor【ApplicationContextAwareProcessor】
	3)、設置忽略的自動裝配的接口EnvironmentAware、EmbeddedValueResolverAware、xxx;
	4)、註冊可以解析的自動裝配;我們能直接在任何組件中自動注入:
			BeanFactory、ResourceLoader、ApplicationEventPublisher、ApplicationContext
	5)、添加BeanPostProcessor【ApplicationListenerDetector】
	6)、添加編譯時的AspectJ;
	7)、給BeanFactory中註冊一些能用的組件;
		environment【ConfigurableEnvironment】、
		systemProperties【Map<String, Object>】、
		systemEnvironment【Map<String, Object>4postProcessBeanFactory(beanFactory);BeanFactory準備工作完成後進行的後置處理工作;
	1)、子類通過重寫這個方法來在BeanFactory創建並預準備完成以後做進一步的設置
======================以上是BeanFactory的創建及預準備工作==================================

[源碼]-Spring容器創建-執行BeanFactoryPostProcessor

5invokeBeanFactoryPostProcessors(beanFactory);執行BeanFactoryPostProcessor的方法;
	BeanFactoryPostProcessor:BeanFactory的後置處理器。在BeanFactory標準初始化之後執行的;
	兩個接口:BeanFactoryPostProcessor、BeanDefinitionRegistryPostProcessor
	1)、執行BeanFactoryPostProcessor的方法;
		先執行BeanDefinitionRegistryPostProcessor
		1)、獲取所有的BeanDefinitionRegistryPostProcessor;
		2)、看先執行實現了PriorityOrdered優先級接口的BeanDefinitionRegistryPostProcessor、
			postProcessor.postProcessBeanDefinitionRegistry(registry)
		3)、在執行實現了Ordered順序接口的BeanDefinitionRegistryPostProcessor;
			postProcessor.postProcessBeanDefinitionRegistry(registry)
		4)、最後執行沒有實現任何優先級或者是順序接口的BeanDefinitionRegistryPostProcessors;
			postProcessor.postProcessBeanDefinitionRegistry(registry)
			
		
		再執行BeanFactoryPostProcessor的方法
		1)、獲取所有的BeanFactoryPostProcessor
		2)、看先執行實現了PriorityOrdered優先級接口的BeanFactoryPostProcessor、
			postProcessor.postProcessBeanFactory()
		3)、在執行實現了Ordered順序接口的BeanFactoryPostProcessor;
			postProcessor.postProcessBeanFactory()
		4)、最後執行沒有實現任何優先級或者是順序接口的BeanFactoryPostProcessor;
			postProcessor.postProcessBeanFactory()

[源碼]-Spring容器創建-註冊BeanPostProcessors

6registerBeanPostProcessors(beanFactory);註冊BeanPostProcessor(Bean的後置處理器)【 intercept bean creation】
		不同接口類型的BeanPostProcessor;在Bean創建前後的執行時機是不一樣的
		BeanPostProcessor、
		DestructionAwareBeanPostProcessor、
		InstantiationAwareBeanPostProcessor、
		SmartInstantiationAwareBeanPostProcessor、
		MergedBeanDefinitionPostProcessor【internalPostProcessors】、
		
		1)、獲取所有的 BeanPostProcessor;後置處理器都默認可以通過PriorityOrdered、Ordered接口來執行優先級
		2)、先註冊PriorityOrdered優先級接口的BeanPostProcessor;
			把每一個BeanPostProcessor;添加到BeanFactory中
			beanFactory.addBeanPostProcessor(postProcessor);
		3)、再註冊Ordered接口的
		4)、最後註冊沒有實現任何優先級接口的
		5)、最終註冊MergedBeanDefinitionPostProcessor;
		6)、註冊一個ApplicationListenerDetector;來在Bean創建完成後檢查是否是ApplicationListener,如果是
			applicationContext.addApplicationListener((ApplicationListener<?>) bean);

[源碼]-Spring容器創建-初始化MessageSource

7initMessageSource();初始化MessageSource組件(做國際化功能;消息綁定,消息解析);
		1)、獲取BeanFactory
		2)、看容器中是否有id爲messageSource的,類型是MessageSource的組件
			如果有賦值給messageSource,如果沒有自己創建一個DelegatingMessageSource;
				MessageSource:取出國際化配置文件中的某個key的值;能按照區域信息獲取;
		3)、把創建好的MessageSource註冊在容器中,以後獲取國際化配置文件的值的時候,可以自動注入MessageSource;
			beanFactory.registerSingleton(MESSAGE_SOURCE_BEAN_NAME, this.messageSource);	
			MessageSource.getMessage(String code, Object[] args, String defaultMessage, Locale locale);

[源碼]-Spring容器創建-初始化事件派發器、監聽器等

8initApplicationEventMulticaster();初始化事件派發器;
		1)、獲取BeanFactory
		2)、從BeanFactory中獲取applicationEventMulticaster的ApplicationEventMulticaster;
		3)、如果上一步沒有配置;創建一個SimpleApplicationEventMulticaster
		4)、將創建的ApplicationEventMulticaster添加到BeanFactory中,以後其他組件直接自動注入
9onRefresh();留給子容器(子類)
		1、子類重寫這個方法,在容器刷新的時候可以自定義邏輯;
10registerListeners();給容器中將所有項目裏面的ApplicationListener註冊進來;
		1、從容器中拿到所有的ApplicationListener
		2、將每個監聽器添加到事件派發器中;
			getApplicationEventMulticaster().addApplicationListenerBean(listenerBeanName);
		3、派發之前步驟產生的事件;

[源碼]-Spring容器創建-Bean創建完成

11finishBeanFactoryInitialization(beanFactory);初始化所有剩下的單實例bean;
	1、beanFactory.preInstantiateSingletons();初始化後剩下的單實例bean
		1)、獲取容器中的所有Bean,依次進行初始化和創建對象
		2)、獲取Bean的定義信息;RootBeanDefinition
		3)、Bean不是抽象的,是單實例的,是懶加載;
			1)、判斷是否是FactoryBean;是否是實現FactoryBean接口的Bean;
			2)、不是工廠Bean。利用getBean(beanName);創建對象
				0getBean(beanName); ioc.getBean();
				1doGetBean(name, null, null, false);
				2、先獲取緩存中保存的單實例Bean。如果能獲取到說明這個Bean之前被創建過(所有創建過的單實例Bean都會被緩存起來)
					從private final Map<String, Object> singletonObjects = new ConcurrentHashMap<String, Object>(256);獲取的
				3、緩存中獲取不到,開始Bean的創建對象流程;
				4、標記當前bean已經被創建
				5、獲取Bean的定義信息;
				6、【獲取當前Bean依賴的其他Bean;如果有按照getBean()把依賴的Bean先創建出來;】
				7、啓動單實例Bean的創建流程;
					1)、createBean(beanName, mbd, args);
					2)、Object bean = resolveBeforeInstantiation(beanName, mbdToUse);讓BeanPostProcessor先攔截返回代理對象;
						【InstantiationAwareBeanPostProcessor】:提前執行;
						先觸發:postProcessBeforeInstantiation();
						如果有返回值:觸發postProcessAfterInitialization()3)、如果前面的InstantiationAwareBeanPostProcessor沒有返回代理對象;調用44)、Object beanInstance = doCreateBean(beanName, mbdToUse, args);創建Bean
						 1)、【創建Bean實例】;createBeanInstance(beanName, mbd, args);
						 	利用工廠方法或者對象的構造器創建出Bean實例;
						 2)、applyMergedBeanDefinitionPostProcessors(mbd, beanType, beanName);
						 	調用MergedBeanDefinitionPostProcessor的postProcessMergedBeanDefinition(mbd, beanType, beanName);
						 3)、【Bean屬性賦值】populateBean(beanName, mbd, instanceWrapper);
						 	賦值之前:
						 	1)、拿到InstantiationAwareBeanPostProcessor後置處理器;
						 		postProcessAfterInstantiation()2)、拿到InstantiationAwareBeanPostProcessor後置處理器;
						 		postProcessPropertyValues()=====賦值之前:===
						 	3)、應用Bean屬性的值;爲屬性利用setter方法等進行賦值;
						 		applyPropertyValues(beanName, mbd, bw, pvs);
						 4)、【Bean初始化】initializeBean(beanName, exposedObject, mbd);
						 	1)、【執行Aware接口方法】invokeAwareMethods(beanName, bean);執行xxxAware接口的方法
						 		BeanNameAware\BeanClassLoaderAware\BeanFactoryAware
						 	2)、【執行後置處理器初始化之前】applyBeanPostProcessorsBeforeInitialization(wrappedBean, beanName);
						 		BeanPostProcessor.postProcessBeforeInitialization();
						 	3)、【執行初始化方法】invokeInitMethods(beanName, wrappedBean, mbd);
						 		1)、是否是InitializingBean接口的實現;執行接口規定的初始化;
						 		2)、是否自定義初始化方法;
						 	4)、【執行後置處理器初始化之後】applyBeanPostProcessorsAfterInitialization
						 		BeanPostProcessor.postProcessAfterInitialization()5)、註冊Bean的銷燬方法;
					5)、將創建的Bean添加到緩存中singletonObjects;
				ioc容器就是這些Map;很多的Map裏面保存了單實例Bean,環境信息。。。。;
		所有Bean都利用getBean創建完成以後;
			檢查所有的Bean是否是SmartInitializingSingleton接口的;如果是;就執行afterSingletonsInstantiated()

[源碼]-Spring容器創建-容器創建完成

12finishRefresh();完成BeanFactory的初始化創建工作;IOC容器就創建完成;
		1)、initLifecycleProcessor();初始化和生命週期有關的後置處理器;LifecycleProcessor
			默認從容器中找是否有lifecycleProcessor的組件【LifecycleProcessor】;如果沒有new DefaultLifecycleProcessor();
			加入到容器;
			
			寫一個LifecycleProcessor的實現類,可以在BeanFactory
				void onRefresh();
				void onClose();	
		2)、	getLifecycleProcessor().onRefresh();
			拿到前面定義的生命週期處理器(BeanFactory);回調onRefresh()3)、publishEvent(new ContextRefreshedEvent(this));發佈容器刷新完成事件;
		4)、liveBeansView.registerApplicationContext(this);

[源碼]-Spring源碼總結

	======總結===========
	1)、Spring容器在啓動的時候,先會保存所有註冊進來的Bean的定義信息;
		1)、xml註冊bean;<bean>
		2)、註解註冊Bean;@Service@Component@Bean、xxx
	2)、Spring容器會合適的時機創建這些Bean
		1)、用到這個bean的時候;利用getBean創建bean;創建好以後保存在容器中;
		2)、統一創建剩下所有的bean的時候;finishBeanFactoryInitialization()3)、後置處理器;BeanPostProcessor
		1)、每一個bean創建完成,都會使用各種後置處理器進行處理;來增強bean的功能;
			AutowiredAnnotationBeanPostProcessor:處理自動注入
			AnnotationAwareAspectJAutoProxyCreator:來做AOP功能;
			xxx....
			增強的功能註解:
			AsyncAnnotationBeanPostProcessor
			....
	4)、事件驅動模型;
		ApplicationListener;事件監聽;
		ApplicationEventMulticaster;事件派發:

servlet3.0-簡介&測試

現在,我們來說說註解版的web,我們以前來寫web的三大組件:Servlet、Filter、Listener,包括SpringMVC的前端控制器DispatcherServlet都需要在web.xml文件中來進行註冊;而在Servlet3.0標準以後,就給我們提供了方便的註解的方式來完成我們這些組件的註冊以及添加,提供了運行時的可插拔的插件能力;、

說明:Servlet3.0及以上的標準是需要Tomcat7及以上的支持;


  1. 創建一個動態的web工程:
    在這裏插入圖片描述

  1. 我們來寫上一個jsp頁面:
<%--
  Created by IntelliJ IDEA.
  User: WH1803054
  Date: 2019/1/17
  Time: 19:07
  To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
  <head>
    <title>$Title$</title>
  </head>
  <body>
    <a href="hello">hello</a>
  </body>
</html>
  1. 我們再來寫上一個servlet,並且用@WebServlet("/hello")來標註,並且指明要攔截哪些路徑:
package com.ldc.servlet;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

@WebServlet("/hello")
public class HelloServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        resp.getWriter().write("hello...");
    }
}
  1. 啓動Tomcat服務器:運行成功
    在這裏插入圖片描述
    點進這個鏈接也會打印字符串:
    在這裏插入圖片描述

同樣,要註冊Filter用@WebFilter註解、註冊Listener用@WebListener註解;如果在註冊的時候,需要一些初始化參數,我們就可以用@WebInitParam註解;


servlet3.0-ServletContainerInitializer

Shared libraries(共享庫) / runtimes pluggability(運行時插件能力)

1、Servlet容器啓動會掃描,當前應用裏面每一個jar包的ServletContainerInitializer的實現
2、提供ServletContainerInitializer的實現類;
	必須綁定在,META-INF/services/javax.servlet.ServletContainerInitializer
	文件的內容就是ServletContainerInitializer實現類的全類名;

總結:容器在啓動應用的時候,會掃描當前應用每一個jar包裏面
META-INF/services/javax.servlet.ServletContainerInitializer
指定的實現類,啓動並運行這個實現類的方法;傳入感興趣的類型;


ServletContainerInitializer;
@HandlesTypes

第一步:我們寫一個MyServletContainerInitializer 類實現ServletContainerInitializer 接口

//容器啓動的時候會將@HandlesTypes指定的這個類型下面的子類(實現類或者子接口等)傳遞過來
//傳入感興趣的類型
@HandlesTypes(value = {HelloService.class})
public class MyServletContainerInitializer implements ServletContainerInitializer {
    /**
     * 在應用啓動的時候,會運行onStartup方法;
     * Set<Class<?>> :感興趣的類型的所有子類型;
     * ServletContext 代表當前的web應用的ServletContext對象,一個web應用相當於是一個ServletContext
     * @throws ServletException
     */
    @Override
    public void onStartup(Set<Class<?>> set, ServletContext servletContext) throws ServletException {
        System.out.println("感興趣的類型:");
        set.forEach(System.out::println);
    }
}

第二步:在這個路徑下新建一個文件

在這裏插入圖片描述
文件的內容就寫我們實現ServletContainerInitializer 這個接口的類MyServletContainerInitializer 的全類名:
在這裏插入圖片描述


在這裏插入圖片描述


最後,我們運行起來,運行結果爲:

感興趣的類型:
class com.ldc.service.HelloServiceExt
class com.ldc.service.AbstractHelloService
class com.ldc.service.HelloServiceImpl


servlet3.0-ServletContext註冊三大組件

首先我們寫一個Servlet:

public class UserServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        resp.getWriter().write("tomcat...");
        System.out.println("UserServlet...doGet...");
    }
}

再來一個Filter:

public class UserFilter implements Filter {
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {

    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        //過濾請求
        System.out.println("UserFilter...doFilter...");
        //放行
        filterChain.doFilter(servletRequest,servletResponse);
    }

    @Override
    public void destroy() {

    }
}

最後,再寫一個Listener:

/**
 * 監聽項目的啓動和停止
 */
public class UserListener implements ServletContextListener {
    //監聽ServletContextEvent的啓動初始化
    @Override
    public void contextInitialized(ServletContextEvent servletContextEvent) {
        System.out.println("UserListener...contextInitialized");
    }
    //監聽ServletContextEvent銷燬
    @Override
    public void contextDestroyed(ServletContextEvent servletContextEvent) {
        System.out.println("UserListener...contextDestroyed");
    }
}

最後,我們來用ServletContext來進行註冊三大組件:

//容器啓動的時候會將@HandlesTypes指定的這個類型下面的子類(實現類或者子接口等)傳遞過來
//傳入感興趣的類型
@HandlesTypes(value = {HelloService.class})
public class MyServletContainerInitializer implements ServletContainerInitializer {
    /**
     * 在應用啓動的時候,會運行onStartup方法;
     * Set<Class<?>> :感興趣的類型的所有子類型;
     * ServletContext 代表當前的web應用的ServletContext對象,一個web應用相當於是一個ServletContext
     * 1)、使用ServletContext註冊Web組件(Servlet、Filter、Listener)
     * 2)、使用編碼的方式,在項目啓動的時候給ServletContext添加組件
     * 必須在項目啓動的時候來添加
     *  (1)ServletContainerInitializer得到ServletContext對象來註冊;
     *  (2)ServletContextListener的方法的參數裏面的ServletContextEvent對象可以獲取ServletContext對象
     */
    @Override
    public void onStartup(Set<Class<?>> set, ServletContext servletContext) throws ServletException {
        System.out.println("感興趣的類型:");
        set.forEach(System.out::println);

        //註冊組件
        Dynamic servlet = servletContext.addServlet("userServlet", new UserServlet());
        //配置servlet的映射信息
        servlet.addMapping("/user");

        //註冊Listener
        servletContext.addListener(UserListener.class);

        //註冊Filter
        FilterRegistration.Dynamic filter = servletContext.addFilter("userFilter", UserFilter.class);
        filter.addMappingForUrlPatterns(EnumSet.of(DispatcherType.REQUEST),true,"/*");
    }
}

我們啓動,然後再瀏覽器上進行訪問:

感興趣的類型:
class com.ldc.service.AbstractHelloService
class com.ldc.service.HelloServiceImpl
class com.ldc.service.HelloServiceExt
UserListener…contextInitialized
UserFilter…doFilter…
UserFilter…doFilter…
UserFilter…doFilter…
UserServlet…doGet…

如果服務器停止了,監聽器也是能監聽得到:

UserListener…contextDestroyed


servlet3.0-與SpringMVC整合分析

Spring5.2.0 WebServlet官方文檔


1、web容器在啓動的時候,會掃描每個jar包下的META-INF/services/javax.servlet.ServletContainerInitializer
2、加載這個文件指定的類SpringServletContainerInitializer
3、spring的應用一啓動會加載感興趣的WebApplicationInitializer接口的下的所有組件;
4、並且爲WebApplicationInitializer組件創建對象(組件不是接口,不是抽象類)
	1)、AbstractContextLoaderInitializer:創建根容器;createRootApplicationContext()2)、AbstractDispatcherServletInitializer:
			創建一個web的ioc容器;createServletApplicationContext();
			創建了DispatcherServlet;createDispatcherServlet();
			將創建的DispatcherServlet添加到ServletContext中;
				getServletMappings();
	3)、AbstractAnnotationConfigDispatcherServletInitializer:註解方式配置的DispatcherServlet初始化器
			創建根容器:createRootApplicationContext()
					getRootConfigClasses();傳入一個配置類
			創建web的ioc容器: createServletApplicationContext();
					獲取配置類;getServletConfigClasses();
	
總結:
	以註解方式來啓動SpringMVC;繼承AbstractAnnotationConfigDispatcherServletInitializer;
實現抽象方法指定DispatcherServlet的配置信息;



springmvc-整合

  1. 導入jar包:
<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.ldc</groupId>
    <artifactId>springmvc-annotation</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>war</packaging>

    <dependencies>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-webmvc</artifactId>
            <version>4.3.11.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>servlet-api</artifactId>
            <version>3.0-alpha-1</version>
            <!--因爲tomcat也有servlet-api,要是項目打成war包的時候,就不要帶上這個jar包,否則就會引起衝突-->
            <scope>provided</scope>
        </dependency>
    </dependencies>


    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-war-plugin</artifactId>
                <version>2.4</version>
                <configuration>
                    <failOnMissingWebXml>false</failOnMissingWebXml>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>
  1. 創建web初始化器,以及父子配置類:
//Web容器啓動的時候創建對象;調用方法來初始化容器以及前端控制器
public class MyWebInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {

    //獲取父容器的配置類:(Spring的配置文件) --->作爲父容器
    @Override
    protected Class<?>[] getRootConfigClasses() {
        return new Class<?>[]{RootConfig.class};
    }

    //獲取web容器的配置類(SpringMVC配置文件) --->作爲一個子容器
    @Override
    protected Class<?>[] getServletConfigClasses() {
        return new Class<?>[]{AppConfig.class};
    }

    //獲取DispatcherServlet的映射信息
    //  /:攔截所有請求(包括靜態資源(xx.js,xx.png),但是不包括*.jsp)
    //  /*:攔截所有請求,連*.jsp頁面都攔截;jsp頁面是tomcat引擎解析的
    @Override
    protected String[] getServletMappings() {
        return new String[]{"/"};
    }
}

//Spring的容器不掃描Controller,父容器
@ComponentScan(value = {"com.ldc."},excludeFilters = {
        @Filter(type = FilterType.ANNOTATION,classes = {Controller.class})
})
public class RootConfig {

}

//SpringMVC只掃描Controller,子容器
//useDefaultFilters = false 禁用默認的過濾規則
@ComponentScan(value = {"com.ldc"},includeFilters = {
        @Filter(type = FilterType.ANNOTATION,classes = {Controller.class})
},useDefaultFilters = false)
public class AppConfig {

}

  1. 我們再來寫一個Controller和Service:
@Controller
public class HelloController {

    @Autowired
    private HelloService helloService;

    @ResponseBody
    @RequestMapping("hello")
    public String hello() {
        String hello = helloService.sayHello("tomcat");
        return hello;
    }

}

@Service
public class HelloService {

    public String sayHello(String name) {
        return "Hello," + name;
    }

}


  1. 測試:我們啓動tomcat服務器來運行測試:
    在這裏插入圖片描述

springmvc-定製與接管SpringMVC

SpringMVC的其他相關的註解的配置參考官方文檔

定製SpringMVC;
1)、@EnableWebMvc:開啓SpringMVC定製配置功能;
	<mvc:annotation-driven/>2)、配置組件(視圖解析器、視圖映射、靜態資源映射、攔截器。。。)
	extends WebMvcConfigurerAdapter

SpringMVC具體配置的參考文檔/a>


我們就可以這樣來寫:

//SpringMVC只掃描Controller,子容器
//useDefaultFilters = false 禁用默認的過濾規則
@ComponentScan(value = {"com.ldc"},includeFilters = {
        @Filter(type = FilterType.ANNOTATION,classes = {Controller.class})
},useDefaultFilters = false)
@EnableWebMvc
public class AppConfig implements WebMvcConfigurer {

    @Override
    public void configurePathMatch(PathMatchConfigurer pathMatchConfigurer) {

    }

    @Override
    public void configureContentNegotiation(ContentNegotiationConfigurer contentNegotiationConfigurer) {

    }

    @Override
    public void configureAsyncSupport(AsyncSupportConfigurer asyncSupportConfigurer) {

    }

    @Override
    public void configureDefaultServletHandling(DefaultServletHandlerConfigurer defaultServletHandlerConfigurer) {
        //將SpringMVC處理不了的請求交給tomcat,專門針對於靜態資源的,這個時候,靜態資源就是可以訪問的
        defaultServletHandlerConfigurer.enable();
    }

    @Override
    public void addFormatters(FormatterRegistry formatterRegistry) {
        //添加自定義的類型轉換器
    }

    @Override
    public void addInterceptors(InterceptorRegistry interceptorRegistry) {

    }

    @Override
    public void addResourceHandlers(ResourceHandlerRegistry resourceHandlerRegistry) {

    }

    @Override
    public void addCorsMappings(CorsRegistry corsRegistry) {

    }

    @Override
    public void addViewControllers(ViewControllerRegistry viewControllerRegistry) {

    }

    @Override
    public void configureViewResolvers(ViewResolverRegistry viewResolverRegistry) {

    }

    @Override
    public void addArgumentResolvers(List<HandlerMethodArgumentResolver> list) {

    }

    @Override
    public void addReturnValueHandlers(List<HandlerMethodReturnValueHandler> list) {

    }

    @Override
    public void configureMessageConverters(List<HttpMessageConverter<?>> list) {

    }

    @Override
    public void extendMessageConverters(List<HttpMessageConverter<?>> list) {

    }

    @Override
    public void configureHandlerExceptionResolvers(List<HandlerExceptionResolver> list) {

    }

    @Override
    public void extendHandlerExceptionResolvers(List<HandlerExceptionResolver> list) {

    }

    @Override
    public Validator getValidator() {
        return null;
    }

    @Override
    public MessageCodesResolver getMessageCodesResolver() {
        return null;
    }
}

但是上面直接實現WebMvcConfigurer接口的方式,有很多的方法用不到,我們可以用這個適配器WebMvcConfigurerAdapter來實現,它實現了WebMvcConfigurer接口:

我們可以來定義一個視圖解析器:

//SpringMVC只掃描Controller,子容器
//useDefaultFilters = false 禁用默認的過濾規則
@ComponentScan(value = {"com.ldc"},includeFilters = {
        @Filter(type = FilterType.ANNOTATION,classes = {Controller.class})
},useDefaultFilters = false)
@EnableWebMvc
public class AppConfig extends WebMvcConfigurerAdapter {
    //定製

    //視圖解析器

    @Override
    public void configureViewResolvers(ViewResolverRegistry registry) {
        //默認所有頁面都是從/WEB-INF/xxx.jsp
        //registry.jsp();
        //我們也可以自己來寫規則
        registry.jsp("/WEB-INF/views/", ".jsp");
    }
}

我們在/WEB-INF/views/這個路徑下新建一個success.jsp

<%--
  Created by IntelliJ IDEA.
  User: WH1803054
  Date: 2019/1/17
  Time: 22:27
  To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Title</title>
</head>
<body>
<h1>success</h1>
</body>
</html>


在這裏插入圖片描述


現在,我們來運行項目,測試:這個時候,就表示視圖解析器配置成功了
在這裏插入圖片描述


我們在這個路徑下放一個圖片,然後再寫一個jsp來訪問它:
在這裏插入圖片描述


這個時候,圖片是顯示不出來的:
在這裏插入圖片描述


現在,我們來配置允許靜態資源的訪問:

//SpringMVC只掃描Controller,子容器
//useDefaultFilters = false 禁用默認的過濾規則
@ComponentScan(value = {"com.ldc"},includeFilters = {
        @Filter(type = FilterType.ANNOTATION,classes = {Controller.class})
},useDefaultFilters = false)
@EnableWebMvc
public class AppConfig extends WebMvcConfigurerAdapter {
    //定製

    //視圖解析器

    @Override
    public void configureViewResolvers(ViewResolverRegistry registry) {
        //默認所有頁面都是從/WEB-INF/xxx.jsp
        //registry.jsp();
        //我們也可以自己來寫規則
        registry.jsp("/WEB-INF/views/", ".jsp");
    }

    //靜態資源的訪問
    @Override
    public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
        configurer.enable();
    }
}

這個時候,我們的圖片就是可以訪問了:
在這裏插入圖片描述


我們寫上一個攔截器:

public class MyFirstInterceptor implements HandlerInterceptor {
    //在目標方法執行之前執行
    @Override
    public boolean preHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o) throws Exception {
        System.out.println("preHandle...");
        return true;
    }

    //在目標方法執行之後執行
    @Override
    public void postHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, ModelAndView modelAndView) throws Exception {
        System.out.println("postHandle...");
    }

    //頁面響應以後執行
    @Override
    public void afterCompletion(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) throws Exception {
        System.out.println("afterCompletion...");
    }
}

我們在配置類裏面添加攔截器:

//SpringMVC只掃描Controller,子容器
//useDefaultFilters = false 禁用默認的過濾規則
@ComponentScan(value = {"com.ldc"},includeFilters = {
        @Filter(type = FilterType.ANNOTATION,classes = {Controller.class})
},useDefaultFilters = false)
@EnableWebMvc
public class AppConfig extends WebMvcConfigurerAdapter {
    //定製

    //視圖解析器

    @Override
    public void configureViewResolvers(ViewResolverRegistry registry) {
        //默認所有頁面都是從/WEB-INF/xxx.jsp
        //registry.jsp();
        //我們也可以自己來寫規則
        registry.jsp("/WEB-INF/views/", ".jsp");
    }

    //靜態資源的訪問
    @Override
    public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
        configurer.enable();
    }

    //配置攔截器
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        //攔截任意的路徑
        registry.addInterceptor(new MyFirstInterceptor()).addPathPatterns("/**");
    }
}

我們重新啓動,並且訪問這個路徑:
在這裏插入圖片描述


我們發現控制檯已經打印了,說明攔截器已經起作用了:

preHandle…
postHandle…
afterCompletion…
preHandle…
postHandle…
afterCompletion…


servlet3.0-異步請求

servlet3.0異步處理
在Servlet 3.0之前,Servlet採用Thread-Per-Request的方式處理請求。
即每一次Http請求都由某一個線程從頭到尾負責處理。
在這裏插入圖片描述
如果一個請求需要進行IO操作,比如訪問數據庫、調用第三方服務接口等,那麼其所對應的線程將同步地等待IO操作完成, 而IO操作是非常慢的,所以此時的線程並不能及時地釋放回線程池以供後續使用,在併發量越來越大的情況下,這將帶來嚴重的性能問題。即便是像Spring、Struts這樣的高層框架也脫離不了這樣的桎梏,因爲他們都是建立在Servlet之上的。爲了解決這樣的問題,Servlet 3.0引入了異步處理,然後在Servlet 3.1中又引入了非阻塞IO來進一步增強異步處理的性能。


我們可以在之前寫的Servlet加上當前的線程:

@WebServlet("/hello")
public class HelloServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        System.out.println(Thread.currentThread()+" start...");
        try {
            sayHello();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        resp.getWriter().write("hello...");
        System.out.println(Thread.currentThread()+" end...");
    }

    private void sayHello() throws InterruptedException {
        System.out.println(Thread.currentThread()+ " processing...");
        Thread.sleep(3000);
    }

}

啓動服務之後,控制檯打印結果爲:我們可以發現從線程開始、處理請求到執行結束從始至終都是Thread[http-nio-8081-exec-3,5,main]這個線程,主線程得不到釋放,當下一個請求進來就得不到處理;

UserFilter…doFilter…
Thread[http-nio-8081-exec-3,5,main] start…
Thread[http-nio-8081-exec-3,5,main] processing…
Thread[http-nio-8081-exec-3,5,main] end…


我們再來加上是哪些線程處理的:

@WebServlet(value = "/async",asyncSupported = true)
public class HelloAsyncServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //1.支持異步處理:asyncSupported = true

        //2.開啓異步模式
        System.out.println("主線程開始..."+Thread.currentThread()+"==>"+ Instant.now().toEpochMilli());
        AsyncContext startAsync = req.startAsync();
        //3.業務邏輯進行異步處理,開始異步處理
        startAsync.start(()-> {
            try {
                System.out.println("副線程開始..."+Thread.currentThread()+"==>"+ Instant.now().toEpochMilli());
                sayHello();
                startAsync.complete();
                //獲取異步上下文
                //AsyncContext asyncContext = req.getAsyncContext();
                //4.獲取響應
                ServletResponse response = startAsync.getResponse();
                response.getWriter().write("hello async...");
                System.out.println("副線程結束..."+Thread.currentThread()+"==>"+ Instant.now().toEpochMilli());
            } catch (Exception e) {
                e.printStackTrace();
            }
        });
        System.out.println("主線程結束..."+Thread.currentThread()+"==>"+ Instant.now().toEpochMilli());
    }

    private void sayHello() throws InterruptedException {
        System.out.println(Thread.currentThread()+ " processing..."+"==>"+ Instant.now().toEpochMilli());
        Thread.sleep(3000);
    }
}

執行結果如下:

感興趣的類型:
class com.ldc.service.HelloServiceImpl
class com.ldc.service.HelloServiceExt
class com.ldc.service.AbstractHelloService
UserListener…contextInitialized
[2019-01-18 04:47:06,991] Artifact servlet3.0:war exploded: Artifact is deployed successfully
[2019-01-18 04:47:06,992] Artifact servlet3.0:war exploded: Deploy took 447 milliseconds
UserFilter…doFilter…
UserFilter…doFilter…
UserFilter…doFilter…
主線程開始…Thread[http-nio-8081-exec-7,5,main]>1547801232248
主線程結束…Thread[http-nio-8081-exec-7,5,main]
>1547801232253
副線程開始…Thread[http-nio-8081-exec-8,5,main]>1547801232253
Thread[http-nio-8081-exec-8,5,main] processing…
>1547801232253
副線程結束…Thread[http-nio-8081-exec-8,5,main]==>1547801235253


現在的處理就是可以表示成下圖:
在這裏插入圖片描述


springmvc-異步請求-返回Callable

@Controller
public class AsyncController {
	 /**
     * 1、控制器返回Callable
     * 2、Spring異步處理,將Callable 提交到 TaskExecutor 使用一個隔離的線程進行執行
     * 3、DispatcherServlet和所有的Filter退出web容器的線程,但是response 保持打開狀態;
     * 4、Callable返回結果,SpringMVC將請求重新派發給容器,恢復之前的處理;
     * 5、根據Callable返回的結果。SpringMVC繼續進行視圖渲染流程等(從收請求-視圖渲染)。
     *
     * preHandle.../springmvc-annotation/async01
     主線程開始...Thread[http-bio-8081-exec-3,5,main]==>1513932494700
     主線程結束...Thread[http-bio-8081-exec-3,5,main]==>1513932494700
     =========DispatcherServlet及所有的Filter退出線程============================

     ================等待Callable執行==========
     副線程開始...Thread[MvcAsync1,5,main]==>1513932494707
     副線程開始...Thread[MvcAsync1,5,main]==>1513932496708
     ================Callable執行完成==========

     ================再次收到之前重發過來的請求========
     preHandle.../springmvc-annotation/async01
     postHandle...(Callable的之前的返回值就是目標方法的返回值)
     afterCompletion...

     異步的攔截器:
     1)、原生API的AsyncListener
     2)、SpringMVC:實現AsyncHandlerInterceptor;
     * @return
     */
    @ResponseBody
    @RequestMapping("/async01")
    public Callable<String> async01() {
        System.out.println("主線程開始..." + Thread.currentThread() + "==>" + Instant.now().getEpochSecond());
        Callable<String> callable = new Callable<String>() {
            @Override
            public String call() throws Exception {
                System.out.println("副線程開始..." + Thread.currentThread() + "==>" + Instant.now().getEpochSecond());
                Thread.sleep(2000);
                System.out.println("副線程結束..." + Thread.currentThread() + "==>" + Instant.now().getEpochSecond());
                return "Callable<String> async01()";
            }
        };
        System.out.println("主線程結束..." + Thread.currentThread() + "==>" + Instant.now().getEpochSecond());
        return callable;
    }

}

此時運行起來的測試結果如下:

preHandle…
主線程開始…Thread[http-nio-8081-exec-7,5,main]>1547802269
主線程結束…Thread[http-nio-8081-exec-7,5,main]
>1547802269
副線程開始…Thread[MvcAsync1,5,main]>1547802269
副線程結束…Thread[MvcAsync1,5,main]
>1547802271
preHandle…
postHandle…
afterCompletion…


springmvc-異步請求-返回DeferredResult

我們來看一個實際的應用場景:
在這裏插入圖片描述


public class DeferredResultQueue {

    private static Queue<DeferredResult<Object>> queue = new ConcurrentLinkedDeque<>();

    public static void save(DeferredResult<Object> deferredResult) {
        queue.add(deferredResult);
    }
    public static DeferredResult<Object> get() {
        return queue.poll();
    }
}

@Controller
public class AsyncController {


    @ResponseBody
    @RequestMapping("/createOrder")
    public DeferredResult<Object> createOrder(){
        DeferredResult<Object> deferredResult = new DeferredResult<>((long)3000, "create fail...");

        DeferredResultQueue.save(deferredResult);

        return deferredResult;
    }


    @ResponseBody
    @RequestMapping("/create")
    public String create(){
        //創建訂單
        String order = UUID.randomUUID().toString();
        DeferredResult<Object> deferredResult = DeferredResultQueue.get();
        deferredResult.setResult(order);
        return "success===>"+order;
    }
}

我們先訪問這個創建訂單createOrder接口:
在這裏插入圖片描述


我們再來訪問這個create接口,此時的結果如圖所示:
在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章