06.Spring Framework 之 Web

1. Servlet3.0 新特性

1.1 Servlet 新特性

web 容器在启动的时候,会扫描每个 jar 包下的 META-INF/services/javax.servlet.ServletContainerInitializer,可以使用 @HandlesTypes 将指定的类传入到 onStartup(Set<Class<?>> set, ServletContext servletContext) 方法中的 set 集合里

1.2 环境搭建

代码已经上传至 https://github.com/masteryourself-tutorial/tutorial-spring ,详见 tutorial-spring-framework/tutorial-spring-framework-servlet3.0 工程

1.2.1 配置文件
1. pom.xml
<packaging>war</packaging>

<dependencies>
    <dependency>
        <groupId>javax.servlet</groupId>
        <artifactId>javax.servlet-api</artifactId>
        <version>4.0.1</version>
        <scope>provided</scope>
    </dependency>
</dependencies>
2. META-INF/services/javax.servlet.ServletContainerInitializer
pers.masteryourself.tutorial.spring.framework.servlet3.MyServletContainerInitializer
1.2.2 核心代码
1. MyServletContainerInitializer
@HandlesTypes(ServletInitializer.class)
public class MyServletContainerInitializer implements ServletContainerInitializer {

    @Override
    public void onStartup(Set<Class<?>> set, ServletContext servletContext) throws ServletException {
        // 打印 @HandlesTypes 传入的类
        for (Class<?> clazz : set) {
            System.out.println("@HandlesTypes 传入的类有:" + clazz.getName());
        }
        // 注册组件
        servletContext.addServlet("MyHttpServlet", MyHttpServlet.class)
                .addMapping("/servlet3");
        servletContext.addListener(MyServletContextListener.class);
        servletContext.addFilter("MyFilter", MyFilter.class)
                .addMappingForUrlPatterns(EnumSet.of(DispatcherType.REQUEST), true, "/*");

    }

}

2. Spring MVC 定制

2.1 Spring MVC 集成原理

spring-web-xxx.RELEASE.jar 里,META-INF/services/javax.servlet.ServletContainerInitializer 所对应的内容是 org.springframework.web.SpringServletContainerInitializer

@HandlesTypes(WebApplicationInitializer.class)
public class SpringServletContainerInitializer implements ServletContainerInitializer {

它在 onStartup() 方法里会去回调每一个 initializer 的 onStartup(servletContext) 方法,然后分别初始化 Spring 容器和 Spring MVC 容器,最后通过 ContextLoaderListener#contextInitialized()HttpServletBean#init() 完成容器的 refresh() 操作
启动 Spring 容器和 Spring MVC 容器

2.2 环境搭建

代码已经上传至 https://github.com/masteryourself-tutorial/tutorial-spring ,详见 tutorial-spring-framework/tutorial-spring-framework-web 工程

2.2.1 配置文件
1. pom.xml
<packaging>war</packaging>

<dependency>
    <groupId>javax.servlet</groupId>
    <artifactId>javax.servlet-api</artifactId>
    <version>4.0.1</version>
    <scope>provided</scope>
</dependency>
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-webmvc</artifactId>
    <version>5.1.6.RELEASE</version>
</dependency>
2.2.2 核心代码
1. MyWebAppInitializer
public class MyWebAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {

    /**
     * 获取 Spring 配置,相当于 Spring 父容器
     *
     * @return
     */
    @Override
    protected Class<?>[] getRootConfigClasses() {
        return new Class[]{SpringConfig.class};
    }

    /**
     * 获取 SpringMVC 配置文件,相当于 Spring 子容器
     *
     * @return
     */
    @Override
    protected Class<?>[] getServletConfigClasses() {
        return new Class[]{SpringMvcConfig.class};
    }

    /**
     * 获取 DispatcherServlet 的映射信息
     * /:拦截所有请求(包括静态资源(xx.js,xx.png)),但是不包括 *.jsp
     * /*:拦截所有请求;连 *.jsp 页面都拦截;jsp 页面是 tomcat 的 jsp 引擎解析的
     *
     * @return
     */
    @Override
    protected String[] getServletMappings() {
        return new String[]{"/"};
    }

}
2. SpringConfig
@ComponentScan(value = "pers.masteryourself.tutorial.spring.framework.web", excludeFilters = {
        @ComponentScan.Filter(type = FilterType.ANNOTATION, classes = {Controller.class})
})
public class SpringConfig {
}
3. SpringMvcConfig
@ComponentScan(value = "pers.masteryourself.tutorial.spring.framework.web", includeFilters = {
        @ComponentScan.Filter(type = FilterType.ANNOTATION, classes = {Controller.class})
}, useDefaultFilters = false)
@EnableWebMvc
public class SpringMvcConfig implements WebMvcConfigurer {

    /**
     * 视图解析器
     *
     * @param registry
     */
    @Override
    public void configureViewResolvers(ViewResolverRegistry registry) {
        registry.jsp("/WEB-INF/views/", ".jsp");
    }

    /**
     * 静态资源访问
     *
     * @param configurer
     */
    @Override
    public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
        configurer.enable();
    }

    /**
     * 拦截器
     *
     * @param registry
     */
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new HandlerInterceptor() {
            @Override
            public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
                System.out.println("MyInterceptor preHandle");
                return true;
            }

            @Override
            public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
                System.out.println("MyInterceptor postHandle");
            }

            @Override
            public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
                System.out.println("MyInterceptor afterCompletion");
            }
        }).addPathPatterns("/**");
    }

}

3. 异步处理

代码已经上传至 https://github.com/masteryourself-tutorial/tutorial-spring ,详见 tutorial-spring-framework/tutorial-spring-framework-async 工程

3.1 异步 Callable

3.1.1 核心代码
1. AsyncController
@Controller
public class AsyncController {

    /**
     * 如果方法返回 {@link Callable},Spring 将会异步处理,将任务提交到 {@link org.springframework.core.task.TaskExecutor},使用一个隔离的线程执行
     * {@link org.springframework.web.servlet.DispatcherServlet} 和所以的 {@link javax.servlet.Filter} 退出 web 容器的线程,但是 response 保持打开状态
     * {@link Callable} 返回结果,Spring MVC 将请求重新派发给容器,恢复之前的处理,根据 {@link Callable} 返回的结果,Spring MVC 继续进行视图渲染流程(接收请求 -> 视图渲染)
     *
     * @return
     */
    @RequestMapping(value = "/async")
    @ResponseBody
    public Callable<String> async() {
        System.out.println("主线程开始:" + Thread.currentThread().getName());
        Callable<String> result = () -> {
            System.out.println("任务线程开始:" + Thread.currentThread().getName());
            TimeUnit.SECONDS.sleep(2);
            System.out.println("任务线程结束:" + Thread.currentThread().getName());
            return "success";
        };
        System.out.println("主线程结束:" + Thread.currentThread().getName());
        return result;

        // 主线程开始:http-nio-8080-exec-4
        //主线程结束:http-nio-8080-exec-4
        //任务线程开始:MvcAsync1
        //任务线程结束:MvcAsync1
    }

}

3.2 长连接 DeferredResult

3.2.1 核心代码
1. AsyncController
@Controller
public class DeferredResultController {

    private Queue<DeferredResult<String>> queue = new ConcurrentLinkedQueue<>();

    /**
     * 可以将返回值定义成 {@link DeferredResult},一旦调用 setResult() 方法,就会返回结果
     *
     * @return
     */
    @ResponseBody
    @RequestMapping("/createOrder")
    public DeferredResult<String> createOrder() {
        DeferredResult<String> deferredResult = new DeferredResult<>((long) 3000, "create fail...");
        queue.add(deferredResult);
        return deferredResult;
    }

    @ResponseBody
    @RequestMapping("/doCreate")
    public String doCreate() {
        DeferredResult<String> deferredResult = queue.poll();
        if (deferredResult == null) {
            return null;
        }
        deferredResult.setResult(UUID.randomUUID().toString());
        return "success";
    }

}
发布了37 篇原创文章 · 获赞 3 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/masteryourself/article/details/104269100
今日推荐