基於注解的springmvc開發


原理簡析

1. 背景知識:org.springframework.web.ServletContainerInitializer接口

在基於注解的servlet開發中,ServletContainerInitializer接口用於代替web.xml。它只有一個方法:onStartup,可以在其中注冊servlet、攔截器(Filter)、監聽器(Listener)這三大組件。另外,ServletContainerInitializer還可以使用@HandlesTypes在onStartup方法的參數列表中注入感興趣的類。servlet容器啟動時,會掃描每個jar包的項目根目錄下的/META-INF/services/javax.servlet.ServletContainerInitializer文件,執行這個文件中指定的ServletContainerInitializer接口的實現類的onStartup方法。

 

2. org.springframework.web包提供的ServletContainerInitializer實現類

org.springframework.web包的/META-INF/services/javax.servlet.ServletContainerInitializer文件指定了ServletContainerInitializer接口的實現類:SpringServletContainerInitializer,首先來看一下這個類的spring源碼:

package org.springframework.web; @HandlesTypes(WebApplicationInitializer.class) //(1) public class SpringServletContainerInitializer implements ServletContainerInitializer {

    @Override
    public void onStartup(Set<Class<?>> webAppInitializerClasses, ServletContext servletContext)
            throws ServletException {

        List<WebApplicationInitializer> initializers = new LinkedList<WebApplicationInitializer>();

        if (webAppInitializerClasses != null) {
            for (Class<?> waiClass : webAppInitializerClasses) {
                // Be defensive: Some servlet containers provide us with invalid classes,
                // no matter what @HandlesTypes says...
                if (!waiClass.isInterface() && !Modifier.isAbstract(waiClass.getModifiers()) &&
                        WebApplicationInitializer.class.isAssignableFrom(waiClass)) {
                    try {
                        initializers.add((WebApplicationInitializer) waiClass.newInstance()); //(2)
                    }
                    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) {
            initializer.onStartup(servletContext); //(3)
        }
    }

}

(1) 通過@HandlesType注解在onStartup方法的參數列表中注入感興趣的類,即WebApplicationInitializer;

(2) 將WebApplicationInitializer的每個實現類,都新建一個實例,並放入initializers列表中;

(3) 遍歷initializers列表,對每個WebApplicationInitializer實例執行其onStartup方法。

那么問題來了:WebApplicationInitializer有哪些實現類,是用來干什么的?

 

3. WebApplicationInitializer的實現類及其功能

WebApplicationInitializer的實現類有很多,重點看一下AbstractAnnotationConfigDispatcherServletInitializer

package org.springframework.web.servlet.support;

public abstract class AbstractAnnotationConfigDispatcherServletInitializer
        extends AbstractDispatcherServletInitializer {

    @Override
    @Nullable
    protected WebApplicationContext createRootApplicationContext() {
        Class<?>[] configClasses = getRootConfigClasses();
        if (!ObjectUtils.isEmpty(configClasses)) {
            AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
            context.register(configClasses);
            return context;
        }
        else {
            return null;
        }
    }

    @Override
    protected WebApplicationContext createServletApplicationContext() {
        AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
        Class<?>[] configClasses = getServletConfigClasses();
        if (!ObjectUtils.isEmpty(configClasses)) {
            context.register(configClasses);
        }
        return context;
    }

    @Nullable
    protected abstract Class<?>[] getRootConfigClasses();

    @Nullable
    protected abstract Class<?>[] getServletConfigClasses();

}

這個類提供了兩個方法的實現,以及兩個抽象方法供子類繼承

(1) createRootApplicationContext:創建根容器;

(2) createServletApplicationContext:創建servlet容器;

(3) getRootConfigClasses:抽象類,用於注冊根容器的配置類,相當於spring.xml;

(4) getServletConfigClasses:抽象的類,用於注冊servlet容器的配置類,相當於springmvc.xml;

另外,它還從AbstractDispatcherServletInitializer類繼承了getServletMappings方法,用於注冊servlet的映射。

因此,我們可以自定義一個WebApplicationInitializer的實現類,繼承AbstractAnnotationConfigDispatcherServletInitializer;在servlet容器啟動時,會創建spring根容器和servlet容器,代替web.xml配置文件。同時,我們可以看到,在基於注解的springmvc開發中,真正用於代替web.xml的是WebApplicationInitializer,而並不是ServletContainerInitializer,這與本文開頭提到的基於注解的servlet開發有些區別。

 

4. 根容器和servlet容器

根容器用於管理@Service、@Repository等業務邏輯層和數據庫交互層組件;

servlet容器用於管理@Controller、視圖解析器、攔截器等跟頁面處理有關的組件。

 

使用步驟

0. 導包或添加依賴:spring-web、spring-webmvc

1. 編寫數據庫訪問層、業務邏輯層、控制層等組件,這個跟基於配置文件的springmvc沒有區別;

2. 編寫根容器和servlet容器的配置類,這里不需要添加@Configuration注解;

3. 自定義WebApplicationInitializer,繼承AbstractAnnotationConfigDispatcherServletInitializer;

4. 在3的實現類中注冊根容器和servlet容器的配置類,以及servlet映射;

5. 在servlet容器中中注冊視圖解析器、攔截器等組件,用法是使servlet容器配置類實現WebMvcConfigurer接口,

    然后選擇相應的方法進行注冊,詳見示例demo。

 

示例Demo

pom文件

<dependency>
  <groupId>org.springframework</groupId>
  <artifactId>spring-web</artifactId>
  <version>5.1.6-SNAPSHOT</version>
</dependency>
<dependency>
  <groupId>org.springframework</groupId>
  <artifactId>spring-webmvc</artifactId>
  <version>5.1.6-SNAPSHOT</version> 
</dependency>

 

業務邏輯層組件

package cn.monolog.annabelle.springmvc.service;

import org.springframework.stereotype.Service;

/**
 * 業務邏輯層組件
 * created on 2019-05-04
 */
@Service
public class BusinessService {

    public String resolve(String source) {
        return "hello " + source;
    }
}

 

控制層組件

package cn.monolog.annabelle.springmvc.controller;

import cn.monolog.annabelle.springmvc.service.BusinessService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

/**
 * 控制層組件
 * created on 2019-05-04
 */
@Controller
@RequestMapping(value = "/business")
public class BusinessController {

    //從容器中自動裝配組件
    @Autowired
    private BusinessService businessService;

    @GetMapping(value = "/resolve")
    @ResponseBody
    public String resolve(String source) {
        String result = this.businessService.resolve(source);
        return result;
    }

}

 

自定義springmvc攔截器

package cn.monolog.annabelle.springmvc.interceptor;

import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * 自定義的springmvc攔截器
 * created on 2019-05-12
 */
public class CustomedInterceptor implements HandlerInterceptor {

    /**
     * 重寫preHandle方法
     * @param request
     * @param response
     * @param handler
     * @return
     * @throws Exception
     */
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        System.out.println("preHandle正在執行...");
        return true;
    }

    /**
     * 重寫postHandle方法
     * @param request
     * @param response
     * @param handler
     * @param modelAndView
     * @throws Exception
     */
    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        System.out.println("postHandle正在執行...");
    }

    /**
     * 重寫afterCompletion方法
     * @param request
     * @param response
     * @param handler
     * @param ex
     * @throws Exception
     */
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        System.out.println("afterCompletion正在執行...");
    }
}

 

根容器的配置類,用於管理數據庫訪問層、業務邏輯層等組件,相當於spring.xml

package cn.monolog.annabelle.springmvc.config;

import org.springframework.context.annotation.ComponentScan;

/**
 * 根容器配置類
 * created on 2019-05-04
 */
@Configuration @ComponentScan(basePackages = {"cn.monolog.annabelle.springmvc.service"}) public class RootConfig { }

 

servlet容器的配置類,用於管理控制層、視圖解析器等組件,相當於springmvc.xml

可以在其中配置視圖解析器、靜態資源解析器、攔截器等組件

package cn.monolog.annabelle.springmvc.config;

import cn.monolog.annabelle.springmvc.interceptor.CustomedInterceptor;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.*;

/**
 * servlet容器配置類
* @EnableWebMvc相當於配置文件中的<mvc:annotation-drivern /> * created on 2019-05-04
*/ @Configuration @ComponentScan(basePackages = {"cn.monolog.annabelle.springmvc.controller"}) @EnableWebMvc public class ServletConfig implements WebMvcConfigurer { /** * 注冊視圖解析器 * @param registry */ @Override public void configureViewResolvers(ViewResolverRegistry registry) { registry.jsp("/WEB-INF/views/", ".jsp"); } /** * 注冊靜態資源解析器 * 將springmvc處理不了的請求交給tomcat * @param configurer */ @Override public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) { configurer.enable(); } /** * 注冊攔截器 * @param registry */ @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(new CustomedInterceptor()); } }

 

自定義的WebApplicationInitializer,用於注冊根容器、servlet容器、servlet映射、攔截器、監聽器等,相當於web.xml

package cn.monolog.annabelle.springmvc.initializer;

import cn.monolog.annabelle.springmvc.config.RootConfig;
import cn.monolog.annabelle.springmvc.config.ServletConfig;
import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer;

/**
 * 自定義web容器啟動器
 * created on 2019-05-04
 */
public class CustomerServletInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {

    /**
     * 注冊根容器配置類
     * @return
     */
    @Override
    protected Class<?>[] getRootConfigClasses() {
        return new Class[]{RootConfig.class};
    }

    /**
     * 注冊servlet容器配置類
     * @return
     */
    @Override
    protected Class<?>[] getServletConfigClasses() {
        return new Class[]{ServletConfig.class};
    }

    /**
     * 注冊servlet的映射
     * @return
     */
    @Override
    protected String[] getServletMappings() {
        return new String[]{"/"};
    }
}

 

測試代碼

<html>
<head>
<title>homepage</title>
<style type="text/css">
    a {
        color: blueviolet;
        font-size: 20px;
    }
</style>
</head>
<body>
<a href="/annabelle/business/resolve?source=violet">start...</a>
</body>
</html>

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM