深度长文回顾web基础组件
什么是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