SpringMVC中的RootApplicationContext上下文和WebApplicationContext上下文,通过注解配置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配置文件,大功告成。写个前台页面,就可以开搞了。注解驱动真香!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值