JavaWeb开发与代码的编写(二十)

JavaWeb开发与代码的编写(二十)

Servlet3

Servlet的传统配置方式

  在JavaWeb开发中, 每次编写一个Servlet都需要在web.xml文件中进行配置,如下所示:

 <servlet>
     <servlet-name>ActionServlet</servlet-name>
     <servlet-class>me.gacl.web.controller.ActionServlet</servlet-class>
 </servlet>
 
 <servlet-mapping>
     <servlet-name>ActionServlet</servlet-name>
     <url-pattern>/servlet/ActionServlet</url-pattern>
 </servlet-mapping>

  每开发一个Servlet,都要在web.xml中配置Servlet才能够使用,这实在是很头疼的事情,所以Servlet3.0之后提供了注解(annotation),使得不再需要在web.xml文件中进行Servlet的部署描述,简化开发流程。本文所讲的基于注解方式配置Servlet不是针对Servlet3.0的,而是基于Servlet2.5的,通过开发自定义注解和注解处理器来实现类似于Servlet3.0的注解方式配置Servlet。

 

基于注解的方式配置Servlet

  JDK1. 5版本之后, JAVA提供了一种叫做Annotation的新数据类型,中文译为注解或标注,它的出现为铺天盖地的XML配置文件提供了一个完美的解决方案,让 JAVA EE开发更加方便快速,也更加干净了。不过Servlet2.5默认情况下是不支持注解方式的配置的,但是我们可以开发自定义注解,然后将注解标注到Servlet上,再针对我们自定义的注解写一个注解处理器,具体的做法如下:

开发用于配置Servlet的相关注解

  1、开发WebServlet注解,用于标注处理请求的Servlet类

 package me.gacl.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 {};
 }

  将Servlet在web.xml中的配置信息使用WebServlet注解来表示,使用注解后,只需要在相应Servlet 类的前面使用类似@WebServlet("/servlet/LoginServlet") 注解就可以达到和上述 web.xml 文件中配置信息一样的目的。注解@WebServlet中的属性值"/servlet/LoginServlet"表示了web.xml 配置文件中 <servlet-mapping> 元素的子元素 <url-pattern> 里的值。通过这样的注解能简化在 XML 文件中配置 Servlet 信息,整个配置文件将会非常简洁干净,开发人员的工作也将大大减少。

  2、开发WebInitParam注解,用于配置Servlet初始化时使用的参数

 package me.gacl.annotation;
 
 import java.lang.annotation.ElementType;
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.lang.annotation.Target;
 
 /**
 * @ClassName: WebInitParam
 * @Description: 定义Servlet的初始化参数注解
 *
 */
 @Retention(RetentionPolicy.RUNTIME)
 @Target(ElementType.TYPE)
 public @interface WebInitParam {
     //参数名
     String paramName() default "";
     //参数的值
     String paramValue() default "";
 }

 

编写处理注解的处理器

  上面简要地介绍了注解的定义声明与使用方式,注解在后台需要一个处理器才能起作用,所以还得针对上面的注解编写处理器,在这里我们使用Filter作为注解的处理器,编写一个AnnotationHandleFilter,代码如下:

  1 package me.gacl.web.filter;
  2 
  3 import java.io.IOException;
  4 import java.lang.reflect.InvocationTargetException;
  5 import java.lang.reflect.Method;
  6 import java.lang.reflect.Modifier;
  7 import java.util.HashMap;
  8 import java.util.Map;
  9 import java.util.Set;
 10 import javax.servlet.Filter;
 11 import javax.servlet.FilterChain;
 12 import javax.servlet.FilterConfig;
 13 import javax.servlet.ServletContext;
 14 import javax.servlet.ServletException;
 15 import javax.servlet.ServletRequest;
 16 import javax.servlet.ServletResponse;
 17 import javax.servlet.http.HttpServletRequest;
 18 import javax.servlet.http.HttpServletResponse;
 19 import me.gacl.annotation.WebInitParam;
 20 import me.gacl.annotation.WebServlet;
 21 import me.gacl.util.ScanClassUtil;
 22 
 23 /**
 24 * @ClassName: AnnotationHandleFilter
 25 * @Description: 使用Filter作为注解的处理器
 28 *
 29 */ 
 30 public class AnnotationHandleFilter implements Filter {
 31 
 32     private ServletContext servletContext = null;
 33     
 34     /* 过滤器初始化时扫描指定的包下面使用了WebServlet注解的那些类
 35      * @see javax.servlet.Filter#init(javax.servlet.FilterConfig)
 36      */
 37     public void init(FilterConfig filterConfig) throws ServletException {
 38         System.out.println("---AnnotationHandleFilter过滤器初始化开始---");
 39         servletContext = filterConfig.getServletContext();
 40         Map<String, Class<?>> classMap = new HashMap<String, Class<?>>();
 41         //获取web.xml中配置的要扫描的包
 42         String basePackage = filterConfig.getInitParameter("basePackage");
 43         //如果配置了多个包,例如:<param-value>me.gacl.web.controller,me.gacl.web.UI</param-value>
 44         if (basePackage.indexOf(",")>0) {
 45             //按逗号进行分隔
 46             String[] packageNameArr = basePackage.split(",");
 47             for (String packageName : packageNameArr) {
 48                 addServletClassToServletContext(packageName,classMap);
 49             }
 50         }else {
 51             addServletClassToServletContext(basePackage,classMap);
 52         }
 53         System.out.println("----AnnotationHandleFilter过滤器初始化结束---");
 54     }
 55     
 56     /**
 57     * @Method: addServletClassToServletContext
 58     * @Description:添加ServletClass到ServletContext中
 60     *
 61     * @param packageName
 62     * @param classMap
 63     */ 
 64     private void addServletClassToServletContext(String packageName,Map<String, Class<?>> classMap){
 65         Set<Class<?>> setClasses =  ScanClassUtil.getClasses(packageName);
 66         for (Class<?> clazz :setClasses) {
 67             if (clazz.isAnnotationPresent(WebServlet.class)) {
 68                 //获取WebServlet这个Annotation的实例
 69                 WebServlet annotationInstance = clazz.getAnnotation(WebServlet.class);
 70                 //获取Annotation的实例的value属性的值
 71                 String annotationAttrValue = annotationInstance.value();
 72                 if (!annotationAttrValue.equals("")) {
 73                     classMap.put(annotationAttrValue, clazz);
 74                 }
 75                 //获取Annotation的实例的urlPatterns属性的值
 76                 String[] urlPatterns = annotationInstance.urlPatterns();
 77                 for (String urlPattern : urlPatterns) {
 78                     classMap.put(urlPattern, clazz);
 79                 }
 80                 servletContext.setAttribute("servletClassMap", classMap);
 81                 System.out.println("annotationAttrValue:"+annotationAttrValue);
 82                 String targetClassName = annotationAttrValue.substring(annotationAttrValue.lastIndexOf("/")+1);
 83                 System.out.println("targetClassName:"+targetClassName);
 84                 System.out.println(clazz);
 85             }
 86         }
 87     }
 88 
 89     public void doFilter(ServletRequest request, ServletResponse response,
 90             FilterChain chain) throws IOException, ServletException {
 91         System.out.println("---进入注解处理过滤器---");
 92         //将ServletRequest强制转换成HttpServletRequest
 93         HttpServletRequest req = (HttpServletRequest)request;
 94         HttpServletResponse res = (HttpServletResponse)response;
 95         Map<String, Class<?>> classMap = (Map<String, Class<?>>) servletContext.getAttribute("servletClassMap");
 96         //获取contextPath
 97         String contextPath = req.getContextPath();
 98         //获取用户请求的URI资源
 99         String uri = req.getRequestURI();
100         //如果没有指明要调用Servlet类中的哪个方法
101         if (uri.indexOf("!")==-1) {
102             //获取用户使用的请求方式
103             String reqMethod = req.getMethod();
104             //获取要请求的servlet路径
105             String requestServletName = uri.substring(contextPath.length(),uri.lastIndexOf("."));
106             //获取要使用的类
107             Class<?> clazz = classMap.get(requestServletName);
108             //创建类的实例
109             Object obj = null;
110             try {
111                 obj = clazz.newInstance();
112             } catch (InstantiationException e1) {
113                 e1.printStackTrace();
114             } catch (IllegalAccessException e1) {
115                 e1.printStackTrace();
116             }
117             Method targetMethod = null;
118             if (reqMethod.equalsIgnoreCase("get")) {
119                 try {
120                      targetMethod = clazz.getDeclaredMethod("doGet",HttpServletRequest.class,HttpServletResponse.class);
121                 } catch (SecurityException e) {
122                     e.printStackTrace();
123                 } catch (NoSuchMethodException e) {
124                     e.printStackTrace();
125                 }
126             }else {
127                 try {
128                     targetMethod = clazz.getDeclaredMethod("doPost",HttpServletRequest.class,HttpServletResponse.class);
129                 } catch (SecurityException e) {
130                     e.printStackTrace();
131                 } catch (NoSuchMethodException e) {
132                     e.printStackTrace();
133                 }
134             }
135             
136             try {
137                 //调用对象的方法进行处理
138                 targetMethod.invoke(obj,req,res);
139             } catch (IllegalArgumentException e) {
140                 e.printStackTrace();
141             } catch (IllegalAccessException e) {
142                 e.printStackTrace();
143             } catch (InvocationTargetException e) {
144                 e.printStackTrace();
145             }
146         }else {
147             //获取要请求的servlet路径
148             String requestServletName = uri.substring(contextPath.length(),uri.lastIndexOf("!"));
149             //获取要调用的servlet的方法
150             String invokeMethodName = uri.substring(uri.lastIndexOf("!")+1,uri.lastIndexOf("."));
151         
152             //获取要使用的类
153             Class<?> clazz = classMap.get(requestServletName);
154             //创建类的实例
155             Object obj = null;
156             try {
157                 obj = clazz.newInstance();
158             } catch (InstantiationException e1) {
159                 e1.printStackTrace();
160             } catch (IllegalAccessException e1) {
161                 e1.printStackTrace();
162             }
163             //获得clazz类定义的所有方法
164             Method[] methods = clazz.getDeclaredMethods();
165             //获取WebServlet这个Annotation的实例
166             WebServlet annotationInstance = clazz.getAnnotation(WebServlet.class);
167             //获取注解上配置的初始化参数数组
168             WebInitParam[] initParamArr = annotationInstance.initParams();
169             Map<String, String> initParamMap = new HashMap<String, String>();
170             for (WebInitParam initParam : initParamArr) {
171                 initParamMap.put(initParam.paramName(), initParam.paramValue());
172             }
173             //遍历clazz类中的方法
174             for (Method method : methods) {
175                 //该方法的返回类型
176                 Class<?> retType = method.getReturnType();
177                 //获得方法名
178                 String methodName = method.getName();
179                 //打印方法修饰符
180                 System.out.print(Modifier.toString(method.getModifiers()));
181                 System.out.print(" "+retType.getName() + " " + methodName +"(");
182                 //获得一个方法参数数组(getparameterTypes用于返回一个描述参数类型的Class对象数组)
183                 Class<?>[] paramTypes = method.getParameterTypes();
184                 for(int j = 0 ; j < paramTypes.length ; j++){
185                      //如果有多个参数,中间则用逗号隔开,否则直接打印参数
186                     if (j > 0){
187                         System.out.print(",");
188                     }  
189                     System.out.print(paramTypes[j].getName());
190                 }
191                 System.out.println(");");
192                 if (method.getName().equalsIgnoreCase("init")) {
193                     try {
194                         //调用Servlet的初始化方法
195                         method.invoke(obj, initParamMap);
196                     } catch (IllegalArgumentException e) {
197                         e.printStackTrace();
198                     } catch (IllegalAccessException e) {
199                         e.printStackTrace();
200                     } catch (InvocationTargetException e) {
201                         e.printStackTrace();
202                     }
203                 }
204             }
205             //获取WebServlet这个Annotation的实例
206             System.out.println("invokeMethodName:"+invokeMethodName);
207             try {
208                 try {
209                     //利用反射获取方法实例,方法的签名必须符合:
210                     //public void 方法名(HttpServletRequest request, HttpServletResponse response)
211                     //例如:public void loginHandle(HttpServletRequest request, HttpServletResponse response)
212                     Method targetMethod = clazz.getDeclaredMethod(invokeMethodName,HttpServletRequest.class,HttpServletResponse.class);
213                     //调用对象的方法进行处理
214                     targetMethod.invoke(obj,req,res);
215                 } catch (SecurityException e) {
216                     e.printStackTrace();
217                 } catch (NoSuchMethodException e) {
218                     e.printStackTrace();
219                 } catch (IllegalArgumentException e) {
220                     e.printStackTrace();
221                 } catch (InvocationTargetException e) {
222                     e.printStackTrace();
223                 } 
224             } catch (IllegalAccessException e) {
225                 e.printStackTrace();
226             }
227         }
228     }
229 
230     public void destroy() {
231 
232     }
233 }

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

 

  在web.xml文件中配置AnnotationHandleFilter过滤器和需要扫描的包

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

 AnnotationHandleFilter过滤器初始化方法init(FilterConfig filterConfig)使用到了一个用于扫描某个包下面的类的工具类ScanClassUtil,ScanClassUtil的代码如下:

 1 package me.gacl.util;
  2 
  3 import java.io.File;
  4 import java.io.FileFilter;
  5 import java.io.IOException;
  6 import java.net.JarURLConnection;
  7 import java.net.URL;
  8 import java.net.URLDecoder;
  9 import java.util.Enumeration;
 10 import java.util.LinkedHashSet;
 11 import java.util.Set;
 12 import java.util.jar.JarEntry;
 13 import java.util.jar.JarFile;
 14 
 15 public class ScanClassUtil {
 16 
 17     /**
 18      * 从包package中获取所有的Class
 19      * 
 20      * @param pack
 21      * @return
 22      */
 23     public static Set<Class<?>> getClasses(String pack) {
 24 
 25         // 第一个class类的集合
 26         Set<Class<?>> classes = new LinkedHashSet<Class<?>>();
 27         // 是否循环迭代
 28         boolean recursive = true;
 29         // 获取包的名字 并进行替换
 30         String packageName = pack;
 31         String packageDirName = packageName.replace('.', '/');
 32         // 定义一个枚举的集合 并进行循环来处理这个目录下的things
 33         Enumeration<URL> dirs;
 34         try {
 35             dirs = Thread.currentThread().getContextClassLoader().getResources(
 36                     packageDirName);
 37             // 循环迭代下去
 38             while (dirs.hasMoreElements()) {
 39                 // 获取下一个元素
 40                 URL url = dirs.nextElement();
 41                 // 得到协议的名称
 42                 String protocol = url.getProtocol();
 43                 // 如果是以文件的形式保存在服务器上
 44                 if ("file".equals(protocol)) {
 45                     System.err.println("file类型的扫描");
 46                     // 获取包的物理路径
 47                     String filePath = URLDecoder.decode(url.getFile(), "UTF-8");
 48                     // 以文件的方式扫描整个包下的文件 并添加到集合中
 49                     findAndAddClassesInPackageByFile(packageName, filePath,
 50                             recursive, classes);
 51                 } else if ("jar".equals(protocol)) {
 52                     // 如果是jar包文件
 53                     // 定义一个JarFile
 54                     System.err.println("jar类型的扫描");
 55                     JarFile jar;
 56                     try {
 57                         // 获取jar
 58                         jar = ((JarURLConnection) url.openConnection())
 59                                 .getJarFile();
 60                         // 从此jar包 得到一个枚举类
 61                         Enumeration<JarEntry> entries = jar.entries();
 62                         // 同样的进行循环迭代
 63                         while (entries.hasMoreElements()) {
 64                             // 获取jar里的一个实体 可以是目录 和一些jar包里的其他文件 如META-INF等文件
 65                             JarEntry entry = entries.nextElement();
 66                             String name = entry.getName();
 67                             // 如果是以/开头的
 68                             if (name.charAt(0) == '/') {
 69                                 // 获取后面的字符串
 70                                 name = name.substring(1);
 71                             }
 72                             // 如果前半部分和定义的包名相同
 73                             if (name.startsWith(packageDirName)) {
 74                                 int idx = name.lastIndexOf('/');
 75                                 // 如果以"/"结尾 是一个包
 76                                 if (idx != -1) {
 77                                     // 获取包名 把"/"替换成"."
 78                                     packageName = name.substring(0, idx)
 79                                             .replace('/', '.');
 80                                 }
 81                                 // 如果可以迭代下去 并且是一个包
 82                                 if ((idx != -1) || recursive) {
 83                                     // 如果是一个.class文件 而且不是目录
 84                                     if (name.endsWith(".class")
 85                                             && !entry.isDirectory()) {
 86                                         // 去掉后面的".class" 获取真正的类名
 87                                         String className = name.substring(
 88                                                 packageName.length() + 1, name
 89                                                         .length() - 6);
 90                                         try {
 91                                             // 添加到classes
 92                                             classes.add(Class
 93                                                     .forName(packageName + '.'
 94                                                             + className));
 95                                         } catch (ClassNotFoundException e) {
 96                                             // log
 97                                             // .error("添加用户自定义视图类错误 找不到此类的.class文件");
 98                                             e.printStackTrace();
 99                                         }
100                                     }
101                                 }
102                             }
103                         }
104                     } catch (IOException e) {
105                         // log.error("在扫描用户定义视图时从jar包获取文件出错");
106                         e.printStackTrace();
107                     }
108                 }
109             }
110         } catch (IOException e) {
111             e.printStackTrace();
112         }
113 
114         return classes;
115     }
116     
117     /**
118      * 以文件的形式来获取包下的所有Class
119      * 
120      * @param packageName
121      * @param packagePath
122      * @param recursive
123      * @param classes
124      */
125     public static void findAndAddClassesInPackageByFile(String packageName,
126             String packagePath, final boolean recursive, Set<Class<?>> classes) {
127         // 获取此包的目录 建立一个File
128         File dir = new File(packagePath);
129         // 如果不存在或者 也不是目录就直接返回
130         if (!dir.exists() || !dir.isDirectory()) {
131             // log.warn("用户定义包名 " + packageName + " 下没有任何文件");
132             return;
133         }
134         // 如果存在 就获取包下的所有文件 包括目录
135         File[] dirfiles = dir.listFiles(new FileFilter() {
136             // 自定义过滤规则 如果可以循环(包含子目录) 或则是以.class结尾的文件(编译好的java类文件)
137             public boolean accept(File file) {
138                 return (recursive && file.isDirectory())
139                         || (file.getName().endsWith(".class"));
140             }
141         });
142         // 循环所有文件
143         for (File file : dirfiles) {
144             // 如果是目录 则继续扫描
145             if (file.isDirectory()) {
146                 findAndAddClassesInPackageByFile(packageName + "."
147                         + file.getName(), file.getAbsolutePath(), recursive,
148                         classes);
149             } else {
150                 // 如果是java类文件 去掉后面的.class 只留下类名
151                 String className = file.getName().substring(0,
152                         file.getName().length() - 6);
153                 try {
154                     // 添加到集合中去
155                     //classes.add(Class.forName(packageName + '.' + className));
156                      //经过回复同学的提醒,这里用forName有一些不好,会触发static方法,没有使用classLoader的load干净
157                     classes.add(Thread.currentThread().getContextClassLoader().loadClass(packageName + '.' + className));  
158                     } catch (ClassNotFoundException e) {
159                     // log.error("添加用户自定义视图类错误 找不到此类的.class文件");
160                     e.printStackTrace();
161                 }
162             }
163         }
164     }
165 }

 经过以上两步,我们的自定义注解和针对注解的处理器都开发好了。

WebServlet注解简单测试

  编写一个用于跳转到Login.jsp页面的LoginUIServlet,LoginUIServlet就是一个普通的java类,不是一个真正的Servlet,然后使用WebServlet注解标注LoginUIServlet类,代码如下:

 package me.gacl.web.UI;
  
  import java.io.IOException;
  import javax.servlet.ServletException;
  import javax.servlet.http.HttpServletRequest;
  import javax.servlet.http.HttpServletResponse;
  import me.gacl.annotation.WebServlet;
  
  @WebServlet("/servlet/LoginUI")
 public class LoginUIServlet {
 
    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);
     }
 }

  在浏览器中输入访问地址:http://gacl-pc:8080/AnnotationConfigServlet/servlet/Login.do,根据web.xml文件的配置,所有后缀名为 .do请求,都会经过AnnotationHandleFilter过滤器的doFilter方法,在doFilter方法的实现代码中,从HttpServletRequest请求对象中得到请求的方式类型(GET/POST)和请求的URI 。如有请求http://gacl-pc:8080/AnnotationConfigServlet/servlet/LoginUI.do,此时请求方法类型为GET, URI 值为/AnnotationConfigServlet/servlet/LoginUI.do。从ServletConext对象中获取到在过滤器中保存的Map结构,根据 URI 获得一个 Key=”/servlet/LoginUI” ,从 Map 结构中根据此Key得到Value ,此时Value就是要请求调用的那个Servlet类,根据Servlet类创建对象实例,再根据前面得到的请求方法类型,能决定调用此Servlet对象实例的 doGet 或 doPost 方法。最终客户端发生的后缀为.do请求,经由AnnotationHandleFilter对请求对象(HttpServletRequest)的分析,从而调用相应某Servlet的doGet或doPost方法,完成了一次客户端请求到服务器响应的过程。

  使用注解后程序流程如下所示:

 

  运行结果如下:

 

  从运行结果中可以看到,我们的注解处理器成功调用了目标Servlet处理用户的请求,通过@WebServlet注解, Servlet不用再在web.xml 文件中进行繁冗的注册,这就是使用@WebServlet注解的方便之处。

 

WebServlet注解复杂测试

  编写Login.jsp页面,代码如下:

复制代码
 <%@ page language="java" pageEncoding="UTF-8"%>
<!DOCTYPE HTML>
  <html>
   <head>
      <title>登录页面</title>
    </head>
    
   <body>
     <fieldset>
         <legend>用户登录</legend>
         <form action="${pageContext.request.contextPath}/servlet/LoginServlet!loginHandle.do" method="post">
             用户名:<input type="text" value="${param.usename}" name="usename">
             <br/>
             密码:<input type="text" value="${param.pwd}" name="pwd">
             <br/>
             <input type="submit" value="登录"/>
         </form>
     </fieldset>
     <hr/>
     <label style="color: red;">${msg}</label>
   </body>
 </html>

 form表单中的action属性的URL="${pageContext.request.contextPath}/servlet/LoginServlet!loginHandle.do",/servlet/LoginServlet表示要访问的是LoginServlet,!后面的loginHandle表示要调用LoginServlet中的loginHandle方法处理此次的请求,也就是说,我们在访问Servlet时,可以在URL中指明要访问Servlet的哪个方法,AnnotationHandleFilter过滤器的doFilter方法在拦截到用户的请求之后,首先获取用户要访问的URI,根据URI判断用户要访问的Servlet,然后再判断URI中是否包含了"!",如果有,那么就说明用户显示指明了要访问Servlet的哪个方法,遍历Servlet类中定义的所有方法,如果找到了URI中的那个方法,那么就调用对应的方法处理用户请求!

  LoginServlet的代码如下:

1 package me.gacl.web.controller;
 2 
 3 import java.io.IOException;
 4 import java.util.Map;
 5 import javax.servlet.ServletException;
 6 import javax.servlet.http.HttpServletRequest;
 7 import javax.servlet.http.HttpServletResponse;
 8 import me.gacl.annotation.WebInitParam;
 9 import me.gacl.annotation.WebServlet;
10 
11 /**
12 * 
13 * @ClassName: LoginServlet
14 * @Description:处理用户登录的Servlet,
15 * LoginServlet现在就是一个普通的java类,不是一个真正的Servlet
18 *
19 */
20 //将开发好的WebServlet注解标注到LoginServlet类上
21 @WebServlet(
22             //Servlet的访问URL
23             value="/servlet/LoginServlet",
24             //Servlet的访问URL,可以使用数组的方式配置多个访问路径
25             urlPatterns={"/gacl/LoginServlet","/xdp/LoginServlet"},
26             //Servlet的初始化参数
27             initParams={
28                     @WebInitParam(paramName="gacl",paramValue="孤傲苍狼"),
29                     @WebInitParam(paramName="bhsh",paramValue="白虎神皇")
30             },
31             name="LoginServlet",
32             description="处理用户登录的Servlet"
33         )
34 public class LoginServlet {
35 
36     public void loginHandle(HttpServletRequest request, HttpServletResponse response) 
37             throws ServletException, IOException{
38         String username = request.getParameter("usename");
39         String pwd = request.getParameter("pwd");
40         if (username.equals("gacl") && pwd.equals("xdp")) {
41             request.getSession().setAttribute("usename", username);
42             request.setAttribute("msg", "欢迎您!"+username);
43             request.getRequestDispatcher("/index.jsp").forward(request, response);
44         }else {
45             request.setAttribute("msg", "登录失败,请检查用户名和密码是否正确!");
46             request.getRequestDispatcher("/Login.jsp").forward(request, response);
47         }
48     }
49     
50     
51     /**
52     * @Method: init
53     * @Description: Servlet初始化
56     * @param config
57     */ 
58     public void init(Map<String, String> initParamMap){
59         System.out.println("--LoginServlet初始化--");
60         System.out.println(initParamMap.get("gacl"));
61         System.out.println(initParamMap.get("bhsh"));
62     }
63 }

 可以看到,我们使用注解方式配置的Servlet已经成功调用了,loginHandle方法处理用户登录请求的完整处理过程如下图所示:

  

  Servlet3.0是支持采用基于注解的方式配置Servlet的,在此我使用过滤器作为注解处理器模拟模拟出了类似Servlet3.0的注解处理方式,简化了Servlet的配置。这种使用自定义注解+注解处理器的方式山寨出来的Servlet3.0大家了解一下即可,了解一下这种处理思路,在实际应用中还是不要这么做了,要真想使用注解的方式配置Servlet还是直接用Servlet3.0吧。

 

Serlet的改进Struts2的引入

Servlet 的用法,我们还是发现其存在很多缺点:

  ①、一个请求对应一个 Servlet,即每一个请求我们都需要在 web.xml 文件中配置映射。如果项目大,请求很多,那么会造成 web.xml 很大,很难维护。

  ②、即便在好几个请求对应一个 Servlet,即在 service() 方法中,通过 if--else 语句来判断执行的代码块。那这样就会造成 service() 方法很拥挤。

  ③、一个项目只存在一个 web.xml 文件,如果一个项目是多人开发,那么整合代码开发过程中会有很多问题。不适合团队开发。

  ④、Servlet中doGet方法和doPost方法中的两个参数reqeust,response拥有严重的容器依赖性。

  ⑤、如果页面上表单中的元素比较复杂,则在Servlet的方法中获取表单元素的数据比较繁琐。

  ⑥、Servlet是单线程的,只要在Servlet中的声明一个实例变量,那么该变量在多线程访问时就会有线程安全问题。

  ⑦、在Servlet中处理异常,如果Servlet中有N个方法,则这N个方法必须都要try--catch。因为子类抛的异常不能大于父类。

那么接下来我们用一个例子来解决上面的问题。

  1、新建一个 Web 工程,名为 ServletIncreased。并在 web.xml 中配置一个过滤器 ServletFilter,这个过滤器会过滤所有以 .do 结尾的 URL 链接

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 xmlns="http://java.sun.com/xml/ns/javaee"
  xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
  http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
   id="WebApp_ID" version="3.0">
    
  <filter>
    <filter-name>ServletFilter</filter-name>
    <filter-class>com.ys.filter.ServletFilter</filter-class>
  </filter>
  <filter-mapping>
    <filter-name>ServletFilter</filter-name>
    <url-pattern>*.do</url-pattern>
  </filter-mapping>
   
</web-app>

2、创建一个 UserServlet,里面有两个方法,insert()和update()方法,调用 insert() 方法会跳转到 insert.jsp 页面,调用 update() 方法会调转到 update.jsp 页面

import java.io.IOException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
 
public class UserServlet {
    //用户插入方法
    public void insert(HttpServletRequest req,HttpServletResponse resp) throws Exception, IOException{
        req.getRequestDispatcher("insert.jsp").forward(req, resp);
    }
     
    //用户更新方法
    public void update(HttpServletRequest req,HttpServletResponse resp) throws Exception, IOException{
        req.getRequestDispatcher("update.jsp").forward(req, resp);
    }
     
}

 3、创建一个配置文件类,里面存放配置文件的关系,通过一个 Map 集合,保存 Servlet 的类名和全类名

import java.util.HashMap;
import java.util.Map;
 
public class ServletNameConfig {
    //定义一个 Servlet 配置文件,Map<key,value>
    //key:表示 Servlet 的类名
    //value:表示 Servlet 类名的全称
    public static Map<String, String> servletMap = new HashMap<>();
     
    static {
        servletMap.put("UserServlet", "com.ys.servlet.UserServlet");
    }
 
}

回头看我们配置的过滤器,ServletFilter

import java.io.IOException;
import java.lang.reflect.Method;
 
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import com.ys.config.ServletNameConfig;
 
public class ServletFilter implements Filter{
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException {
        HttpServletRequest req = (HttpServletRequest) request;
        HttpServletResponse resp = (HttpServletResponse) response;
         
        String reqURL = req.getRequestURI(); //  /ServletIncreased/UserServlet.do
        String[] strs = reqURL.split("/");
        //定义 Servlet 的全类名
        String servletAllName = null;
        if(strs[2] != null){
            //得到 请求路径的 servlet 类名
            String servletName = strs[2].substring(0, strs[2].indexOf("."));
            //根据获取的 Servlet 类名,由配置文件 ServletNameConfig 里面的map 得到 全类名
            servletAllName = ServletNameConfig.servletMap.get(servletName);
        }
        //获取请求方法名
        String methodName = req.getParameter("method");
        System.out.println(servletAllName+"---"+methodName);
        try {
            //通过反射调用执行方法
            Class obj = Class.forName(servletAllName);
            Method method = obj.getDeclaredMethod
                    (methodName, HttpServletRequest.class,HttpServletResponse.class);
            method.invoke(obj.newInstance(), req,resp);
        } catch (Exception e) {
            e.printStackTrace();
        }      
    }
 
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
    }
    @Override
    public void destroy() {
    }
 
}

 整体的项目结构如下:

  

然后将整个项目发布到 tomcat 服务器运行,发布的方法可以如下:

然后我们在浏览器输入如下链接:http://localhost:8080/ServletIncreased/UserServlet.do?method=insert

  那么就会调用 UserServlet 的 insert 方法,进而跳转到 insert.jsp 页面

  

如果我们在浏览器输入如下链接:将 insert 改为 update

   http://localhost:8080/ServletIncreased/UserServlet.do?method=update

那么就会调用 UserServlet 的update 方法,进而调转到 update.jsp 页面

  

分析:这个改进主要是配置了一个过滤器,然后通过过滤器的 doFilter() 方法,我们可以通过请求路径获得请求URL,然后通过字符串的截取方法得到 Servlet 的名称。通过配置文件保存的 Servlet类名和全类名的对应关系得到全类名;然后利用反射的原理,通过 invoke() 方法来动态调用方法。这里我们并没有解决上面所有的问题,比如严重的容器依赖性我们这里还有。

 

Struts2

  Struts2是一个基于MVC设计模式的Web应用框架,它本质上相当于一个servlet,在MVC设计模式中,Struts2作为控制器(Controller)来建立模型与视图的数据交互。Struts 2是Struts的下一代产品,是

struts 1和WebWork的技术基础上进行了合并的全新的Struts 2框架。其全新的Struts 2的体系结构与Struts 1的体系结构差别巨大。Struts 2以WebWork为核心,采用拦截器的机制来处理用户的请求,这样的

设计也使得业务逻辑控制器能够与ServletAPI完全脱离开,所以Struts 2可以理解为WebWork的更新产品。虽然从Struts 1到Struts 2有着太大的变化,但是相对于WebWork,Struts 2的变化很小。

第一步:创建一个 web 工程,并将相应的 jar 包复制到 lib 目录下

  

 第二步:在 WEB-INF 目录下,创建 web.xml 文件,并添加 struts2 拦截器

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 xmlns="http://java.sun.com/xml/ns/javaee"
 xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
  http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
   id="WebApp_ID" version="3.0">
    
  <!-- 配置Struts2的过滤器 -->
    <filter>
        <filter-name>struts2</filter-name>
        <filter-class>org.apache.struts2.dispatcher.ng.filter.StrutsPrepareAndExecuteFilter</filter-class>
    </filter>
    <filter-mapping>
        <filter-name>struts2</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>
</web-app>

第三步:在 src 目录下,创建一个Java文件,HelloWorldAction

public class HelloWorldAction {
     
    public String execute(){
        System.out.println("执行Action.....");
        return "success";
    }
 
}

第四步:在 src 目录下,创建 struts.xml 文件,并添加如下代码

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE struts PUBLIC
    "-//Apache Software Foundation//DTD Struts Configuration 2.1.7//EN"
    "http://struts.apache.org/dtds/struts-2.1.7.dtd">
<struts>
   <package name="user" namespace="/" extends="struts-default">
        <action name="helloAction" class="com.ys.action.HelloWorldAction">
            <result name="success">success.jsp</result>
        </action>
   </package> 
</struts>

   第五步:我们创建一个 index.jsp 文件,然后点击页面上的超链接,跳转到 success.jsp 页面

index.jsp 页面:

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
    "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>
    <a href="helloAction">点击跳转到 success.jsp 页面</a>
</body>
</html>  

success.jsp页面

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML
    4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>
    success.jsp
</body>
</html>

 验证:我们将项目发布到 tomcat 服务器,然后输入链接:http://localhost:8080/HelloStruts2/index.jsp

  

  然后鼠标点击 超链接,发现页面跳转到 如下界面:

  

struts2 执行流程

tomcat 服务器启动时做的工作

  ①、加载 web.xml 文件配置的过滤器,调用过滤器的 init()方法,初始化所有资源文件,主要包括 default.properties 文件,struts-default.xml,strut-plugin.xml,struts.xml 文件

 

struts2 执行流程

1、客户端初始化一个指向Servlet容器(例如Tomcat)的请求;

2、这个请求经过一系列的过滤器(Filter)(这些过滤器中有一个叫做ActionContextCleanUp的可选过滤器,这个过滤器对于Struts2和其他框架的集成很有帮助,例如:SiteMesh Plugin);

3、接着FilterDispatcher被调用,FilterDispatcher询问ActionMapper来决定这个请求是否需要调用某个Action;

4、如果ActionMapper决定需要调用某个Action,FilterDispatcher把请求的处理交给ActionProxy;

5、ActionProxy通过Configuration Manager询问框架的配置文件,找到需要调用的Action类;

6、ActionProxy创建一个ActionInvocation的实例。

7、ActionInvocation实例使用命名模式来调用,在调用Action的过程前后,涉及到相关拦截器(Intercepter)的调用。

8、一旦Action执行完毕,ActionInvocation负责根据struts.xml中的配置找到对应的返回结果。返回结果通常是(但不总是,也可能是另外的一个Action链)一个需要被表示的JSP或者FreeMarker的模版。在表示的过程中可以使用Struts2框架中继承的标签。在这个过程中需要涉及到ActionMapper。

 

简单模拟spring MVC

  在Spring MVC中,将一个普通的java类标注上Controller注解之后,再将类中的方法使用RequestMapping注解标注,那么这个普通的java类就够处理Web请求,示例代码如下:

 /**
  * 使用Controller注解标注LoginUI类
  */
 @Controller
 public class LoginUI {
     
     //使用RequestMapping注解指明forward1方法的访问路径  
     @RequestMapping("LoginUI/Login2")
     public View forward1(){
         //执行完forward1方法之后返回的视图
         return new View("/login2.jsp");  
     }
     
     //使用RequestMapping注解指明forward2方法的访问路径  
     @RequestMapping("LoginUI/Login3")
     public View forward2(){
         //执行完forward2方法之后返回的视图
         return new View("/login3.jsp");  
     } 
 }

 spring通过java annotation就可以注释一个类为action ,在方法上添加上一个java annotation 就可以配置请求的路径了,那么这种机制是如何实现的呢,今天我们使用"自定义注解+Servlet"来简单模拟一下Spring MVC中的这种注解配置方式。

编写注解

Controller注解

  开发Controller注解,这个注解只有一个value属性,默认值为空字符串,代码如下:

 package me.gacl.annotation;
 
 import java.lang.annotation.ElementType;
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.lang.annotation.Target;
 
 /**
 * @ClassName: Controller
 * @Description: 自定义Controller注解
 *
 */ 
 @Retention(RetentionPolicy.RUNTIME)
 @Target(ElementType.TYPE)
 public @interface Controller {
 
     public String value() default "";
 }

RequestMapping注解

  开发RequestMapping注解,用于定义请求路径,这个注解只有一个value属性,默认值为空字符串,代码如下:

package me.gacl.annotation;
  
  import java.lang.annotation.ElementType;
  import java.lang.annotation.Retention;
  import java.lang.annotation.RetentionPolicy;
 import java.lang.annotation.Target;
  
 /**
  * 定义请求路径的java annotation
  */
 @Target(ElementType.METHOD) 
 @Retention(RetentionPolicy.RUNTIME)  
 public @interface RequestMapping {
 
     public String value() default "";
 }

 以上就是我们自定义的两个注解,注解的开发工作就算是完成了,有了注解之后,那么就必须针对注解来编写处理器,否则我们开发的注解配置到类或者方法上面是不起作用的,这里我们使用Servlet来作为注解的处理器。

 

编写核心的注解处理器

开发AnnotationHandleServlet

  这里使用一个Servlet来作为注解处理器,编写一个AnnotationHandleServlet,代码如下:

  1 package me.gacl.web.controller;
  2 
  3 import java.io.IOException;
  4 import java.lang.reflect.InvocationTargetException;
  5 import java.lang.reflect.Method;
  6 import java.util.Set;
  7 import javax.servlet.ServletConfig;
  8 import javax.servlet.ServletException;
  9 import javax.servlet.http.HttpServlet;
 10 import javax.servlet.http.HttpServletRequest;
 11 import javax.servlet.http.HttpServletResponse;
 12 import me.gacl.annotation.Controller;
 13 import me.gacl.annotation.RequestMapping;
 14 import me.gacl.util.BeanUtils;
 15 import me.gacl.util.RequestMapingMap;
 16 import me.gacl.util.ScanClassUtil;
 17 import me.gacl.web.context.WebContext;
 18 import me.gacl.web.view.DispatchActionConstant;
 19 import me.gacl.web.view.View;
 20 
 21 /**
 22  * <p>ClassName: AnnotationHandleServlet<p>
 23  * <p>Description: AnnotationHandleServlet作为自定义注解的核心处理器以及负责调用目标业务方法处理用户请求<p>
 24  * @author xudp
 25  * @version 1.0 V
 26  */
 27 public class AnnotationHandleServlet extends HttpServlet {
 28     
 29     private String pareRequestURI(HttpServletRequest request){
 30         String path = request.getContextPath()+"/";
 31         String requestUri = request.getRequestURI();
 32         String midUrl = requestUri.replaceFirst(path, "");
 33         String lasturl = midUrl.substring(0, midUrl.lastIndexOf("."));
 34         return lasturl;
 35     }
 36     
 37     public void doGet(HttpServletRequest request, HttpServletResponse response)
 38             throws ServletException, IOException {
 39         this.excute(request, response);
 40     }
 41 
 42     public void doPost(HttpServletRequest request, HttpServletResponse response)
 43             throws ServletException, IOException {
 44         this.excute(request, response);
 45     }
 46     
 47     private void excute(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException{
 48         //将当前线程中HttpServletRequest对象存储到ThreadLocal中,以便在Controller类中使用
 49         WebContext.requestHodler.set(request);
 50         //将当前线程中HttpServletResponse对象存储到ThreadLocal中,以便在Controller类中使用
 51         WebContext.responseHodler.set(response);
 52         //解析url
 53         String lasturl = pareRequestURI(request);
 54         //获取要使用的类
 55         Class<?> clazz = RequestMapingMap.getRequesetMap().get(lasturl);
 56         //创建类的实例
 57         Object classInstance = BeanUtils.instanceClass(clazz);
 58         //获取类中定义的方法
 59         Method [] methods = BeanUtils.findDeclaredMethods(clazz);
 60         Method method = null;
 61         for(Method m:methods){//循环方法,找匹配的方法进行执行
 62             if(m.isAnnotationPresent(RequestMapping.class)){
 63                 String anoPath = m.getAnnotation(RequestMapping.class).value();
 64                 if(anoPath!=null && !"".equals(anoPath.trim()) && lasturl.equals(anoPath.trim())){
 65                     //找到要执行的目标方法
 66                     method = m;
 67                     break;
 68                 }
 69             }
 70         }
 71         try {
 72             if(method!=null){
 73                 //执行目标方法处理用户请求
 74                 Object retObject = method.invoke(classInstance);
 75                 //如果方法有返回值,那么就表示用户需要返回视图
 76                 if (retObject!=null) {
 77                     View view = (View)retObject;
 78                     //判断要使用的跳转方式
 79                     if(view.getDispathAction().equals(DispatchActionConstant.FORWARD)){
 80                         //使用服务器端跳转方式
 81                         request.getRequestDispatcher(view.getUrl()).forward(request, response);
 82                     }else if(view.getDispathAction().equals(DispatchActionConstant.REDIRECT)){
 83                         //使用客户端跳转方式
 84                         response.sendRedirect(request.getContextPath()+view.getUrl());
 85                     }else{
 86                         request.getRequestDispatcher(view.getUrl()).forward(request, response);
 87                     }
 88                 }
 89             }
 90         } catch (IllegalArgumentException e) {
 91             e.printStackTrace();
 92         } catch (IllegalAccessException e) {
 93             e.printStackTrace();
 94         } catch (InvocationTargetException e) {
 95             e.printStackTrace();
 96         }
 97     }
 98 
 99     @Override
100     public void init(ServletConfig config) throws ServletException {
101         /**
102          * 重写了Servlet的init方法后一定要记得调用父类的init方法,
103          * 否则在service/doGet/doPost方法中使用getServletContext()方法获取ServletContext对象时
104          * 就会出现java.lang.NullPointerException异常
105          */
106         super.init(config); 
107         System.out.println("---初始化开始---");
108         //获取web.xml中配置的要扫描的包
109         String basePackage = config.getInitParameter("basePackage");
110         //如果配置了多个包,例如:<param-value>me.gacl.web.controller,me.gacl.web.UI</param-value>
111         if (basePackage.indexOf(",")>0) {
112             //按逗号进行分隔
113             String[] packageNameArr = basePackage.split(",");
114             for (String packageName : packageNameArr) {
115                 initRequestMapingMap(packageName);
116             }
117         }else {
118             initRequestMapingMap(basePackage);
119         }
120         System.out.println("----初始化结束---");
121     }
122     
123     /**
124     * @Method: initRequestMapingMap
125     * @Description:添加使用了Controller注解的Class到RequestMapingMap中
127     * @param packageName
128     */ 
129     private void initRequestMapingMap(String packageName){
130         Set<Class<?>> setClasses =  ScanClassUtil.getClasses(packageName);
131         for (Class<?> clazz :setClasses) {
132             if (clazz.isAnnotationPresent(Controller.class)) {
133                 Method [] methods = BeanUtils.findDeclaredMethods(clazz);
134                 for(Method m:methods){//循环方法,找匹配的方法进行执行
135                     if(m.isAnnotationPresent(RequestMapping.class)){
136                         String anoPath = m.getAnnotation(RequestMapping.class).value();
137                         if(anoPath!=null && !"".equals(anoPath.trim())){
138                             if (RequestMapingMap.getRequesetMap().containsKey(anoPath)) {
139                                 throw new RuntimeException("RequestMapping映射的地址不允许重复!");
140                             }
141                             RequestMapingMap.put(anoPath, clazz);
142                         }
143                     }
144                 }
145             }
146         }
147     }
148 }

 这里说一下AnnotationHandleServlet的实现思路

  1、AnnotationHandleServlet初始化(init)时扫描指定的包下面使用了Controller注解的类,如下图所示:

  

  2、遍历类中的方法,找到类中使用了RequestMapping注解标注的那些方法,获取RequestMapping注解的value属性值,value属性值指明了该方法的访问路径,以RequestMapping注解的value属性值作为key,Class类作为value将存储到一个静态Map集合中。如下图所示:

  

  当用户请求时(无论是get还是post请求),会调用封装好的execute方法 ,execute会先获取请求的url,然后解析该URL,根据解析好的URL从Map集合中取出要调用的目标类 ,再遍历目标类中定义的所有方法,找到类中使用了RequestMapping注解的那些方法,判断方法上面的RequestMapping注解的value属性值是否和解析出来的URL路径一致,如果一致,说明了这个就是要调用的目标方法,此时就可以利用java反射机制先实例化目标类对象,然后再通过实例化对象调用要执行的方法处理用户请求。服务器将以下图的方式与客户端进行交互  

  

  另外,方法处理完成之后需要给客户端发送响应信息,比如告诉客户端要跳转到哪一个页面,采用的是服务器端跳转还是客户端方式跳转,或者发送一些数据到客户端显示,那么该如何发送响应信息给客户端呢,在此,我们可以设计一个View(视图)类,对这些操作属性进行封装,其中包括跳转的路径 、展现到页面的数据、跳转方式。这就是AnnotationHandleServlet的实现思路。

在Web.xml文件中注册AnnotationHandleServlet

  在web.xml文件中配置AnnotationHandleServlet和需要扫描的包

 <servlet>
     <servlet-name>AnnotationHandleServlet</servlet-name>
     <servlet-class>me.gacl.web.controller.AnnotationHandleServlet</servlet-class>
     <init-param>
          <description>配置要扫描包及其子包, 如果有多个包,以逗号分隔</description>
         <param-name>basePackage</param-name>
         <param-value>me.gacl.web.controller,me.gacl.web.UI</param-value>
         <!-- <param-value>me.gacl.web.controller</param-value> -->
     </init-param>
     <load-on-startup>1</load-on-startup>
   </servlet>
 
   <servlet-mapping>
     <servlet-name>AnnotationHandleServlet</servlet-name>
     <!-- 拦截所有以.do后缀结尾的请求 -->
     <url-pattern>*.do</url-pattern>
   </servlet-mapping>

 

相关代码讲解

BeanUtils

  BeanUtils工具类主要是用来处理一些反射的操作

1 package me.gacl.util;
 2 
 3 import java.lang.reflect.Constructor;
 4 import java.lang.reflect.Field;
 5 import java.lang.reflect.InvocationTargetException;
 6 import java.lang.reflect.Method;
 7 import java.lang.reflect.Modifier;
 8 
 9 /**
10  * 对java反射中操作的一些封装
11  */
12 public class BeanUtils {
13     
14     /**
15      * 实例化一个class 
16      * @param <T>
17      * @param clazz Person.class
18      * @return
19      */
20     public static <T> T instanceClass(Class<T> clazz){
21         if(!clazz.isInterface()){
22             try {
23                 return clazz.newInstance();
24             } catch (InstantiationException e) {
25                 e.printStackTrace();
26             } catch (IllegalAccessException e) {
27                 e.printStackTrace();
28             }
29         }
30         return null;
31     }
32     
33     /**
34      * 通过构造函数实例化
35      * @param <T>
36      * @param ctor
37      * @param args
38      * @return
39      * @throws IllegalArgumentException
40      * @throws InstantiationException
41      * @throws IllegalAccessException
42      * @throws InvocationTargetException
43      */
44     public static <T> T instanceClass(Constructor<T> ctor, Object... args)
45             throws IllegalArgumentException, InstantiationException, 
46             IllegalAccessException, InvocationTargetException{
47         makeAccessible(ctor);
48         return ctor.newInstance(args);//调用构造方法实例化
49     }
50     
51     /**
52      * 查找某个class的方法
53      * @param clazz
54      * @param methodName
55      * @param paramTypes
56      * @return
57      * @throws SecurityException
58      * @throws NoSuchMethodException
59      */
60     public static  Method findMethod(Class<?> clazz, String methodName, Class<?>... paramTypes){
61         try {
62             return clazz.getMethod(methodName, paramTypes);
63         } catch (NoSuchMethodException e) {
64             return findDeclaredMethod(clazz, methodName, paramTypes);//返回共有的方法
65         }
66     }
67     
68     public static Method findDeclaredMethod(Class<?> clazz, String methodName, Class<?>[] paramTypes){
69         try {
70             return clazz.getDeclaredMethod(methodName, paramTypes);
71         }
72         catch (NoSuchMethodException ex) {
73             if (clazz.getSuperclass() != null) {
74                 return findDeclaredMethod(clazz.getSuperclass(), methodName, paramTypes);
75             }
76             return null;
77         }
78     }
79     
80     public static Method [] findDeclaredMethods(Class<?> clazz){
81             return clazz.getDeclaredMethods();
82     }
83     
84     public static void makeAccessible(Constructor<?> ctor) {
85         if ((!Modifier.isPublic(ctor.getModifiers()) 
86                 || !Modifier.isPublic(ctor.getDeclaringClass().getModifiers()))
87                 && !ctor.isAccessible()) {
88             ctor.setAccessible(true);//如果是私有的 设置为true 使其可以访问
89         }
90     }
91     
92     public static Field[] findDeclaredFields(Class<?> clazz){
93         return clazz.getDeclaredFields();
94     }
95 }

RequestMapingMap

  该类是用于存储方法的访问路径,AnnotationHandleServlet初始化时会将类(使用Controller注解标注的那些类)中使用了RequestMapping注解标注的那些方法的访问路径存储到RequestMapingMap中。

package me.gacl.util;
 
 import java.util.HashMap;
 import java.util.Map;
 
 /**
 * @ClassName: RequestMapingMap
 * @Description: 存储方法的访问路径
 *
 */ 
 public class RequestMapingMap {
 
    /**
     * @Field: requesetMap
     *          用于存储方法的访问路径
     */ 
     private static Map<String, Class<?>> requesetMap = new HashMap<String, Class<?>>();
     
     public static Class<?> getClassName(String path) {
         return requesetMap.get(path);
     }
 
     public static void put(String path, Class<?> className) {
         requesetMap.put(path, className);
     }

    public static Map<String, Class<?>> getRequesetMap() {
         return requesetMap;
     }
 }

ScanClassUtil

  扫描某个包下面的类的工具类

 1 package me.gacl.util;
  2 
  3 import java.io.File;
  4 import java.io.FileFilter;
  5 import java.io.IOException;
  6 import java.net.JarURLConnection;
  7 import java.net.URL;
  8 import java.net.URLDecoder;
  9 import java.util.Enumeration;
 10 import java.util.LinkedHashSet;
 11 import java.util.Set;
 12 import java.util.jar.JarEntry;
 13 import java.util.jar.JarFile;
 14 
 15 /**
 16 * @ClassName: ScanClassUtil
 17 * @Description: 扫描指定包或者jar包下面的class
 20 *
 21 */ 
 22 public class ScanClassUtil {
 23 
 24     /**
 25      * 从包package中获取所有的Class
 26      * 
 27      * @param pack
 28      * @return
 29      */
 30     public static Set<Class<?>> getClasses(String pack) {
 31 
 32         // 第一个class类的集合
 33         Set<Class<?>> classes = new LinkedHashSet<Class<?>>();
 34         // 是否循环迭代
 35         boolean recursive = true;
 36         // 获取包的名字 并进行替换
 37         String packageName = pack;
 38         String packageDirName = packageName.replace('.', '/');
 39         // 定义一个枚举的集合 并进行循环来处理这个目录下的things
 40         Enumeration<URL> dirs;
 41         try {
 42             dirs = Thread.currentThread().getContextClassLoader().getResources(
 43                     packageDirName);
 44             // 循环迭代下去
 45             while (dirs.hasMoreElements()) {
 46                 // 获取下一个元素
 47                 URL url = dirs.nextElement();
 48                 // 得到协议的名称
 49                 String protocol = url.getProtocol();
 50                 // 如果是以文件的形式保存在服务器上
 51                 if ("file".equals(protocol)) {
 52                     System.err.println("file类型的扫描");
 53                     // 获取包的物理路径
 54                     String filePath = URLDecoder.decode(url.getFile(), "UTF-8");
 55                     // 以文件的方式扫描整个包下的文件 并添加到集合中
 56                     findAndAddClassesInPackageByFile(packageName, filePath,
 57                             recursive, classes);
 58                 } else if ("jar".equals(protocol)) {
 59                     // 如果是jar包文件
 60                     // 定义一个JarFile
 61                     System.err.println("jar类型的扫描");
 62                     JarFile jar;
 63                     try {
 64                         // 获取jar
 65                         jar = ((JarURLConnection) url.openConnection())
 66                                 .getJarFile();
 67                         // 从此jar包 得到一个枚举类
 68                         Enumeration<JarEntry> entries = jar.entries();
 69                         // 同样的进行循环迭代
 70                         while (entries.hasMoreElements()) {
 71                             // 获取jar里的一个实体 可以是目录 和一些jar包里的其他文件 如META-INF等文件
 72                             JarEntry entry = entries.nextElement();
 73                             String name = entry.getName();
 74                             // 如果是以/开头的
 75                             if (name.charAt(0) == '/') {
 76                                 // 获取后面的字符串
 77                                 name = name.substring(1);
 78                             }
 79                             // 如果前半部分和定义的包名相同
 80                             if (name.startsWith(packageDirName)) {
 81                                 int idx = name.lastIndexOf('/');
 82                                 // 如果以"/"结尾 是一个包
 83                                 if (idx != -1) {
 84                                     // 获取包名 把"/"替换成"."
 85                                     packageName = name.substring(0, idx)
 86                                             .replace('/', '.');
 87                                 }
 88                                 // 如果可以迭代下去 并且是一个包
 89                                 if ((idx != -1) || recursive) {
 90                                     // 如果是一个.class文件 而且不是目录
 91                                     if (name.endsWith(".class")
 92                                             && !entry.isDirectory()) {
 93                                         // 去掉后面的".class" 获取真正的类名
 94                                         String className = name.substring(
 95                                                 packageName.length() + 1, name
 96                                                         .length() - 6);
 97                                         try {
 98                                             // 添加到classes
 99                                             classes.add(Class
100                                                     .forName(packageName + '.'
101                                                             + className));
102                                         } catch (ClassNotFoundException e) {
103                                             // log
104                                             // .error("添加用户自定义视图类错误 找不到此类的.class文件");
105                                             e.printStackTrace();
106                                         }
107                                     }
108                                 }
109                             }
110                         }
111                     } catch (IOException e) {
112                         // log.error("在扫描用户定义视图时从jar包获取文件出错");
113                         e.printStackTrace();
114                     }
115                 }
116             }
117         } catch (IOException e) {
118             e.printStackTrace();
119         }
120 
121         return classes;
122     }
123     
124     /**
125      * 以文件的形式来获取包下的所有Class
126      * 
127      * @param packageName
128      * @param packagePath
129      * @param recursive
130      * @param classes
131      */
132     public static void findAndAddClassesInPackageByFile(String packageName,
133             String packagePath, final boolean recursive, Set<Class<?>> classes) {
134         // 获取此包的目录 建立一个File
135         File dir = new File(packagePath);
136         // 如果不存在或者 也不是目录就直接返回
137         if (!dir.exists() || !dir.isDirectory()) {
138             // log.warn("用户定义包名 " + packageName + " 下没有任何文件");
139             return;
140         }
141         // 如果存在 就获取包下的所有文件 包括目录
142         File[] dirfiles = dir.listFiles(new FileFilter() {
143             // 自定义过滤规则 如果可以循环(包含子目录) 或则是以.class结尾的文件(编译好的java类文件)
144             public boolean accept(File file) {
145                 return (recursive && file.isDirectory())
146                         || (file.getName().endsWith(".class"));
147             }
148         });
149         // 循环所有文件
150         for (File file : dirfiles) {
151             // 如果是目录 则继续扫描
152             if (file.isDirectory()) {
153                 findAndAddClassesInPackageByFile(packageName + "."
154                         + file.getName(), file.getAbsolutePath(), recursive,
155                         classes);
156             } else {
157                 // 如果是java类文件 去掉后面的.class 只留下类名
158                 String className = file.getName().substring(0,
159                         file.getName().length() - 6);
160                 try {
161                     // 添加到集合中去
162                     //classes.add(Class.forName(packageName + '.' + className));
163                      //经过回复同学的提醒,这里用forName有一些不好,会触发static方法,没有使用classLoader的load干净
164                     classes.add(Thread.currentThread().getContextClassLoader().loadClass(packageName + '.' + className));  
165                     } catch (ClassNotFoundException e) {
166                     // log.error("添加用户自定义视图类错误 找不到此类的.class文件");
167                     e.printStackTrace();
168                 }
169             }
170         }
171     }
172 }

WebContext

  WebContext主要是用来存储当前线程中的HttpServletRequest和HttpServletResponse,当别的地方需要使用HttpServletRequest和HttpServletResponse,就可以通过requestHodler和responseHodler获取,通过WebContext.java这个类 ,我们可以在作为Controller的普通java类中获取当前请求的request、response或者session相关请求类的实例变量,并且线程间互不干扰的,因为用到了ThreadLocal这个类。

 package me.gacl.web.context;
 
 import javax.servlet.ServletContext;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 import javax.servlet.http.HttpSession;
 
 /**
  * WebContext主要是用来存储当前线程中的HttpServletRequest和HttpServletResponse
  * 当别的地方需要使用HttpServletRequest和HttpServletResponse,就可以通过requestHodler和responseHodler获取
  **/
 public class WebContext {
 
     public static ThreadLocal<HttpServletRequest> requestHodler = new ThreadLocal<HttpServletRequest>();
     public static ThreadLocal<HttpServletResponse> responseHodler = new ThreadLocal<HttpServletResponse>();
    
     public HttpServletRequest getRequest(){
            return requestHodler.get();
     }
    
     public HttpSession getSession(){
            return requestHodler.get().getSession();
     }
    
     public ServletContext getServletContext(){
            return requestHodler.get().getSession().getServletContext();
     }
    
     public HttpServletResponse getResponse(){
         return responseHodler.get();
     }
 }

View

  一个视图类,对一些客户端响应操作进行封装,其中包括跳转的路径 、展现到页面的数据、跳转方式

 package me.gacl.web.view;
  
  /**
   * 视图模型
   **/
 public class View {
  
      private String url;//跳转路径
     
     private String dispathAction = DispatchActionConstant.FORWARD;//跳转方式
 
     public View(String url) {
         this.url = url;
     }
     
     public View(String url,String name,Object value) {
         this.url = url;
         ViewData view = new ViewData();
         view.put(name, value);
     }
     
     
     public View(String url,String name,String dispathAction ,Object value) {
        this.dispathAction = dispathAction;
         this.url = url;
         ViewData view = new ViewData();//请看后面的代码
        view.put(name, value);
     }
     
     
     public String getUrl() {
        return url;
     }
     
     
     public void setUrl(String url) {
         this.url = url;
     }
 
     public String getDispathAction() {
         return dispathAction;
     }
 
     public void setDispathAction(String dispathAction) {
         this.dispathAction = dispathAction;
     }
 }

ViewData

  request范围的数据存储类,当需要发送数据到客户端显示时,就可以将要显示的数据存储到ViewData类中。使用ViewData.put(String name,Object value)方法往request对象中存数据。

 package me.gacl.web.view;
 
 import javax.servlet.http.HttpServletRequest;
 
 import me.gacl.web.context.WebContext;

 /**
  * 需要发送到客户端显示的数据模型
  */
 public class ViewData {
     
    private HttpServletRequest request;
     
     public ViewData() {
         initRequest();
     }
 
     private void initRequest(){
         //从requestHodler中获取request对象
         this.request = WebContext.requestHodler.get();
     }
     
     public void put(String name,Object value){
         this.request.setAttribute(name, value);
     }
 }

DispatchActionConstant

  一个跳转方式的常量类

 package me.gacl.web.view;
 
  /**
   * 跳转常量
   */
  public class DispatchActionConstant {
  
      public static String FORWARD = "forward";//服务器跳转
      
     public static String REDIRECT = "redirect";//客户端跳转
 }

 

Controller注解和RequestMapping注解测试

简单测试

  编写一个LoginUI类,用于跳转到具体的jsp页面,代码如下:

 package me.gacl.web.UI;
 
 import me.gacl.annotation.Controller;
 import me.gacl.annotation.RequestMapping;
 import me.gacl.web.view.View;
 /**
  * 使用Controller注解标注LoginUI类
  */
 @Controller
 public class LoginUI {
     
     //使用RequestMapping注解指明forward1方法的访问路径  
     @RequestMapping("LoginUI/Login2")
     public View forward1(){
         //执行完forward1方法之后返回的视图
         return new View("/login2.jsp");  
     }
     
     //使用RequestMapping注解指明forward2方法的访问路径  
     @RequestMapping("LoginUI/Login3")
     public View forward2(){
         //执行完forward2方法之后返回的视图
         return new View("/login3.jsp");  
     } 
 }

 运行结果如下所示:

复杂测试

  编写用于处理用户登录请求的Controller,代码如下:

1 package me.gacl.web.controller;
 2 
 3 import java.io.IOException;
 4 import javax.servlet.http.HttpServletRequest;
 5 import javax.servlet.http.HttpServletResponse;
 6 import me.gacl.annotation.Controller;
 7 import me.gacl.annotation.RequestMapping;
 8 import me.gacl.web.context.WebContext;
 9 import me.gacl.web.view.View;
10 import me.gacl.web.view.ViewData;
11 
12 /**
13 * 
14 * @ClassName: LoginServlet2
15 * @Description:处理用户登录的Servlet,
16 * LoginServlet现在就是一个普通的java类,不是一个真正的Servlet
19 *
20 */
21 @Controller //使用Controller注解标注LoginServlet2
22 public class LoginServlet2 {
23 
24     /**
25     * @Method: loginHandle
26     * @Description:处理以普通方式提交的请求
29     * @return View
30     */
31     //使用RequestMapping注解标注loginHandle方法,指明loginHandle方法的访问路径是login/handle
32     @RequestMapping("login/handle")
33     public View loginHandle(){
34         //创建一个ViewData对象,用于存储需要发送到客户端的响应数据
35         ViewData viewData = new ViewData();
36         //通过WebContext类获取当前线程中的HttpServletRequest对象
37         HttpServletRequest request = WebContext.requestHodler.get();
38         //接收提交上来的参数
39         String username =request.getParameter("usename");
40         String pwd = request.getParameter("pwd");
41         if (username.equals("gacl") && pwd.equals("xdp")) {
42             request.getSession().setAttribute("usename", username);
43             //将响应数据存储到ViewData对象中
44             viewData.put("msg", "欢迎您!"+username);
45             //返回一个View对象,指明要跳转的视图的路径
46             return new View("/index.jsp");
47         }else {
48             //将响应数据存储到ViewData对象中
49             viewData.put("msg", "登录失败,请检查用户名和密码是否正确!");
50             //返回一个View对象,指明要跳转的视图的路径
51             return new View("/login2.jsp");
52         }
53     }
54     
55     /**
56     * @Method: ajaxLoginHandle
57     * @Description: 处理以AJAX方式提交的请求
59     *
60     * @throws IOException
61     */ 
62     //使用RequestMapping注解标注ajaxLoginHandle方法,指明ajaxLoginHandle方法的访问路径是ajaxLogin/handle
63     @RequestMapping("ajaxLogin/handle")
64     public void ajaxLoginHandle() throws IOException{
65         //通过WebContext类获取当前线程中的HttpServletRequest对象
66         HttpServletRequest request = WebContext.requestHodler.get();
67         //接收提交上来的参数
68         String username =request.getParameter("usename");
69         String pwd = request.getParameter("pwd");
70         //通过WebContext类获取当前线程中的HttpServletResponse对象
71         HttpServletResponse response = WebContext.responseHodler.get();
72         if (username.equals("gacl") && pwd.equals("xdp")) {
73             request.getSession().setAttribute("usename", username);
74             response.getWriter().write("success");
75         }else {
76             response.getWriter().write("fail");
77         }
78     }
79 }

 编写用于测试的jsp页面,代码如下所示:

  Login2.jsp登录页面

  <%@ page language="java" pageEncoding="UTF-8"%>
  <!DOCTYPE HTML>
  <html>
    <head>
      <title>login2.jsp登录页面</title>
    </head>
    
    <body>
     <fieldset>
         <legend>用户登录</legend>
         <form action="${pageContext.request.contextPath}/login/handle.do" method="post">
             用户名:<input type="text" value="${param.usename}" name="usename">
             <br/>
             密码:<input type="text" value="${param.pwd}" name="pwd">
             <br/>
             <input type="submit" value="登录"/>
         </form>
     </fieldset>
     <hr/>
     <label style="color: red;">${msg}</label>
   </body>
 </html>

 login3.jsp登录页面

<%@ page language="java" pageEncoding="UTF-8"%>
 <!DOCTYPE HTML>
 <html>
   <head>
     <title>login3登录页面</title>
     <script type="text/javascript" src="${pageContext.request.contextPath}/ajaxUtil.js"></script>
     <script type="text/javascript" src="${pageContext.request.contextPath}/js/Utils.js"></script>
      <script type="text/javascript">
     
         function login(){
              Ajax.request({
                  url : "${pageContext.request.contextPath}/ajaxLogin/handle.do",
                  data : {
                      "usename" : document.getElementById("usename").value,
                      "pwd" : document.getElementById("pwd").value
                  },
                  success : function(xhr) {
                      onData(xhr.responseText);
                 },
                  error : function(xhr) {
                      
                  }
              });
         }
         
         function onData(responseText){
             if(responseText=="success"){
                 //window.location.href="index.jsp";//改变url地址
                 /*
                window.location.replace("url"):将地址替换成新url,
                 该方法通过指定URL替换当前缓存在历史里(客户端)的项目,因此当使用replace方法之后,
                 你不能通过“前进”和“后 退”来访问已经被替换的URL,这个特点对于做一些过渡页面非常有用!
                 */
                 location.replace(g_basePath+"/index.jsp");
            }else{
                 alert("用户名和密码错误");
             }
         }
     </script>
   </head>
   
  <body>
     <fieldset>
         <legend>用户登录</legend>
         <form>
             用户名:<input type="text" name="usename" id="usename">
             <br/>
             密码:<input type="text" name="pwd" id="pwd">
             <br/>
             <input type="button" value="登录" onclick="login()"/>
         </form>
     </fieldset>
   </body>
 </html>

  index.jsp页面代码如下:

 <%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>
 
 <!DOCTYPE HTML>
 <html>
   <head>
     <title>首页</title>
   </head>
   
   <body>
         登录的用户名:${usename}
     <br/>
     ${msg}
   </body>
 </html>

  jsp页面中使用到的Utils.js代码如下:

 //立即执行的js
 (function() {
     //获取contextPath
     var contextPath = getContextPath();
     //获取basePath
      var basePath = getBasePath();
     //将获取到contextPath和basePath分别赋值给window对象的g_contextPath属性和g_basePath属性
     window.g_contextPath = contextPath;
     window.g_basePath = basePath;
 })();
 
 /**
  * @author 孤傲苍狼
  * 获得项目根路径,等价于jsp页面中
  *  <%
         String basePath = request.getScheme()+"://"+request.getServerName()+":"+request.getServerPort()+path+"/";
     %>
 * 使用方法:getBasePath();
  * @returns 项目的根路径
  *  
  */
 function getBasePath() {
     var curWwwPath = window.document.location.href;
     var pathName = window.document.location.pathname;
     var pos = curWwwPath.indexOf(pathName);
     var localhostPath = curWwwPath.substring(0, pos);
     var projectName = pathName.substring(0, pathName.substr(1).indexOf('/') + 1);
     return (localhostPath + projectName);
 }
 
 /**
  * @author 孤傲苍狼
 * 获取Web应用的contextPath,等价于jsp页面中
 *  <%
         String path = request.getContextPath();
     %>
  * 使用方法:getContextPath();
  * @returns /项目名称(/EasyUIStudy_20141104)
  */
 function getContextPath() {
     return window.document.location.pathname.substring(0, window.document.location.pathname.indexOf('\/', 1));
 };

测试结果如下:

 

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

wespten

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

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

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

打赏作者

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

抵扣说明:

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

余额充值