SpringMVC註解驅動開發

前言

此文章是對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。運行結果如下:
在這裏插入圖片描述

該項目的詳細啓動流程如下:

  1. tomcat啓動時調用從SpringServletContainerInitializer的onStartup方法。找到我們自定義的初始化類。
    在這裏插入圖片描述
    從上圖可以看出是使用了我們的自定義初始化類WebAnnotationInitializer 。

  2. 爲我們的初始化類創建對象,並且調用它的onStartup()方法。
    在這裏插入圖片描述

  3. 由於我們沒有複寫父類AbstractDispatcherServletInitializer 的onStartup方法,所以實際上是調用AbstractDispatcherServletInitializer 的onStartup方法。
    在這裏插入圖片描述

  4. AbstractDispatcherServletInitializer 的onStartup方法首先調用了它的父類AbstractContextLoaderInitializer的onStartup方法。
    在這裏插入圖片描述
    該方法主要是註冊一個上下文的監聽器和創建IOC父容器。

  5. 創建IOC父容器。
    在這裏插入圖片描述
    因爲我們的自定義初始化類中複寫了該方法,所以實際調用的是我們的自定義初始化類的方法。
    在這裏插入圖片描述

  6. 調用完父類的onStartup方法,就返回到AbstractDispatcherServletInitializer的onStartup方法。
    在這裏插入圖片描述
    註冊一個DispatcherServlet到servlet上下文中。

在這裏插入圖片描述
註冊springmvc子容器,因爲我們的自定義初始化類中複寫了該方法,所以實際調用的是我們的自定義初始化類的方法。
在這裏插入圖片描述

  1. 創建DispatcherServlet並註冊到servletContext中。
    在這裏插入圖片描述

  2. 添加Mapping的前綴。
    在這裏插入圖片描述
    因爲我們的自定義初始化類中複寫了該方法,所以實際調用的是我們的自定義初始化類的方法。
    在這裏插入圖片描述

  3. 獲取Filter並添加到servletContext中
    在這裏插入圖片描述
    因爲我們的自定義初始化類中複寫了該方法,所以實際調用的是我們的自定義初始化類的方法。

  4. 執行一些自定義的註冊,由於我們demo沒有複寫該具體方法,所以不做任何事。
    在這裏插入圖片描述
    至此,基本初始化完畢,可以接收請求了。

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