spring注解驱动(servlet):注解的web,Shared libraries / runtimes pluggability,整合SpringMVC,异步请求处理

快速启动一个注解的web

之前呀,我们自己写一个servletfilter listener,如果想使用起来,就得在web,xml里面
配置好,现在我们不需要在web.xml里面配置了,就是使用一个注解就可以了
但是支持注解的是servlet3.0以上才支持注解,servlet3.0的规范属于JSR315系列的,
如果想看API就去https://www.jcp.org/en/home/index这里搜索servlet3.0进行下载
对应的文档,还有就是只有Tomcat7.0以上才支持servlet3.0
现在我们快熟创建一个动态web工程

页面

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
	<a href="HelloWorld">HelloWorld</a>
</body>
</html>

java代码
按照之前,我们应该在web.xml里面进行配置

	<servlet>
		<servlet-name>HelloWorld</servlet-name>
		<servlet-class>jane.HelloWorld</servlet-class>
	</servlet>
	<servlet-mapping>
		<servlet-name>HelloWorld</servlet-name>
		<url-pattern>/HelloWorld</url-pattern>
	</servlet-mapping>

而现在我们不需要在web.xml里面配置了,而是直接加@WebServlet("/HelloWorld")注解

package jane;

import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

@WebServlet("/HelloWorld")
public class HelloWorld extends HttpServlet
{
	private static final long serialVersionUID = 1L;

	protected void service(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException
	{
		response.getWriter().write("Hello World");
	}

}

Shared libraries / runtimes pluggability

Shared libraries (共享库)/ runtimes pluggability(运行时插件能力)
根据里面的解释

An instance of the ServletContainerInitializer is looked up via the jar 
services API by the container at container / application startup time. 
The framework providing an implementation of the 
ServletContainerInitializer MUST bundle in the META-INF/services directory 
of the jar file a file called javax.servlet.ServletContainerInitializer, 
as per the jar services API, that points to the implementation class 
of the ServletContainerInitializer.

就是在Servlet容器启动的时候会扫描当前应用的每一个jar包里面的
ServletContainerInitializer的实现类,这个框架提供了一个
ServletContainerInitializer实现类的方法,就是必须绑定在
META-INF/services目录下一个叫javax.servlet.ServletContainerInitializer
的文件,文件里面的内容就是指向ServletContainerInitializer实现类的全类名

我们自己快速开始尝试吧

那这里为了方便就不写jar包了,直接在当前项目的类路径下写
META-INF/servicesjavax.servlet.ServletContainerInitializer文件
在里面写上ServletContainerInitializer实现类的全类名

写好那个文件路径,写好对应的值,我这里是jane.servlet.MyServletContainerInitializer
编写对应的实现类

package jane.servlet;

import java.util.EnumSet;
import java.util.Set;

import javax.servlet.DispatcherType;
import javax.servlet.FilterRegistration;
import javax.servlet.ServletContainerInitializer;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.ServletRegistration.Dynamic;
import javax.servlet.annotation.HandlesTypes;
/*
 * 容器启动的时候会将@HandlesTypes指定的这个类型下面的子类,实现类,子接口等等传递过来
 * 但是自身是不会传递过来的,
 */
@HandlesTypes(value = {})
public class MyServletContainerInitializer implements ServletContainerInitializer
{
	/*
	 * onStartup()方法就是应用启动的时候运行
	 * Set<Class<?>> arg0:这个就是上面@HandlesTypes注解传入的感兴趣的类型,
	 * 					  我们可以根据传入的类型创建对象
	 * 
	 * ServletContext arg1:代表当前web应用的ServletContext,一个web应用对应一个ServletContext
	 * 
	 * 使用ServletContext来注册Web组件(Servlet,Filter,Listener)
	 * 之前我们注册web组件的时候,都是可以在web.xml里面写,但是现在没有了web.xml
	 * 如果是我们自己写的组件想加入容器中加上@WebServlet之类的注解就行
	 * 但是如果我们想加入第三方的jar包的组件,我们就没有办法再加注解了,
	 * 所以这里使用ServletContext来注册Web组件
	 * 
	 * 可以给容器添加组件的方法大概有两种
	 * (必须在项目启动的时候才能给容器添加组件,启动完成后不能再添加组件):
	 * 1.首先是这里写的这个方法,使用ServletContainerInitializer
	 * 2.就是ServletContextListener里面的contextInitialized可以得到
	 * 	ServletContext进行添加组件
	 */
	@Override
	public void onStartup(Set<Class<?>> arg0, ServletContext servletContext) throws ServletException
	{
		//注册组件
		//注册Servlet
		Dynamic addServlet = servletContext.addServlet("userServlet", new UserServlet());
		//配置Servlet的映射信息
		addServlet.addMapping("/user");
		
		//注册Listener
		servletContext.addListener(UserListener.class);
		
		//注册Filter
		FilterRegistration.Dynamic addFilter = servletContext.addFilter("userfilter", UserFilter.class);
		/*
		 * 配置Filter的映射信息
		 * dispatcherTypes:是需要拦截什么类型的请求
		 *      FORWARD,
			    INCLUDE,
			    REQUEST,
			    ASYNC,
			    ERROR
		 */
		
		addFilter.addMappingForUrlPatterns(EnumSet.of(DispatcherType.REQUEST), true, "/*");
	}

}

编写对应的三大组件

package jane.servlet;

import java.io.IOException;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class UserServlet extends HttpServlet
{
	private static final long serialVersionUID = 1L;

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

package jane.servlet;

import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
//监听项目的启动和停止
public class UserListener implements ServletContextListener
{
	@Override
	public void contextInitialized(ServletContextEvent sce)
	{
		System.out.println("UserListener...contextInitialized");
	}
	@Override
	public void contextDestroyed(ServletContextEvent sce)
	{
		System.out.println("UserListener...contextDestroyed");
	}
}

package jane.servlet;

import java.io.IOException;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;

public class UserFilter implements Filter
{

	@Override
	public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
			throws IOException, ServletException
	{
		System.out.println("UserFilter...doFilter");
		chain.doFilter(request, response);
	}
}

整合SpringMVC

原理

整合这方面在官方文档里面写得挺详细的,具体可以参考官方文档
官网
对应的框架
对应文档
例子
我们利用上面刚刚说的知识利用起来整合
在Maven Dependencies的spring-web的jar包里面存在这样的资源
在这里插入图片描述

按照上面说的,web容器在启动的时候,会扫描每个jar包下的
META-INF/servicesjavax.servlet.ServletContainerInitializer文件,
根据里面的值,加载SpringServletContainerInitializer的实现类
进入SpringServletContainerInitializer查看一番
可以看到spring一启动就会加载感兴趣类型WebApplicationInitializer接口下的
所有的组件,并且如果它接口下的组件不是接口,不是抽象类,那么就会为它们创建对象

在这里插入图片描述

进入WebApplicationInitializer接口,
发现它有三个抽象类,那么我们一个一个地查看它们干了什么

在这里插入图片描述

进入AbstractContextLoaderInitializer
可以发现这个抽象类创建了一个根容器,而且加入一个ContextLoaderListener

在这里插入图片描述

进入AbstractDispatcherServletInitializer,由名知义,这是DispatherServlet初始化器
首先看出它是调用父类的onStartup()方法,然后调用registerDispatcherServlet()
在里面创建了一个web的IOC容器,还创建了dispatcherServlet
然后将dispatcherServlet添加到servletContext中,然后进行配置
registration.addMapping(getServletMappings());中的getServletMappings()
方法由我们来进行实现

在这里插入图片描述

进入AbstractAnnotationConfigDispatcherServletInitializer(注解配置DispatcherServlet初始化器)
里面有两个方法
	创建根容器:重写createRootApplicationContext,是根据实现的getRootConfigClasses();方法获得
	创建web的IOC容器:重写createServletApplicationContext,根据实现的getServletConfigClasses();方法获得

这两个容器在官方文档里面有图示,各自执行各自的功能

在这里插入图片描述
在这里插入图片描述

实操

pom.xml文件
<project xmlns="http://maven.apache.org/POM/4.0.0"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<groupId>jane</groupId>
	<artifactId>AnnotationSpringMVC</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<packaging>war</packaging>

	<dependencies>
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-webmvc</artifactId>
			<version>4.3.11.RELEASE</version>
		</dependency>
		
		<dependency>
			<groupId>javax.servlet</groupId>
			<artifactId>servlet-api</artifactId>
			<version>3.0-alpha-1</version>
			<scope>provided</scope>
		</dependency>
	</dependencies>

	<!-- pom.xml文件因为你是web项目但是没有web.xml文件而报错
	所以我们加了这个插件设置<failOnMissingWebXml>false</failOnMissingWebXml>
	让它没有这个文件也不会报错 -->
	<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>
	
</project>
继承AbstractAnnotationConfigDispatcherServletInitializer
package jane;

import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer;

import jane.config.RootConfig;
import jane.config.WebAppConfig;

//这个类呢就是web容器启动的时候创建对象,里面的方法能初始化容器的前端控制器
public class MyAnnotationConfigDispatcherServletInitializer extends AbstractAnnotationConfigDispatcherServletInitializer
{
	//这个是获取根容器的配置类,相当于之前的spring配置文件,这里作为父容器
	@Override
	protected Class<?>[] getRootConfigClasses()
	{
		return new Class<?>[] {RootConfig.class};
	}
	//这里是获取web容器的配置类,就是之前的springMVC的配置文件,这里作为子容器
	@Override
	protected Class<?>[] getServletConfigClasses()
	{
		return new Class<?>[] {WebAppConfig.class};
	}
	//获取DispatherServlet的映射信息
	//这里写"/"就可以了,这是拦截所有请求,包括静态资源*.png,*.js,*.html,但是不包括*.jsp
	//而"/*",也是拦截所有请求,连*.jsp页面都会进行拦截,jsp页面是Tomcat的jsp引擎解析的
	@Override
	protected String[] getServletMappings()
	{
		return new String[] {"/"};
	}

}

配置类
package jane.config;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.ComponentScan.Filter;
import org.springframework.context.annotation.FilterType;
import org.springframework.stereotype.Controller;
//Spring不扫描Controller,是个父容器
@ComponentScan(value = ("jane"),excludeFilters = 
	{
			@Filter(type = FilterType.ANNOTATION,classes = {Controller.class})
	}
		)
public class RootConfig
{

}

package jane.config;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.ComponentScan.Filter;
import org.springframework.context.annotation.FilterType;
import org.springframework.stereotype.Controller;
//SpringMVC只扫描Controller,是个子容器
@ComponentScan(value = ("jane"),includeFilters = 
	{
			@Filter(type = FilterType.ANNOTATION,classes = {Controller.class})
	},useDefaultFilters = false
		)
public class WebAppConfig
{

}

相关组件
package jane.controller;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

import jane.service.HelloService;

@Controller
public class HelloController
{
	@Autowired
	HelloService helloService;
	
	@ResponseBody
	@RequestMapping("/hello")
	public String hello()
	{
		String sayHello = helloService.sayHello("good boy");
		return sayHello;
	}
}

package jane.service;

import org.springframework.stereotype.Service;

@Service
public class HelloService
{
	public String sayHello(String name)
	{
		return "Hello"+name;
	}
}

更多的配置

按照之前,我们还有很多的配置配置在springMVC的配置文件中,但是现在我们如何配置呢?

官方文档里面的MVC config有说
官方文档

首先我们得在配置类里面写上@EnableWebMvc,开启SpringMVC自定义配置
相当于配置文件里面的<mvc:annotation-driven/>
然后In Java config implement WebMvcConfigurer interface:
就是在配置类里面实现WebMvcConfigurer 接口
这个接口有很多的抽象方法来实现上面的需求
但是因为实现WebMvcConfigurer接口的话需要重写所有方法,为了想只重写想要得方法
所以就继承它的实现类WebMvcConfigurerAdapter,这个实现类什么都没写
我们想实现哪个方法就重写哪个方法
package jane.config;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.ComponentScan.Filter;
import org.springframework.context.annotation.FilterType;
import org.springframework.stereotype.Controller;
import org.springframework.web.servlet.config.annotation.DefaultServletHandlerConfigurer;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.ViewResolverRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;

import jane.controller.MyInterceptor;
//SpringMVC只扫描Controller,是个子容器
@ComponentScan(value = ("jane"),includeFilters = 
	{
			@Filter(type = FilterType.ANNOTATION,classes = {Controller.class})
	},useDefaultFilters = false
		)
@EnableWebMvc
public class WebAppConfig extends WebMvcConfigurerAdapter
{
	//我们来写之前在配置文件里面经常写的几个配置
	//视图解析器
	@Override
	public void configureViewResolvers(ViewResolverRegistry registry)
	{
		registry.jsp("/WEB-INF/view/", ".jsp");
	}
	//静态资源访问
	@Override
	public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer)
	{
		//相当于之前配置的<mvc:default-servlet-handler/>
		configurer.enable();
	}
	//拦截器
	@Override
	public void addInterceptors(InterceptorRegistry registry)
	{
		//"/**"拦截任意多重路径的请求
		registry.addInterceptor(new MyInterceptor()).addPathPatterns("/**");
	}
}

异步请求处理

对于客服端的请求,服务器里面都有一个主线程池里面的创建好的线程进行响应工作
如果一个客户端的请求需要处理的时间很长,那么这个客户的请求就会一直占用着这个主线程
但是线程池里面的线程是有限的,如果还有很多的请求来到服务器,但是主线程池已经
没有空闲的线程来处理这些请求了,就会造成无法响应之类的
servlet3.0就可以开启异步请求
就是客户端向服务器请求的时候,如果这个请求处理的时间很长,那么就可以
开启一个子线程来处理它,子线程处理完后将结果响应回去,
而主线程会很快空闲下来回到线程池里面提供下一个请求来使用

web中如何实现

package jane.servlet;

import java.io.IOException;
import javax.servlet.AsyncContext;
import javax.servlet.ServletException;
import javax.servlet.ServletResponse;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

@WebServlet(value = "/Async",asyncSupported = true)
public class HelloAsyncServlet extends HttpServlet
{
	/*
	 * 1.首先让它支持异步处理asyncSupported = true
	 * 2.开启异步模式
	 */
	@Override
	protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
	{
		System.out.println("主线程开始"+Thread.currentThread());
		
		AsyncContext startAsync = req.startAsync();
		startAsync.start(new Runnable()
		{
			@Override
			public void run()
			{
				try
				{
					System.out.println("子线程开始"+Thread.currentThread());
					Thread.sleep(2000);
					
					
					//然后想响应出去
					//获取到异步上下文
					AsyncContext asyncContext = req.getAsyncContext();
					//获取响应
					ServletResponse response = asyncContext.getResponse();
					response.getWriter().write("Hello World Asunc");
					
					//这个说明异步处理调用完毕
					startAsync.complete();
					System.out.println("子线程结束"+Thread.currentThread());
				} catch (Exception e)
				{
					System.out.println("异常");
					e.printStackTrace();
				}
			}
		});
		System.out.println("主线程结束"+Thread.currentThread());
	}
}

SpringMVC中如何实现

其实官方文档也写得很详细:文档

package jane.controller;

import java.util.concurrent.Callable;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.context.request.async.DeferredResult;

@Controller
public class AsyncController
{
/*
下面代码的结果:
preHandle
preHandle
postHandle
afterCompletion

官网这样说流程:
Callable processing:

Controller returns a Callable.
控制器返回的是Callable,而不是之前的url拼接或者json对象之类的

Spring MVC calls request.startAsync() and submits the Callable 
to a TaskExecutor for processing in a separate thread.
springmvc会开始异步请求处理,并且将Callable对象提交到TaskExecutor(任务执行器)
里面使用的是一个隔离的线程执行它

Meanwhile the DispatcherServlet and all Filter’s exit the Servlet 
container thread but the response remains open.
DispatcherServlet和所有的Filter退出容器线程,但是response保持打开状态

Eventually the Callable produces a result and Spring MVC dispatches 
the request back to the Servlet container to complete processing.
Callable处理返回结果,并且SpringMVC将请求重新派发给容器去完成这个处理

The DispatcherServlet is invoked again and processing resumes with the 
asynchronously produced return value from the Callable.
DispatcherServlet再次执行,根据Callable的返回值SpringMVC继续进行视图渲染等等

分析结果:
1.接收到请求:http://localhost:8080/AnnotationSpringMVC/async
2.preHandle
3.主线程开始
4.主线程结束
5.DispatcherServlet和所有的Filter退出容器线程
6.Callable执行
7.开启子线程
8.子线程结束
9.重新接收到请求:http://localhost:8080/AnnotationSpringMVC/async
10.preHandle
11.Callable的之前的返回值就是目标方法的返回值
12.postHandle
13.afterCompletion
14.将页面显示,页面需要的Callable的值
*/
	@ResponseBody
	@RequestMapping("/async")
	public Callable<String> async()
	{
		Callable<String> callable = new Callable<String>()
		{

			@Override
			public String call() throws Exception
			{
				return "public Callable<String> async()";
			}
		};
		return callable;
	}
	@ResponseBody
	@RequestMapping("/DeferredResult")
	public DeferredResult<Object> Getname()
	{
		//第一个参数代表留给其他线程多长时间来处理,过了这个时间就会后面的报错
		DeferredResult<Object> deferredResult = new DeferredResult<Object>((long)3000, "没线程处理/失败");
		//一般是将这个deferredResult保存下来,
		//然后等待其他的进程来处理,来处理这个的可以自己手动调用,
		//或者有监听器自动调用,当发现有线程调用了deferredResult.setResult(order);方法
		//代表处理完成了,就会在页面显示对应的结果
		
		/*
		 * 下面程序执行的结果
		 *  preHandle
			preHandle
			postHandle
			afterCompletion
		 */
		new Thread(new Runnable()
		{
			@Override
			public void run()
			{
				try
				{
					Thread.currentThread().sleep(2000);
				} catch (InterruptedException e)
				{
					e.printStackTrace();
				}
				deferredResult.setResult("child over");
			}
		}).start();
		return deferredResult;
		
	}
}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

ReflectMirroring

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值