注解配置SpringMVC原理简述
1. 准备知识
1.1 两个应用上下文
在SpringMVC中,有两个应用上下文:RootApplicationContext、WebApplicationContext。其中,WebApplicationContext是DispatcherServlet专属的上下文,用来加载Controller、ViewResolver、HandlerMapping等web相关的Bean。RootApplicationContext则是加载数据库、@Service等中间件中的Bean。其中,WebApplicationContext继承了RootApplicationContext中的所有Bean,以便在@Controller中注入@Service等依赖。
记住这一点,接下来就可以分析SpringMVC的加载原理了。
1.2到1.4内容比较多,也可以直接阅读第二节: 2. SpringMVC配置案例。
1.2 ServletContext配置方法(Configuration Methods)
在Servlet3.0之后,Servlet新增了3个配置方法,可以通过编程的方式动态地装配Servlet、Filter、Listener。其中每种方法又有对应的重载版本。
addServlet
ServletRegistration.Dynamic addServlet(String servletName, String className);
ServletRegistration.Dynamic addServlet(String servletName, Servlet servlet);
ServletRegistration.Dynamic addServlet(String servletName,
Class<? extends Servlet> servletClass);
addFilter
FilterRegistration.Dynamic addFilter(String filterName, String className);
FilterRegistration.Dynamic addFilter(String filterName, Filter filter);
FilterRegistration.Dynamic addFilter(String filterName,
Class<? extends Filter> filterClass);
addLister
public void addListener(String className);
public <T extends EventListener> void addListener(T t);
void addListener(Class<? extends EventListener> listenerClass);
1.3 运行时插拔
注意:以上2个配置方法,只能在ServletContextListener#contextInitialized或ServletContainerInitializer#onStartup方法中被调用。
ServletContextListener用于监听servlet上下文的生命周期
当web应用或者容器启动时,ServletContainerInitializer#onStartup方法将被回调。并通过@HandleTypes注解指定要处理的类型,对应类型的子类(包括抽象类)组成的集合作为onStartup方法的第一个参数
package javax.servlet;
import java.util.Set;
public interface ServletContainerInitializer {
//第二个参数是,ServletContext
void onStartup(Set<Class<?>> var1, ServletContext var2) throws ServletException;
}
1.4 SpringServletContainerInitializer
SpringServletContainerInitializer继承了ServletContainerInitializer 。
@HandlesTypes({WebApplicationInitializer.class}元注解表明了在容器或应用启动时,动态执行WebApplicationInitializer实现类的onStartup方法。
@HandlesTypes({WebApplicationInitializer.class})
public class SpringServletContainerInitializer implements ServletContainerInitializer {
public SpringServletContainerInitializer() {
}
public void onStartup(@Nullable Set<Class<?>> webAppInitializerClasses, ServletContext servletContext) throws ServletException {
List<WebApplicationInitializer> initializers = new LinkedList();
Iterator var4;
if (webAppInitializerClasses != null) {
var4 = webAppInitializerClasses.iterator();
while(var4.hasNext()) {
Class<?> waiClass = (Class)var4.next();
if (!waiClass.isInterface() && !Modifier.isAbstract(waiClass.getModifiers()) && WebApplicationInitializer.class.isAssignableFrom(waiClass)) {
try {
initializers.add((WebApplicationInitializer)ReflectionUtils.accessibleConstructor(waiClass, new Class[0]).newInstance());
} catch (Throwable var7) {
throw new ServletException("Failed to instantiate WebApplicationInitializer class", var7);
}
}
}
}
if (initializers.isEmpty()) {
servletContext.log("No Spring WebApplicationInitializer types detected on classpath");
} else {
servletContext.log(initializers.size() + " Spring WebApplicationInitializers detected on classpath");
AnnotationAwareOrderComparator.sort(initializers);
var4 = initializers.iterator();
while(var4.hasNext()) {
WebApplicationInitializer initializer = (WebApplicationInitializer)var4.next();
initializer.onStartup(servletContext);
}
}
}
}
WebApplicationInitializer接口及相关的3个实现类(都是抽象类)
package org.springframework.web;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
public interface WebApplicationInitializer {
void onStartup(ServletContext var1) throws ServletException;
}
1.4.1 AbstractContextLoaderInitializer
protected访问修饰符的方法:createRootApplicationContext(),就是实例化RootApplication。AbstractContextLoaderInitializer可以替换以web.xml中配置ContextLoaderListener的方式,不过spring官方并不推荐这种方式(因为核心前端控制器DispatcherServlet相关逻辑还需要开发人员来实现),咱们接着往下看AbstractDispatcherServletInitializer。
public abstract class AbstractContextLoaderInitializer implements WebApplicationInitializer {
protected final Log logger = LogFactory.getLog(this.getClass());
public AbstractContextLoaderInitializer() {
}
public void onStartup(ServletContext servletContext) throws ServletException {
this.registerContextLoaderListener(servletContext);
}
protected void registerContextLoaderListener(ServletContext servletContext) {
WebApplicationContext rootAppContext = this.createRootApplicationContext();
if (rootAppContext != null) {
ContextLoaderListener listener = new ContextLoaderListener(rootAppContext);
listener.setContextInitializers(this.getRootApplicationContextInitializers());
servletContext.addListener(listener);
} else {
this.logger.debug("No ContextLoaderListener registered, as createRootApplicationContext() did not return an application context");
}
}
@Nullable
protected abstract WebApplicationContext createRootApplicationContext();
@Nullable
protected ApplicationContextInitializer<?>[] getRootApplicationContextInitializers() {
return null;
}
}
1.4.2 AbstractDispatcherServletInitializer
很明显,它继承了AbstractContextLoaderInitializer抽象类,装配了dispatcherServlet,但是并没有实现父类的createRootApplicationContext(),那么createRootApplicationContext是什么时候实现的呢,接着往下看AbstractAnnotationConfigDispatcherServletInitializer。
注意:AbstractDispatcherServletInitializer适用于Spring XML来驱动spring配置。
public abstract class AbstractDispatcherServletInitializer extends AbstractContextLoaderInitializer {
。。。其他方法略
public void onStartup(ServletContext servletContext) throws ServletException {
super.onStartup(servletContext);
this.registerDispatcherServlet(servletContext);
}
//重点看下registerDispatcherServlet方法
protected void registerDispatcherServlet(ServletContext servletContext) {
String servletName = this.getServletName();
Assert.hasLength(servletName, "getServletName() must not return null or empty");
WebApplicationContext servletAppContext = this.createServletApplicationContext();
Assert.notNull(servletAppContext, "createServletApplicationContext() must not return null");
//实例化servletAppContext
FrameworkServlet dispatcherServlet = this.createDispatcherServlet(servletAppContext);
Assert.notNull(dispatcherServlet, "createDispatcherServlet(WebApplicationContext) must not return null");
//初始化专属于Dispatcher的WebApplicaitonContext
dispatcherServlet.setContextInitializers(this.getServletApplicationContextInitializers());
//动态装配DispatcherServlet
Dynamic registration = servletContext.addServlet(servletName, dispatcherServlet);
if (registration == null) {
throw new IllegalStateException("Failed to register servlet with name '" + servletName + "'. Check if there is another servlet registered under the same name.");
} else {
registration.setLoadOnStartup(1);
registration.addMapping(this.getServletMappings());
registration.setAsyncSupported(this.isAsyncSupported());
Filter[] filters = this.getServletFilters();
if (!ObjectUtils.isEmpty(filters)) {
Filter[] var7 = filters;
int var8 = filters.length;
for(int var9 = 0; var9 < var8; ++var9) {
Filter filter = var7[var9];
this.registerServletFilter(servletContext, filter);
}
}
this.customizeRegistration(registration);
}
}
protected abstract WebApplicationContext createServletApplicationContext();
protected FrameworkServlet createDispatcherServlet(WebApplicationContext servletAppContext) {
return new DispatcherServlet(servletAppContext);
}
}
1.4.3 AbstractAnnotationConfigDispatcherServletInitializer
显然,这个类又继承了AbstractDispatcherServletInitializer,它实现了父类的createServletApplicationContext方法,并且实现了AbstractContextLoaderInitializer#createRootApplicationContext()方法。又通过模板方法模式,暴露了2个抽象方法:getRootConfigClasses、getServletConfigClasses。只需要引入RootConfig和ServletConfig对应的@Configuration类就可以完成配置。注解大法好!!!
看到类中的Annotation这个单词我们就能知道,这个类适用于通过注解来驱动Spring配置。
public abstract class AbstractAnnotationConfigDispatcherServletInitializer extends AbstractDispatcherServletInitializer {
@Nullable
protected WebApplicationContext createRootApplicationContext() {
Class<?>[] configClasses = this.getRootConfigClasses();
if (!ObjectUtils.isEmpty(configClasses)) {
AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
context.register(configClasses);
return context;
} else {
return null;
}
}
protected WebApplicationContext createServletApplicationContext() {
AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
Class<?>[] configClasses = this.getServletConfigClasses();
if (!ObjectUtils.isEmpty(configClasses)) {
context.register(configClasses);
}
return context;
}
@Nullable
protected abstract Class<?>[] getRootConfigClasses();
@Nullable
protected abstract Class<?>[] getServletConfigClasses();
}
2. SpringMVC配置案例
2.1 自定义的Intializer类,继承了AbstractAnnotationConfigDispatcherServletInitializer
public class PreventPneumoniaWebAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
@Override
protected Class<?>[] getRootConfigClasses() {
return new Class[]{RootConfig.class};
}
@Override
protected Class<?>[] getServletConfigClasses() {
return new Class[]{WebConfig.class};
}
@Override
protected String[] getServletMappings() {
return new String[]{"/"};
}
}
2.2编写@Configuration类
RootConfig.java
@Configuration
@ComponentScan(basePackages = {"cn.xxx.dao","cn.xxx.service"},
excludeFilters = {@Filter(type=FilterType.ANNOTATION, value= EnableWebMvc.class)})
@ImportResource("classpath:spring/spring-all.xml")
public class RootConfig {
}
WebConfig.java
//表明这是一个java配置类
@Configuration
//开启springMvc
@EnableWebMvc
//开启组件扫描,不用显式配置控制器
@ComponentScan({"cn.xxx.control"})
public class WebConfig extends WebMvcConfigurerAdapter {
//配置JSP视图解析器
@Bean
public ViewResolver viewResolver(){
InternalResourceViewResolver resolver = new InternalResourceViewResolver();
resolver.setPrefix("/src/module/pneumonia/");
resolver.setSuffix(".html");
resolver.setExposeContextBeansAsAttributes(true);
return resolver;
}
//配置静态资源的处理
@Override
public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
configurer.enable();
}
}
2.3 把@Controller类也贴出来
/**
* @author java_shj
* @desc 简单的一个springMvc控制器
* @createTime 2019/9/30 11:29
**/
@Controller
//类级别的请求处理
public class HomeController {
private static Logger logger = LoggerFactory.getLogger(HomeController.class);
//方法级别的请求处理
@RequestMapping(method = RequestMethod.GET,value = {"/home","/"})
public String home(){
logger.info("首页方法触发了");
return "index";
}
@RequestMapping(method = RequestMethod.GET, value = {"/index"})
public String homeNew() {
return "preventPneumonia";
}
}
2.4 引入自定义Servlet
/**
* @author java_shj
* @desc 空谷项目自定义Initializer,来注册自定义的Servlet、Filter、Listener,并映射到对应的url
* @createTime 2020/1/10 17:36
**/
public class EvServletInitializer implements WebApplicationInitializer {
@Override
public void onStartup(ServletContext servletContext) throws ServletException {
// 注册想要引入的Servlet
Dynamic validateCodeServlet = servletContext.addServlet("validateCodeServlet", ValidateCodeServlet.class);
// 映射Servlet
validateCodeServlet.addMapping("/validateCodeServlet");
}
}
2.5 其他
操作数据库是,可能需要引入spring-mybatis等配置文件,见RootConfig.java中引入xml文件,引入其他xml文件同理。
@ImportResource("classpath:spring/spring-all.xml")
至此,不需要web.mxl配置文件,大功告成。写个前台页面,就可以开搞了。注解驱动真香!