一.什么是Servlet

Servlet(Server Applet)是Java Servlet的简称,称为小服务程序或服务连接器,用Java编写的服务器端程序,主要功能在于交互式地浏览和修改数据,生成动态Web内容,它工作在客户端请求与服务器响应的中间层,处理请求和发送响应的过程都是由Servlet的程序来完成的。

 

二.tomact和servlet的关系

Tomcat 是Web应用服务器,是一个Servlet/JSP容器。Tomcat 作为Servlet容器,负责处理客户请求,把请求传送给Servlet,并将Servlet的响应传送回给客户。而Servlet是一种运行在支持Java语言的服务器上的组件。Tomcat将http请求文本接收并解析,然后封装成HttpServletRequest类型的request对象,所有的HTTP头数据读可以通过request对象调用对应的方法查询到。Tomcat同时会要响应的信息封装为HttpServletResponse类型的response对象,通过设置response属性就可以控制要输出到浏览器的内容,然后将response交给tomcat,tomcat就会将其变成响应文本的格式发送给浏览器。

 

 

三.Servlet运行过程

       Servlet程序是由WEB服务器调用,web服务器收到客户端的Servlet访问请求后:
  ①Web服务器首先检查是否已经装载并创建了该Servlet的实例对象。如果是,则直接执行第④步,否则,执行第②步。
  ②装载并创建该Servlet的一个实例对象。
  ③调用Servlet实例对象的init()方法。
  ④创建一个用于封装HTTP请求消息的HttpServletRequest对象和一个代表HTTP响应消息的HttpServletResponse对象,然后调用Servlet的service()方法并将请求和响应对象作为参数传递进去。
  ⑤WEB应用程序被停止或重新启动之前,Servlet引擎将卸载Servlet,并在卸载之前调用Servlet的destroy()方法

 

四.Servlet的生命周期

Servlet 生命周期可被定义为从创建直到毁灭的整个过程。以下是 Servlet 遵循的过程:

第一步:Servlet 通过调用 init () 方法进行初始化。

init 方法被设计成只调用一次。当用户调用一个 Servlet 时,就会创建一个 Servlet 实例,每一个用户请求都会产生一个新的线程,init() 方法简单地创建或加载一些数据,这些数据将被用于 Servlet 的整个生命周期。在用户发送请求时调用init(),并不是容器启动时加载(tomact)时调用。

第二步:Servlet 调用 service() 方法来处理客户端的请求。

Servlet 容器(即 Web 服务器)调用 service() 方法来处理来自客户端(浏览器)的请求,并把格式化的响应写回给客户端,service() 方法检查 HTTP 请求类型(GET、POST、PUT、DELETE 等),并在适当的时候调用 doGet、doPost、doPut,doDelete 等方法。service 方法不需要覆写,如果覆写了别忘了super.service(request,response),不然不会去调用doGet或者doPost方法

第三步:Servlet 通过调用 destroy() 方法终止(结束)。

destroy() 方法只会调用一次,在 Servlet 生命周期结束时被调用。destroy() 方法可以让您的 Servlet 关闭数据库连接、停止后台线程、把 Cookie 列表或点击计数器写入到磁盘,并执行其他类似的清理活动。

第四步:最后,Servlet 是由 JVM 的垃圾回收器进行垃圾回收的

在调用 destroy() 方法之后,servlet 对象被标记为垃圾回收。

 

五.Servlet部署

需要告诉容器把请求给哪个servlet,所以需要在web.xml部署路径。

<servlet>

  <servlet-name>HelloWorld</servlet-name>

  <servlet-class>包名.类名</servlet-class>

</servlet>

<servlet-mapping>

  <servlet-name>HelloWorld</servlet-name>

  <url-pattern>/HelloWorld</url-pattern>

</servlet-mapping>

1.<servlet>

<servlet>标签用于配置 Servlet 类信息。<servlet-name>是 Servlet 的标识,必须保证在整个xml 中唯一。<servlet-class>是这个 Servlet 对应的类名。

2.<servlet-mapping>

<servlet-mapping>标签用于配置Servlet 的访问路径。<servlet-name> 需要和<servlet>中的<servlet-name>保持一致。<url-pattern>指定访问路径。

如上面配置的就能接收到http://localhost:8080/HelloWorld的请求。但是在servlet3.0后有了注解,可以用@WebServlet("/HelloWorld")来代替上面的配置文件。

 

六.模拟Servlet

用注解方式来模拟Servlet开发。我简单的说说注解是什么,注解(Annotation)相当于一种标记,在程序中加入注解就等于为程序打上某种标记,没有加,则等于没有任何标记,以后,javac编译器、开发工具和其他程序可以通过反射来了解你的类及各种元素上有无何种标记,看你的程序有什么标记,就去干相应的事,标记可以加在包、类,属性、方法,方法的参数以及局部变量上。@Retention(RetentionPolicy.RUNTIME)表示注解的生存周期,@Target(ElementType.TYPE)表示注解在哪。

1.先创建WebServlet注解

package Annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * 自定义WebServlet注解,模拟Servlet3.0的WebServlet注解
 * @Target 注解的属性值表明了 @WebServlet注解只能用于类或接口定义声明的前面, 
 * @WebServlet注解有一个必填的属性 value 。
 * 调用方式为: @WebServlet(value="/xxxx") ,
 * 因语法规定如果属性名为 value 且只填 value属性值时,可以省略 value属性名,即也可以写作:@WebServlet("/xxxx") 
 */
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface WebServlet {
    //Servlet的访问URL
    String value();
    //Servlet的访问URL
    String[] urlPatterns() default {""};
    //Servlet的描述
    String description() default "";
    //Servlet的显示名称
    String displayName() default "";
    //Servlet的名称
    String name() default "";
    //Servlet的init参数
    WebInitParam[] initParams() default {};
}

创建WebInitParam注解

package Annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface WebInitParam {
    //参数名
    String paramName() default "";
    //参数的值
    String paramValue() default "";
}

 

2.因为要模拟servlet,所以不能直接去创建servlet,应该要实现filter。

 

package Server;

import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import Annotation.WebInitParam;
import Annotation.WebServlet;
import Tool.ScanClassUtil;

public class AnnotationHandleFilter implements Filter {

	private ServletContext servletContext = null;

	/*
	 * 过滤器初始化时扫描指定的包下面使用了WebServlet注解的那些类
	 * 
	 * @see javax.servlet.Filter#init(javax.servlet.FilterConfig)
	 */
	public void init(FilterConfig filterConfig) throws ServletException {
		System.out.println("---AnnotationHandleFilter过滤器初始化开始---");
		servletContext = filterConfig.getServletContext();
		Map<String, Class<?>> classMap = new HashMap<String, Class<?>>();
		// 获取web.xml中配置的要扫描的包
		String basePackage = filterConfig.getInitParameter("basePackage");
		// 如果配置了多个包,例如:<param-value>me.gacl.web.controller,me.gacl.web.UI</param-value>
		if (basePackage.indexOf(",") > 0) {
			// 按逗号进行分隔
			String[] packageNameArr = basePackage.split(",");
			for (String packageName : packageNameArr) {
				addServletClassToServletContext(packageName, classMap);
			}
		} else {
			addServletClassToServletContext(basePackage, classMap);
		}
		System.out.println("----AnnotationHandleFilter过滤器初始化结束---");
	}

	/**
	 * @Method: addServletClassToServletContext
	 * @Description:添加ServletClass到ServletContext中
	 * @param packageName
	 * @param classMap
	 */
	private void addServletClassToServletContext(String packageName, Map<String, Class<?>> classMap) {
		Set<Class<?>> setClasses = ScanClassUtil.getClasses(packageName);
		for (Class<?> clazz : setClasses) {
			if (clazz.isAnnotationPresent(WebServlet.class)) {
				// 获取WebServlet这个Annotation的实例
				WebServlet annotationInstance = clazz.getAnnotation(WebServlet.class);
				// 获取Annotation的实例的value属性的值
				String annotationAttrValue = annotationInstance.value();
				if (!annotationAttrValue.equals("")) {
					classMap.put(annotationAttrValue, clazz);
				}
				// 获取Annotation的实例的urlPatterns属性的值
				String[] urlPatterns = annotationInstance.urlPatterns();
				for (String urlPattern : urlPatterns) {
					classMap.put(urlPattern, clazz);
				}
				servletContext.setAttribute("servletClassMap", classMap);
				System.out.println("annotationAttrValue:" + annotationAttrValue);
				String targetClassName = annotationAttrValue.substring(annotationAttrValue.lastIndexOf("/") + 1);
				System.out.println("targetClassName:" + targetClassName);
				System.out.println(clazz);
			}
		}
	}

	public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
			throws IOException, ServletException {
		System.out.println("---进入注解处理过滤器---");
		// 将ServletRequest强制转换成HttpServletRequest
		HttpServletRequest req = (HttpServletRequest) request;
		HttpServletResponse res = (HttpServletResponse) response;
		@SuppressWarnings("unchecked")
		Map<String, Class<?>> classMap = (Map<String, Class<?>>) servletContext.getAttribute("servletClassMap");
		// 获取contextPath
		String contextPath = req.getContextPath();
		// 获取用户请求的URI资源
		String uri = req.getRequestURI();
		// 如果没有指明要调用Servlet类中的哪个方法
		if (uri.indexOf("!") == -1) {
			// 获取用户使用的请求方式
			String reqMethod = req.getMethod();
			// 获取要请求的servlet路径
			String requestServletName = uri.substring(contextPath.length(), uri.lastIndexOf("."));
			// 获取要使用的类
			Class<?> clazz = classMap.get(requestServletName);
			// 创建类的实例
			Object obj = null;
			try {
				obj = clazz.newInstance();
			} catch (InstantiationException e1) {
				e1.printStackTrace();
			} catch (IllegalAccessException e1) {
				e1.printStackTrace();
			}
			Method targetMethod = null;
			if (reqMethod.equalsIgnoreCase("get")) {
				try {
					targetMethod = clazz.getDeclaredMethod("doGet", HttpServletRequest.class,
							HttpServletResponse.class);
				} catch (SecurityException e) {
					e.printStackTrace();
				} catch (NoSuchMethodException e) {
					e.printStackTrace();
				}
			} else {
				try {
					targetMethod = clazz.getDeclaredMethod("doPost", HttpServletRequest.class,
							HttpServletResponse.class);
				} catch (SecurityException e) {
					e.printStackTrace();
				} catch (NoSuchMethodException e) {
					e.printStackTrace();
				}
			}

			try {
				// 调用对象的方法进行处理
				targetMethod.invoke(obj, req, res);
			} catch (IllegalArgumentException e) {
				e.printStackTrace();
			} catch (IllegalAccessException e) {
				e.printStackTrace();
			} catch (InvocationTargetException e) {
				e.printStackTrace();
			}
		} else {
			// 获取要请求的servlet路径
			String requestServletName = uri.substring(contextPath.length(), uri.lastIndexOf("!"));
			// 获取要调用的servlet的方法
			String invokeMethodName = uri.substring(uri.lastIndexOf("!") + 1, uri.lastIndexOf("."));

			// 获取要使用的类
			Class<?> clazz = classMap.get(requestServletName);
			// 创建类的实例
			Object obj = null;
			try {
				obj = clazz.newInstance();
			} catch (InstantiationException e1) {
				e1.printStackTrace();
			} catch (IllegalAccessException e1) {
				e1.printStackTrace();
			}
			// 获得clazz类定义的所有方法
			Method[] methods = clazz.getDeclaredMethods();
			// 获取WebServlet这个Annotation的实例
			WebServlet annotationInstance = clazz.getAnnotation(WebServlet.class);
			// 获取注解上配置的初始化参数数组
			WebInitParam[] initParamArr = annotationInstance.initParams();
			Map<String, String> initParamMap = new HashMap<String, String>();
			for (WebInitParam initParam : initParamArr) {
				initParamMap.put(initParam.paramName(), initParam.paramValue());
			}
			// 遍历clazz类中的方法
			for (Method method : methods) {
				// 该方法的返回类型
				Class<?> retType = method.getReturnType();
				// 获得方法名
				String methodName = method.getName();
				// 打印方法修饰符
				System.out.print(Modifier.toString(method.getModifiers()));
				System.out.print(" " + retType.getName() + " " + methodName + "(");
				// 获得一个方法参数数组(getparameterTypes用于返回一个描述参数类型的Class对象数组)
				Class<?>[] paramTypes = method.getParameterTypes();
				for (int j = 0; j < paramTypes.length; j++) {
					// 如果有多个参数,中间则用逗号隔开,否则直接打印参数
					if (j > 0) {
						System.out.print(",");
					}
					System.out.print(paramTypes[j].getName());
				}
				System.out.println(");");
				if (method.getName().equalsIgnoreCase("init")) {
					try {
						// 调用Servlet的初始化方法
						method.invoke(obj, initParamMap);
					} catch (IllegalArgumentException e) {
						e.printStackTrace();
					} catch (IllegalAccessException e) {
						e.printStackTrace();
					} catch (InvocationTargetException e) {
						e.printStackTrace();
					}
				}
			}
			// 获取WebServlet这个Annotation的实例
			System.out.println("invokeMethodName:" + invokeMethodName);
			try {
				try {
					// 利用反射获取方法实例,方法的签名必须符合:
					// public void 方法名(HttpServletRequest request,
					// HttpServletResponse response)
					// 例如:public void loginHandle(HttpServletRequest request,
					// HttpServletResponse response)
					Method targetMethod = clazz.getDeclaredMethod(invokeMethodName, HttpServletRequest.class,
							HttpServletResponse.class);
					// 调用对象的方法进行处理
					targetMethod.invoke(obj, req, res);
				} catch (SecurityException e) {
					e.printStackTrace();
				} catch (NoSuchMethodException e) {
					e.printStackTrace();
				} catch (IllegalArgumentException e) {
					e.printStackTrace();
				} catch (InvocationTargetException e) {
					e.printStackTrace();
				}
			} catch (IllegalAccessException e) {
				e.printStackTrace();
			}
		}
	}

	public void destroy() {

	}
}

AnnotationHandleFilter过滤器初始化时扫描指定的包下面使用了WebServlet注解的那些类,然后将类存储到一个Map集合中,再将Map集合存储到servletContext对象中

3.需要在web.xml里配置过滤器

<filter>
        <description>注解处理过滤器</description>
        <filter-name>AnnotationHandleFilter</filter-name>
        <filter-class>Server.AnnotationHandleFilter</filter-class>
        <init-param>
            <description>配置要扫描包及其子包, 如果有多个包,以逗号分隔</description>
            <param-name>basePackage</param-name>
            <param-value>Test</param-value>
        </init-param>
    </filter>
    
    <filter-mapping>
        <filter-name>AnnotationHandleFilter</filter-name>
        <!-- 拦截后缀是.do的请求 -->
        <url-pattern>*.do</url-pattern>
    </filter-mapping>

4.需要的工具类

AnnotationHandleFilter过滤器初始化方法init(FilterConfig filterConfig)使用到了一个用于扫描某个包下面的类的工具类ScanClassUtil,通过上面的配置文件需要去扫描哪些类带有注解标签的,有的话把类用map集合装起来。

 

5.简单测试

package Test;

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

@WebServlet("/Login")
public class LoginServlet {

    public void doGet(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException{
        request.getRequestDispatcher("/Login.jsp").forward(request, response);
    }
    
    public void doPost(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        doGet(request, response);
    }
}

总结:容器加载的时候,filter的init方法也加载了,当用户请求时,容器会把请求给servlet,但在service方法运行前会先调用dofilter方法,在上面覆写了dofilter方法,但在初始化filter的时候,获得该项目路径,获得配置文件中的basePackage,再通过ScanClassUtil类把需要扫描的包下面所有的类用集合装起来。然后通过addServletClassToServletContext方法去遍历这个类元素上是否加了WebServlet注解,如果有就得到WebServlet里的value值和属性值,并重新放到map集合里。再运行dofilter方法,并得到请求的url路径,通过拆分得到要使用的类,再去map集合里取出来通过反射实例化,通过用户的请求去调用doget还是dopost方法。

Logo

CSDN联合极客时间,共同打造面向开发者的精品内容学习社区,助力成长!

更多推荐