Spring web
8 Spring web
8.1 Servlet 3.0
Servlet2.0的时候,我们定义的Servlet、Filter、Listener、包括SpringMVC的DispatcherServlet都需要在web.xml中进行注册,但是从Servlet3.0之后,也可以使用注解快速搭建web应用,需要注意的一点时Servlet30是需TomCat 7以上的,当然Servlet3.0是JSR315规范Jsr网站
开始搭建Servlet 3.0环境
<%@ page language="java" contentType="text/html; charset=utf-8" pageEncoding="utf-8" %>
<!
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title></title>
</head>
<body>
<h2>Hello World!</h2>
<a href="/hello">hello</a>
</body>
</html>
@WebServlet("/hello")
public class HelloServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.getWriter().write("hello");
}
}
JSR规范:在容器启动的时候,会扫描当前应用每一个jar包里边/META-INF/services/javax.servlet.ServletContainerInitializer
文件中所指定的实现类、启动并运行这个实现类的方法
//容器启动的时候会把HandlesTypes指定的这个类型下面的子类(实现类、子接口)传递过来
@HandlesTypes(value = {HelloService.class})
public class MyServletContainerInitializer implements ServletContainerInitializer {
/**
*
* @param set 感兴趣类型的所有子类型
* @param servletContext 用来代表当前应用的ServletContext,每个应用一个
* @throws ServletException
*/
@Override
public void onStartup(Set<Class<?>> set, ServletContext servletContext) throws ServletException {
System.out.println("感兴趣的类型");
for (Class<?> c :set){
System.out.println(c);
}
System.out.println("应用启动");
}
}
public interface HelloService {
}
public interface HelloServiceExt extends HelloService {
}
public interface HelloServiceImpl extends HelloService {
}
8.2 Servlet3.0注册组件
由于Servlet3.0使用全注解的方式,没有了web.xml的配置文件,所以我们使用自定义的ServletContainerInitializer在Tomcat启动的时候来添加组件
//容器启动的时候会把HandlesTypes指定的这个类型下面的子类(实现类、子接口)传递过来
@HandlesTypes(value = {HelloService.class})
public class MyServletContainerInitializer implements ServletContainerInitializer {
/**
*
* @param set 感兴趣类型的所有子类型
* @param servletContext 用来代表当前应用的ServletContext,每个应用一个
* @throws ServletException
*/
@Override
public void onStartup(Set<Class<?>> set, ServletContext servletContext) throws ServletException {
System.out.println("感兴趣的类型");
for (Class<?> c :set){
System.out.println(c);
}
System.out.println("应用启动");
ServletRegistration.Dynamic userServlet = servletContext.addServlet("userServlet", new UserServlet());
userServlet.addMapping("/user");
servletContext.addListener(UserListner.class);
FilterRegistration.Dynamic userFilter = servletContext.addFilter("userFilter", UserFilter.class);
userFilter.addMappingForUrlPatterns(EnumSet.of(DispatcherType.REQUEST),true,"/*");
}
}
public class UserServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.getWriter().write("tomcat......");
}
}
public class UserFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
System.out.println("UserFilter ....Filter");
filterChain.doFilter(servletRequest,servletResponse);
}
@Override
public void destroy() {
}
}
public class UserListner implements ServletContextListener {
@Override
public void contextDestroyed(ServletContextEvent sce) {
System.out.println("contextDestroyed....");
}
@Override
public void contextInitialized(ServletContextEvent sce) {
//这块也可以添加组件
ServletContext servletContext = sce.getServletContext();
servletContext.addFilter("",UserFilter.class);
System.out.println("contextInitialized");
}
}
8.3 Servlet3.0整合SpringMVC
Spring整合SpringMVC官方给了两种方式,可以参考Spring的官方文档参考手册
public class MyWebApplicationInitializer implements WebApplicationInitializer {
@Override
public void onStartup(ServletContext servletCxt) {
// Load Spring web application configuration
AnnotationConfigWebApplicationContext ac = new AnnotationConfigWebApplicationContext();
ac.register(AppConfig.class);
ac.refresh();
// Create and register the DispatcherServlet
DispatcherServlet servlet = new DispatcherServlet(ac);
ServletRegistration.Dynamic registration = servletCxt.addServlet("app", servlet);
registration.setLoadOnStartup(1);
registration.addMapping("/app/*");
}
}
<web-app>
<!--注册一个监听器在Web容器Tomcat启动的时候加载配置文件,启动Spring容器,称为父容器-->
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/app-context.xml</param-value>
</context-param>
<!--注册中央调度器,来加载SpringMvc的容器,称为子容器-->
<servlet>
<servlet-name>app</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value></param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>app</servlet-name>
<url-pattern>/app/*</url-pattern>
</servlet-mapping>
</web-app>
以上两种是Spring官方文档给的两种方式,这里不适用以上两种方式,首先加入Spring-webmvc的依赖
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>4.0.1</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>4.3.9.RELEASE</version>
</dependency>
</dependencies>
可以看出,在加载了Spring-webmvc的依赖之后,会自动加载一下依赖
标红的这个文件中配置了
org.springframework.web.SpringServletContainerInitializer
1、web容器在启动的时候会扫描每个jar包 下的META-INF/services/java.servlet.ServletContainerInitializer
2、加载这个文件的启动类
也就是说web容器中会自动扫描这个类,执行web容器的初始化工作
@HandlesTypes(WebApplicationInitializer.class)
public class SpringServletContainerInitializer implements ServletContainerInitializer {
@Override
public void onStartup(Set<Class<?>> webAppInitializerClasses, ServletContext servletContext)
throws ServletException {
List<WebApplicationInitializer> initializers = new LinkedList<WebApplicationInitializer>();
if (webAppInitializerClasses != null) {
for (Class<?> waiClass : webAppInitializerClasses) {
// Be defensive: Some servlet containers provide us with invalid classes,
// no matter what @HandlesTypes says...
//如果感兴趣的类不是接口、不是抽象的、并且是WebApplicationInitializer旗下的
if (!waiClass.isInterface() && !Modifier.isAbstract(waiClass.getModifiers()) &&
WebApplicationInitializer.class.isAssignableFrom(waiClass)) {
try {
//则为这个类创建实例
initializers.add((WebApplicationInitializer) waiClass.newInstance());
}
catch (Throwable ex) {
throw new ServletException("Failed to instantiate WebApplicationInitializer class", ex);
}
}
}
}
if (initializers.isEmpty()) {
servletContext.log("No Spring WebApplicationInitializer types detected on classpath");
return;
}
servletContext.log(initializers.size() + " Spring WebApplicationInitializers detected on classpath");
AnnotationAwareOrderComparator.sort(initializers);
for (WebApplicationInitializer initializer : initializers) {
initializer.onStartup(servletContext);
}
}
}
3、所以Spring容器一启动会加载@HandlesTypes(WebApplicationInitializer.class)指定的WebApplicationInitializer接口下的所有组件
4、并为这些组件创建对象
我们挨个分析WebApplicationInitializer 下的三个实现类
1、AbstractContextLoaderInitializer
public abstract class implements WebApplicationInitializer {
/** Logger available to subclasses */
protected final Log logger = LogFactory.getLog(getClass());
@Override
public void onStartup(ServletContext servletContext) throws ServletException {
registerContextLoaderListener(servletContext);
}
protected void registerContextLoaderListener(ServletContext servletContext) {
//创建一个根容器
WebApplicationContext rootAppContext = createRootApplicationContext();
if (rootAppContext != null) {
//创建一个容器监听器
ContextLoaderListener listener = new ContextLoaderListener(rootAppContext);
listener.setContextInitializers(getRootApplicationContextInitializers());
servletContext.addListener(listener);
}
else {
logger.debug("No ContextLoaderListener registered, as " +
"createRootApplicationContext() did not return an application context");
}
}
protected abstract WebApplicationContext createRootApplicationContext();
protected ApplicationContextInitializer<?>[] getRootApplicationContextInitializers() {
return null;
}
}
2、AbstractDispatcherServletInitializer
protected void registerDispatcherServlet(ServletContext servletContext) {
String servletName = getServletName();
Assert.hasLength(servletName, "getServletName() must not return empty or null");
//创建一个web的ioc容器
WebApplicationContext servletAppContext = createServletApplicationContext();
Assert.notNull(servletAppContext,
"createServletApplicationContext() did not return an application " +
"context for servlet [" + servletName + "]");
//创建了一个Servlet
FrameworkServlet dispatcherServlet = createDispatcherServlet(servletAppContext);
dispatcherServlet.setContextInitializers(getServletApplicationContextInitializers());
//将创建的Servlet添加到Tomcat容器中
ServletRegistration.Dynamic registration = servletContext.addServlet(servletName, dispatcherServlet);
Assert.notNull(registration,
"Failed to register servlet with name '" + servletName + "'." +
"Check if there is another servlet registered under the same name.");
registration.setLoadOnStartup(1);
//子类去实现
registration.addMapping(getServletMappings());
registration.setAsyncSupported(isAsyncSupported());
Filter[] filters = getServletFilters();
if (!ObjectUtils.isEmpty(filters)) {
for (Filter filter : filters) {
registerServletFilter(servletContext, filter);
}
}
customizeRegistration(registration);
}
3、AbstractAnnotationConfigDispatcherServletInitializer
注解方式配置的DispatcherServlet初始化器
public abstract class AbstractAnnotationConfigDispatcherServletInitializer
extends AbstractDispatcherServletInitializer {
//传入一个主配置类创建一个根容器
@Override
protected WebApplicationContext createRootApplicationContext() {
//获取配置类信息,是抽象的可以自定义
Class<?>[] configClasses = getRootConfigClasses();
if (!ObjectUtils.isEmpty(configClasses)) {
AnnotationConfigWebApplicationContext rootAppContext = new AnnotationConfigWebApplicationContext();
rootAppContext.register(configClasses);
return rootAppContext;
}
else {
return null;
}
}
@Override
protected WebApplicationContext createServletApplicationContext() {
//创建web的ioc容器:
AnnotationConfigWebApplicationContext servletAppContext = new AnnotationConfigWebApplicationContext();
//获取配置类是抽象的
Class<?>[] configClasses = getServletConfigClasses();
if (!ObjectUtils.isEmpty(configClasses)) {
servletAppContext.register(configClasses);
}
return servletAppContext;
}
总结:以注解方式启动SpringMVC容器:继承AbstractAnnotationConfigDispatcherServletInitializer,实现抽象方法指定DispatcherServlet的配置信息,Spring官方也是这样描述容器的关系的
public class MyWebAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
/**
* 获取根容器的配置类:(Spring的配置文件)父容器
* @return
*/
@Override
protected Class<?>[] getRootConfigClasses() {
return new Class[]{RootConfig.class};
}
/**
* 获取web容器的配置类(SpringMVC配置文件)子容器
* @return
*/
@Override
protected Class<?>[] getServletConfigClasses() {
return new Class[]{AppConfig.class};
}
/**
* 获取DispatcherServlet的映射信息
* 注意: "/" 是拦截所有请求(包括静态资源(xx.js,xx.png),但是不包括*.jsp);
* "/*" 也是拦截所有请求,*jsp也拦截,当然是找不到处理方法的,jsp是由tomcat的jsp引擎解析的
* @return
*/
@Override
protected String[] getServletMappings() {
return new String[]{"/"};
}
}
//Spring容器不扫描Controller父容器
@ComponentScan(value = "com.chengzi",excludeFilters = {
@ComponentScan.Filter(type = FilterType.ANNOTATION,classes = {Controller.class})
})
public class RootConfig {
}
//SpringMVC只扫描Controller:子容器
@ComponentScan(value = "com.chengzi",includeFilters = {
@ComponentScan.Filter(type = FilterType.ANNOTATION,classes = {Controller.class})
},useDefaultFilters = false)
public class AppConfig {
}
@Controller
public class HelloController {
@Autowired
HelloSevice helloService;
@ResponseBody
@RequestMapping("/hello")
public String hello(){
String tom = helloService.hello("tom");
return tom;
}
}
@Service
public class HelloSevice {
public String hello(String name) {
return "hi"+ name;
}
}
8.4 SpringMVC定制
没有了xml配置文件,如何配置视图解析器、静态资源映射、拦截器。。。等?,可以参见Spring的官方文档
开启SpringMVC定制功能
//SpringMVC只扫描Controller:子容器
@ComponentScan(value = "com.chengzi",includeFilters = {
@ComponentScan.Filter(type = FilterType.ANNOTATION,classes = {Controller.class})
},useDefaultFilters = false)
@EnableWebMvc
/**
* <mvc:annotation-driven/> 相当于开启了注解驱动
*/
public class AppConfig extends WebMvcConfigurerAdapter {
/**
* 视图解析器
* @param registry
*
*/
@Override
public void configureViewResolvers(ViewResolverRegistry registry) {
//默认所有的页面都从 /WEB/INF/ **.jsp
//registry.jsp();
registry.jsp("/WEB-INF/views/",".jsp");
}
/**
* 开启tomcat对静态资源的访问,否则图片等静态资源会被当做请求处理
*/
@Override
public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
configurer.enable();
}
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new MyFirstInterceptor()).addPathPatterns("/**");
}
}
public class MyFirstInterceptor implements HandlerInterceptor {
/**
*目标方法执行之前执行
*/
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("preHandle....");
return true;
}
/**
* 目标方法执行正确之后执行
*/
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
System.out.println("postHandle.....");
Class<?> aClass = handler.getClass();
System.out.println(aClass);
}
/**
* 页面响应之后执行
*/
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
}
}
8.5 Servlet3.0异步请求处理
在Servlet3.0之前,Servlet采用Thread-Pre-Request的方式处理请求。,即每一次Http请求都是由某一个线程从头到尾负责处理,如果一个请求需要进行IO操作,比如访问数据库、调用第三方接口等,那么其对应的线程将同步的等待IO操作完成,而IO操作是非常慢的,所以此时的线程并不能即使地释放回线程池以后以供后续使用,在并发量越来越大的情况下,将带来严重的性能问题,即便是像Spring、Struts这样的高层框架也脱离不了这样的束缚,因为他们都是建立在Servlet上的,为了解决这样的问题,Servlet3.0引入了异步处理,然后在Servlet3.1中又引入了非阻塞IO来进一步增强异步处理的性能
@WebServlet(value = "/hello")
public class HelloServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println(Thread.currentThread() + "start.....");
try {
sayHello();
} catch (Exception e) {
e.printStackTrace();
}
resp.getWriter().write("hello");
System.out.println(Thread.currentThread() + "end.....");
}
public void sayHello() throws Exception{
System.out.println(Thread.currentThread() + "processing.....");
Thread.sleep(3000);
}
}
控制台打印结果:
Thread[http-nio-8080-exec-3,5,main]start.....
Thread[http-nio-8080-exec-3,5,main]processing.....
Thread[http-nio-8080-exec-3,5,main]end.....
@WebServlet(value = "/async",asyncSupported = true)
public class HelloAsyncServlet extends HttpServlet {
@Override
protected void doGet(final HttpServletRequest req, final HttpServletResponse resp) throws ServletException, IOException {
//1.支持异步处理asyncSupported = true
//2.开启异步处理
System.out.println("主线程start..:" + Thread.currentThread() +":::" + System.currentTimeMillis() );
final AsyncContext startAsync = req.startAsync();
//3.业务逻辑进行异步处理
startAsync.start(new Runnable() {
@Override
public void run() {
try {
System.out.println("副线程start..:" + Thread.currentThread()+":::" + System.currentTimeMillis() );
sayHello();
startAsync.complete();
//获取到异步上下文
AsyncContext asyncContext = req.getAsyncContext();
ServletResponse response = asyncContext.getResponse();
//4.获取响应
response.getWriter().write("111");
} catch (Exception e) {
System.out.println("副线程end..:" + Thread.currentThread()+":::" + System.currentTimeMillis() );
e.printStackTrace();
}
}
});
System.out.println("主线程end..:" + Thread.currentThread()+":::" + System.currentTimeMillis() );
}
public void sayHello() throws Exception{
System.out.println(Thread.currentThread() + "processing....."+":::" + System.currentTimeMillis() );
Thread.sleep(3000);
}
}
主线程start..:Thread[http-nio-8080-exec-4,5,main]:::1569572865431
主线程end.. :Thread[http-nio-8080-exec-4,5,main]:::1569572865435
副线程start..:Thread[http-nio-8080-exec-5,5,main]:::1569572865436
Thread[http-nio-8080-exec-5,5,main]processing...:::1569572865436
副线程end.. :Thread[http-nio-8080-exec-5,5,main]:::1569572868437
8.6 SpringMVC异步请求返回
@Controller
public class AsyncController {
@RequestMapping("/async01")
@ResponseBody
public Callable<String> async01(){
System.out.println("主线程start..."+Thread.currentThread() + "=>" + System.currentTimeMillis());
Callable<String> callable = new Callable<String>() {
@Override
public String call() throws Exception {
System.out.println("副线程start..."+Thread.currentThread() + "=>" + System.currentTimeMillis());
Thread.sleep(10000);
System.out.println("副线程end....."+Thread.currentThread() + "=>" + System.currentTimeMillis());
return "Callable....";
}
};
System.out.println("主线程end....."+Thread.currentThread() + "=>" + System.currentTimeMillis());
return callable;
}
}
控制台打印结果:
preHandle....
主线程start...Thread[http-nio-8080-exec-6,5,main]=>1569575843000
主线程end.....Thread[http-nio-8080-exec-6,5,main]=>1569575843001
副线程start...Thread[MvcAsync1,5,main]=>1569575843010
副线程end.....Thread[MvcAsync1,5,main]=>1569575853010
preHandle....
postHandle.....
1、控制器返回Callable
2、Spring异步处理:将Callable提交到一个TaskExecutor,任务执行器,使用一个隔离的线程进行执行
3、DispatcherServlet和所有的Filter退出web容器的线程,但是 response保持打开的状态
4、Callable返回结果,SpringMVC将所有请求重新派发给容器,恢复之前的处理
5、根据Callable返回结果,SpringMVC继续进行视图渲染等(从收请求->渲染视图)
preHandle执行了两次,异步的情况下拦截器并不能拦截到业务逻辑的处理,异步拦截器:
(1)原生API的AsyncListener
(2)SpringMVC的情况下实现AsyncHandlerInterceptor
8.7 SpringMVC异步请求
@Controller
public class AsyncController {
@GetMapping("/quotes")
@ResponseBody
public DeferredResult<String> quotes() {
DeferredResult<String> deferredResult = new DeferredResult<String>((long)3000,"创建订单超时");
DeferredResultQueue.save(deferredResult);
return deferredResult;
}
@GetMapping("/create")
@ResponseBody
public String create() {
//创建订单
String s = UUID.randomUUID().toString();
DeferredResult deferredResult = DeferredResultQueue.get();
deferredResult.setResult(s);
return "success";
}
}
public class DeferredResultQueue {
private static Queue<DeferredResult<String>> queue = new ConcurrentLinkedQueue<>();
public static void save(DeferredResult<String> deferredResult){
queue.add(deferredResult);
}
public static DeferredResult get(){
return queue.poll();
}
}