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。
通过下面的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(1);
registration.addMapping(getServletMappings());
registration.setAsyncSupported(isAsyncSupported());
Filter filters[] = getServletFilters();
if(!ObjectUtils.isEmpty(filters))
{
Filter afilter[] = filters;
int i = afilter.length;
for(int j = 0; 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的支持。
按照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上。
在典型的Spring MVC应用中,我们会需要DispatcherServlet和ContextLoaderListener。AbstractAnnotationConfigDispatcherServletInitializer会自动
注册它们。但是如果需要在web.xml中注册的话,那就需要我们自己注册。
<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>canaldisplay-name>
<context-param>
<param-name>contextConfigLocationparam-name>
<param-value>
classpath*:/applicationContext-*.xml
param-value>
context-param>
<filter>
<filter-name>encodingFilterfilter-name>
<filter-class>org.springframework.web.filter.CharacterEncodingFilterfilter-class>
<init-param>
<param-name>encodingparam-name>
<param-value>UTF-8param-value>
init-param>
<init-param>
<param-name>forceEncodingparam-name>
<param-value>trueparam-value>
init-param>
filter>
<filter-mapping>
<filter-name>encodingFilterfilter-name>
<url-pattern>/*url-pattern>
filter-mapping>
<servlet>
<servlet-name>Dispatcher Servletservlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServletservlet-class>
<init-param>
<description>Spring MVC定义Bean文件,该文件为空配置,所有配置交给上级WebApplicationContext来处理description>
<param-name>contextConfigLocationparam-name>
<param-value>WEB-INF/beans.xmlparam-value>
init-param>
<load-on-startup>1load-on-startup>
servlet>
<servlet-mapping>
<servlet-name>Dispatcher Servletservlet-name>
<url-pattern>*.htmlurl-pattern>
servlet-mapping>
<listener>
<listener-class>
org.springframework.web.context.ContextLoaderListener
listener-class>
listener>
<filter>
<filter-name>springSecurityFilterChainfilter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxyfilter-class>
filter>
<filter-mapping>
<filter-name>springSecurityFilterChainfilter-name>
<url-pattern>/web/*url-pattern>
filter-mapping>
<servlet>
<servlet-name>CXFServiceservlet-name>
<servlet-class>org.apache.cxf.transport.servlet.CXFServletservlet-class>
<load-on-startup>2load-on-startup>
servlet>
<servlet-mapping>
<servlet-name>CXFServiceservlet-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配置
<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">
<context-param>
<param-name>contextClassparam-name>
<param-value>org.springframework.web.context.support.AnnotationConfigWebApplicationContextparam-value>
context-param>
<context-param>
<param-name>contextConfigLocationparam-name>
<param-value>com.cn.test.config.RootConfigparam-value>
context-param>
<listener>
<listener-class>
org.springframework.web.context.ContextLoaderListener
listener-class>
listener>
<servlet>
<servlet-name>Dispatcher Servletservlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServletservlet-class>
<init-param>
<param-name>contextClassparam-name>
<param-value>org.springframework.web.context.support.AnnotationConfigWebApplicationContextparam-value>
init-param>
<init-param>
<param-name>contextConfigLocationparam-name>
<param-value>com.cn.test.config.WebConfigparam-value>
init-param>
<load-on-startup>1load-on-startup>
servlet>
<servlet-mapping>
<servlet-name>Dispatcher Servletservlet-name>
<url-pattern>/web/*url-pattern>
servlet-mapping>
<filter>
<filter-name>customerFilterfilter-name>
<filter-class>com.cn.test.filter.CustomerFilterfilter-class>
filter>
<filter-mapping>
<filter-name>customerFilterfilter-name>
<url-pattern>/web/*url-pattern>
filter-mapping>
web-app>
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()};
}
}
<servlet>
<servlet-name>Dispatcher Servletservlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServletservlet-class>
<init-param>
<param-name>contextClassparam-name>
<param-value>org.springframework.web.context.support.AnnotationConfigWebApplicationContextparam-value>
init-param>
<init-param>
<param-name>contextConfigLocationparam-name>
<param-value>com.cn.test.config.WebConfigparam-value>
init-param>
<load-on-startup>1load-on-startup>
<multipart-config>
<location>/tmp/uploadslocation>
<file-size-threshold>0file-size-threshold>
<max-file-size>2097152max-file-size>
<max-request-size>4194304max-request-size>
multipart-config>
servlet>
<servlet-mapping>
<servlet-name>Dispatcher Servletservlet-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(4096);//最大内存大小
resolver.setMaxUploadSize(2097152);//上传文件大小限制
return resolver;
}
xml文件设置
id="multipartResolver"
class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
<property name="maxUploadSize" value="2097152" />
<property name="maxInMemorySize" value="4096" />
处理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 = 0; 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>
<value>*Servicevalue>
list>
property>
<property name="interceptorNames">
<list>
<value>exceptionHandlervalue>
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[1];
trace[0] = ex.getStackTrace()[0];
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[0]){
logger.error("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!");
logger.error("className:{},fileName:{},methodName:{},lineNumber:{},cause:{}.",trace[0].getClassName(),
trace[0].getFileName(),trace[0].getMethodName(),trace[0].getLineNumber(),ex);
}
rb.language(Locale.SIMPLIFIED_CHINESE);
Response r = rb.build();
return r;
}
}
ExceptionMapper在包:javax.ws.rs-api.jar中
id="invokeFaultExceptionMapper" class="com.canal.api.exception.InvokeFaultExceptionMapper"/>
控制类通知,这个类会包含一个或多个如下类型的方法:
1、@ExceptionHandler注解标注的方法–使所有的控制类异常在一个地方统一处理。参考Spring boot中使用的异常捕获
2、@InitBinder注解标注的方法
3、@ModelAttribute注解标注的方法