什么是Serlvet ?# 全称 server applet 运行在服务端的小程序: 首先来说,这个servlet是java语言编写的出来的应用程序,换句话说servlet拥有java语言全部的优点,比如跨越平台,一次编译到处运行 其次: 相对于CGI(common gateway interface)规范而言,CGI是针对每一个用户的请求创建一个进程处理,而servlet所在的服务器会对每一个请求创建一个线程来处理,虽然线程数量有上限,但是相对于创建进程来说,后者对系统资源的开销更小 然后就是: 现在盛行javaWeb服务器Tomcat也是java语言编写的,毕竟Tomcat有Serlvet容器支持,所以servlet和web服务器之间无缝连接 Servlet其实一个接口,一套规范,不同的厂家对它有不同的实现,tomcat也是如此, web服务器会把解析http协议信息的逻辑封装进他们的Servlet中,比如将用户发送的请求(request) HttpRequestServlet, 把响应给用户http报文的逻辑封装进HttpResponseServlet中, 然后web服务器负责不同组件,不同servlet之间的调度关系, 什么是调度呢? 比如说: 通过某个URL找到指定的Servlet,回调Servlet的service()方法处理请求 Servlet的体系结构# servlet接口的实现类 servlet接口的实现类如上图 Servlet在java中是一个接口,封装了被浏览器访问到服务器(tomcat)的规则 添加serlvet# 通过web.xml# Copy Camel Routes app1 com.changwu.web.MyServlet 1 app1 /app1 通过注解# 舍弃web.xml是serlet3.0添加全注解技术, 这个注解的属性和需要在xml中配置的对应的 需要Tomcat7及以上才支持 Copy @Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface WebServlet { /** * servlet-name */ String name() default ""; /** * The URL patterns of the servlet */ String[] value() default {}; /** * servlet的资源路径, 可以为一个servlet配置多个访问路径 */ String[] urlPatterns() default {}; /** * 启动级别默认是-1,同样意味着依然是第一次访问时初始化 */ int loadOnStartup() default -1; /** * The init parameters of the servlet */ WebInitParam [] initParams() default {}; /** * Declares whether the servlet supports asynchronous operation mode. * * @see javax.servlet.ServletRequest#startAsync * @see javax.servlet.ServletRequest#startAsync(ServletRequest, * ServletResponse) */ boolean asyncSupported() default false; /** * The small-icon of the servlet */ String smallIcon() default ""; /** * The large-icon of the servlet */ String largeIcon() default ""; /** * The description of the servlet */ String description() default ""; /** * The display name of the servlet */ String displayName() default ""; } servlet的路径定义规则# /xxx Copy @WebServlet(urlPatterns = {"/app1","/app2"}) /xxx/yyy Copy @WebServlet(urlPatterns = {"/app1/app2"}) /xxx/* Copy @WebServlet(urlPatterns = {"/app1/*"}) *.do Copy @WebServlet(urlPatterns = {"*.do"}) 执行原理:# tomcat读取xml配置文件中配置servlet,根据用户配置的加载时机,通过反射技术创建出对象实例 用户的请求报文经过tomcat的解析,分发到的Servlet下面,进行不同的回调处理 Servlet接口的方法# 初始化方法, 创建servlet时执行一次 什么时候被创建: 默认情况下 第一次访问时被创建 一般我们都在web.xml配置,让Servlet在启动时完成加载 1默认这个值是-1, 表示第一次访问时被创建, 整数表示启动时初始化 此外: Servlet的init()方法仅仅被执行一次,说明serlet是单例的,那么在并发的情况的就可能出现线程安全问题 , 解决: 尽量不要在serlvet中定义成员变量,我们最好去成员方法中定义变量,即使定义了, 不要提供set()方法,仅仅提供的get() Copy @Override public void init(ServletConfig servletConfig) throws ServletException { System.out.println("init....."); } 获取serlvet config 配置对象 Copy // @Override public ServletConfig getServletConfig() { return null; } 提供服务的方法, 每次serlvet被访问都会执行一次 Copy @Override public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException { System.out.println("--------------------service------------"); } 获取serlvet 的信息, 版本等 Copy @Override public String getServletInfo() { return null; } 服务器正常关闭前, 销毁servlet时 回调 服务器非正常关闭,不会执行 Copy @Override public void destroy() { System.out.println("destroy"); } Servlet3.0新特性# Servlet3.0中的重大升级是ServletContainerInitializer,通过这个技术使我们可以为现有的组件写出可插拔的组件,与之相对应的是Servlet的新规范如下: 在执行的路径下面创建指定的文件 Copy /classpath: --META-INF (目录) --services (目录) --javax.servlet.ServletContainerInitializer (文件) 我们可以在上面的文件中配置一个类的全类名,这个类是谁无所谓,但是只要它实现了这个ServletContainnerInitializer接口,并重写它的onStart()方法,于是当容器(tomcat)启动的时候就会调用这个类的 onStart()方法 这个规范带来的革命决定是历史性的,有了它我们的代码就有了可插拔的能力,不信可以回想一下传统的配置文件,如果想给项目进行升级,还不想改动xml文件,那是不可能的,但是现在不同了,只要让我们的类实现这个ServletContainnerInitializer,重写它的方法,它的onStart()就会被回调,而其他的功能不受响应,去掉这个类,项目整体也不受响应 示例: 容器启动的时候,会把容器中,被@HandlerTypes(value={Test.class}) 中指定的所有Test.class实现类(子类,子接口)的实例传递进下面的set集合 Copy @HandlesTypes(Test.class) public class ChangWuInitializer implements ServletContainerInitializer { @Override public void onStartup(Set> set, ServletContext servletContext) throws ServletException { System.out.println(set); } } 通过上面onstart()方法可以看到,第二个参数位置上是 ServletContext, 这个对象是什么?有啥用? 在下文中单独开一个模块说 ServletContext# tomcat会为每一个web项目创建一个全局唯一的ServeltContext,这个对象里面封装着整个应用的信息,常用的当作域对象,所有的servlet之间共享数据,同时他还可以获取出web.xml文件中的数据 功能:# 获取MIME类型 MIME类型是互联网通信中定义的文件数据类型 格式: 大类型/小类型 如: test/html 在tomcat的配置文件目录中存在web.xml ,里面的存在大量的MEMI类型的数据,都可以从ServletContext中获取出来 Copy String getMimeType(String file) 域对象(共享数据) 范围: 类似于Session,通过ServletContext对象我们也可以实现数据共享,但值得注意的是,Session是只能在一个客户端中共享数据,而ServletContext中的数据是在所有客户端中都可以实现数据共享的。 方法: Copy setAttribute(String name,Onject obj); getAttribute(String name); removeAttribute(String name); 获取文件真实的文件路径 方法 Copy this.getServletContext().getRealPath("/"); // 现在访问的目录是tomcat中和WEB-INF同级目录 实现请求转发 Copy // 方式1: request.getRequestDispatcher("/url").forward(req,res); // 方式2: this.getServletContext().getRequestDispatcher("/url").forward(req,res); 获取web应用的初始化参数 我们可以用标签为servlet配置初始化参数,然后使用ServletConfig对象获取这些参数,假如有如下的MyServlet,它的配置为: Copy MyServlet com.gavin.servlet.MyServlet encoding utf-8 获取: Copy String encoding = this.getServletConfig().getInitParameter("encoding"); 如何获取:# ServletContext在web应用上下文中以单例的形式存在,下面两种获取方式得到的ServletContext是同一个对象 Copy ServletContext servlet1 = request.getServletContext(); ServletContext servlet2 = this.getServletContext(); this.getServletConfig().getServletContext(); 生命周期# 服务器一启动就创建,服务器关闭时才销毁 注册三大web组件(servlet filter listener)# Servlet Copy addServlet、createServlet、getServletRegistration、getServletRegistrations Filter Copy addFilter、createFilter、getFilterRegistration、getFilterRegistrations 监听器 Copy addListener、createListener Spring-web对Servlet3.0的应用# spring-web-Init体系图 先上一张继承体系图,下面围绕这张图片展开 Copy @HandlesTypes({WebApplicationInitializer.class}) public class SpringServletContainerInitializer implements ServletContainerInitializer { public SpringServletContainerInitializer() { } public void onStartup(@Nullable Set> webAppInitializerClasses, ServletContext servletContext) throws ServletException { List 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)ReflectionUtils.accessibleConstructor(waiClass, new Class[0]).newInstance()); } catch (Throwable var7) { throw new ServletException("Failed to instantiate WebApplicationInitializer class", var7); } } } } ... } 可以看到,Spring应用一启动就会加载WebApplicationInitializer接口下的所有组件,并且,只要这些组件不是接口,不是抽象类,Spring就为它们创建实例 更进一步看一下上下文中WebApplicationInitializer接口的实现类 AbstractContextLoaderInitializer# 看他对onstart()方法的重写, 主要干了什么呢? 注册了一个上下文的监听器(借助这个监听器读取SpringMvc的配置文件),初始化应用的上下文 Copy public abstract class AbstractContextLoaderInitializer implements WebApplicationInitializer { protected final Log logger = LogFactory.getLog(this.getClass()); public AbstractContextLoaderInitializer() { } public void onStartup(ServletContext servletContext) throws ServletException { this.registerContextLoaderListener(servletContext); } protected void registerContextLoaderListener(ServletContext servletContext) { WebApplicationContext rootAppContext = this.createRootApplicationContext(); if (rootAppContext != null) { ContextLoaderListener listener = new ContextLoaderListener(rootAppContext); listener.setContextInitializers(this.getRootApplicationContextInitializers()); servletContext.addListener(listener); } else { this.logger.debug("No ContextLoaderListener registered, as createRootApplicationContext() did not return an application context"); } } AbstractDispatcherServletInitializer# 见名知意,他是DispatcherServlet的初始化器,他主要做了什么事呢? 上面看了,它的父类初始化上下文,于是它调用父类的构造,往上传递web环境的上下文 紧接着添加DispatcherServlet Copy public abstract class AbstractDispatcherServletInitializer extends AbstractContextLoaderInitializer { public static final String DEFAULT_SERVLET_NAME = "dispatcher"; public AbstractDispatcherServletInitializer() { } public void onStartup(ServletContext servletContext) throws ServletException { super.onStartup(servletContext); this.registerDispatcherServlet(servletContext); } protected void registerDispatcherServlet(ServletContext servletContext) { String servletName = this.getServletName(); Assert.hasLength(servletName, "getServletName() must not return null or empty"); // 可以看一下,它创建的是web的容器 WebApplicationContext servletAppContext = this.createServletApplicationContext(); Assert.notNull(servletAppContext, "createServletApplicationContext() must not return null"); // 创建负责调度的 DispatcherServlet FrameworkServlet dispatcherServlet = this.createDispatcherServlet(servletAppContext); Assert.notNull(dispatcherServlet, "createDispatcherServlet(WebApplicationContext) must not return null"); dispatcherServlet.setContextInitializers(this.getServletApplicationContextInitializers()); // 添加Servlet Dynamic registration = servletContext.addServlet(servletName, dispatcherServlet); if (registration == null) { throw new IllegalStateException("Failed to register servlet with name '" + servletName + "'. Check if there is another servlet registered under the same name."); } else { // 添加servlet的mapping信息 registration.setLoadOnStartup(1); registration.addMapping(this.getServletMappings()); registration.setAsyncSupported(this.isAsyncSupported()); Filter[] filters = this.getServletFilters(); if (!ObjectUtils.isEmpty(filters)) { Filter[] var7 = filters; int var8 = filters.length; for(int var9 = 0; var9 < var8; ++var9) { Filter filter = var7[var9]; this.registerServletFilter(servletContext, filter); } } this.customizeRegistration(registration); } ... } AbstractAnnotationConfigDispatcherServletInitializer# createRootApplicationContext重写了父类的创建上下文的方法,我觉得这算是一个高潮吧, 因为啥呢,AnnotationConfigWebApplicationContext是SpringMvc使用的应用的上下文,怎么创建的源码在下面,其实我有在Spring源码阅读中写过这个方面的笔记,下面仅仅是将配置类传递给Spring的bean工厂,并没有对配置类进行其他方面的解析,或者是扫描包啥的 createServletApplicationContext()重写了它父类的创建serlvet上下文的方法, 有个点,大家有没有发现,SpringMvc的上下文和Servlet的上下文是同一个对象,都是AnnotationConfigWebApplicationContext,不同点就是添加了if-else分支判断,防止重复创建 Copy public abstract class AbstractAnnotationConfigDispatcherServletInitializer extends AbstractDispatcherServletInitializer { public AbstractAnnotationConfigDispatcherServletInitializer() { } @Nullable protected WebApplicationContext createRootApplicationContext() { Class[] configClasses = this.getRootConfigClasses(); if (!ObjectUtils.isEmpty(configClasses)) { AnnotationConfigWebApplicationContext context = new 有没有大神了解这个情况, SpringMvc的应用上下文和Servlet应用上下文竟然是同一个(); context.register(configClasses); return context; } else { return null; } } protected WebApplicationContext createServletApplicationContext() { AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext(); Class[] configClasses = this.getServletConfigClasses(); if (!ObjectUtils.isEmpty(configClasses)) { context.register(configClasses); } return context; } 对比官网推荐的启动案例:# 下面的是Spring官网推荐是通过注解的配置方法,仔细看看,其实和上面的Spring-Web模块的做法是一样的 Copy 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