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.測試
控制面板—系統(tǒng)和安全—管理工具—服務(wù),找到Apache Tomcat Tomcat9服務(wù)項(xiàng),右擊該項(xiàng),點(diǎn)“啟動”,啟動該服務(wù)。
地址欄輸入http://localhost:8080或 http://127.0.0.1:8080
如果出現(xiàn)tomcat示例主頁,則表示服務(wù)器安裝成功。
2.新建動態(tài)web項(xiàng)目
-
step1.新建web項(xiàng)目
3.原生的servlet3.0測試
以前來寫web的三大組件:以前寫servlet filter listener都需要在web.xml進(jìn)行注冊,包括springmvc的前端控制器DispactherServlet也需要在web.xml注冊,現(xiàn)在可以通過注解的方式快速搭建我們的web應(yīng)用。
3.1 簡單的測試
- 新增請求地址,請求地址為order
index.jsp
<html>
<head>
<title>$Title$</title>
</head>
<body>
<a href="order">order</a>
</body>
</html>
- 建立一個原生的servlet來處理 order的請求
JamesServlet.java
@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(運(yùn)行時插件)的原理,在后面的框架整合里,用得比較多。
在web容器啟動時為提供給第三方組件機(jī)會做一些初始化的工作,例如注冊servlet或者filters等,servlet規(guī)范(JSR356)中通過ServletContainerInitializer實(shí)現(xiàn)此功能。
每個框架要使用ServletContainerInitializer就必須在對應(yīng)的jar包的META-INF/services 目錄創(chuàng)建一個名為javax.servlet.ServletContainerInitializer的文件,文件內(nèi)容指定具體的ServletContainerInitializer實(shí)現(xiàn)類,那么,當(dāng)web容器啟動時就會運(yùn)行這個初始化器做一些組件內(nèi)的初始化工作。
- 在src下創(chuàng)建META-INF/services目錄
創(chuàng)建javax.servlet.ServletContainerInitializer文件。
com.wangzhen.servlet.JamesServletContainerInitializer
- 新建JamesServletContainerInitializer實(shí)現(xiàn)ServletContainerInitializer接口
JamesServletContainerInitializer.java
@HandlesTypes(value={JamesService.class})
public class JamesServletContainerInitializer implements ServletContainerInitializer {
/**
* tomcat啟動時加載應(yīng)用的時候,會運(yùn)行onStartup方法;
*
* Set<Class<?>> arg0:感興趣的類型的所有子類型(對實(shí)現(xiàn)了JamesService接口相關(guān)的);
* ServletContext arg1:代表當(dāng)前Web應(yīng)用的ServletContext;一個Web應(yīng)用一個ServletContext;
*
* 1)、使用ServletContext注冊Web組件(Servlet、Filter、Listener)
* 2)、使用編碼的方式,在項(xiàng)目啟動的時候給ServletContext里面添加組件;
* 必須在項(xiàng)目啟動的時候來添加;
* 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);//當(dāng)傳進(jìn)來后,可以根據(jù)自己需要利用反射來創(chuàng)建對象等操作
}
}
}
并新建JamesService接口的所有子類型
JamesServiceOther
JamesServiceImpl
AbstractJamesService測試結(jié)果
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
- 總結(jié)
實(shí)質(zhì)是基于運(yùn)行時插件的機(jī)制,啟動并運(yùn)行這個ServletContainerInitializer,在整合springmvc的時候會用到
3.3 使用ServletContext注冊web組件
其實(shí)就是Servlet,Filter,Listener三大組件。
對于我們自己寫的JamesServlet,我們可以使用@WebServlet注解來加入JamesServlet組件,
但若是我們要導(dǎo)入第三方阿里的連接池或filter,以前的web.xml方式就可通過配置加載就可以了,但現(xiàn)在我們使用ServletContext注入進(jìn)來。
@Override
public void onStartup(Set<Class<?>> arg0, ServletContext arg1) throws ServletException {
System.out.println("感興趣的類型:");
for (Class<?> claz : arg0) {
System.out.println(claz);//當(dāng)傳進(jìn)來后,可以根據(jù)自己需要利用反射來創(chuàng)建對象等操作
}
//注冊servlet組件
javax.servlet.ServletRegistration.Dynamic servlet = arg1.addServlet("orderServlet", new OrderServlet());
//配置servlet的映射信息(路徑請求)
servlet.addMapping("/orderTest");
//注冊監(jiān)聽器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 {
//監(jiān)聽ServletContext銷毀
@Override
public void contextDestroyed(ServletContextEvent arg0) {
// TODO Auto-generated method stub
System.out.println("UserListener...contextDestroyed...");
}
//監(jiān)聽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...");
}
}
-
測試結(jié)果
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...
注意:在運(yùn)行的過程中,是不可以注冊組件, 和IOC道理一樣,出于安全考慮。
4.SpringMVC
4.1 簡單的項(xiàng)目
- 新建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包的時候不要把它打進(jìn)去,不然會重復(fù)。
-
查看maven的一個spring-web.jar包
內(nèi)容是:
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的映射信息
// /:攔截所有請求(包括靜態(tài)資源(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 禁用默認(rèn)的過濾規(guī)則;
@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
測試結(jié)果:

4.2 定制SpringMVC
現(xiàn)在使用配置注解,定制我們的springmvc,可看官網(wǎng)描述,加入@EnableWebMvc,來定制配置功能。
- 直接在JamesAppConfig實(shí)現(xiàn)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解析器,默認(rèn)所有的頁面都從/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控制類加一個解析器的定制頁面返回
//相當(dāng)于找 /WEB-INF/pages/ok.jsp
@RequestMapping("/ok")
public String ok(){
return "ok";
}
-
測試結(jié)果
-
出現(xiàn)的問題
解決方法:配置springmvclearn.iml
4.同步和異步處理
4.1 同步處理
客戶端發(fā)出請求后,一直等待服務(wù)端響應(yīng)。


- 修改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);//模擬業(yè)務(wù)操作
}
}
結(jié)果:
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進(jìn)行處理的,線程從頭執(zhí)行到尾,會造成資源占用不能釋放。
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("副線程結(jié)束……"+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("主線程結(jié)束……"+Thread.currentThread()+"end....."+System.currentTimeMillis());
//主線程的資源斷開……
}
public void buyCards() throws InterruptedException{
System.out.println(Thread.currentThread()+".............");
Thread.sleep(5000);//模擬業(yè)務(wù)操作
}
}
報錯:
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();方法的調(diào)用時機(jī)錯誤,應(yīng)該在子線程完全處理完成業(yè)務(wù)邏輯的最后調(diào)用。
修改:
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("副線程結(jié)束……"+Thread.currentThread()+"end....."+System.currentTimeMillis());
結(jié)果:
主線程開始……Thread[http-nio-8080-exec-6,5,main]start.....1536967088156
主線程結(jié)束……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].............
副線程結(jié)束……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("主線程結(jié)束..."+Thread.currentThread()+"==>"+System.currentTimeMillis());
return callable;
}
}
結(jié)果:
Thread[http-nio-8080-exec-4,5,main]----preHandle-------------/springmvclearn/order01
主線程開始...Thread[http-nio-8080-exec-4,5,main]==>1536970626335
主線程結(jié)束...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 訂單示例
需求描述:以創(chuàng)建訂單為例,tomcat啟動線程1來完成一個請求,但實(shí)際上是訂單服務(wù)才能創(chuàng)建訂單,那么tomcat線程應(yīng)該把請求轉(zhuǎn)發(fā)給訂單服務(wù),使用消息中間件來處理,訂單服務(wù)把處理結(jié)果也放到消息中間件,由tomcat的線程N(yùn)拿到結(jié)果后,響應(yīng)給客戶端。

- 創(chuàng)建隊(duì)列
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 {
//其實(shí)相當(dāng)于我們說的tomcat的線程1,來處理用戶請求,并將請求的操作放到Queue隊(duì)列里
@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;
}
}
測試結(jié)果:
超過5s沒有g(shù)et

正常情況


參考
- 1)Java servlet3.1官方參考文檔
- 2)注解實(shí)現(xiàn)SpringMVC異步處理中的問題
- 3)SpringMVC官方參考文檔
- 4)享學(xué)課堂James老師筆記








