前言
此文章是對SpringMVC註解開發的demo配置以及通過Debug對啓動流程做一個大概的分析。
介紹
通過SpringMVC註解驅動開發,我們就無需使用web.xml、springmvc配置文件對spring mvc進行配置。
SpringMVC註解驅動開發是基於Servlet3.0新特性的基礎上實現的,準確來說是基於servlet3.0的ServletContainerInitializer接口實現的。
接口和類介紹:
public interface ServletContainerInitializer {
//servlet3.0的新接口,tomcat會在啓動時調用該接口的實現類方法。
public void onStartup(Set<Class<?>> c, ServletContext ctx)
throws ServletException;
}
//ServletContainerInitializer 接口的實現類,在spring的web包下,如果是基於springmvc註解驅開發,
//tomcat容器會在啓動時加載工程時調用該類的onStartup方法。
//真正處理springmvc啓動的接口用該註解HandlesTypes定義。
@HandlesTypes(WebApplicationInitializer.class)
public class SpringServletContainerInitializer implements ServletContainerInitializer {
/**
*
* @param webAppInitializerClasses 該參數就是真正進行初始化的實現類對象集合,就是上面HandlesTypes註解的value值
* WebApplicationInitializer接口的所有實現類對象集合。
* @param servletContext servlet上下文
* @throws ServletException
*/
@Override
public void onStartup(@Nullable Set<Class<?>> webAppInitializerClasses, ServletContext servletContext)
throws ServletException {
//定義初始化對象的集合
List<WebApplicationInitializer> initializers = new LinkedList<>();
if (webAppInitializerClasses != null) {
//遍歷webAppInitializerClasses
for (Class<?> waiClass : webAppInitializerClasses) {
//這裏做出一些判斷、防止一些servlet容器爲我們提供無效的處理類
// 首先該類不能是接口,並且不能是抽象類,且必須是WebApplicationInitializer接口的實現類
if (!waiClass.isInterface() && !Modifier.isAbstract(waiClass.getModifiers()) &&
WebApplicationInitializer.class.isAssignableFrom(waiClass)) {
try {
//符合上面判斷進入這裏。
//爲符合規則的處理類(WebApplicationInitializer實現類)創建對象並添加到集合List中
initializers.add((WebApplicationInitializer)
ReflectionUtils.accessibleConstructor(waiClass).newInstance());
}
catch (Throwable ex) {
throw new ServletException("Failed to instantiate WebApplicationInitializer class", ex);
}
}
}
}
//如果集合爲空,也就是沒有處理對象。直接返回。
if (initializers.isEmpty()) {
servletContext.log("No Spring WebApplicationInitializer types detected on classpath");
return;
}
servletContext.log(initializers.size() + " Spring WebApplicationInitializers detected on classpath");
//排序
AnnotationAwareOrderComparator.sort(initializers);
for (WebApplicationInitializer initializer : initializers) {
//遍歷處理對象,執行處理方法onStartup
initializer.onStartup(servletContext);
}
}
}
在spring-web包的META-INF.services下有一個javax.servlet.ServletContainerInitializer文件,文件的內容是org.springframework.web.SpringServletContainerInitializer ,這個是使用SPI機制創建的SpringServletContainerInitializer 對象。SPI機制詳情查看相關文章。
//該接口是springmvc初始化的處理的頂級接口。
//類結構如下圖
public interface WebApplicationInitializer {
void onStartup(ServletContext servletContext ) throws ServletException;
}
spring爲該接口的提供的都是抽象類,所以我們要使用註解驅動的springmvc,就要實現自己的實現類。我們可以通過繼承AbstractDispatcherServletInitializer或者AbstractAnnotationConfigDispatcherServletInitializer抽象類實現自己的啓動類。
這裏就通過繼承AbstractDispatcherServletInitializer實現吧。
所以要配置一個註解驅動的springmvc項目環境,基本有以下幾步:
第一步:引入相關Jar包。
第二步:編寫spring配置類,用於配置spring IOC父容器。
第三步:編寫springmvc配置類,用於配置spring mvc子容器。
第四步:編寫AbstractDispatcherServletInitializer實現類,完成對springmvc的初始化。
springmvc註解驅動環境搭建
第一步:引入jar包
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.1.9.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.1.9.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-support</artifactId>
<version>5.1.9.RELEASE</version>
</dependency>
<!--必須3.0以上版本-->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>javax.servlet.jsp</groupId>
<artifactId>javax.servlet.jsp-api</artifactId>
<version>2.3.1</version>
<scope>provided</scope>
</dependency>
第二步:編寫spring配置文件和springmvc配置文件
/**
* @author YeHaocong
* @decription spring配置文件 用於創建IOC父容器
*/
@Configuration
//掃描com.spring.springmvcdemo包及其子包,但是排除Controller註解,這是爲了實現父子容器,表現層的Bean保存在子容器WEB IOC容器
//其他Bean保存在父容器中,子容器可以訪問父容器的bean,父容器不能訪問子容器的bean。
@ComponentScan(basePackages = "com.spring.springmvcdemo",
excludeFilters = {@ComponentScan.Filter(type = FilterType.ANNOTATION,value = {Controller.class})})
public class SpringConfig {
}
/**
* @author YeHaocong
* @decription springMVC配置文件,用於配置Bean到子容器中。
*/
@Configuration
//掃描web包及其子包
@ComponentScan(basePackages = "com.spring.springmvcdemo.web")
public class SpringMVCConfig {
//配置一個視圖解析器到springmvc子容器中。
@Bean
public ViewResolver viewResolver(){
InternalResourceViewResolver viewResolver = new InternalResourceViewResolver();
viewResolver.setPrefix("/WEB-INF/pages/");
viewResolver.setSuffix(".jsp");
return viewResolver;
}
}
第三步:編寫springmvc初始化類
這裏選擇繼承AbstractDispatcherServletInitializer。
public abstract class AbstractDispatcherServletInitializer extends AbstractContextLoaderInitializer {
/**
* DispatcherServlet servlet名稱
*/
public static final String DEFAULT_SERVLET_NAME = "dispatcher";
//初始化方法
@Override
public void onStartup(ServletContext servletContext) throws ServletException {
//先調用父類的onStartup方法
super.onStartup(servletContext);
//該類的onStartup方法主要是註冊一個DispatcherServlet到servletContext中,用於完成對請求的分發。
registerDispatcherServlet(servletContext);
}
/**
* 註冊一個DispatcherServlet
*/
protected void registerDispatcherServlet(ServletContext servletContext) {
//獲取servlet名稱 dispatcher
String servletName = getServletName();
Assert.hasLength(servletName, "getServletName() must not return empty or null");
WebApplicationContext servletAppContext = createServletApplicationContext();
Assert.notNull(servletAppContext,
"createServletApplicationContext() did not return an application " +
"context for servlet [" + servletName + "]");
//創建一個dispatcherServlet
FrameworkServlet dispatcherServlet = createDispatcherServlet(servletAppContext);
dispatcherServlet.setContextInitializers(getServletApplicationContextInitializers());
//註冊到servletContext上下文中
ServletRegistration.Dynamic registration = servletContext.addServlet(servletName, dispatcherServlet);
Assert.notNull(registration,
"Failed to register servlet with name '" + servletName + "'." +
"Check if there is another servlet registered under the same name.");
registration.setLoadOnStartup(1);
//添加mapping 前綴,就是類似項目名稱,端口號localhost:8080/前綴/真正的requestmapping
registration.addMapping(getServletMappings());
//設置是否支持異步
registration.setAsyncSupported(isAsyncSupported());
//獲取過濾器
Filter[] filters = getServletFilters();
if (!ObjectUtils.isEmpty(filters)) {
for (Filter filter : filters) {
//把過濾器註冊到servletContext上下文中
registerServletFilter(servletContext, filter);
}
}
//自定義註冊
customizeRegistration(registration);
}
/**
*
*/
protected String getServletName() {
return DEFAULT_SERVLET_NAME;
}
/**
* 創建一個web容器
*/
protected abstract WebApplicationContext createServletApplicationContext();
/**
*
*/
protected FrameworkServlet createDispatcherServlet(WebApplicationContext servletAppContext) {
return new DispatcherServlet(servletAppContext);
}
/**
*
*/
@Nullable
protected ApplicationContextInitializer<?>[] getServletApplicationContextInitializers() {
return null;
}
/**
*
*/
protected abstract String[] getServletMappings();
/**
* 獲取過濾器,返回空,如果要在springmvc啓動時註冊過濾器可以覆蓋該方法
*/
@Nullable
protected Filter[] getServletFilters() {
return null;
}
/**
* 註冊過濾器到servletContext上下文中
*/
protected FilterRegistration.Dynamic registerServletFilter(ServletContext servletContext, Filter filter) {
String filterName = Conventions.getVariableName(filter);
Dynamic registration = servletContext.addFilter(filterName, filter);
if (registration == null) {
int counter = -1;
while (counter == -1 || registration == null) {
counter++;
registration = servletContext.addFilter(filterName + "#" + counter, filter);
Assert.isTrue(counter < 100,
"Failed to register filter '" + filter + "'." +
"Could the same Filter instance have been registered already?");
}
}
registration.setAsyncSupported(isAsyncSupported());
registration.addMappingForServletNames(getDispatcherTypes(), false, getServletName());
return registration;
}
private EnumSet<DispatcherType> getDispatcherTypes() {
return (isAsyncSupported() ?
EnumSet.of(DispatcherType.REQUEST, DispatcherType.FORWARD, DispatcherType.INCLUDE, DispatcherType.ASYNC) :
EnumSet.of(DispatcherType.REQUEST, DispatcherType.FORWARD, DispatcherType.INCLUDE));
}
/**
*
*/
protected boolean isAsyncSupported() {
return true;
}
/**
*
*/
protected void customizeRegistration(ServletRegistration.Dynamic registration) {
}
}
我的實現類:
public class WebAnnotationInitializer extends AbstractDispatcherServletInitializer {
//創建一個Springmvc子容器。
@Override
protected WebApplicationContext createServletApplicationContext() {
//創建容器對象
AnnotationConfigWebApplicationContext servletContext = new AnnotationConfigWebApplicationContext();
//使用SpringMVCConfig配置類對容器進行初始化。
servletContext.register(SpringMVCConfig.class);
//返回
return servletContext;
}
//設置項目訪問url的前綴,比如如下設置,訪問hello接口就要 http://localhost:8080/springmvc/hello
//如果不想用前綴,就使用 “/”就好。
@Override
protected String[] getServletMappings() {
return new String[]{"/springmvc/*"};
}
//創建spring IOC的父容器/根容器
@Override
protected WebApplicationContext createRootApplicationContext() {
//創建
AnnotationConfigWebApplicationContext rootContext = new AnnotationConfigWebApplicationContext();
//傳入SpringConfig配置類對容器進行初始化
rootContext.register(SpringConfig.class);
//返回
return rootContext;
}
/**
* 在springmvc初始化時添加一些過濾器
* @return
*/
@Override
protected Filter[] getServletFilters() {
//添加一個編碼過濾器
CharacterEncodingFilter filter = new CharacterEncodingFilter();
filter.setEncoding("UTF-8");
return new Filter[]{filter};
}
}
最後一步:編寫controller類用於測試:
/**
* @author YeHaocong
* @decription controller
*/
@Controller
public class SpringMVCDemoController {
@RequestMapping("/hello")
public String springMVCDemo(){
System.out.println("進入此方法");
//返回視圖
return "springmvc";
}
}
jsp所在位置
添加web容器,我這裏是tomcat8.5。
啓動tomcat。運行結果如下:
該項目的詳細啓動流程如下:
-
tomcat啓動時調用從SpringServletContainerInitializer的onStartup方法。找到我們自定義的初始化類。
從上圖可以看出是使用了我們的自定義初始化類WebAnnotationInitializer 。 -
爲我們的初始化類創建對象,並且調用它的onStartup()方法。
-
由於我們沒有複寫父類AbstractDispatcherServletInitializer 的onStartup方法,所以實際上是調用AbstractDispatcherServletInitializer 的onStartup方法。
-
AbstractDispatcherServletInitializer 的onStartup方法首先調用了它的父類AbstractContextLoaderInitializer的onStartup方法。
該方法主要是註冊一個上下文的監聽器和創建IOC父容器。 -
創建IOC父容器。
因爲我們的自定義初始化類中複寫了該方法,所以實際調用的是我們的自定義初始化類的方法。
-
調用完父類的onStartup方法,就返回到AbstractDispatcherServletInitializer的onStartup方法。
註冊一個DispatcherServlet到servlet上下文中。
註冊springmvc子容器,因爲我們的自定義初始化類中複寫了該方法,所以實際調用的是我們的自定義初始化類的方法。
-
創建DispatcherServlet並註冊到servletContext中。
-
添加Mapping的前綴。
因爲我們的自定義初始化類中複寫了該方法,所以實際調用的是我們的自定義初始化類的方法。
-
獲取Filter並添加到servletContext中
因爲我們的自定義初始化類中複寫了該方法,所以實際調用的是我們的自定義初始化類的方法。 -
執行一些自定義的註冊,由於我們demo沒有複寫該具體方法,所以不做任何事。
至此,基本初始化完畢,可以接收請求了。