反射 + 注解 知识点

一 反射

1.1 类加载

在class文件加载到jvm中时,会对应创建一个Class对象;分为三个步骤:加载、连接、初始化

加载
    * 将class文件加载到内存区域,对应生成一个Class对象
连接
    * 验证:验证类的结构是否正确
    * 准备:初始化静态成员
    * 解析:将字节转换成jvm能够执行的引用(对象、变量、方法)
初始化
    * 将对象中的成员变量初始化

---------------

  • 加载时机
* 创建类对象的实例
* 访问类的静态变量,或者为静态变量赋值
* 调用类的静态方法
* 使用反射方式来强制创建某个类或接口对应的java.lang.Class对象
		Class.forName("com.mysql.jdbc.Driver");创建了Driver类的运行时对象
* 初始化某个类的子类
* 直接使用java.exe命令来运行某个主类

------------------------------

1.2 类加载器

负责将.class文件加载到内存中,并为之生成对应的Class对象

  • 分类
Bootstrap ClassLoader 根类加载器
	也被称为引导类加载器,负责Java核心类的加载
	比如System,String等。在JDK中JRE的lib目录下rt.jar文件中
	
Extension ClassLoader 扩展类加载器
	负责JRE的扩展目录中jar包的加载。
	在JDK中JRE的lib目录下ext目录
	
Sysetm ClassLoader 系统类加载器
	负责在JVM启动时加载来自java命令的class文件,以及classpath环境变量所指定的jar包和类路径

------------------------------

1.3 类反射机制

通过类的Class对象,动态去调用类中的属性和方法

  • 获取Class对象方式
* 全类名=包名.类名
    Class.forName("全类名")
* 编译期
    类名.class
* 运行时
    对象名.getClass()

------------------------------

1.4 反射结合工厂模式

工厂模式是我们最常用的实例化对象模式了,是用工厂方法代替new操作的一种模式

  • 榨汁的案例,使用工厂设计模式
public class FruitFactory {
    public static Fruit getFruit(String fruitName) throws Exception {
        return (Fruit) Class.forName(fruitName).newInstance();
    }
}

public class Demo05 {
    public static void main(String[] args) throws Exception {
        ///获取苹果
        Fruit fruit1 = FruitFactory.getFruit("com.qfedu.bean.Apple");
        //获取香蕉
        Fruit fruit2 = FruitFactory.getFruit("com.qfedu.bean.Banana");

        //存在的问题:
        //com.qfedu.bean.Apple对应的对象
        //将以上"com.qfedu.bean.Apple"字符串写到java代码中,合适吗?
        //不合适!!!全类名发生改变了,那你就必须修改java源代码,必须要重新部署项目!
        //"com.qfedu.bean.Apple"字符串和java程序的耦合性非常高!!
        //"com.qfedu.bean.Apple"字符串不能放到java代码中,而应该放置到配置文件中!!
        //仅仅修改配置文件,是不需要重新部署工程的!
        //这也就是为什么要将"com.mysql.jdbc.Driver"配置到jdbc.properties中!
        //解耦!!降低耦合!!
        //总结:
        //配置文件+工厂模式+反射+注解+xml解析 就是spring框架的核心原理!!!
    }
}

------------------------------

1.5 反射获取构造方法

  • Constructor
Class类的newInstance()方法是使用该类无参的构造函数创建对象, 如果一个类没有无参的
构造函数, 就不能这样创建了,可以调用Class类的getConstructor(String.class,int.class)方
法获取一个指定的构造函数然后再调用Constructor类的newInstance("张三",20)方法创建对象
  • 访问public修饰的构造器
//获取User类对应的Class对象
Class<?> clazz = Class.forName("com.qfedu.bean.User");
//获取无参构造方法对象
Constructor<?> c1 = clazz.getConstructor();
//使用无参创建User类对象
Object obj1 = c1.newInstance();
System.out.println(obj1);
//获取User类对应的有参构造方法对象
Constructor<?> c2 = clazz.getConstructor(Integer.class, String.class, String.class);
//使用有参创建User对象
Object obj2 = c2.newInstance(1, "张三", "root");
System.out.println(obj2);

---------------

  • 访问非public修饰的构造器(使用暴力反射)
onstructor<?> c3 = clazz.getDeclaredConstructor(String.class, String.class);
//暴力反射,让私有构造器对象可以被外部访问
c3.setAccessible(true);
Object obj3 = c3.newInstance("tom", "20000101");
System.out.println(obj3);

------------------------------

扫描二维码关注公众号,回复: 11087054 查看本文章

1.6 反射获取成员变量

  • Field
Class.getField(String)方法可以获取类中的指定字段(可见的), 如果是私有的可以用
getDeclaedField("name")方法获取,通过set(obj, "李四")方法可以设置指定对象上该字段的
值, 如果是私有的需要先调用setAccessible(true)设置访问权限,用获取的指定的字段调用
get(obj)可以获取指定对象中该字段的值

---------------

//获取User类的Class对象
Class<User> clazz = User.class;
User user = clazz.newInstance();
//操作public修饰的成员变量
        Field idField = clazz.getField("id");
        //设置该成员变量值
            //obj:需要设置的对象
            //value:需要设置的值
            //给user对象的id属性设置值为250
        idField.set(user,250);
        System.out.println(user);
        //获取该成员变量值
        Object idValue = idField.get(user);
        System.out.println(idValue);
//操作非public修饰的成员变量
        Field usernameField = clazz.getDeclaredField("username");
        usernameField.setAccessible(true);
        usernameField.set(user,"坤坤");

        System.out.println(user);
        Object usernameValue = usernameField.get(user);
        System.out.println(usernameValue);

------------------------------

1.7 反射获取方法

  • Method
Class.getMethod(String, Class...) 和 Class.getDeclaredMethod(String, Class...)方法可以
获取类中的指定方法,调用invoke(Object, Object...)可以调用该方法

---------------

//获取User类的Class对象
        Class<User> clazz = User.class;
        User user = clazz.newInstance();
//获取public修饰的成员方法
        Method method01 = clazz.getMethod("setPassword", String.class);
        //使用方法对象
        //obj:哪个对象在执行该方法
        //args:方法执行时所需的参数值
        method01.invoke(user,"123456");
        System.out.println(user);
//操作非public修饰的成员方法
        Method method02 = clazz.getDeclaredMethod("show");
        method02.setAccessible(true);
        Object result = method02.invoke(user);
        System.out.println(result);

invoke方法后的返回值就是原有方法执行之后的返回值

------------------------------

1.8 反射越过泛型检查

java中的泛型的作用范围在编译期,也就是说在运行时,是没有泛型的!
反射技术,可以在程序运行时,动态地调用List类中add方法,往集合中添加任意类型的元素

//创建List集合对象
List<Integer> list = new ArrayList<Integer>();
list.add(1);
list.add(2);
list.add(3);
System.out.println(list);

//泛型只在编译期有效!!!
//反射越过泛型检查
//反射可以在程序运行时,动态地调用List中的add方法去添加元素
Class<? extends List> clazz = list.getClass();
Method add = clazz.getDeclaredMethod("add", Object.class);
add.setAccessible(true);
Object result = add.invoke(list, "hello , generic type !");
System.out.println(list);

------------------------------

1.9 反射通用方法

  • 给指定对象的指定字段设置指定值
public static void main(String[] args) throws Exception {
        User user = new User();
        setValue(user,"id" , 123);
        System.out.println(user);
    }

    /**
     * 给指定对象的指定属性设置指定值
     * @param obj : 指定对象
     * @param fieldName : 指定属性
     * @param value : 指定值
     */
    public static void setValue(Object obj , String fieldName , Object value) throws Exception {
        Class<?> clazz = obj.getClass();
        //方法名规范:如果只有一个单词,所有的字母全都小写。如果有多个单词,从第二个单词开始,首字母大写!!!
        //变量名规范: 如果只有一个单词,所有的字母全都小写。如果有多个单词,从第二个单词开始,首字母大写!!!
        //username setUsername
        //根据属性名称获取对应的set方法名称
//        String methodName = "set" + "U" + "sername";
        String methodName = "set" + fieldName.substring(0,1).toUpperCase() + fieldName.substring(1);
        Field field = clazz.getDeclaredField(fieldName);
        //获取字段的数据类型
        Class<?> fieldType = field.getType();
        //获取到set方法对象
        Method method = clazz.getMethod(methodName, fieldType);
        //执行set方法
        method.invoke(obj , value);
    }

------------------------------

1.10 反射结合配置文件

编写bean.properties,配置对象的唯一标识及对象的全类名,根据这段配置创建一个对象
//需求:编写bean.properties,配置对象的唯一标识及对象的全类名,根据这段配置创建一个对象
Properties properties = new Properties();
//将bean.properties中的数据存储到inputStream中
InputStream inputStream =Demo11.class.getClassLoader().getResourceAsStream("bean.properties");
//将bean.properties中的数据绑定到了Properties中!
properties.load(inputStream);
//获取全类名
String className = properties.getProperty("bean01");
//根据上述全类名创建了一个对象!
Object obj = Class.forName(className).newInstance();
System.out.println(obj);

String className2 = properties.getProperty("bean02");
Object obj2 = Class.forName(className2).newInstance();
System.out.println(obj2);
  • bean.properties
bean01=com.qfedu.bean.User
bean02=com.qfedu.bean.Banana

------------------------------

1.11 静态代理设计模式

强被代理类的功能

前提
1,代理类和被代理类实现同一个接口
2,代理类中持有被代理类的对象
3,在增强方法中使用被代理类对象调用方法
//1,自定义一个代理类(增强类)实现和被代理类(被增强类)相同的接口
public class UserDaoImplProxy implements UserDao{
  
	//2,在代理类中声明被代理类的引用
	private UserDao userDao ;
	public UserDaoImplProxy(){
		userDao = new UserDaoImpl();
	}

	@Override
	public void addUser() {
		//3,在代理类的方法中使用被代理类调用方法
		System.out.println("权限校验");
		userDao.addUser();
		System.out.println("日志记录");
	}
  
	@Override
	public void deleteUser() {
		userDao.deleteUser();
	}
	
	@Override
	public void updateUser() {
		userDao.updateUser();
	}

	@Override
	public void selectUser() {
		userDao.selectUser();
	}
}

---------------

  • 特点
* 缺点:必须要重写被代理类接口的所有的方法(包括不需要增强的方法,增高了耦合性)
* 作用:增强被代理类的功能
* 特点:可以控制被代理类对象

------------------------------

1.12 装饰者设计模式

  • 前提
1,代理类和被代理类实现同一个接口
2,代理类中持有被代理类的引用
3,在增强方法中使用被代理类对象调用方法

---------------

  • 开发步骤
  * 定义装饰类(增强类)实现和被装饰类(被增强类)相同的接口
  * 在装饰类中声明被装饰类的引用
  * 在装饰类的方法中,使用被装饰调用原方法

---------------

  //1,定义装饰类(增强类)实现和被装饰类(被增强类)相同的接口
  public class UserDaoWrapper implements UserDao {
  
  //    2,在装饰类中声明被装饰类的引用
      private UserDao userDao ;
  
      public UserDaoWrapper(){
      }
      public UserDaoWrapper(UserDao userDao) {
          this.userDao = userDao;
      }
  
      @Override
      public void addUser() {
          System.out.println("权限校验");
          userDao.addUser();
          System.out.println("日志记录");
      }
  
      @Override
      public void deleteUser() {
          userDao.deleteUser();
      }
  
      @Override
      public void updateUser() {
          userDao.updateUser();
      }
  
      @Override
      public void selectUser() {
        
         userDao.selectUser();
      }
  }
* 作用:在不侵入被装饰类源码的前提下,增强某个功能!

* 特点:不能控制被装饰类对象

* 缺点:需要重写接口中的所有方法,破坏了单一职责原则

------------------------------

1.13 Proxy动态代理

动态代理:在程序运行过程中产生的这个代理对象,而程序运行过程中产生对象其实就是我们
刚才反射讲解的内容,所以,动态代理其实就是通过反射来生成一个代理

基于接口的方法增强

  • 方式一:定义类实现InvocationHandler接口
    • 增强addUser方法
  public static void main(String[] args) {
  
          UserDao userDao = new UserDaoImpl();//被代理类对象
          UserDao userDaoProxy = (UserDao) Proxy.newProxyInstance(
                  userDao.getClass().getClassLoader(),
                  userDao.getClass().getInterfaces(),
                  new MyInvocationHandler(userDao));
          userDaoProxy.addUser();
          userDaoProxy.deleteUser();
          userDaoProxy.updateUser();
          userDaoProxy.selectUser();
  }
  
  /**
   * 增强代理类
   */
  class MyInvocationHandler implements InvocationHandler{
  
      //声明一个被代理类的引用
      private UserDao userDao;
  
      public MyInvocationHandler(UserDao userDao) {
          this.userDao = userDao;
      }
  
      @Override
      public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
  //        Method method : 被代理类的原方法
  //        Object[] args :被代理类的方法的实际参数
          //执行被代理类中的原方法
          String methodName = method.getName();
          Object returnValue = null;
          if ("addUser".equals(methodName)) {
              //只有addUser需要增强
              System.out.println("权限校验");
              returnValue = method.invoke(userDao, args);//执行被代理类中的原方法
              System.out.println("日志记录");
          } else {
              //其他的方法不增强
              method.invoke(userDao,args);//执行被代理类中的原方法
          }
  
          return returnValue;
      }
  }

------------------------------

  • 方式二:使用InvocationHandler接口的匿名内部类对象
    • 增强deleteUser方法
public static void main(String[] args) {
          UserDao userDao = new UserDaoImpl();
          //使用匿名内部类对象
          UserDao userDaoProxy = (UserDao) Proxy.newProxyInstance(
                  userDao.getClass().getClassLoader(),
                  userDao.getClass().getInterfaces(),
                  new InvocationHandler() {//处理增强
                      @Override
                      public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                          String methodName = method.getName();
                          Object returnValue = null;
                          if ("deleteUser".equals(methodName)) {
                              System.out.println("权限校验");
                              returnValue = method.invoke(userDao, args);
                              System.out.println("日志记录");
                          } else {
                              returnValue = method.invoke(userDao, args);
                          }
                          return returnValue;
                      }
                  });
          userDaoProxy.deleteUser();
  
          userDaoProxy.addUser();
      }

二 注解

2.1 注解介绍

一个修饰符

  • 定义
注解annotation是Java语言中用于描述类,成员变量,构造方法,成员方法,方法参数及包声明的
特殊的修饰符.用于描述这些信息的元数据.例如@Override用于描述一个方法是在子类中重
写的方法

---------------

  • 特点
是JDK5.0之后引入的特性.注解是以”@注解名”在代码中存在的

---------------

  • 作用
跟踪代码依赖性
执行编译时格式检查
代替已有的配置文件

------------------------------

2.2 java内置注解

  • @Overirde 标记指定方法是一个重写方法,否则报错
标记在成员方法上,用于标识当前方法是重写父类方法,编译器在对该方法进行编译时会检查
是否符合重写规则,如果不符合,编译报错
@Target(ElementType.METHOD)
  @Retention(RetentionPolicy.SOURCE)
  	public @interface Override {
  }

---------------

  • @Deprecated 标记一个类、字段、方法是一个过时的
如果开发者调用了被标记为过时的方法,编译器在编译期进行警告
@Documented
  @Retention(RetentionPolicy.RUNTIME)
  @Target(value={CONSTRUCTOR, FIELD, LOCAL_VARIABLE, METHOD, PACKAGE, PARAMETER, TYPE})
  public @interface Deprecated {
  }

---------------

  • @SuppressWarings
可放置在类和方法上,该注解的作用是阻止编译器发出某些警告信息,该注解为单值注解,只有
一个value参数,该参数为字符串数组类型,参数值常用的有
@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE})
  @Retention(RetentionPolicy.SOURCE)
  public @interface SuppressWarnings {
      String[] value();
  }

value : 注解所压制的警告的类型

	unchecked 未检查的转化,如集合没有指定类型还添加元素
    unused 未使用的变量
    resource 有泛型未指定类型
    path 在类路径,原文件路径中有不存在的路径
    deprecation 使用了某些不赞成使用的类和方法
    fallthrough switch语句执行到底没有break关键字
    rawtypes 没有写泛型,比如: List list = new ArrayList();
    all 全部类型的警告,用的最多是all

------------------------------

2.3 注解分类

  • 根据注解的参数个数分为三类
标记注解:没有参数的注解,仅用自身的存在与否为程序提供信息,如@Override 注解,该注解
没有参数,用于表示当前方法为重写方法

单值注解:只有一个参数的注解,如果该参数的名字为value,那么可以省略参数名,如
@SuppressWarnings(“all”),可以简写为@SuppressWarnings(“all”).

完整注解:有多个参数的注解

------------------------------

2.4 元注解概述

作用在自定义注解上,规定自定义注解的作用区域、存活策略

---------------

2.4.1 常用的元注解
  • @Target 规定自定义注解的作用区域
作用:用于描述当前定义的注解可以应用的范围 该注解仅有一个属性value,该属性值
为ElementType数组类型 ElementType为枚举类型,枚举值和作用说明如下:

TYPE 当前定义的注解可以应用在类、接口和枚举的类型定义部分

FILED 当前定义的注解可以应用在成员变量上

METHOD 当前定义的注解可以应用在成员方法上

PARAMETER 当前定义的注解可以应用在方法参数上

CONSTRUCTOR 当前定义的注解可以应用在构造方法上

LOCAL_VARIABLE 当前定义的注解可以应用在局部变量上

ANNOTATION_TYPE 当前定义的注解可以应用在注解类型上

PACKAGE 当前定义的注解可以应用在包定义语句上

---------------

  • @Retention 规定自定义注解的存活策略
作用:用于描述当前定义的注解可以保留的时间长短 该注解只有一个value参数,参数类型
为RetentionPolicy. RetentionPolicy类型为枚举类型

SOURCE 当前定义的注解仅仅停留在源码中,编译时即除去

CLASS 当前定义的注解保留到编译后的字节码中,运行时无法获取注解信息

RUNTIME 当前定义的注解可以保留到运行时,通过反射机制可以获取注解信息

------------------------------

2.5 自定义注解

  • 格式
public @interface 注解名 {
      数据类型 属性名1() default 默认值1;
      数据类型 属性名2() ;
  }
public @interface MyAnnotation01 {  
      String username() default "root";
      String password() default "root123";
      int age() default 16;
      String value();
  }

value属性单独使用时,是可以省略"value="

2.5.1 自定义注解@MyTest

测试类中,方法上如果使用了@MyTest注解,该方法就会执行

开发步骤
  * 自定义注解@MyTest
  * 在测试类Test01上使用@MyTest
  * 让@MyTest注解生效
    * 获取到Test01类对应的Class对象
    * 获取Test01类中的所有方法
    * 判断方法上是否有@MyTest注解
      * 如果有,就将该方法执行
      * 如果没有,就不处理
/**
* @MyTest注解
* @MyTest的存活策略必须是RUNTIME
*/
@Retention(RetentionPolicy.RUNTIME)
  public @interface MyTest {
      String value() default "";
  }
/*
* Test01测试类
*/
public class Test01 {
  
      @Test
      public void test01(){
          System.out.println("test01");
      }
  
      @MyTest
      public void test02(){
          System.out.println("test02");
      }
  
      @MyTest
      public void test03(){
          System.out.println("test03");
      }
  
      public void test04(){
          System.out.println("test04");
      }
  
  }
/*
* demo类
*/
public class Demo02 {  
      public static void main(String[] args) {
          //使用反射,扫描Test01类里面有哪些方法有@MyTest注解
          //如果有@MyTest注解,就将起执行
          //如果没有@MyTest注解,不做任何处理
  
          //1,获取Test01类对应的Class对象
          Class<Test01> clazz = Test01.class;
          //2,获取Test01类下所有的方法对象
          Method[] methods = clazz.getMethods();
          //stream流 lambda表达式
          Arrays.stream(methods).forEach(method -> {
              //method就是单个方法对象
              //3,判断方法上是否有@MyTest注解
              boolean present = method.isAnnotationPresent(MyTest.class);
              if (present) {
                  //方法上有@MyTest注解,执行方法
                  try {
                      method.invoke(clazz.newInstance());
                  } catch (Exception e) {
                      e.printStackTrace();
                  }
              } else {
                  //方法上没有@MyTest注解
              }
          });  
      }  
  }
2.5.2 自定义注解@JDBCInfo

用注解@JDBCInfo替代jdbc.properties配置文件

开发步骤
  * 自定义注解@JDBCInfo
  * 在JDBCUtils工具类上使用@JDBCInfo
  * 在JDBCUtils工具类中静态代码块中,获取@JDBCInfo注解上的属性,并给JDBCUtils工具类中成员变量赋值
/*
* 自定义注解@JDBCInfo
*/
@Target(ElementType.TYPE)
  @Retention(RetentionPolicy.RUNTIME)
  public @interface JDBCInfo {
      String driverClass() default "com.mysql.jdbc.Driver";
      String jdbcUrl() default "jdbc:mysql://localhost:3306/day54";
      String user() default "root";
      String password() default "123465";
  }
/*
* 在JDBCUtils工具类上使用@JDBCInfo
* 反射读取注解@JDBCInfo中的内容
*/
@JDBCInfo(password = "root")
  public class JDBCUtils {
  
      private static final String DRIVERCLASS ;
      private static final String JDBCURL;
      private static final String USER;
      private static final String PASSWORD;
  
      static {
          Class<JDBCUtils> clazz = JDBCUtils.class;
          boolean present = clazz.isAnnotationPresent(JDBCInfo.class);
          if (present) {
              //JDBCUtils类上有@JDBCInfo注解,获取该注解
              JDBCInfo jdbcInfo = clazz.getAnnotation(JDBCInfo.class);
              //从@JDBCInfo注解获取driverClass、jdbcUrl、user、password属性值
              DRIVERCLASS = jdbcInfo.driverClass();
              JDBCURL = jdbcInfo.jdbcUrl();
              USER = jdbcInfo.user();
              PASSWORD = jdbcInfo.password();
          } else {
              //JDBCUtils类上没有@JDBCInfo注解,从properties文件
              DRIVERCLASS = "";
              JDBCURL = "";
              USER = "";
              PASSWORD = "";
          }
      }
  }

------------------------------

2.6 反射、注解、设计模式

2.6.1 初级版
开发步骤
  * 自定义注解@SystemLog
    * className
    * methodName
  * 定义一个UserDao接口,且在该接口使用注解@SystemLog
  * 编写装饰者设计模式
    * 获取UserDao实现子类的Class对象
    * 获取UserDao接口的Class对象
    * 获取UserDao接口中的方法对象
/*
* 自定义注解@SystemLog
* 注解存活策略:RUNTIME
*/
@Retention(RetentionPolicy.RUNTIME)
    public @interface SystemLog {    
        String className();//记录类名
        String methodName();//记录方法名    
    }
/*
* 定义一个UserDao接口,且在该接口使用注解@SystemLog
* 设置@SystemLog中className属性、methodName属性
*/
public interface UserDao {    
	@SystemLog(className = "com.qfedu.dao.UserDao" , methodName = "addUser")
    void addUser() throws Exception; 
           
    void deleteUser() throws Exception;
    
    @SystemLog(className = "com.qfedu.dao.UserDao" , methodName = "updateUser")
    void updateUser() throws Exception;    
}
/*
* 写装饰者设计模式
*/
public class UserDaoWrapper implements UserDao{
    
        private UserDao userDao;
    
        public UserDaoWrapper(UserDao userDao) {
            this.userDao = userDao;
        }
    
        @Override
        public void addUser() throws Exception {
            userDao.addUser();
            printLog("addUser");
        }
    
        @Override
        public void deleteUser() throws Exception {
            userDao.deleteUser();
            printLog("deleteUser");
        }
    
        @Override
        public void updateUser() throws Exception {
            userDao.updateUser();
            printLog("updateUser");
        }
    
        /**
         * 日志记录
         * @param runMethodName
         */
        private void printLog(String runMethodName) throws Exception {
            //判断接口上对应的方法中是否有@SystemLog注解
            //获取UserDao接口实现子类的Class对象
            Class<? extends UserDao> sonClazz = userDao.getClass();
            //获取UserDao接口的Class对象
            Class<?>[] interfaces = sonClazz.getInterfaces();
            Class<?> fatherClazz = interfaces[0];
            //获取接口中对应的方法对象(addUser方法)
            Method method = fatherClazz.getMethod(runMethodName);
            if (null !=  method) {
                //判断方法上是否有@SystemLog注解
                boolean present = method.isAnnotationPresent(SystemLog.class);
                if (present) {
                    //方法有@SystemLog注解,打印日志
                    SimpleDateFormat format = new SimpleDateFormat("yyyy年MM月dd日 hh:mm:ss");
                    Date currentDate = new Date();
                    String currentTimeStr = format.format(currentDate);
                    SystemLog systemLog = method.getAnnotation(SystemLog.class);
                    String className = systemLog.className();
                    String methodName = systemLog.methodName();
                    System.out.println(currentTimeStr + " --- " + className + "类中 ---" + methodName + "()方法 --- 运行了");
                }
            }
        }
    }
public static void main(String[] args) throws Exception{
        UserDao userDao = new UserDaoImpl();
        UserDaoWrapper userDaoWrapper = new UserDaoWrapper(userDao);
        userDaoWrapper.addUser();

        userDaoWrapper.deleteUser();

        userDaoWrapper.updateUser();
    }
存在的问题
  * 由装饰者设计模式的弊端导致每个方法中都需要调用printLog方法
2.6.2 优化版
开发步骤
  * 自定义注解@SystemLog
    * className
    * methodName
  * 定义一个UserDao接口,且在该接口使用注解@SystemLog
  * 动态代理
    * 获取UserDao接口中的方法对象
/*
* 自定义注解 @SystemLog
*/
@Retention(RetentionPolicy.RUNTIME)
    public @interface SystemLog {    
        String className();//记录类名
        String methodName();//记录方法名    
    }
/*
* 定义一个UserDao接口,且在该接口使用注解@SystemLog
* 设置@SystemLog中className属性、methodName属性
*/
public interface UserDao {   
	@SystemLog(className = "com.qfedu.dao.UserDao" , methodName = "addUser")
    void addUser() throws Exception;
    
    void deleteUser() throws Exception;
    
    @SystemLog(className = "com.qfedu.dao.UserDao" , methodName = "updateUser")
    void updateUser() throws Exception;    
    }
/*
* 动态代理
*/
public static void main(String[] args) {
	UserDao userDao = new UserDaoImpl();
    UserDao userDaoProxy = (UserDao) Proxy.newProxyInstance(
    		userDao.getClass().getClassLoader(),
            userDao.getClass().getInterfaces(),
            new InvocationHandler() {
            	@Override
                public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                    //获取到接口中方法对象 , 比如 : UserDao接口中addUser方法
                    //之前
                    //先获取实现子类的Class对象
                    //再获取到接口的Class对象
                    //再获取到接口中的方法对象
                    //现在
                    //再获取到接口的Class对象  -- userDao.getClass().getInterfaces(),
                    //再获取到接口中的方法对象  --  Method method
                    //method : 就是接口中的方法对象
                    Object returnValue = null;
                    if (null != method) {
                        boolean present = method.isAnnotationPresent(SystemLog.class);
                        if (present) {
                            //如果有@SystemLog注解 , 执行原有功能 ,  打印日志
                            returnValue = method.invoke(userDao, args);
                            String currentTimeStr = new SimpleDateFormat("yyyy年MM月dd日 hh:mm:ss") .format(new Date());
                            SystemLog systemLog = method.getAnnotation(SystemLog.class);
                            String className = systemLog.className();
                            String methodName = systemLog.methodName();
                            System.out.println(currentTimeStr + " --- " + className + "类中 ---" + methodName + "()方法 --- 运行了");
                        } else {
                            //如果没有@SystemLog注解, 执行原有功能, 不打印日志
                            returnValue = method.invoke(userDao, args);
                        }
                    }
                    return returnValue;
                }
            });  
    userDaoProxy.addUser();
}
发布了62 篇原创文章 · 获赞 0 · 访问量 1979

猜你喜欢

转载自blog.csdn.net/qq_41841482/article/details/105543704