day36(注解、V23:反射机制重构DispatcherServlet)

day36(注解、V23:反射机制重构DispatcherServlet)

1.注解

1.定义

  • 注解在开发中常被我们利用到反射机制中,辅助反射机制做更多灵活的操作

  • 注解在如今JAVA流行的框架中被大量的应用,简化了以前繁琐的配置工作。

package reflect.annotations;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 注解
* 注解在开发中常被我们利用到反射机制中,辅助反射机制做更多灵活的操作
* 注解在如今JAVA流行的框架中被大量的应用,简化了以前繁琐的配置工作。
*
* 注解可以在:
* 类上,属性上,方法上,构造器上,以及参数上使用
* 可以通过java内置的注解@Target来说明当前注解可以被应用的位置,对应的值被定义在ElementType上
* 例如:
* @Target(ElementType.TYPE) 注解只能被用于类上
* @Target({ElementType.TYPE,ElementType.METHOD}) 注解只能被用于类上或方法上
* 当可以用于多个位置时,需要定义成数组的方式包含所有ElementType的值,即{}包含
*
*
* @Retention注解,用于标注当前注解的保留级别,有三个选项
* * RetentionPolicy.SOURCE 注解仅保留在源代码中
* * RetentionPolicy.CLASS 注解保留在字节码中,但是反射机制不能调用
* * RetentionPolicy.RUNTIME 注解保留在字节码文件中,并且可以被反射机制所使用
* * 当不指定@Retention时,默认的保留级别为CLASS,因此我们通常都需要明确指出保留级别为RUNTIME
*/

@Target({ElementType.TYPE,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface AutoRunClass {
}

2.使用范围

类上,属性上,方法上,构造器上,以及参数上使用

  • 可以通过java内置的注解@Target来说明当前注解可以被应用的位置,对应的值被定义在ElementType上

  • 例如:

  • @Target(ElementType.TYPE) 注解只能被用于类上

  • @Target({ElementType.TYPE,ElementType.METHOD}) 注解只能被用于类上或方法上

  • 当可以用于多个位置时,需要定义成数组的方式包含所有ElementType的值,即{}包含

3.@Retention注解,用于标注当前注解的保留级别,有三个选项

  • RetentionPolicy.SOURCE 注解仅保留在源代码中

  • RetentionPolicy.CLASS 注解保留在字节码中,但是反射机制不能调用

  • RetentionPolicy.RUNTIME 注解保留在字节码文件中,并且可以被反射机制所使用

  • 当不指定@Retention时,默认的保留级别为CLASS,因此我们通常都需要明确指出保留级别为RUNTIME

4.是否被注解了:isAnnotationPresent

package reflect;

import reflect.annotations.AutoRunClass;

/**
* 在反射机制中查看注解
* */
public class ReflectDemo8 {
   public static void main(String[] args) throws Exception {
  // Class cls=Class.forName(reflect.Person);
   Class cls=Class.forName(reflect.ReflectDemo7);
   boolean tf= cls.isAnnotationPresent(AutoRunClass.class);
   if (tf){
       System.out.println(cls.getName()+:被注解@AutoRunClass标注了);
  }else {
       System.out.println(cls.getName()+:没有被注解@AutoRunClass标注);
  }

  }
}

1.练习:(标注的类)

自动化实例与当前Test3在同一个包中的被@AutoRunClass标注的类
package reflect;

import reflect.annotations.AutoRunClass;

import java.io.File;

/**
* 自动实例化与当前类Test3在同一个包中被@AutoRunClass标注的类
*/
public class Test3 {
   public static void main(String[] args) throws Exception {
       File dir = new File(
               Test3.class.getResource(.).toURI()
      );
       //通过当前类Test3的类对象获取所在的包名
       String packageName = Test3.class.getPackage().getName();
       //获取Test3.class文件所在的目录中所有.class文件
       File[] subs = dir.listFiles(f->f.getName().endsWith(.class));
       for(File sub : subs) {
           //获取字节码文件的文件名
           String fileName = sub.getName();
           String className = fileName.substring(0, fileName.indexOf(.));
           //加载该类的类对象
           Class cls = Class.forName(packageName + . + className);
           if(cls.isAnnotationPresent(AutoRunClass.class)){
               System.out.println(实例化:+className);
               Object o = cls.newInstance();
          }
      }
  }
}

2.练习:(标注的方法)

自动调用与Test4在同一个包中那些被@AutoRunClass标注的类中所有被@AutoRunMethod标注的方法
package reflect;

import reflect.annotations.AutoRunClass;
import reflect.annotations.AutoRunMethod;

import java.io.File;
import java.lang.reflect.Method;
import java.net.URISyntaxException;

/**
* 自动调用与Test4在同一个包中那些被@AutoRunClass标注的类中所有被@AutoRunMethod标注的方法
*/
public class Test4 {
   public static void main(String[] args) throws Exception {
       File dir = new File(
               Test4.class.getResource(.).toURI()
      );
       //通过当前类Test3的类对象获取所在的包名
       String packageName = Test4.class.getPackage().getName();
       //获取Test3.class文件所在的目录中所有.class文件
       File[] subs = dir.listFiles(f->f.getName().endsWith(.class));
       for(File sub : subs) {
           //获取字节码文件的文件名
           String fileName = sub.getName();
           String className = fileName.substring(0, fileName.indexOf(.));
           //加载该类的类对象
           Class cls = Class.forName(packageName + . + className);
           if(cls.isAnnotationPresent(AutoRunClass.class)){
               Object o = cls.newInstance();
               //获取该类定义的所有方法
               Method[] methods = cls.getDeclaredMethods();
               for(Method method : methods){

                   if(method.isAnnotationPresent(AutoRunMethod.class)){
                       System.out.println(自动调用+className+类的方法:+method.getName());
                       method.invoke(o);
                  }
              }

          }
      }
  }
}

5.注解传参

1.概念

package reflect.annotations;

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


/*该注解只能用于标注方法*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface AutoRunMethod {
   /*
        定义参数的格式为:
        格式:类型 参数名() [default 默认值]
        注:default可选,用于为当前参数定义默认值。如果不指定,则使用注解时必须为此参数赋值。

        使用注解传参时格式:
        @注解名(参数名1=参数值1[,参数名2=参数值2,....])

        如果注解@AutoRunMethod只有一个参数,且参数名为num时,那么使用时格式如下:
        @AutoRunMethod(num=1)

        =============重点=============
        如果注解中只有一个参数,参数名建议选取value,这样的好处是,使用时可以不指定参数民,如:
        @AutoRunMethod(1)

        如果指定了默认值,则可以不指定参数,例如:
        @AutoRunMethod()   此时注解中参数的使用default的默认值

     */
   //为注解定义一个int型的参数
//   int num() default 1;//一个参数时,参数名不建议选取value以外的名字。@AutoRunMethod(num=3)
   int value () default 1;//格式:类型 参数名()
}

2.重点

如果注解中只有一个参数,参数名建议选取value,这样的好处是,使用是可以不指定参数民,如: @AutoRunMethod(1)

3.练习

自动调用与Test4在同一个包中那些被@AutoRunClass标注的类中所有被@AutoRunMethod标注的方法n次,n对应的是注解@AutoRunMethod传入的参数值

package reflect;

import reflect.annotations.AutoRunClass;
import reflect.annotations.AutoRunMethod;

import java.io.File;
import java.lang.reflect.Method;
import java.net.URISyntaxException;

/**
* 自动调用与Test4在同一个包中那些被@AutoRunClass标注的类中所有被@AutoRunMethod标注的方法
* n次,n对应的是注解@AutoRunMethod传入的参数值
*
*
*/
public class Test5 {
   public static void main(String[] args) throws Exception {
       File dir = new File(
               Test4.class.getResource(.).toURI()
      );
       //通过当前类Test3的类对象获取所在的包名
       String packageName = Test4.class.getPackage().getName();
       //获取Test3.class文件所在的目录中所有.class文件
       File[] subs = dir.listFiles(f->f.getName().endsWith(.class));
       for(File sub : subs) {
           //获取字节码文件的文件名
           String fileName = sub.getName();
           String className = fileName.substring(0, fileName.indexOf(.));
           //加载该类的类对象
           Class cls = Class.forName(packageName + . + className);
           if(cls.isAnnotationPresent(AutoRunClass.class)){
               Object o = cls.newInstance();
               //获取该类定义的所有方法
               Method[] methods = cls.getDeclaredMethods();
               for(Method method : methods){

                   if(method.isAnnotationPresent(AutoRunMethod.class)){
                       AutoRunMethod arm = method.getAnnotation(AutoRunMethod.class);
                       int value = arm.value();
                       System.out.println(自动调用+className+类的方法:+method.getName()+()+value+次);
                       for(int i=0;i<value;i++) {
                           method.invoke(o);
                      }
                  }
              }
          }
      }
  }
}

2.V23(反射机制重构DispatcherServlet)

1.主要内容

  • 利用反射机制重构DispatcherServlet,使得将来添加新的业务时DispatcherServlet 不必再添加分支判断(不进行修改)

  • 实现: 1:新建包com.webserver.annotation 2:在annotation包下添加两个注解 @Controller:用于标注哪些类是处理业务的Controller类 @RequestMapping:用于标注处理某个业务请求的业务方法 3:将com.webserver.controller包下的所有Controller类添加注解@Controller 并将里面用于处理某个业务的方法标注@RequestMapping并指定该方法处理的请求 4:DispatcherServlet在处理请求时,先扫描controller包下的所有Controller类 并找到处理该请求的业务方法,使用反射调用.

2.Controller注解

该注解用于标注那些在Controller中用于处理某个请求的业务方法的使用

package com.webserver.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**该注解用于标注那些在Controller中用于处理某个请求的业务方法的使用
* */
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Controller {

}

 

3.RequestMapping注解

该注解用于标注那些在Controller中用于处理某个请求的业务方法使用

package com.webserver.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 该注解用于标注那些在Controller中用于处理某个请求的业务方法使用
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface RequestMapping {
   String value();//用于指定标注的方法所处理的请求路径,例:/myweb/reg
}

4.controller里的所有文件加入Controller和RequestMapping注解

ToolsController、ArticleController、UserController

举例如下:

package com.webserver.controller;  import com.webserver.annotation.Controller; import com.webserver.annotation.RequestMapping; import com.webserver.http.HttpServletRequest; import com.webserver.http.HttpServletResponse; import qrcode.QRCodeUtil;  import java.io.FileOutputStream; @Controller public class ToolsController {     @RequestMapping(/myweb/createQr)     public void createQr(HttpServletRequest request, HttpServletResponse response){         String content = request.getParameter(content);         System.out.println(content:===========+content);         try {             QRCodeUtil.encode(content,logo.jpg,response.getOutputStream(),true);             response.setContentType(image/jpeg);          } catch (Exception e) {             e.printStackTrace();         }      }      public static void main(String[] args) {         String message = http://doc.canglaoshi.org;         try {             //参数1:二维码上包含的文本信息  参数2:图片生成的位置 //            QRCodeUtil.encode(message,./qr.jpg);             //参数1:二维码上包含的文本信息  参数2:图片生成后会通过该流写出 //            QRCodeUtil.encode(message,new FileOutputStream(./qr.jpg));             //参数1:二维码上包含的文本信息  参数2:二维码中间的logo图片 参数3:图片生成的位置 参数4:是否需要压缩logo图片到中间大小 //            QRCodeUtil.encode(message,logo.jpg,./qr.jpg,true);              QRCodeUtil.encode(message,logo.jpg,new FileOutputStream(./qr.jpg),true);              System.out.println(二维码生成完毕!);         } catch (Exception e) {             e.printStackTrace();         }       } }  

5.DispatcherServlet

package com.webserver.core;  import com.webserver.annotation.Controller; import com.webserver.annotation.RequestMapping; import com.webserver.controller.ArticleController; import com.webserver.controller.ToolsController; import com.webserver.controller.UserController; import com.webserver.http.HttpServletRequest; import com.webserver.http.HttpServletResponse;  import java.io.File; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.net.URISyntaxException;  /**  * 用于处理请求  */ public class DispatcherServlet {     private static File root;     private static File staticDir;      static {         try {             root = new File(DispatcherServlet.class.getClassLoader().getResource(.).toURI());             staticDir = new File(root, static);         } catch (URISyntaxException e) {             e.printStackTrace();         }     }      public void service(HttpServletRequest request, HttpServletResponse response) {         /*获取路径*/         String path = request.getRequestURI();  //        首先判断是否为请求一个业务      /*             1扫描controller包下所有的类,利用反射机制加载,并判断是否被@Controller标注了             2如果被@Controller标注了,则获取该类中所有本类中定义的方法,并判断是否被              @RequestMapping标注了             3如果被@RequestMapping标注了,则获取该方法上这个注解中指定的参数value,这个参数              表示的就是该方法处理的是哪个请求。因此得到该参数后判断是否为path的值,如果是,则              说明该方法就是处理本次请求的业务方法了,从而调用该方法即可              如果扫描了所有的Controller以及里面所有的业务方法都没有与path匹配的,则说明本次             请求不是处理业务,则执行下面原有的响应文件或404的操作          */         try {             File dir = new File(                     DispatcherServlet.class.getClassLoader().getResource(./com/webserver/controller).toURI()             );             String packName = com.webserver.controller;             File[] subs = dir.listFiles(f -> f.getName().endsWith(.class));             for (File sub : subs) {                 String fileName = sub.getName();                 String className = fileName.substring(0, fileName.indexOf(.));                 //加载类对象                 Class cls = Class.forName(packName + . + className);                 //判断该类是否被@Controller标注了                 if (cls.isAnnotationPresent(Controller.class)) {                     //实例化该Controller                     Object obj = cls.newInstance();                     Method[] methods = cls.getMethods();                     for (Method method : methods) {                         //判断该方法是否被@RequestMapping标注了                         if (method.isAnnotationPresent(RequestMapping.class)) {                             //获取该注解                             RequestMapping arm = method.getAnnotation(RequestMapping.class);                             //获取该注解的参数(该方法处理的请求路径)                             String value = arm.value();                             System.out.println(方法: + cls.getName() + ,被注解的值: + value);                             if (value.equals(path)) {                                 //执行该方法                                 method.invoke(obj, request, response);                                 return;                             }                         }                     }                 }             }         } catch (Exception e) {             e.printStackTrace();         }         File file = new File(staticDir, path);         System.out.println(资源是否存在: + file.exists());          if (file.isFile()) {//当file表示的文件真实存在且是一个文件时返回true             response.setContentFile(file);           } else {//要么file表示的是一个目录,要么不存在             response.setStatusCode(404);             response.setStatusReason(NotFound);             file = new File(staticDir, root/404.html);             response.setContentFile(file);          }          //测试添加一个额外的响应头         response.addHeader(Server, WebServer);      } }