Servlet 3.0提供了既能在容器中動态注冊servlet的方法,也提供了通過實作ServletContainerInitializer接口的方法實作在容器啟動階段為容器動态注冊Servlet、Filter和listeners。
容器會在應用的啟動階段,調用所有實作ServletContainerInitializer接口類中的onStartup()方法。
而Spring 3.2中,則進一步簡化了這點,隻需要實作WebApplicationInitializer接口就可以了,檢視這個接口的源碼,裡面也非常簡單,隻有一個方法,傳入的參數是ServletContext
public interface WebApplicationInitializer
{
public abstract void onStartup(ServletContext servletcontext)
throws ServletException;
}
spring提供了相關的實作類->
AbstractAnnotationConfigDispatcherServletInitializer、AbstractDispatcherServletInitializer
AbstractContextLoaderInitializer可以動态注冊DispatcherServlet。
一、自定義DispatcherServlet配置
通過下面的spring的實作類AbstractAnnotationConfigDispatcherServletInitializer相關源碼:
public void onStartup(ServletContext servletContext) throws ServletException
{
registerContextLoaderListener(servletContext);
}
protected void registerDispatcherServlet(ServletContext servletContext)
{
String servletName = getServletName();
Assert.hasLength(servletName, "getServletName() may not return empty or null");
WebApplicationContext servletAppContext = createServletApplicationContext();
Assert.notNull(servletAppContext, (new StringBuilder()).append("createServletApplicationContext() did not return an application context for servlet [").append(servletName).append("]").toString());
DispatcherServlet dispatcherServlet = new DispatcherServlet(servletAppContext);
javax.servlet.ServletRegistration.Dynamic registration = servletContext.addServlet(servletName, dispatcherServlet);
Assert.notNull(registration, (new StringBuilder()).append("Failed to register servlet with name '").append(servletName).append("'.").append("Check if there is another servlet registered under the same name.").toString());
registration.setLoadOnStartup();
registration.addMapping(getServletMappings());
registration.setAsyncSupported(isAsyncSupported());
Filter filters[] = getServletFilters();
if(!ObjectUtils.isEmpty(filters))
{
Filter afilter[] = filters;
int i = afilter.length;
for(int j = ; j < i; j++)
{
Filter filter = afilter[j];
registerServletFilter(servletContext, filter);
}
}
customizeRegistration(registration);
}
我們可以知道AbstractAnnotationConfigDispatcherServletInitializer将DispatcherServlet注冊到Servlet容器中之後,會調用customizeRegistration()方法,并将Servlet注冊後得到的Dynamic registration傳遞進來。是以通過customizeRegistration()方法的重寫我們可以對DispatcherServlet進行額外的配置。如下代碼所示:
public class SpitterWebAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer{
@Override
protected Class<?>[] getRootConfigClasses() {
// TODO Auto-generated method stub
return new Class<?>[]{RootConfig.class};
}
@Override
protected Class<?>[] getServletConfigClasses() {
// TODO Auto-generated method stub
return new Class<?>[]{WebConfig.class};
}
@Override
protected String[] getServletMappings() {
// TODO Auto-generated method stub
return new String[] {"/web/*"};
}
//重寫customizeRegistration方法,實作DispatcherServlet的額外配置
@Override
protected void customizeRegistration(javax.servlet.ServletRegistration.Dynamic dynamic){
}
}
借助customizeRegistration()方法中的javax.servlet.ServletRegistration.Dynamic dynamic,我們能完成多項任務。
包括通過調用setLoadOnStartup()設定load-on-start的優先級,通過setInitOarameter()設定初始化參數。通過setMultipartConfig()配置Servlet3.0對multipart的支援。
![](https://img.laitimes.com/img/9ZDMuAjOiMmIsIjOiQnIsICdzFWRoRXdvN1LclHdpZXYyd2LcBzNvwVZ2x2bzNXak9CX90TQNNkRrFlQKBTSvwFbslmZvwFMwQzLcVmepNHdu9mZvwFVywUNMZTY18CX052bm9CX90zdOVTRU1keFR0TzIlMZZXUYpVd1kmYr50MZV3YyI2cKJDT29GRjBjUIF2LcRHelR3LcJzLctmch1mclRXY39DM3MjM1gjMxITNxITM2EDMy8CX0Vmbu4GZzNmLn9Gbi1yZtl2Lc9CX6MHc0RHaiojIsJye.jpg)
二、添加其他的Servlet和Filter
按照AbstractAnnotationConfigDispatcherServletInitializer的定義,他會建立DispatcherServlet和ContextLoaderListener。
但是,如果你想注冊其他的servlet、filter或Listen的話,那該怎麼辦呢?
基于java的初始化容器(initializer)的一個好處就在于,我們可以定義任意數量的初始化類。是以,我們想往web容器中注冊其他元件的話,隻需建立一個新的初始化容器
就可以了。最簡單的方法就是實作Spring的WebApplicationInitializer接口
下面執行個體->添加一個過濾器:
過濾器配置:
public class FilterConfig implements WebApplicationInitializer {
@Override
public void onStartup(ServletContext servletcontext) throws ServletException {
// TODO Auto-generated method stub
Dynamic filter = servletcontext.addFilter("myFilter",CustomerFilter.class);
filter.addMappingForUrlPatterns(null, false, "/web/*");
}
}
這裡addMappingForUrlPatterns(EnumSet dispatcherTypes, boolean isMatchAfter, String urlPatterns[])方法,urlPatterns映射需要執行過濾的路徑
CustomerFilter類
public class CustomerFilter implements Filter{
@Override
public void init(FilterConfig filterconfig) throws ServletException {
// TODO Auto-generated method stub
System.out.println("CustomerFilter init...");
}
@Override
public void doFilter(ServletRequest servletrequest, ServletResponse servletresponse, FilterChain filterchain)
throws IOException, ServletException {
// TODO Auto-generated method stub
System.out.println("測試過濾器.....");
filterchain.doFilter(servletrequest, servletresponse);
}
@Override
public void destroy() {
// TODO Auto-generated method stub
System.out.println("CustomerFilter destroy...");
}
}
這個類需要實作Filter
同理:可添加servlet和listen,隻要類實作WebApplicationInitializer,并重寫其中的onStartup方法
如果要将應用部署到支援Servlet3.0的容器中,那麼WebApplicationInitializer提供了一種通用的方式,是現在JAVA中注冊Servlet、Filter和Listener。不過,如果你隻是注冊Filter,并且該Filter隻會映射到DispatcherServlet上的話,那麼在AbstractAnnotationConfigDispatcherServletInitializer中還有一種快捷方式。
為了注冊Filter,并将其映射到DispatcherServlet,所需要做的僅僅是重寫AbstractAnnotationConfigDispatcherServletInitializer的getServletFilters()方法。如下所示:
@Override
protected Filter[] getServletFilters() {
// TODO Auto-generated method stub
return new Filter[]{new CustomerFilter()};
}
getServletFilters方法傳回的所有Filter都會映射到DispatcherServlet上。
三、在web.xml中聲明DispatcherServlet
在典型的Spring MVC應用中,我們會需要DispatcherServlet和ContextLoaderListener。AbstractAnnotationConfigDispatcherServletInitializer會自動
注冊它們。但是如果需要在web.xml中注冊的話,那就需要我們自己注冊。
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
version="3.0">
<display-name>canal</display-name>
<!--設定上下文配置檔案的位置:檔案會被ContextLoaderListener加載-->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>
classpath*:/applicationContext-*.xml
</param-value>
</context-param>
<!-- 設定字元過濾器非必須-->
<filter>
<filter-name>encodingFilter</filter-name>
<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
<init-param>
<param-name>encoding</param-name>
<param-value>UTF-8</param-value>
</init-param>
<init-param>
<param-name>forceEncoding</param-name>
<param-value>true</param-value>
</init-param>
</filter>
<!-- 指定字元過濾器映射路徑非必須 -->
<filter-mapping>
<filter-name>encodingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<!-- Spring MVC前端處理器,注冊DispatcherServlet -->
<servlet>
<servlet-name>Dispatcher Servlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<!--檔案beans.xml會被DispatcherServlet加載-->
<init-param>
<description>Spring MVC定義Bean檔案,該檔案為空配置,所有配置交給上級WebApplicationContext來處理</description>
<param-name>contextConfigLocation</param-name>
<param-value>WEB-INF/beans.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<!--DispatcherServlet映射路徑,所有以.html結尾的路徑 -->
<servlet-mapping>
<servlet-name>Dispatcher Servlet</servlet-name>
<url-pattern>*.html</url-pattern>
</servlet-mapping>
<!--注冊ContextLoaderListener-->
<listener>
<listener-class>
org.springframework.web.context.ContextLoaderListener
</listener-class>
</listener>
<!--配置其他過濾器及servlet-->
<filter>
<filter-name>springSecurityFilterChain</filter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
</filter>
<filter-mapping>
<filter-name>springSecurityFilterChain</filter-name>
<url-pattern>/web/*</url-pattern>
</filter-mapping>
<servlet>
<servlet-name>CXFService</servlet-name>
<servlet-class>org.apache.cxf.transport.servlet.CXFServlet</servlet-class>
<load-on-startup>2</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>CXFService</servlet-name>
<url-pattern>/web/*</url-pattern>
</servlet-mapping>
</web-app>
ContextLoaderListener和DispatcherServlet各自都會加載一個Spring應用上下文。上下文contextConfigLocation指定了xml檔案的位置。
要在Spring MVC中使用基于Java的配置,我們需要告訴DispatcherServlet和ContextLoaderListener使用AnnotationConfigWebApplicationContext,
這是WebApplicationContext的一個實作類。他會加載Java配置類,而不是使用xml。要實作這種配置,我們可以設定contextClass上下文參數以及
DispatcherServlet的初始化參數。
如下所示,新的web.xml基于java配置
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
version="3.0">
<!-- 指定使用Java配置 -->
<context-param>
<param-name>contextClass</param-name>
<param-value>org.springframework.web.context.support.AnnotationConfigWebApplicationContext</param-value>
</context-param>
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>com.cn.test.config.RootConfig</param-value>
</context-param>
<listener>
<listener-class>
org.springframework.web.context.ContextLoaderListener
</listener-class>
</listener>
<!-- Spring MVC前端處理器,注冊DispatcherServlet -->
<servlet>
<servlet-name>Dispatcher Servlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<!--檔案beans.xml會被DispatcherServlet加載-->
<init-param>
<param-name>contextClass</param-name>
<param-value>org.springframework.web.context.support.AnnotationConfigWebApplicationContext</param-value>
</init-param>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>com.cn.test.config.WebConfig</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<!--DispatcherServlet映射路徑,所有以.html結尾的路徑 -->
<servlet-mapping>
<servlet-name>Dispatcher Servlet</servlet-name>
<url-pattern>/web/*</url-pattern>
</servlet-mapping>
<!--配置其他過濾器及servlet-->
<filter>
<filter-name>customerFilter</filter-name>
<filter-class>com.cn.test.filter.CustomerFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>customerFilter</filter-name>
<url-pattern>/web/*</url-pattern>
</filter-mapping>
</web-app>
四、配置multipart解析器
DispatcherServlet并沒有實作任何解析mulipart請求資料的功能,他将該任務委托給了Spring中的MultipartResolver政策接口的實作。
通過這個實作類來解析multipart請求的内容。從3.1開始,Spring内置類兩個MultipartResolver的實作供我們選擇:
1、CommonsMultipartResolver:使用Jakarta Commons FileUpload解析multipart請求;
2、StandardServletMultipartResolver依賴于Servlet3.0對multipart請求的支援。始于Spring3.1(優選方案)
使用Servlet3.0解析multipart請求
相容Servlet3.0的StandardServletMultipartResolver沒有任何構造參數,也沒有要設定的屬性。這樣,在Spring應用的上下文中,将其
聲明為bean就會變得非常簡單,如下所示:
@Bean
public MultipartResolver multipartResolver(){
return new StandardServletMultipartResolver();
}
既然@Bean方法如此簡單,那麼我們該如何限制StandardServletMultipartResolver的工作方式呢?怎麼設定上傳檔案的大小及臨時存儲目錄呢?
對于沒有構造函數和設定屬性的StandardServletMultipartResolver來說,這似乎是很難限制的。
其實并不是這樣的,我們是有辦法設定StandardServletMultipartResolver的限制條件的,隻不過不在Spring中配置StandardServletMultipartResolver,而隻要在Servlet中指定multipart的配置。還記得我們前面所說的customizeRegistration()方法嗎?下面就用上了此方法:
//重載customizeRegistration方法,實作DispatcherServlet的額外配置
@Override
protected void customizeRegistration(javax.servlet.ServletRegistration.Dynamic dynamic){
//"/tmp/uploads"為臨時存儲路徑
MultipartConfigElement configElement = new MultipartConfigElement("/tmp/uploads");
dynamic.setMultipartConfig(configElement);
}
整個檔案:
public class SpitterWebAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer{
@Override
protected Class<?>[] getRootConfigClasses() {
// TODO Auto-generated method stub
return new Class<?>[]{RootConfig.class};
}
@Override
protected Class<?>[] getServletConfigClasses() {
// TODO Auto-generated method stub
return new Class<?>[]{WebConfig.class};
}
@Override
protected String[] getServletMappings() {
// TODO Auto-generated method stub
return new String[] {"/web/*"};
}
//重載customizeRegistration方法,實作DispatcherServlet的額外配置
@Override
protected void customizeRegistration(javax.servlet.ServletRegistration.Dynamic dynamic){
//"/tmp/uploads"為臨時存儲路徑--強制設定
MultipartConfigElement configElement = new MultipartConfigElement("/tmp/uploads");
dynamic.setMultipartConfig(configElement);
}
@Override
protected Filter[] getServletFilters() {
// TODO Auto-generated method stub
return new Filter[]{new CustomerFilter()};
}
}
使用web.xml配置的代碼片段
<servlet>
<servlet-name>Dispatcher Servlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<!--檔案beans.xml會被DispatcherServlet加載-->
<init-param>
<param-name>contextClass</param-name>
<param-value>org.springframework.web.context.support.AnnotationConfigWebApplicationContext</param-value>
</init-param>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>com.cn.test.config.WebConfig</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
<multipart-config>
<location>/tmp/uploads</location>
<file-size-threshold>0</file-size-threshold>
<max-file-size>2097152</max-file-size><!-- 2M -->
<max-request-size>4194304</max-request-size><!-- 4M -->
</multipart-config>
</servlet>
<!--DispatcherServlet映射路徑,所有以.html結尾的路徑 -->
<servlet-mapping>
<servlet-name>Dispatcher Servlet</servlet-name>
<url-pattern>/web/*</url-pattern>
</servlet-mapping>
配置Jakarta Commons FileUpload multipart解析器
@Bean
public MultipartResolver multipartResolver() throws IOException{
CommonsMultipartResolver resolver = new CommonsMultipartResolver();
resolver.setUploadTempDir(new FileSystemResource("臨時檔案路徑"));//,非必須設定
resolver.setMaxInMemorySize();//最大記憶體大小
resolver.setMaxUploadSize();//上傳檔案大小限制
return resolver;
}
xml檔案設定
<bean id="multipartResolver"
class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
<property name="maxUploadSize" value="2097152" />
<property name="maxInMemorySize" value="4096" />
</bean>
處理multipart請求
在controller方法的接收參數上添加@RequestPart(“file”) byte[] file
例如:
@RequestMapping(value="/home/file",method=RequestMethod.POST)//處理對/web/home/test的請求
public String home_file(Model model,@RequestPart("file") byte[] file){
}
若是送出表單時,沒有選擇圖檔,那麼這個數組是空,而非null。那麼我們要如何将byte數組轉化為存儲檔案那,看下一部分
接收MultipartFile
public String home_file(Model model,@RequestPart("file") MultipartFile file){
}
五、處理異常
項目中實際用到的統一捕獲異常方式:
1、Spring boot中使用的異常捕獲
@Component
@ControllerAdvice
public class GlobalDefaultExceptionHandler{
private Logger exception = LoggerFactory.getLogger("exception");
//添加全局異常處理流程,根據需要設定需要處理的異常
@ExceptionHandler(value=Exception.class)
@ResponseBody
public MsgHeader defaultErrorHandler(HttpServletRequest request,
Exception e) throws Exception
{
//按需重新封裝需要傳回的錯誤資訊
//此處列印錯誤日志
e.printStackTrace();
return new MsgHeader(CodeEnum.EXCEPTION.getCode(),CodeEnum.EXCEPTION.getDesc_enu());
}
//添加全局異常處理流程,捕獲用戶端自己的異常
@ExceptionHandler(value={ServiceException.class})
@ResponseBody
public MsgHeader jsonErrorHandler(HttpServletRequest request,
ServiceException e) throws Exception
{
//此處列印錯誤日志
exception.error(e.getCode()+"---"+e.getDesc());
return new MsgHeader(e.getCode(), e.getDesc());
}
//添加全局異常處理流程,捕獲服務層抛出的自定義異常
@ExceptionHandler(value={BaseException.class})
@ResponseBody
public MsgHeader jsonErrorHandler(HttpServletRequest request,
BaseException e) throws Exception
{
//此處列印錯誤日志
exception.error(e.getCode()+"---"+e.getMsg());
return new MsgHeader(e.getCode(), e.getMsg());
}
}
@ExceptionHandler标注的方法處理給定的異常
類級别使用@ControllerAdvice注解:标明他是一個控制器通知
@ResponseBody傳回json格式資料
這個類一定要配置在spring能夠掃描到的位置
2、Spring中統一捕獲異常
public class ExceptionAdvisor implements ThrowsAdvice{
private static final Logger log = LoggerFactory.getLogger(ExceptionAdvisor.class);
public void afterThrowing(Method method, Object[] args, Object target,
Exception ex) throws Throwable
{
// 在背景中輸出錯誤異常異常資訊,通過log4j輸出。
log.info("**************************************************************");
log.info("Error happened in class: " + target.getClass().getName());
log.info("Error happened in method: " + method.getName());
for (int i = ; i < args.length; i++)
{
log.info("arg[" + i + "]: " + args[i]);
}
log.info("Exception class: " + ex.getClass().getName());
log.info("ex.getMessage():" + ex.getMessage());
log.info("**************************************************************");
// 在這裡判斷異常,根據不同的異常傳回錯誤。
if (ex.getClass().equals(ConstraintViolationException.class)){
ex.printStackTrace();
ConstraintViolationException exc = (ConstraintViolationException) ex;
String enumName = exc.getConstraintViolations().iterator().next().getMessage();
log.info("enumName--------"+enumName);
CodeEnum enumCode;
try {
enumCode = CodeEnum.valueOf(enumName);
} catch (Exception e) {
//若是名稱不能成功轉化為枚舉,則給定common枚舉
enumCode = CodeEnum.VALIDATE_COMMON;
}
log.info("code:"+enumCode.getCode()+"--enu desc--"+enumCode.getDesc_enu());
throw new BaseException(enumCode.getCode(), enumCode.getDesc_enu());
}else{
ex.printStackTrace();
throw ex;
}
}
}
springContext.xml中添加配置
<bean id="exceptionHandler" class="com.isgo.gallerydao.core.exception.ExceptionAdvisor"></bean>
<bean class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator" >
<property name="beanNames">
<list> <!-- 配置需要進行日志記錄的Service和Dao -->
<value>*Service</value> <!-- Service層的Bean ID 命名要以Service結尾 -->
</list>
</property>
<property name="interceptorNames">
<list>
<value>exceptionHandler</value>
</list>
</property>
</bean>
3、CXF配置統一捕獲異常:
import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.ResponseBuilder;
import javax.ws.rs.ext.ExceptionMapper;
public class InvokeFaultExceptionMapper implements ExceptionMapper {
private static Logger logger = LogManager.getLogger("exception");
@Override
public Response toResponse(Throwable ex) {
StackTraceElement[] trace = new StackTraceElement[];
trace[] = ex.getStackTrace()[];
ex.setStackTrace(trace);
ResponseBuilder rb = Response.status(Response.Status.OK);
rb.type("application/json;charset=UTF-8");
if (ex instanceof ServiceException) {//自定義的異常類
ServiceException e = (ServiceException) ex;
ServiceExceptionEntity entity = new ServiceExceptionEntity(e.getCode(),e.getDesc());
rb.entity(entity);
}else{
ServiceExceptionEntity entity = new ServiceExceptionEntity(CodeEnum.EXCEPTION.getCode(),
CodeEnum.EXCEPTION.getDesc_enu());
rb.entity(entity);
}
if(null!=trace[]){
logger.error("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!");
logger.error("className:{},fileName:{},methodName:{},lineNumber:{},cause:{}.",trace[].getClassName(),
trace[].getFileName(),trace[].getMethodName(),trace[].getLineNumber(),ex);
}
rb.language(Locale.SIMPLIFIED_CHINESE);
Response r = rb.build();
return r;
}
}
ExceptionMapper在包:javax.ws.rs-api.jar中
<bean id="invokeFaultExceptionMapper" class="com.canal.api.exception.InvokeFaultExceptionMapper"/>
六、@ControllerAdvice注解
控制類通知,這個類會包含一個或多個如下類型的方法:
1、@ExceptionHandler注解标注的方法–使所有的控制類異常在一個地方統一處理。參考Spring boot中使用的異常捕獲
2、@InitBinder注解标注的方法
3、@ModelAttribute注解标注的方法