SpringMVC

servlet代码
SpringMVC代码

1.Tomcat9安装配置

  • step1.配置Java
    JAVA_HOME
    PATH=%JAVA_HOME%\bin;
    CLASSPATH=.;%JAVA_HOME%\lib\dt.jar;%JAVA_HOME%\lib\tools.jar;
  • step2.配置tomcat
    TOMCAT_HOME=D:\apache-tomcat-9.0.12
    CATALINA_HOME=D:\apache-tomcat-9.0.12
    PATH=%CATALINA_HOME%\bin
    CLASSPATH=%CATALINA_HOME%\lib\servlet-api.jar
  • step3.安装
    cmd输入service install Tomcat9
  • step4.测试
    控制面板—系统和安全—管理工具—服务,找到Apache Tomcat Tomcat9服务项,右击该项,点“启动”,启动该服务。
    地址栏输入http://localhost:8080或 http://127.0.0.1:8080
    如果出现tomcat示例主页,则表示服务器安装成功。

2.新建动态web项目

  • step1.新建web项目


3.原生的servlet3.0测试

以前来写web的三大组件:以前写servlet filter listener都需要在web.xml进行注册,包括springmvc的前端控制器DispactherServlet也需要在web.xml注册,现在可以通过注解的方式快速搭建我们的web应用。

3.1 简单的测试

  • 新增请求地址,请求地址为order
    index.jsp
<html>
  <head>
    <title>$Title$</title>
  </head>
  <body>
  <a href="order">order</a>
  </body>
</html>
@WebServlet("/order")
public class JamesServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        resp.getWriter().write("order success...");
    }
}
  • 启动tomcat后测试


3.2 ServletContainerInitializer初始化web容器

Shared libraries(共享库) and runtimes pluggability(运行时插件)的原理,在后面的框架整合里,用得比较多。
在web容器启动时为提供给第三方组件机会做一些初始化的工作,例如注册servlet或者filters等,servlet规范(JSR356)中通过ServletContainerInitializer实现此功能。
每个框架要使用ServletContainerInitializer就必须在对应的jar包的META-INF/services 目录创建一个名为javax.servlet.ServletContainerInitializer的文件,文件内容指定具体的ServletContainerInitializer实现类,那么,当web容器启动时就会运行这个初始化器做一些组件内的初始化工作。

com.wangzhen.servlet.JamesServletContainerInitializer
@HandlesTypes(value={JamesService.class})
public class JamesServletContainerInitializer implements ServletContainerInitializer {
    /**
     * tomcat启动时加载应用的时候,会运行onStartup方法;
     *
     * Set<Class<?>> arg0:感兴趣的类型的所有子类型(对实现了JamesService接口相关的);
     * ServletContext arg1:代表当前Web应用的ServletContext;一个Web应用一个ServletContext;
     *
     * 1)、使用ServletContext注册Web组件(Servlet、Filter、Listener)
     * 2)、使用编码的方式,在项目启动的时候给ServletContext里面添加组件;
     *      必须在项目启动的时候来添加;
     *      1)、ServletContainerInitializer得到的ServletContext;
     *      2)、ServletContextListener得到的ServletContext;
     */
    @Override
    public void onStartup(Set<Class<?>> arg0, ServletContext arg1) throws ServletException {
        System.out.println("感兴趣的类型:");
        for (Class<?> claz : arg0) {
            System.out.println(claz);//当传进来后,可以根据自己需要利用反射来创建对象等操作
        }
    }

}
  • 并新建JamesService接口的所有子类型
    JamesServiceOther
    JamesServiceImpl
    AbstractJamesService

  • 测试结果

Connected to server
[2018-09-14 06:57:00,891] Artifact wangzhen-servlet3:war exploded: Artifact is being deployed, please wait...
感兴趣的类型:
interface com.wangzhen.service.JamesServiceOther
class com.wangzhen.service.AbstractJamesService
class com.wangzhen.service.JamesServiceImpl
  • 总结
    实质是基于运行时插件的机制,启动并运行这个ServletContainerInitializer,在整合springmvc的时候会用到

3.3 使用ServletContext注册web组件

其实就是Servlet,Filter,Listener三大组件。
对于我们自己写的JamesServlet,我们可以使用@WebServlet注解来加入JamesServlet组件,
但若是我们要导入第三方阿里的连接池或filter,以前的web.xml方式就可通过配置加载就可以了,但现在我们使用ServletContext注入进来。

   @Override
    public void onStartup(Set<Class<?>> arg0, ServletContext arg1) throws ServletException {
        System.out.println("感兴趣的类型:");
        for (Class<?> claz : arg0) {
            System.out.println(claz);//当传进来后,可以根据自己需要利用反射来创建对象等操作
        }
        //注册servlet组件
        javax.servlet.ServletRegistration.Dynamic servlet = arg1.addServlet("orderServlet", new OrderServlet());
        //配置servlet的映射信息(路径请求)
        servlet.addMapping("/orderTest");

        //注册监听器Listener
        arg1.addListener(OrderListener.class);

        //注册Filter
        javax.servlet.FilterRegistration.Dynamic filter = arg1.addFilter("orderFilter", OrderFilter.class);
        //添加Filter的映射信息,可以指定专门来拦截哪个servlet
        filter.addMappingForUrlPatterns(EnumSet.of(DispatcherType.REQUEST), true, "/*");

    }

}
  • 新建三个组件
public class OrderFilter implements Filter {

    @Override
    public void destroy() {
        // TODO Auto-generated method stub

    }

    @Override
    public void doFilter(ServletRequest arg0, ServletResponse arg1, FilterChain arg2)
            throws IOException, ServletException {
        // 过滤请求
        System.out.println("UserFilter...doFilter...");
        //放行
        arg2.doFilter(arg0, arg1);

    }

    @Override
    public void init(FilterConfig arg0) throws ServletException {
        // TODO Auto-generated method stub
    }
}
public class OrderListener implements ServletContextListener {


    //监听ServletContext销毁
    @Override
    public void contextDestroyed(ServletContextEvent arg0) {
        // TODO Auto-generated method stub
        System.out.println("UserListener...contextDestroyed...");
    }

    //监听ServletContext启动初始化
    @Override
    public void contextInitialized(ServletContextEvent arg0) {
        // TODO Auto-generated method stub
        ServletContext servletContext = arg0.getServletContext();
        System.out.println("UserListener...contextInitialized...");
    }

}
public class OrderServlet extends HttpServlet {

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // TODO Auto-generated method stub
        resp.getWriter().write("jamesServlet...");
    }

}
  • 测试结果


UserListener...contextInitialized...
[2018-09-14 07:38:34,459] Artifact wangzhen-servlet3:war exploded: Artifact is deployed successfully
[2018-09-14 07:38:34,459] Artifact wangzhen-servlet3:war exploded: Deploy took 567 milliseconds
UserFilter...doFilter...
UserFilter...doFilter...
UserFilter...doFilter...
UserFilter...doFilter...
14-Sep-2018 19:38:43.547 信息 [ContainerBackgroundProcessor[StandardEngine[Catalina]]] org.apache.catalina.startup.HostConfig.deployDirectory Deploying web application directory [D:\apache-tomcat-9.0.12\webapps\manager]
14-Sep-2018 19:38:43.587 信息 [ContainerBackgroundProcessor[StandardEngine[Catalina]]] org.apache.catalina.startup.HostConfig.deployDirectory Deployment of web application directory [D:\apache-tomcat-9.0.12\webapps\manager] has finished in [40] ms
UserFilter...doFilter...

注意:在运行的过程中,是不可以注册组件, 和IOC道理一样,出于安全考虑。

4.SpringMVC

4.1 简单的项目

  • 新建maven工程,配置pom.xml。
    <groupId>com.wangzhen</groupId>
    <artifactId>springmvc-learn</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>war</packaging>

    <dependencies>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-webmvc</artifactId>
            <version>5.0.6.RELEASE</version>
        </dependency>

        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>servlet-api</artifactId>
            <version>3.0-alpha-1</version>
            <scope>provided</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-war-plugin</artifactId>
                <version>2.4</version>
                <configuration>
                    <failOnMissingWebXml>false</failOnMissingWebXml>
                </configuration>
            </plugin>
        </plugins>
    </build>

注意:tomcat也有servlet,maven打jar包的时候不要把它打进去,不然会重复。

  • 查看maven的一个spring-web.jar包



    内容是:

org.springframework.web.SpringServletContainerInitializer
  • JamesWebAppInitializer
public class JamesWebAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {

    //获取根容器的配置类;(Spring的配置文件)   父容器;
    @Override
    protected Class<?>[] getRootConfigClasses() {
        //指定配置类(配置文件)位置
        return new Class<?>[]{JamesRootConfig.class} ;
    }

    //获取web容器的配置类(SpringMVC配置文件)  子容器;
    @Override
    protected Class<?>[] getServletConfigClasses() {
        return new Class<?>[]{JamesAppConfig.class} ;
    }

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

}
  • 新建两个配置类JamesRootConfig和JamesAppConfig,形成父子容器的效果
//Spring的容器不扫描controller;父容器
@ComponentScan(value="com.wangzhen",excludeFilters={
        @ComponentScan.Filter(type=FilterType.ANNOTATION,classes={Controller.class})
})
public class JamesRootConfig {

}
//SpringMVC只扫描Controller;子容器
//useDefaultFilters=false 禁用默认的过滤规则;
@ComponentScan(value="com.wangzhen",includeFilters={
        @ComponentScan.Filter(type=FilterType.ANNOTATION,classes={Controller.class})
},useDefaultFilters=false)
public class JamesAppConfig  {
}
  • 测试类
Controller
public class OrderController   {
    @Autowired
    OrderService orderService;

    @ResponseBody
    @RequestMapping("/buy")
    public String buy(){
        return orderService.goBuy("12345678");
    }
}
@Service
public class OrderService   {
    public String goBuy(String orderId){
        return "orderId===="+orderId;
    }
}
  • Run/Debug Configurations




测试结果:


4.2 定制SpringMVC

现在使用配置注解,定制我们的springmvc,可看官网描述,加入@EnableWebMvc,来定制配置功能。

  • 直接在JamesAppConfig实现WebMvcConfigurer。
@ComponentScan(value="com.wangzhen",includeFilters={
        @ComponentScan.Filter(type=FilterType.ANNOTATION,classes={Controller.class})
},useDefaultFilters=false)
@EnableWebMvc
public class JamesAppConfig extends WebMvcConfigurerAdapter {

    //定制视图解析器
    @Override
    public void configureViewResolvers(ViewResolverRegistry registry) {
        //比如我们想用JSP解析器,默认所有的页面都从/WEB-INF/AAA.jsp
        registry.jsp("/WEB-INF/pages/",".jsp");
    }
}
  • 新建目录src/main/webapp/WEB-INF/pages,添加ok.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>ok</title>
</head>
<body>
successful!
</body>
</html>
  • 在OrderController控制类加一个解析器的定制页面返回
    //相当于找 /WEB-INF/pages/ok.jsp
    @RequestMapping("/ok")
    public String ok(){
        return "ok";
    }

4.同步和异步处理

4.1 同步处理

客户端发出请求后,一直等待服务端响应。



  • 修改JamesServlet,测试
@WebServlet("/order")
public class JamesServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        System.out.println(Thread.currentThread()+ "start...");
        try {
            buyCards();
        } catch (Exception e) {
            e.printStackTrace();
        }
        resp.getWriter().write("order successful...");
        System.out.println(Thread.currentThread() + "end...");
    }

    public void buyCards() throws InterruptedException{
        System.out.println(Thread.currentThread()+".............");
        Thread.sleep(5000);//模拟业务操作
    }
}

结果:

Thread[http-nio-8080-exec-2,5,main]start...
Thread[http-nio-8080-exec-2,5,main].............
Thread[http-nio-8080-exec-2,5,main]end...

从头到尾都是由同一个线程2进行处理的,线程从头执行到尾,会造成资源占用不能释放。

4.2 异步请求

@WebServlet(value="/asyncOrder", asyncSupported = true)
public class OrderAsyncServlet extends HttpServlet{
    //支持异步处理asyncSupported = true
    //重写doget方法
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //151个……
        System.out.println("主线程开始……"+Thread.currentThread()+"start....."+System.currentTimeMillis());
        AsyncContext startAsync = req.startAsync();
        
        startAsync.start(new Runnable() {
            
            @Override
            public void run() {
                try {
                    System.out.println("副线程开始……"+Thread.currentThread()+"start....."+System.currentTimeMillis());
                
                    buyCards();
                    startAsync.complete();
                    AsyncContext asyncContext = req.getAsyncContext();
                    ServletResponse response = asyncContext.getResponse();
                    response.getWriter().write("order sucesful....");
                    System.out.println("副线程结束……"+Thread.currentThread()+"end....."+System.currentTimeMillis());
                    
                } catch (InterruptedException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                } catch (IOException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
            }
        });
        
        System.out.println("主线程结束……"+Thread.currentThread()+"end....."+System.currentTimeMillis());
        //主线程的资源断开……
    }

    public void buyCards() throws InterruptedException{
        System.out.println(Thread.currentThread()+".............");
        Thread.sleep(5000);//模拟业务操作
    }
}

报错:

Exception in thread "http-nio-8080-exec-4" java.lang.IllegalStateException: It is illegal to call this method if the current request is not in asynchronous mode (i.e. isAsyncStarted() returns false)
    at org.apache.catalina.connector.Request.getAsyncContext(Request.java:1758)
    at org.apache.catalina.connector.RequestFacade.getAsyncContext(RequestFacade.java:1068)
    at com.wangzhen.servlet.OrderAsyncServlet$1.run(OrderAsyncServlet.java:38)
    at org.apache.catalina.core.AsyncContextImpl$RunnableWrapper.run(AsyncContextImpl.java:515)
    at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1135)
    at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:635)
    at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
    at java.base/java.lang.Thread.run(Thread.java:844)

原因:AsyncContext.complete();方法的调用时机错误,应该在子线程完全处理完成业务逻辑的最后调用。
修改:

                    System.out.println("副线程开始……"+Thread.currentThread()+"start....."+System.currentTimeMillis());

                    buyCards();
                    AsyncContext asyncContext = req.getAsyncContext();
                    ServletResponse response = asyncContext.getResponse();
                    response.getWriter().write("order sucesful....");
                    startAsync.complete();
                    System.out.println("副线程结束……"+Thread.currentThread()+"end....."+System.currentTimeMillis());

结果:

主线程开始……Thread[http-nio-8080-exec-6,5,main]start.....1536967088156
主线程结束……Thread[http-nio-8080-exec-6,5,main]end.....1536967088172
副线程开始……Thread[http-nio-8080-exec-6,5,main]start.....1536967088173
Thread[http-nio-8080-exec-6,5,main].............
副线程结束……Thread[http-nio-8080-exec-6,5,main]end.....1536967093173

4.3 SpringMVC的异步请求

4.3.1 异步order01

@Controller
public class AsyncOrderController {

    @ResponseBody
    @RequestMapping("/order01")
    public Callable<String> order01(){
        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 "order buy successful........";
            }
        };

        System.out.println("主线程结束..."+Thread.currentThread()+"==>"+System.currentTimeMillis());
        return callable;
    }

}

结果:

Thread[http-nio-8080-exec-4,5,main]----preHandle-------------/springmvclearn/order01
主线程开始...Thread[http-nio-8080-exec-4,5,main]==>1536970626335
主线程结束...Thread[http-nio-8080-exec-4,5,main]==>1536970626341
副线程开始...Thread[MvcAsync1,5,main]==>1536970626352
副线程开始...Thread[MvcAsync1,5,main]==>1536970628353
Thread[http-nio-8080-exec-5,5,main]----preHandle-------------/springmvclearn/order01
Thread[http-nio-8080-exec-5,5,main]----postHandle-------------
Thread[http-nio-8080-exec-5,5,main]----afterCompletion-------------

4.3.2 订单示例

需求描述:以创建订单为例,tomcat启动线程1来完成一个请求,但实际上是订单服务才能创建订单,那么tomcat线程应该把请求转发给订单服务,使用消息中间件来处理,订单服务把处理结果也放到消息中间件,由tomcat的线程N拿到结果后,响应给客户端。


  • 创建队列
public class JamesDeferredQueue {

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

    public static void save(DeferredResult<Object> deferredResult){
        queue.add(deferredResult);
    }

    public static DeferredResult<Object> get( ){
        return queue.poll();
    }

}
  • 模拟两个线程/createOrder和/get
@Controller
public class AsyncOrderController {

    //其实相当于我们说的tomcat的线程1,来处理用户请求,并将请求的操作放到Queue队列里
    @ResponseBody
    @RequestMapping("/createOrder")
    public DeferredResult<Object> createOrder(){
        DeferredResult<Object> deferredResult = new DeferredResult<>((long)5000, "create fail...");
        JamesDeferredQueue.save(deferredResult);
        return deferredResult;
    }

    @ResponseBody
    @RequestMapping("/get")
    public String get(){
        String order = UUID.randomUUID().toString();
        DeferredResult<Object> deferredResult = JamesDeferredQueue.get();
        deferredResult.setResult(order);
        JamesDeferredQueue.save(deferredResult);
        return "get method order = " + order;
    }
}

测试结果:
超过5s没有get


正常情况



参考

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,445评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,889评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,047评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,760评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,745评论 5 367
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,638评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,011评论 3 398
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,669评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,923评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,655评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,740评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,406评论 4 320
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,995评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,961评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,197评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,023评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,483评论 2 342

推荐阅读更多精彩内容

  • Based on Java™ Servlet Specification v3.1 [TOC] Servlet和S...
    0x70e8阅读 1,304评论 0 7
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,594评论 18 139
  • Spring Boot 参考指南 介绍 转载自:https://www.gitbook.com/book/qbgb...
    毛宇鹏阅读 46,733评论 6 342
  • SpringMVC原理分析 Spring Boot学习 5、Hello World探究 1、POM文件 1、父项目...
    jack_jerry阅读 1,265评论 0 1
  • 我记得街边那间牛肉面馆 也记得步行街那台跳舞机 和路边少了两瓣的四叶草 吃面时的微笑 跳舞时的俊逸 拈花时的温情 ...
    小楼听风花语迟阅读 46评论 0 0