在以往开发JavaWeb项目中,我们可以在web.xml中配置三大组件:Listener,Filter,Servlet,在Servlet 3.0(Tomcat 7.0+)版本以后,
支持使用注解来配置这三大组件,并且可以使用注解在Servlet容器启动后初始化之前做一些事情,因此web.xml并不是必须的。
通过注解来注册三大组件
1. @WebListener
@WebListener
public class SmartListener implements ServletContextListener
{
@Override
public void contextInitialized(ServletContextEvent servletContextEvent)
{
}
@Override
public void contextDestroyed(ServletContextEvent servletContextEvent)
{
}
}
2. @WebFilter
有urlPatterns,initParams等注解属性可配置,与web.xml中配置Filter一致
@WebFilter("/*")
public class SmartFilter implements Filter
{
@Override
public void init(FilterConfig filterConfig) throws ServletException
{
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException
{
chain.doFilter(request, response);
}
@Override
public void destroy()
{
}
}
3. @WebServlet
有urlPatterns,loadOnStartup,initParams等注解属性可配置,与web.xml中配置Servlet一致
@WebServlet("/smartServlet")
public class SmartServlet extends HttpServlet
{
private static final long serialVersionUID = 1093068431684465016L;
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
{
response.getWriter().println("SmartServlet GET");
}
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
{
response.getWriter().println("SmartServlet POST");
}
}
以往web.xml中经典的Spring Web MVC配置
contextConfigLocation
classpath:spring/*.spring.xml
org.springframework.web.context.ContextLoaderListener
characterEncodingFilter
org.springframework.web.filter.CharacterEncodingFilter
encoding
UTF-8
forceEncoding
true
characterEncodingFilter
/*
dispatcherServlet
org.springframework.web.servlet.DispatcherServlet
contextConfigLocation
classpath:spring/dispatcher.spring.xml
1
dispatcherServlet
/
而在Servlet 3.0+版本中,我们可以通过在jar包配置ServletContainerInitializer接口的实现类,在容器启动时注册三大组件
Servlet容器启动时会扫描应用包中每一个jar包中ServletContainerInitializer接口的实现类,该实现类全类名必须在jar包
/META-INF/service/javax.servlet.ServletContainerInitializer文件中配置
/**
* 把SmartServletContainerInitializer全类路径放入到/META-INF/service/javax.servlet.ServletContainerInitializer中
*/
public class SmartServletContainerInitializer implements ServletContainerInitializer
{
/**
* 在Servlet容器启动时调用onStartup方法
*/
@Override
public void onStartup(Set
{
// 这里可注册Servlet三大主键
// servletContext.addListener(className);
// servletContext.addFilter(filterName, filterClass).addMappingForUrlPatterns(EnumSet.of(DispatcherType.REQUEST), true, "/*");
// servletContext.addServlet(servletName, className).addMapping("/");
}
}
ServletContainerInitializer实现类上面可以使用@HandlesTypes({XXX.class})注解,在启动执行onStartup方法时,会将该注解中配置的类
的所有子类作为参数Set
例如spring-web-4.3.16.RELEASE.jar中有
/META-INF/service/javax.servlet.ServletContainerInitializer文件,里面的内容为
org.springframework.web.SpringServletContainerInitializer
查看SpringServletContainerInitializer.java源码
@HandlesTypes({WebApplicationInitializer.class})
public class SpringServletContainerInitializer implements ServletContainerInitializer
{
/**
* 传入WebApplicationInitializer类的所有子类
*/
public void onStartup(Set> webAppInitializerClasses, ServletContext servletContext) throws ServletException
{
List initializers = new LinkedList();
if (webAppInitializerClasses != null)
{
for (Class> waiClass : webAppInitializerClasses)
{
// 过滤掉所有WebApplicationInitializer子类中的接口类和抽象类
if ((!waiClass.isInterface()) && (!Modifier.isAbstract(waiClass.getModifiers())) &&
(WebApplicationInitializer.class.isAssignableFrom(waiClass)))
{
try
{
// 通过反射创建对象
initializers.add((WebApplicationInitializer)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);
}
}
}
可以看到,在SpringServletContainerInitializer类中,Servlet容器启动时,会执行onStartup方法,并把类上注解
@HandlesTypes({WebApplicationInitializer.class})中WebApplicationInitializer.java接口的所有子类作为集合参数
webAppInitializerClasses传入,那我们就可以通过实现WebApplicationInitializer.java接口,从而在Servlet启动时注册
一些组件和启动Spring IOC容器
WebApplicationInitializer.java接口又提供了多层子接口
WebApplicationInitializer
AbstractContextLoaderInitializer
AbstractDispatcherServletInitializer
AbstractAnnotationConfigDispatcherServletInitializer
别急,来一个个分析
AbstractContextLoaderInitializer.java中提供了onStartup具体实现
public abstract class AbstractContextLoaderInitializer implements WebApplicationInitializer
{
protected final Log logger = LogFactory.getLog(getClass());
public void onStartup(ServletContext servletContext) throws ServletException
{
// 调用registerContextLoaderListener方法
registerContextLoaderListener(servletContext);
}
protected void registerContextLoaderListener(ServletContext servletContext)
{
// 创建Spring IOC容器
WebApplicationContext rootAppContext = createRootApplicationContext();
if (rootAppContext != null)
{
ContextLoaderListener listener = new ContextLoaderListener(rootAppContext);
listener.setContextInitializers(getRootApplicationContextInitializers());
// 创建好的容器通过ContextLoaderListener保存到servletContext中
servletContext.addListener(listener);
}
else
{
logger.debug("No ContextLoaderListener registered, as createRootApplicationContext() did not return an application context");
}
}
/**
* 对子类暴露createRootApplicationContext,用于创建Spring IOC容器
*/
protected abstract WebApplicationContext createRootApplicationContext();
protected ApplicationContextInitializer>[] getRootApplicationContextInitializers()
{
return null;
}
}
AbstractDispatcherServletInitializer.java
public abstract class AbstractDispatcherServletInitializer extends AbstractContextLoaderInitializer
{
public static final String DEFAULT_SERVLET_NAME = "dispatcher";
public void onStartup(ServletContext servletContext) throws ServletException
{
// 先创建Spring IOC容器
super.onStartup(servletContext);
// 再创建Spring Web MVC容器
registerDispatcherServlet(servletContext);
}
protected void registerDispatcherServlet(ServletContext servletContext)
{
String servletName = getServletName();
Assert.hasLength(servletName, "getServletName() must not return empty or null");
// 创建Spring Web MVC容器
WebApplicationContext servletAppContext = createServletApplicationContext();
Assert.notNull(servletAppContext,
"createServletApplicationContext() did not return an application context for servlet [" + servletName
+ "]");
FrameworkServlet dispatcherServlet = createDispatcherServlet(servletAppContext);
dispatcherServlet.setContextInitializers(getServletApplicationContextInitializers());
// 把dispatcherServlet添加到Servlet容器中
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);
registration.addMapping(getServletMappings());
registration.setAsyncSupported(isAsyncSupported());
// 这里可以注册一些Filter
Filter[] filters = getServletFilters();
if (!ObjectUtils.isEmpty(filters))
{
for (Filter filter : filters)
{
registerServletFilter(servletContext, filter);
}
}
customizeRegistration(registration);
}
protected String getServletName()
{
// Servlet名称默认提供
return DEFAULT_SERVLET_NAME;
}
/**
* 对子类暴露createServletApplicationContext方法,用于创建Spring Web MVC容器
*/
protected abstract WebApplicationContext createServletApplicationContext();
protected FrameworkServlet createDispatcherServlet(WebApplicationContext servletAppContext)
{
return new DispatcherServlet(servletAppContext);
}
protected ApplicationContextInitializer>[] getServletApplicationContextInitializers()
{
return null;
}
/**
* 配置dispatcherServlet拦截的路径
*/
protected abstract String[] getServletMappings();
protected Filter[] getServletFilters()
{
return null;
}
protected FilterRegistration.Dynamic registerServletFilter(ServletContext servletContext, Filter filter)
{
String filterName = Conventions.getVariableName(filter);
FilterRegistration.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, new String[] { getServletName() });
return registration;
}
private EnumSet 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)
{
}
}
AbstractAnnotationConfigDispatcherServletInitializer.java
public abstract class AbstractAnnotationConfigDispatcherServletInitializer extends AbstractDispatcherServletInitializer
{
/**
* 通过Java配置类创建Spring IOC容器,向下暴露getRootConfigClasses方法
*/
protected WebApplicationContext createRootApplicationContext()
{
Class>[] configClasses = getRootConfigClasses();
if (!ObjectUtils.isEmpty(configClasses))
{
AnnotationConfigWebApplicationContext rootAppContext = new AnnotationConfigWebApplicationContext();
rootAppContext.register(configClasses);
return rootAppContext;
}
return null;
}
/**
* 通过Java配置类创建Spring Web MVC容器,向下暴露getServletConfigClasses方法
*/
protected WebApplicationContext createServletApplicationContext()
{
AnnotationConfigWebApplicationContext servletAppContext = new AnnotationConfigWebApplicationContext();
Class>[] configClasses = getServletConfigClasses();
if (!ObjectUtils.isEmpty(configClasses))
{
servletAppContext.register(configClasses);
}
return servletAppContext;
}
protected abstract Class>[] getRootConfigClasses();
protected abstract Class>[] getServletConfigClasses();
}
经过上面的分析,我们只需要实现AbstractAnnotationConfigDispatcherServletInitializer.java接口,
并实现getRootConfigClasses和getServletConfigClasses方法,就可以实现全注解配置Spring Web MVC开发环境
新建一个简单的Maven工程,打包方式选择war,如下图
在pom.xml中加入servlet-api,注意需要3.0+版本
加入Spring Web MVC依赖
加入Spring Aop依赖
加入数据源依赖等等
此时IDE会报错,提示Web项目必须有web.xml,可以在pom.xml中配置
新建一个AbstractAnnotationConfigDispatcherServletInitializer接口实现类
public class SmartWebConfigServletInitializer extends AbstractAnnotationConfigDispatcherServletInitializer
{
@Override
public void onStartup(ServletContext servletContext) throws ServletException
{
// 调用父类方法创建两个容器
super.onStartup(servletContext);
CharacterEncodingFilter characterEncodingFilter = new CharacterEncodingFilter("UTF-8");
characterEncodingFilter.setForceEncoding(true);
// 注册字符编码过滤器,也可以复写getServletFilters方法来注册Filter
servletContext.addFilter("characterEncodingFilter", characterEncodingFilter)
.addMappingForUrlPatterns(EnumSet.of(DispatcherType.REQUEST), true, "/*");
// 相当于如下配置
/*
characterEncodingFilter
org.springframework.web.filter.CharacterEncodingFilter
encoding
UTF-8
forceEncoding
true
characterEncodingFilter
/*
*/
}
@Override
protected Class>[] getRootConfigClasses()
{
return new Class>[] { SmartApplicationConfig.class };
// 相当于如下配置
/*
contextConfigLocation
classpath:spring/*.spring.xml
org.springframework.web.context.ContextLoaderListener
*/
}
@Override
protected Class>[] getServletConfigClasses()
{
return new Class>[] { SmartDispatcherConfig.class };
// 相当于如下配置
/*
dispatcherServlet
org.springframework.web.servlet.DispatcherServlet
contextConfigLocation
classpath:spring/dispatcher.spring.xml
1
dispatcherServlet
/
*/
}
@Override
protected String[] getServletMappings()
{
// / 拦截所有请求,包括静态资源,但是不包括jsp
// /* 拦截所有请求,包括jsp
return new String[] { "/" };
}
}
在上面SmartWebConfigServletInitializer.java类中,我们用SmartApplicationConfig.class代替了以往传统的Spring容器xml配置文件,
用SmartDispatcherConfig.class代替了Spring Web的xml配置文件
SmartApplicationConfig.java中,可以配置装配Bean,配置包扫描,导入properties配置,开启支持Aop,配置数据源,事务管理器等等
@ComponentScan(value = "com.realjt.smart", excludeFilters = {
@Filter(type = FilterType.ANNOTATION, classes = { Controller.class, ControllerAdvice.class }) })
@PropertySource(value = { "classpath:jdbc.properties" }, encoding = "UTF-8", ignoreResourceNotFound = true)
@EnableAspectJAutoProxy
@EnableTransactionManagement
public class SmartApplicationConfig
{
}
SmartDispatcherConfig.java中,一样可以配置包扫描,使用@EnableWebMvc注解以及继承WebMvcConfigurerAdapter
来配置更多Spring Web MVC相关配置,更多配置可以参考Spring官方文档
@ComponentScan(value = "com.realjt.smart", useDefaultFilters = false, includeFilters = {
@Filter(type = FilterType.ANNOTATION, classes = { Controller.class, ControllerAdvice.class }) })
@EnableWebMvc
public class SmartDispatcherConfig extends WebMvcConfigurerAdapter
{
@Override
public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer)
{
// 开启静态资源放行
configurer.enable();
}
@Override
public void configureViewResolvers(ViewResolverRegistry registry)
{
// 注册视图解析器
registry.jsp("/WEB-INF/jsp/", ".jsp");
}
@Override
public void addInterceptors(InterceptorRegistry registry)
{
// 注册拦截器
}
@Override
public void addFormatters(FormatterRegistry registry)
{
}
@Override
public void configureMessageConverters(List> converters)
{
Charset charset = Charset.forName("UTF-8");
List mediaTypes = new ArrayList<>();
mediaTypes.add(new MediaType("application", "json", charset));
mediaTypes.add(new MediaType("text", "html", charset));
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.setDateFormat(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));
StringHttpMessageConverter stringHttpMessageConverter = new StringHttpMessageConverter(charset);
MappingJackson2HttpMessageConverter jackson2HttpMessageConverter = new MappingJackson2HttpMessageConverter();
jackson2HttpMessageConverter.setSupportedMediaTypes(mediaTypes);
jackson2HttpMessageConverter.setObjectMapper(objectMapper);
converters.add(stringHttpMessageConverter);
converters.add(jackson2HttpMessageConverter);
}
}
至此,我们就可以编写Controller,Service,Repository等组件,并启动运行Web项目,默认访问index.jsp或index.html。