spring注解驱动开发-10 Servlet3.0

前言

在以前(不知多久以前,我大学期间学的servlet就是通过@WebServlet注解注册了,虽然现在也不会去使用了,但还是值得我们去回顾回顾),我们需要在代码中添加servlet、filter、listener、DispatcherServlet等组件都需要在web.xml中进行配置,servlet3.0后,为我们提供了相应的注解,简化了我们的开发,本文先介绍了servlet3.0,讲解了ServletContainerInitializer,然后介绍了如何整合springMVC,最后介绍了servlet3.0的异步请求处理。文章课程链接:尚硅谷spring注解驱动教程(雷神)

servlet3.0简介

servlet3.0为我们提供了一些注解,简化我们的开发,servlet、filter、listener的注册对应注解@WebServlet、@WebFilter、@WebListener,要使用初始化参数可使用@WebInitParam,具体可以参考相关文档。注意,servlet3.0需要在tomcat7.0版本及以上版本才能使用,下面我们写一个简单的例子,代码如下:

@WebServlet("/hello")
public class HelloServlet extends HttpServlet {

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        resp.getWriter().write("hello...");
    }
}

这样我们就能通过 /hello 访问到了,并给我们返回 hello…

ServletContainerInitializer

shared libraries(共享库) / runtimes pluggability(运行时插件)

servlet容器启动会扫描当前应用里面每一个jar包的ServletContainerInitializer实现类(扫描位置为MATE-INF/services/javax.servlet.ServletContainerInitializer),我们可以提供一个ServletContainerInitializer的实现类(该实现类必须绑定在MATE-INF/services/javax.servlet.ServletContainerInitializer文件中,文件的内容为ServletContainerInitializer实现类的全类名),如图
在这里插入图片描述
下面,我们就来写一个,代码如下:

// 容器启动时,会将@HandlesTypes指定的这个类型下面的子类(实现类,子接口等)传递过来
@HandlesTypes(value = {HelloServlet.class}) 
// HelloServlet的子类会通过Set<Class<?>> set传递过来(不含本身),可以拿过来创建对象等
public class MyServletContainerInitializer implements ServletContainerInitializer {

    /**
     * 应用启动的时候会运行onStartup方法
     * set:感兴趣类型的所有子类型
     *
     * servletContext :代表当前web应用的ServletContext,一个web应用一个ServletContext,
     * 可以用来注册web组件(servlet、filter、listener),解决我们注册第三方jar包组件的问题
     * 1、这种是使用编码的方式,在项目启动的时候给ServletContext里面添加组件(必须在项目启动的时候,运行时不行)
     * 2、也可以在ServletContextListener中实现的方法中拿到ServletContextEvent进行注册
     * public class UserListener implements ServletContextListener {
     *     @Override
     *     public void contextInitialized(ServletContextEvent sce) {
     * 			sce.addxxx
     *         System.out.println("初始化");
     *     }
     *
     *     @Override
     *     public void contextDestroyed(ServletContextEvent sce) {
     *         System.out.println("销毁");
     *     }
     * }
     */
    @Override
    public void onStartup(Set<Class<?>> set, ServletContext servletContext) throws ServletException {
        System.out.println("感兴趣的类型" + set);

        // 注册组件,先自己创建相应的web组件实现类
        // 注册servlet
        ServletRegistration.Dynamic servlet = servletContext.addServlet("helloServlet", new HelloServlet());
        // 返回的ServletRegistration.Dynamic用来指定映射信息
        servlet.addMapping("/hello");

        // 注册listener
        servletContext.addListener(UserListener.class);

        // 注册filter
        FilterRegistration.Dynamic filter = servletContext.addFilter("userFilter", userFilter.class);
        filter.addMappingForUrlPatterns(EnumSet.of(DispatcherType.REQUEST), true, "/*");
    }
}

写好后,运行,即可看到相应的打印信息

整合SpringMVC

分析

整合之前,我们先来分析一下,看这张图,是不是很熟悉
在这里插入图片描述点开spring-web包中,包含了我们前面讲到的META-INF>services包,存在javax.servlet.ServletContainerInitializer这个文件,值为org.springframework.web.SpringServletContainerInitializer,下面,我们就贴上这个实现类的源码,对他进行分析

// spring应用一启动就会加载WebApplicationInitializer接口下的所有组件,并且为这些组件创建对象
// (不是接口或抽象类)
@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);
                    }
                }
            }
            // ...
        }
    }
}

从SpringServletContainerInitializer中,我们看到,他是要为WebApplicationInitializer的非接口或非抽象类的子类创建对象,下面我们就看看他有哪些子类,并做了什么操作,首先看看有哪些子类

在这里插入图片描述
他们其中的部分关系如图
在这里插入图片描述

从图中看到有6个子类,这里就不依次进行源码分析,只做文字描述

  • AbstractContextLoaderInitializer

创建根容器:createRootApplicationContext(),如果跟容器不为空,创建ContextLoaderListener并将其放到ServletContext中

  • AbstractDispatcherServletInitializer

是AbstractContextLoaderInitializer的子类,
先是创建一个web的IOC容器:this.createServletApplicationContext();
然后创建了一个DispatcherServlet:this.createDispatcherServlet(servletAppContext);
再将DispatcherServlet加入到ServletContext中,再通过返回的Dynamic配置映射信息等(跟前面我们自己实现的一样),添加映射:registration.addMapping(this.getServletMappings()),这个getServletMappings()留给我们自己来写

  • AbstractAnnotationConfigDispatcherServletInitializer

AbstractDispatcherServletInitializer的子类,注解方式配置的DispatcherServlet初始化器
创建根容器:createRootApplicationContext(),获取配置类,注册到注解的IOC容器AnnotationConfigWebApplicationContext中并返回
创建web的IOC容器:createServletApplicationContext(),获取配置类,注册到注解的IOC容器AnnotationConfigWebApplicationContext中并返回(跟上一步一模一样)

  • 剩下的三个待补充

小结

以注解方式来启动springMVC,继承 AbstractAnnotationConfigDispatcherServletInitializer,实现抽象方法,指定DispatcherServlet的配置信息

整合

上面,我们对springMVC的整合进行了分析,下面我们对其进行实现,继承AbstractAnnotationConfigDispatcherServletInitializer,代码如下

// web容器启动的时候创建对象,调用方法来初始化容器以及前端控制器
public class MyWebAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
    // 获取根容器的配置类,(spring的配置文件),父容器
    @Override
    protected Class<?>[] getRootConfigClasses() {
    		 // RootConfig为父容器,扫描除Controller的其他的组件
        return new Class[]{RootConfig.class};
    }

    // 获取web容器的配置类(springMVC的配置文件),子容器
    @Override
    protected Class<?>[] getServletConfigClasses() {
    		 // AppConfig为子容器,只扫描Controller
        return new Class[]{AppConfig.class};
    }

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

定制springMVC

在以前,我们整合springMVC时,需要写mvc.xml配置文件,配置我们的高级功能开启(<mvc:annotation-driven />)、拦截器(mvc:interceptors</mvc:interceptors>)、视图映射等,那现在不用配置文件了该怎么配置了,我们可以通过实现WebMvcConfigurer并使用@EnableWebMvc来进行配置,代码如下

@EnableWebMvc
public class AppConfig implements WebMvcConfigurer {
    @Override
    public void configureViewResolvers(ViewResolverRegistry registry) {
        // 默认所有的页面都从/WEB-INF/ xxx.jsp
        registry.jsp();
        // 也可以自己配置
        registry.jsp("/WEB-INF/views/",".jsp");
    }

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

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
    		 // 配置拦截器
        registry.addInterceptor(new MyInterceptor()).addPathPatterns("/**");
    }
}

servlet3.0的异步请求处理

在servlet3.0以前,servlet采用Thread-Per-Request的方式处理请求,即每一次http请求都是由某一个线程从头到尾负责处理,如果一个请求需要进行IO操作,比如数据库访问、调用第三方服务接口等,那么其所对应的线程将同步的等待IO操作完成,而IO操作是非常慢的,此时的线程被占用着,不能被回收继续使用,在大并发情况下,就会带来严重的性能问题。为了解决这个问题,servlet3.0引入了异步处理,servlet3.1又引入了非阻塞IO来进一步增强异步处理的性能。下面我们将通过servlet方式和springMVC方式实现异步处理。

servlet实现

@WebServlet("/hello")
public class AsyncServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // 1.支持异步请求处理asyncSupported=true
        // 2.开启异步模式
        System.out.println("主线程开始---" + Thread.currentThread() + "时间:" + System.currentTimeMillis());
        AsyncContext asyncContext = req.startAsync();

        // 3.业务逻辑进行异步处理
        asyncContext.start(() -> {
            try {
                System.out.println("副线程开始---" + Thread.currentThread() + "时间:" + System.currentTimeMillis());
                sayHello();
                asyncContext.complete();
                // 这里也可以获取到异步上下文,和上面的一样
                AsyncContext context = req.getAsyncContext();
                // 4.获取响应
                ServletResponse response = context.getResponse();
                response.getWriter().write("hello async...");
                System.out.println("副线程结束---" + Thread.currentThread() + "时间:" + System.currentTimeMillis());
            } catch (Exception e) {
                e.printStackTrace();
            }
        });
        System.out.println("主线程结束---" + Thread.currentThread() + "时间:" + System.currentTimeMillis());
    }

    private void sayHello() throws InterruptedException {
        System.out.println("processing..." + Thread.currentThread() + "时间:" + System.currentTimeMillis());
        Thread.sleep(3000);
    }
}

springMVC实现

参照官方文档,springMVC可以通过两种方式实现(返回值),一是Callable,二是DeferredResult,代码如下:
Callable实现

@RestController
public class AsyncController {

    @RequestMapping("/async01")
    public Callable<String> async01() {
        System.out.println("主线程开始---"+Thread.currentThread()+" 执行时间:"+System.currentTimeMillis());
        Callable<String> callable = new Callable<String>(){

            @Override
            public String call() throws Exception {
                System.out.println("副线程开始---"+Thread.currentThread()+" 执行时间:"+System.currentTimeMillis());
                Thread.sleep(2000);
                System.out.println("副线程结束---"+Thread.currentThread()+" 执行时间:"+System.currentTimeMillis());
                return "async01执行";
            }
        };
        System.out.println("主线程结束---"+Thread.currentThread()+" 执行时间:"+System.currentTimeMillis());
        return callable;
    }
}

结果
在这里插入图片描述>控制器返回Callable,Spring异步处理,将Callbale提交到TaskExecutor使用一个隔离线程进行执行,DispatcherServlet和所有的Filter退出web容器的线程,但是response保持打开状态,Callable返回结果,springMVC将请求重新派发给容器,恢复之前的处理,根据Callable返回的结果,springMVC继续进行视图渲染流程等

DeferredResult实现

在我们实际开发过程中,异步处理是这样来做的,如图,应用1开启一个请求,他需要由应用2来完成某些操作,这时,他会将消息放在消息中间件中(MQ、kafka等),应用2监听是否有消息,监听到就处理,处理完再将消息返回消息中间件,应用1也要监听,监听到就返回。

在这里插入图片描述

DeferredResult就能帮我们简单的实现上述过程,我们接收到不能及时处理的请求,就创建一个DeferredResult对象,然后直接返回该DeferredResult对象,当处理完时,拿到这个DeferredResult对象,调用setResult()方法,即可实现异步处理,代码如下

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

    @RequestMapping("async02")
    public DeferredResult<Object> async02() {
        // 设置超时时间,超时返回提示消息(处理失败!)
        DeferredResult result = new DeferredResult(3000l,"处理失败!");
        save(result);
        return result;
    }

    private void save(DeferredResult result) {
        queue.add(result);
    }

    private DeferredResult get() {
        return queue.poll();
    }

    @RequestMapping("/isSuccess")
    public String isSuccess() {
        DeferredResult result = get();
        result.setResult("成功!");
        return "success";
    }

end…

到这里,spring注解驱动开发的学习就完结了,相信认真看完视频的同学收获还是挺多。学无止境,尤其是我们搞技术的。在这个内卷的时代,只有不断的提升自己,才能走得长远,加油吧,打工人!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值