如果你是小白,这套资料可以帮你成为大牛,如果你有丰富开发经验,这套资料可以帮你突破瓶颈
2022web全套视频教程前端架构 H5 vue node 小程序 视频+资料+代码+面试题.
现在,我们来说说注解版的web,我们以前来写web的三大组件:Servlet、Filter、Listener,包括SpringMVC的前端控制器DispatcherServlet都需要在web.xml文件中来进行注册;而在Servlet3.0标准以后,就给我们提供了方便的注解的方式来完成我们这些组件的注册以及添加,提供了运行时的可插拔的插件能力;
说明:Servlet3.0及以上的标准是需要Tomcat7及以上的支持;
如果使用全注解开发,那么就可以去掉web.xml配置文件了,转用编码方式进行替代
<dependency> <groupId>javax.servlet</groupId> <artifactId>javax.servlet-api</artifactId> <version>4.0.1</version> <scope>provided</scope> </dependency> <dependency> <groupId>javax.servlet.jsp</groupId> <artifactId>javax.servlet.jsp-api</artifactId> <version>2.2.1</version> <scope>provided</scope> </dependency>
@WebServlet("/hello")//指定当前servlet的请求映射路径 public class MyServlet extends HttpServlet { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { resp.setContentType("text/html;charset=utf-8"); resp.getWriter().write("大忽悠"); } }
Servlet 3.0 注解 @WebServlet @WebFilter @WebListener
@WebFilter 的使用
@WebServlet的使用方法
resp.setContentType("text/html;charset=utf-8");
Shared libraries(共享库) / runtimes pluggability(运行时插件能力) 1、Servlet容器启动会扫描,当前应用里面每一个jar包的ServletContainerInitializer的实现 2、提供ServletContainerInitializer的实现类; 必须绑定在,META-INF/services/javax.servlet.ServletContainerInitializer 文件的内容就是ServletContainerInitializer实现类的全类名; 总结:容器在启动应用的时候,会扫描当前应用每一个jar包里面 META-INF/services/javax.servlet.ServletContainerInitializer 指定的实现类,启动并运行这个实现类的方法;传入感兴趣的类型; ServletContainerInitializer; @HandlesTypes;
//容器启动的时候会将@HandlesTypes指定的这个类型下面的子类(实现类或者子接口等)传递过来 //传入感兴趣的类型 @HandlesTypes(value = {HelloService.class}) public class MyServletContainerInitializer implements ServletContainerInitializer { /** * 在应用启动的时候,会运行onStartup方法; * Set<Class<?>> :感兴趣的类型的所有子类型; * ServletContext 代表当前的web应用的ServletContext对象,一个web应用相当于是一个ServletContext * @throws ServletException */ @Override public void onStartup(Set<Class<?>> set, ServletContext servletContext) throws ServletException { System.out.println("感兴趣的类型:"); set.forEach(System.out::println); } }
文件的内容就写我们实现ServletContainerInitializer 这个接口的类MyServletContainerInitializer 的全类名:
最后,我们运行起来,运行结果为:
感兴趣的类型: class com.ldc.service.HelloServiceExt class com.ldc.service.AbstractHelloService class com.ldc.service.HelloServiceImpl
首先我们写一个Servlet:
public class MyServlet extends HttpServlet { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { resp.setContentType("text/html;charset=utf-8"); resp.getWriter().write("大忽悠"); } }
再来一个Filter:
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("放行"); //放行 filterChain.doFilter(servletRequest,servletResponse); } @Override public void destroy() { } }
最后,再写一个Listener:
/** * 监听项目的启动和停止 */ public class UserListener implements ServletContextListener { //监听ServletContextEvent的启动初始化 @Override public void contextInitialized(ServletContextEvent servletContextEvent) { System.out.println("项目启动"); } //监听ServletContextEvent销毁 @Override public void contextDestroyed(ServletContextEvent servletContextEvent) { System.out.println("项目停止"); } }
最后,我们来用ServletContext来进行注册三大组件:
//容器启动的时候会将@HandlesTypes指定的这个类型下面的子类(实现类或者子接口等)传递过来 //传入感兴趣的类型 @HandlesTypes(value = {HelloService.class}) public class MyServletContainerInitializer implements ServletContainerInitializer { /** * 在应用启动的时候,会运行onStartup方法; * Set<Class<?>> :感兴趣的类型的所有子类型; * ServletContext 代表当前的web应用的ServletContext对象,一个web应用相当于是一个ServletContext * 1)、使用ServletContext注册Web组件(Servlet、Filter、Listener) * 2)、使用编码的方式,在项目启动的时候给ServletContext添加组件 * 必须在项目启动的时候来添加 * (1)ServletContainerInitializer得到ServletContext对象来注册; * (2)ServletContextListener的方法的参数里面的ServletContextEvent对象可以获取ServletContext对象 */ @Override public void onStartup(Set<Class<?>> set, ServletContext servletContext) throws ServletException { System.out.println("感兴趣的类型:"); set.forEach(System.out::println); //注册组件 ServletRegistration.Dynamic servlet = servletContext.addServlet("userServlet", new MyServlet()); //配置servlet的映射信息 servlet.addMapping("/hello"); //注册Listener servletContext.addListener(UserListener.class); //注册Filter FilterRegistration.Dynamic filter = servletContext.addFilter("userFilter", UserFilter.class); //拦截什么方式的请求,和要拦截的请求路径 filter.addMappingForUrlPatterns(EnumSet.of(DispatcherType.REQUEST),true,"/*"); } }
我们启动,然后再浏览器上进行访问:
1.使用ServletContext注册Web组件(Servlet、Filter、Listener)
2.使用编码的方式,在项目启动的时候给ServletContext添加组件, 必须在项目启动的时候来添加
2.1 ServletContainerInitializer得到ServletContext对象来注册;
2.2 ServletContextListener的方法的参数里面的ServletContextEvent对象可以获取ServletContext对象,然后在项目启动的时候进行注册,即利用监听器进行组件注册
<dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>4.3.11.RELEASE</version> </dependency>
1、web容器在启动的时候,会扫描每个jar包下的META-INF/services/javax.servlet.ServletContainerInitializer 2、加载这个文件指定的类SpringServletContainerInitializer 3、spring的应用一启动会加载感兴趣的WebApplicationInitializer接口的下的所有组件; 4、并且为WebApplicationInitializer组件创建对象(组件不是接口,不是抽象类) 1)、AbstractContextLoaderInitializer:创建根容器;createRootApplicationContext(); 2)、AbstractDispatcherServletInitializer: 创建一个web的ioc容器;createServletApplicationContext(); 创建了DispatcherServlet;createDispatcherServlet(); 将创建的DispatcherServlet添加到ServletContext中; getServletMappings(); 3)、AbstractAnnotationConfigDispatcherServletInitializer:注解方式配置的DispatcherServlet初始化器 创建根容器:createRootApplicationContext() getRootConfigClasses();传入一个配置类 创建web的ioc容器: createServletApplicationContext(); 获取配置类;getServletConfigClasses(); 总结: 以注解方式来启动SpringMVC;继承AbstractAnnotationConfigDispatcherServletInitializer; 实现抽象方法指定DispatcherServlet的配置信息;
下面我们找到了SpringServletContainerInitializer 继承ServletContainerInitializer 的实现类源码如下:
@HandlesTypes({WebApplicationInitializer.class}) public class SpringServletContainerInitializer implements ServletContainerInitializer { public SpringServletContainerInitializer() { } public void onStartup(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)waiClass.newInstance()); } catch (Throwable var7) { throw new ServletException("Failed to instantiate WebApplicationInitializer class", var7); } } } } if (initializers.isEmpty()) { servletContext.log("No Spring WebApplicationInitializer types detected on classpath"); } else { servletContext.log(initializers.size() + " Spring WebApplicationInitializers detected on classpath"); AnnotationAwareOrderComparator.sort(initializers); var4 = initializers.iterator(); while(var4.hasNext()) { WebApplicationInitializer initializer = (WebApplicationInitializer)var4.next(); initializer.onStartup(servletContext); } } } }
//Web容器启动的时候创建对象;调用方法来初始化容器以及前端控制器 public class MyWebInitializer extends AbstractAnnotationConfigDispatcherServletInitializer { //获取父容器的配置类:(Spring的配置文件) --->作为父容器 @Override protected Class<?>[] getRootConfigClasses() { return new Class<?>[]{RootConfig.class}; } //获取web容器的配置类(SpringMVC配置文件) --->作为一个子容器 @Override protected Class<?>[] getServletConfigClasses() { return new Class<?>[]{AppConfig.class}; } //获取DispatcherServlet的映射信息 // /:拦截所有请求(包括静态资源(xx.js,xx.png),但是不包括*.jsp) // /*:拦截所有请求,连*.jsp页面都拦截;jsp页面是tomcat引擎解析的 @Override protected String[] getServletMappings() { return new String[]{"/"}; } }
spring是父配置类:
//Spring的容器不扫描Controller,父容器 @ComponentScan(value = {"com.ldc."},excludeFilters = { @ComponentScan.Filter(type = FilterType.ANNOTATION,classes = {Controller.class}) }) public class RootConfig {}
springmvc是子配置类:
//SpringMVC只扫描Controller,子容器 //useDefaultFilters = false 禁用默认的过滤规则 @ComponentScan(value = {"com.ldc"},includeFilters = { @ComponentScan.Filter(type = FilterType.ANNOTATION,classes = {Controller.class}) },useDefaultFilters = false) public class AppConfig { }
@Controller public class HelloController { @Autowired private HelloService helloService; @ResponseBody @RequestMapping("hello") public String hello() { String hello = helloService.sayHello("tomcat"); return hello; } } @Service public class HelloService { public String sayHello(String name) { return "Hello," + name; } }
以前进行的xml配置:
将springmvc处理不了的请求交给tomcat的模板引擎解析,这样静态资源就可以访问 <mvc:default-servlet-handler/> springmvc高级功能开启 <mvc:annotation-driven/> 配置拦截器 <mvc:interceptors><mvc:interceptors/> 配置视图映射(发送的请求不想通过controller,只想直接地跳转到目标页面) <mvc:view-controller path="/hello" view-name="hello"></mvc:view-controller> ...
定制SpringMVC; 1)、@EnableWebMvc:开启SpringMVC定制配置功能; <mvc:annotation-driven/>; 2)、配置组件(视图解析器、视图映射、静态资源映射、拦截器。。。) 通过一个配置类来实现接口:extends WebMvcConfigurerAdapter 这样就可以来实现定制配置了
我们就可以这样来写:
//SpringMVC只扫描Controller,子容器 //useDefaultFilters = false 禁用默认的过滤规则 @ComponentScan(value = {"com.ldc"},includeFilters = { @Filter(type = FilterType.ANNOTATION,classes = {Controller.class}) },useDefaultFilters = false) @EnableWebMvc public class AppConfig implements WebMvcConfigurer { @Override public void configurePathMatch(PathMatchConfigurer pathMatchConfigurer) { } @Override public void configureContentNegotiation(ContentNegotiationConfigurer contentNegotiationConfigurer) { } @Override public void configureAsyncSupport(AsyncSupportConfigurer asyncSupportConfigurer) { } @Override public void configureDefaultServletHandling(DefaultServletHandlerConfigurer defaultServletHandlerConfigurer) { //将SpringMVC处理不了的请求交给tomcat,专门针对于静态资源的,这个时候,静态资源就是可以访问的 defaultServletHandlerConfigurer.enable(); } @Override public void addFormatters(FormatterRegistry formatterRegistry) { //添加自定义的类型转换器 } @Override public void addInterceptors(InterceptorRegistry interceptorRegistry) { } @Override public void addResourceHandlers(ResourceHandlerRegistry resourceHandlerRegistry) { } @Override public void addCorsMappings(CorsRegistry corsRegistry) { } @Override public void addViewControllers(ViewControllerRegistry viewControllerRegistry) { } @Override public void configureViewResolvers(ViewResolverRegistry viewResolverRegistry) { } @Override public void addArgumentResolvers(List<HandlerMethodArgumentResolver> list) { } @Override public void addReturnValueHandlers(List<HandlerMethodReturnValueHandler> list) { } @Override public void configureMessageConverters(List<HttpMessageConverter<?>> list) { } @Override public void extendMessageConverters(List<HttpMessageConverter<?>> list) { } @Override public void configureHandlerExceptionResolvers(List<HandlerExceptionResolver> list) { } @Override public void extendHandlerExceptionResolvers(List<HandlerExceptionResolver> list) { } @Override public Validator getValidator() { return null; } @Override public MessageCodesResolver getMessageCodesResolver() { return null; } }
//SpringMVC只扫描Controller,子容器 //useDefaultFilters = false 禁用默认的过滤规则 @ComponentScan(value = {"com.ldc"},includeFilters = { @ComponentScan.Filter(type = FilterType.ANNOTATION,classes = {Controller.class}) },useDefaultFilters = false) @EnableWebMvc public class AppConfig extends WebMvcConfigurerAdapter { //定制 //视图解析器 @Override public void configureViewResolvers(ViewResolverRegistry registry) { //默认所有页面都是从/WEB-INF/xxx.jsp /* * public UrlBasedViewResolverRegistration jsp() { return this.jsp("/WEB-INF/", ".jsp"); } * */ //registry.jsp(); //我们也可以自己来写规则 registry.jsp("/WEB-INF/views/", ".jsp"); } }
我们在/WEB-INF/views/这个路径下新建一个success.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %> <html> <head> <title>Title</title> </head> <body> <h1>success</h1> </body> </html>
//SpringMVC只扫描Controller,子容器 //useDefaultFilters = false 禁用默认的过滤规则 @ComponentScan(value = {"com.ldc"},includeFilters = { @Filter(type = FilterType.ANNOTATION,classes = {Controller.class}) },useDefaultFilters = false) @EnableWebMvc public class AppConfig extends WebMvcConfigurerAdapter { //定制 //视图解析器 @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(); } }
这个时候,我们的图片就是可以访问了:
public class MyFirstInterceptor implements HandlerInterceptor { //在目标方法执行之前执行 @Override public boolean preHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o) throws Exception { System.out.println("preHandle..."); return true; } //在目标方法执行之后执行 @Override public void postHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, ModelAndView modelAndView) throws Exception { System.out.println("postHandle..."); } //页面响应以后执行 @Override public void afterCompletion(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) throws Exception { System.out.println("afterCompletion..."); } }
//SpringMVC只扫描Controller,子容器 //useDefaultFilters = false 禁用默认的过滤规则 @ComponentScan(value = {"com.ldc"},includeFilters = { @ComponentScan.Filter(type = FilterType.ANNOTATION,classes = {Controller.class}) },useDefaultFilters = false) @EnableWebMvc public class AppConfig extends WebMvcConfigurerAdapter { //定制 //视图解析器 @Override public void configureViewResolvers(ViewResolverRegistry registry) { //默认所有页面都是从/WEB-INF/xxx.jsp /* * public UrlBasedViewResolverRegistration jsp() { return this.jsp("/WEB-INF/", ".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 MyFirstInterceptor()).addPathPatterns("/**"); } }
我们重新启动,并且访问这个路径:
我们发现控制台已经打印了,说明拦截器已经起作用了:
preHandle… postHandle… afterCompletion…
servlet3.0异步处理:
在Servlet 3.0之前,Servlet采用Thread-Per-Request的方式处理请求。
即每一次Http请求都由某一个线程从头到尾负责处理。
如果一个请求需要进行IO操作,比如访问数据库、调用第三方服务接口等,那么其所对应的线程将同步地等待IO操作完成, 而IO操作是非常慢的,所以此时的线程并不能及时地释放回线程池以供后续使用,在并发量越来越大的情况下,这将带来严重的性能问题。即便是像Spring、Struts这样的高层框架也脱离不了这样的桎梏,因为他们都是建立在Servlet之上的。为了解决这样的问题,Servlet 3.0引入了异步处理,然后在Servlet 3.1中又引入了非阻塞IO来进一步增强异步处理的性能。
我们可以在之前写的Servlet加上当前的线程:
@WebServlet("/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 (InterruptedException e) { e.printStackTrace(); } resp.getWriter().write("hello..."); System.out.println(Thread.currentThread()+" end..."); } private void sayHello() throws InterruptedException { System.out.println(Thread.currentThread()+ " processing..."); Thread.sleep(3000); } }
启动服务之后,控制台打印结果为:我们可以发现从线程开始、处理请求到执行结束从始至终都是Thread[http-nio-8081-exec-3,5,main]这个线程,主线程得不到释放,当下一个请求进来就得不到处理;
UserFilter…doFilter… Thread[http-nio-8081-exec-3,5,main] start… Thread[http-nio-8081-exec-3,5,main] processing… Thread[http-nio-8081-exec-3,5,main] end…
我们再来加上是哪些线程处理的:
@WebServlet(value = "/async",asyncSupported = true) public class HelloAsyncServlet extends HttpServlet { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { //1.支持异步处理:asyncSupported = true //2.开启异步模式 System.out.println("主线程开始..."+Thread.currentThread()+"==>"+ Instant.now().toEpochMilli()); AsyncContext startAsync = req.startAsync(); //3.业务逻辑进行异步处理,开始异步处理 startAsync.start(()-> { try { System.out.println("副线程开始..."+Thread.currentThread()+"==>"+ Instant.now().toEpochMilli()); sayHello(); startAsync.complete(); //获取异步上下文 //AsyncContext asyncContext = req.getAsyncContext(); //4.获取响应 ServletResponse response = startAsync.getResponse(); response.getWriter().write("hello async..."); System.out.println("副线程结束..."+Thread.currentThread()+"==>"+ Instant.now().toEpochMilli()); } catch (Exception e) { e.printStackTrace(); } }); System.out.println("主线程结束..."+Thread.currentThread()+"==>"+ Instant.now().toEpochMilli()); } private void sayHello() throws InterruptedException { System.out.println(Thread.currentThread()+ " processing..."+"==>"+ Instant.now().toEpochMilli()); Thread.sleep(3000); } }
执行结果如下:
感兴趣的类型: class com.ldc.service.HelloServiceImpl class com.ldc.service.HelloServiceExt class com.ldc.service.AbstractHelloService UserListener…contextInitialized [2019-01-18 04:47:06,991] Artifact servlet3.0:war exploded: Artifact is deployed successfully [2019-01-18 04:47:06,992] Artifact servlet3.0:war exploded: Deploy took 447 milliseconds UserFilter…doFilter… UserFilter…doFilter… UserFilter…doFilter… 主线程开始…Thread[http-nio-8081-exec-7,5,main]>1547801232248 主线程结束…Thread[http-nio-8081-exec-7,5,main]>1547801232253 副线程开始…Thread[http-nio-8081-exec-8,5,main]>1547801232253 Thread[http-nio-8081-exec-8,5,main] processing…>1547801232253 副线程结束…Thread[http-nio-8081-exec-8,5,main]==>1547801235253
现在的处理就是可以表示成下图:
@Controller public class AsyncController { /** * 1、控制器返回Callable * 2、Spring异步处理,将Callable 提交到 TaskExecutor 使用一个隔离的线程进行执行 * 3、DispatcherServlet和所有的Filter退出web容器的线程,但是response 保持打开状态; * 4、Callable返回结果,SpringMVC将请求重新派发给容器,恢复之前的处理; * 5、根据Callable返回的结果。SpringMVC继续进行视图渲染流程等(从收请求-视图渲染)。 * * preHandle.../springmvc-annotation/async01 主线程开始...Thread[http-bio-8081-exec-3,5,main]==>1513932494700 主线程结束...Thread[http-bio-8081-exec-3,5,main]==>1513932494700 =========DispatcherServlet及所有的Filter退出线程============================ ================等待Callable执行========== 副线程开始...Thread[MvcAsync1,5,main]==>1513932494707 副线程开始...Thread[MvcAsync1,5,main]==>1513932496708 ================Callable执行完成========== ================再次收到之前重发过来的请求======== preHandle.../springmvc-annotation/async01 postHandle...(Callable的之前的返回值就是目标方法的返回值) afterCompletion... 异步的拦截器: 1)、原生API的AsyncListener 2)、SpringMVC:实现AsyncHandlerInterceptor; * @return */ @ResponseBody @RequestMapping("/async01") public Callable<String> async01() { System.out.println("主线程开始..." + Thread.currentThread() + "==>" + Instant.now().getEpochSecond()); Callable<String> callable = new Callable<String>() { @Override public String call() throws Exception { System.out.println("副线程开始..." + Thread.currentThread() + "==>" + Instant.now().getEpochSecond()); Thread.sleep(2000); System.out.println("副线程结束..." + Thread.currentThread() + "==>" + Instant.now().getEpochSecond()); return "Callable<String> async01()"; } }; System.out.println("主线程结束..." + Thread.currentThread() + "==>" + Instant.now().getEpochSecond()); return callable; } }
此时运行起来的测试结果如下:
preHandle…
主线程开始…Thread[http-nio-8081-exec-7,5,main]>1547802269
主线程结束…Thread[http-nio-8081-exec-7,5,main]>1547802269
副线程开始…Thread[MvcAsync1,5,main]>1547802269
副线程结束…Thread[MvcAsync1,5,main]>1547802271
Callable返回结果,SpringMVC将请求重新派发给容器,恢复之前的处理;
preHandle…
postHandle…
afterCompletion…
我们来看一个实际的应用场景:
public class DeferredResultQueue { private static Queue<DeferredResult<Object>> queue = new ConcurrentLinkedDeque<>(); public static void save(DeferredResult<Object> deferredResult) { queue.add(deferredResult); } public static DeferredResult<Object> get() { return queue.poll(); } }
@Controller public class AsyncController { @ResponseBody @RequestMapping("/createOrder") public DeferredResult<Object> createOrder() { //设置当前请必须在三秒内执行完,否则显示错误消息 DeferredResult<Object> deferredResult = new DeferredResult<>((long)3000, "create fail..."); DeferredResultQueue.save(deferredResult); return deferredResult; } @ResponseBody @RequestMapping("/create") public String create(){ //创建订单 String order = UUID.randomUUID().toString(); DeferredResult<Object> deferredResult = DeferredResultQueue.get(); //只要这边设置了处理结果,对应的那边就可以得到返回 deferredResult.setResult(order); return "success===>"+order; } }
我们先访问这个创建订单createOrder接口:
我们再来访问这个create接口,此时的结果如图所示:
在来看createOrder的返回结果: