反射及注解

类加载概念和类加载时机

反射及注解

类加载概念

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

加载:

  • 将class文件加载到内存区域,对应生成一个Class对象

连接:

  • 验证:验证类的结构是否正确
  • 准备:初始化静态成员
  • 解析:将字节转换成jvm能够执行的引用(对象、变量、方法)

初始化:

  • 将对象中的成员变量初始化

类加载时机

1、创建类对象的实例

2、访问类的静态成员变量,或者为静态变量赋值

3、调用类的静态方法

4、使用反射方式来强制创建某个类或接口对应的java.lang.Class对象

Class.forName(“com.mysql.jdbc.Driver”);创建了Driver类的运行时对象

5、初始化某个类的子类

初始化Fahter类的子类Son类:Father类也会加载
Son类会使用到Father类中的成员变量,Father类中成员变量就需要进行初始化,就需要将Father加载到内存,子类要调用父类的构造方法

6、直接使用java.exe命令来运行某个主类

类加载器

概念:将class文件加载进内存,并生成对应的Class对象

分类

根类加载器:Bootstrap ClassLoader

  • 加载java中的核心类,主要加载C:\Program Files\Java\jdk1.8.0_101\jre\lib\rt.jar中的类

扩展类加载器:Extension ClassLoader

  • 加载java中的扩展类,主要加载C:\Program Files\Java\jdk1.8.0_101\jre\lib\ext所有jar包中的类

系统类加载器:Sysetm ClassLoader

  • 加载开发人员编写的自定义类、以及一些第三方的jar包中类

反射概述

类反射机制

概念:通过类的Class对象,在程序运行时,动态去调用类中的属性和方法;也就是说反射操控的对象是正在内存运行的类,可以通过反射清楚得知类中的相关信息

反射及注解

获取class对象方式

全类名=包名.类名

Class.forName(“全类名”)

编译期

类名.class

运行时

对象名.getClass()

// 获取Class对象的三种方式
public static void main(String[] args) throws Exception {
    // 只知道包名+类名,可以获取到User对应的Class对象
    Class<?> clazz01 = Class.forName("com.atmin.bean.User");
    // 编译期,已经有了Class对象
    Class clazz02 = User.class;
    // 运行时
    User user = new User();
    Class<? extends User> clazz03 = user.getClass();
    // clazz01、clazz02、clazz03有什么特点?
    // 都是同一个对象,同一个类的Class对象(运行时对象、字节码文件对象)只有一个对象
    System.out.println(clazz01 == clazz02);// true
    System.out.println(clazz02 == clazz03);// true
    System.out.println(clazz01 == clazz03);// true
}

反射结合工厂模式

// 苹果
// 复用性太差,导致耦合度较高,维护性也差,需要用到继承
public class Apple extends Fruit {
    // 苹果的特有属性
    private int height;// 高度
    public Apple() {
    }
    public Apple(String fruitName, int height) {
        super(fruitName);
        this.height = height;
    }
}
// 香蕉
public class Banana extends Fruit {
    private int width;
    public Banana() {
    }
    public Banana(String fruitName, int width) {
        super(fruitName);
        this.width = width;
    }
}
// 水果类: 提高了可维护性
public class Fruit {
    private String fruitName;

    public Fruit() {
    }

    public Fruit(String fruitName) {
        this.fruitName = fruitName;
    }

    public String getFruitName() {
        return fruitName;
    }

    public void setFruitName(String fruitName) {
        this.fruitName = fruitName;
    }

    @Override
    public String toString() {
        return "Fruit{" +
                "fruitName='" + fruitName + '\'' +
                '}';
    }
}

原始手段

直接new对象

public static void main(String[] args) {
    // 获取一个苹果
    Apple apple = new Apple("红富士", 10);
    // 获取一个香蕉
    Banana banana = new Banana("黄金大香蕉", 10);
    // 耦合性太高
    // Apple类和其他组件的耦合性太高
}

使用继承

解决Apple类、Banana类的代码复用性

Banana类、Apple继承Fruit类

使用工厂模式

解决Banana、Apple和其他模块耦合较高

反射及注解
public class FruitFactory1 {
    public static Apple getApple() {
        return new Apple("红富士", 10);
    }

    public static Banana getBanana() {
        return new Banana("东亚小香蕉", 10);
    }
}
public static void main(String[] args) {
    //获取一个苹果
    Apple apple = FruitFactory1.getApple();
    // 获取一个香蕉
    Banana banana = FruitFactory1.getBanana();
}

结合多态:可扩展性更强

public class FruitFactory2 {
    // 父类引用指向子类对象
    public static Fruit getFruit(String fruitName){
        if ("apple".equals(fruitName)) {
            return new Apple("红富士", 10);
        } else if ("banana".equals(fruitName)) {
            return new Banana("南亚小香蕉", 10);
        }
        return null;
    }
}
public static void main(String[] args) {
    // 获取一个苹果
    Apple apple = (Apple) FruitFactory2.getFruit("apple");
    // 获取一个香蕉
    Apple banana = (Apple) FruitFactory2.getFruit("banana");
}

使用反射机制

解决工厂的getFruit方法中if…else代码过多

public class FruitFactory3 {
    // 通过反射的方式创建对象
    public static Fruit getFruit(String fruitName) throws Exception {
        // newInstance():相当于创建一个对象
        return (Fruit) Class.forName(fruitName).newInstance();
    }
}
public static void main(String[] args) throws Exception {
    // 获取苹果
    Fruit fruit1 = FruitFactory3.getFruit("com.atmin.bean.Apple");
    System.out.println(fruit1);// Fruit{fruitName='null'}
    //获取香蕉
    Fruit fruit2 = FruitFactory3.getFruit("com.atmin.bean.Banana");
    System.out.println(fruit2);// Fruit{fruitName='null'}
}

反射常用API

反射操作构造器

// 反射操作构造方法
public static void main(String[] args) throws Exception {
    // 获取User类对应的Class对象
    Class<?> clazz = Class.forName("com.atmin.bean.User");
    // 获取无参构造方法对象
    Constructor<?> c1 = clazz.getConstructor();
    // 使用无参创建User类对象
    // newInstance():根据构造方法对象,创建对应的类对象
    Object obj1 = c1.newInstance();
    System.out.println(obj1);// User{id=null, username='null', password='null'}

    // 获取User类对应的有参构造方法对象,需要传入参数类型的class对象
    Constructor<?> c2 = clazz.getConstructor(Integer.class, String.class, String.class);
    // 使用有参创建User对象
    Object obj2 = c2.newInstance(1, "张三", "root");
    System.out.println(obj2);// User{id=1, username='张三', password='root'}
    // 以上操作的都是公共的构造方法

    // 使用private修饰有参构造,当前构造只能在本类中使用
    // getDeclaredConstructor:任何类型的构造器都能拿到
    Constructor<?> c3 = clazz.getDeclaredConstructor(String.class, String.class);
    // 暴力反射,让私有构造器对象可以被外部访问
    c3.setAccessible(true);// 需要加上这一行代码,如果不加会报非法访问异常,不能访问私有成员
    Object obj3 = c3.newInstance("张三", "20000101");
    System.out.println(obj3);
}

反射操作成员变量

// 反射操作成员变量
public static void main(String[] args) throws Exception {
    // 获取User类的Class对象
    Class<User> clazz = User.class;
    // 通过clazz拿到User对象
    User user = clazz.newInstance();
    // 操作public修饰的成员变量,返回id属性对应的字段对象
    Field idField = clazz.getField("id");

    // 设置该成员变量值
    // 参数1: obj: 需要设置的对象
    // 参数2: value: 需要设置的值
    // 给user对象的id属性设置值为250
    // 成员变量属于对象(User)
    idField.set(user, 250);// 设置
    System.out.println(user);// User{id=250, username='null', password='null'}
    // 获取该成员变量值,传一个对象
    Object idValue = idField.get(user);// 获取
    System.out.println(idValue);// 250

    // 操作非public修饰的成员变量
    // getDeclaredField可以拿到私有的
    Field usernameField = clazz.getDeclaredField("username");
    // 暴力反射
    usernameField.setAccessible(true);
    usernameField.set(user, "坤坤");// 设置
    System.out.println(user);// User{id=250, username='坤坤', password='null'}
    Object usernameValue = usernameField.get(user);// 获取
    System.out.println(usernameValue);// 坤坤
}

反射操作成员方法

// 反射操作成员方法
public static void main(String[] args) throws Exception {
    // 获取User类的Class对象
    Class<User> clazz = User.class;
    // 拿到User对象
    User user = clazz.newInstance();

    // 获取public修饰的成员方法
    // 参数1: 方法名
    // 参数2: 方法需要的参数(形参)类型的Class对象
    // 拿到setPassword对应的方法对象
    Method method01 = clazz.getMethod("setPassword", String.class);
    // 使用方法对象,需要传入指定对象和指定参数
    // 参数1: obj: 哪个对象在执行该方法,一般普通成员方法都是对象来执行
    // 参数2: args: 方法执行时所需的参数值
    method01.invoke(user,"123456");// 执行该方法
    System.out.println(user);// User{id=null, username='null', password='123456'}

    // 操作非public修饰的成员方法
    Method method02 = clazz.getDeclaredMethod("show");
    method02.setAccessible(true);
    // invoke这里需要传一个对象
    // result执行方法之后返回的结果(返回值)
    Object result = method02.invoke(user);
    System.out.println(result);// hello world
}

反射应用场景

反射越过泛型检查

java中的泛型的作用范围在编译期,也就是说在运行时,是没有泛型的

反射技术,可以在程序运行时,动态地调用List类中add方法,往集合中添加任意类型的元素

public static void main(String[] args) throws Exception {
    // 创建List集合对象,只能存储整数,泛型规定了这个集合里面的元素只能是Integer
    List<Integer> list = new ArrayList<>();
    list.add(1);
    list.add(2);
    list.add(3);
    System.out.println(list);// [1, 2, 3]

    // 泛型只在编译期有效,运行时就已经没有泛型了,那就在运行时往集合里面动态添加
    // 反射越过泛型检查
    // 反射可以在程序运行时,动态地调用List中的add方法去添加元素
    Class<? extends List> clazz = list.getClass();
    // 拿到add方法
    // 泛型本质就是Object
    Method add = clazz.getDeclaredMethod("add", Object.class);
    // 暴力反射
    add.setAccessible(true);
    Object result = add.invoke(list, "hello , generic type !");
    System.out.println(list);// [1, 2, 3, hello , generic type !]
    System.out.println(result);// true
}

反射通用方法

需求:给指定对象的指定字段设置指定值

一般,java实体类中的字段都是private修饰,那么如果要操作,就需要使用暴力反射,而暴力反射在开发中尽量不要使用

代码实现:

1、获取指定字段对应的set方法名称

2、获取set方法对象,获取指定字段类型

3、执行set方法

public static void main(String[] args) throws Exception {
    User user = new User();
    // 字段名需要完全一致
    setValue(user, "id", 123);
    System.out.println(user);// User{id=123, username='null', password='null'}
}

// 需求:给指定对象的指定属性设置指定值
// obj:指定对象,fieldName:指定属性,value:指定值
// Spring框架:成员变量值的注入底层就是这么做的
public static void setValue(Object obj, String fieldName, Object value) throws Exception {
    Class<?> clazz = obj.getClass();
    // 方法名规范(满足驼峰):如果只有一个单词,所有的字母全都小写,如果有多个单词,从第二个单词开始,首字母大写
    // 变量名规范(满足驼峰): 如果只有一个单词,所有的字母全都小写,如果有多个单词,从第二个单词开始,首字母大写
    // username -> setUsername
    // 根据属性名称动态获取对应的set方法名称
    // 一般情况下不推荐使用暴力反射操作set方法,根据属性名称获取对应的set方法名称
    // String methodName = "set" + "U" + "sername";
    String methodName = "set" + fieldName.substring(0,1).toUpperCase() + fieldName.substring(1);
    // 拿到字段,因为需要字段类型确定set方法参数类型
    Field field = clazz.getDeclaredField(fieldName);
    // 获取字段的数据类型
    Class<?> fieldType = field.getType();
    // 获取到set方法对象
    // 拿set方法对象不仅要传方法名,还得传方法调用的时候所需要的操作类型
    Method method = clazz.getMethod(methodName, fieldType);
    // 执行set方法
    method.invoke(obj, value);
}

反射结合配置文件

# bean01:对象的唯一标识
# com.atmin.bean.User:对象所对应的全类名
# 根据这段配置创建一个对象
bean01=com.atmin.bean.User
bean02=com.atmin.bean.Banana
public static void main(String[] args) throws Exception {
    // 需求:编写bean.properties,配置对象的唯一标识及对象的全类名,根据这段配置创建一个对象
    Properties properties = new Properties();
    // 将bean.properties中的数据存储到输入流inputStream中
    // 这种方式读配置文件只能放到src下
    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);// User{id=null, username='null', password='null'}

    String className2 = properties.getProperty("bean02");
    Object obj2 = Class.forName(className2).newInstance();
    System.out.println(obj2);// Fruit{fruitName='null'}
}

框架原理【概述】

public static void main(String[] args) throws Exception {
    // 获取苹果
    Fruit fruit1 = FruitFactory3.getFruit("com.atmin.bean.Apple");
    System.out.println(fruit1);
    //获取香蕉
    Fruit fruit2 = FruitFactory3.getFruit("com.atmin.bean.Banana");
    System.out.println(fruit2);

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

静态代理设计模式

反射及注解

概念:增强被代理类的功能

步骤

  • 自定义代理类实现和被代理类相同的接口
  • 在代理类中声明被代理类的对象
  • 在代理类的方法中使用被代理类调用方法
public interface UserDao {
    void addUser();
    void deleteUser();
    void updateUser();
    void selectUser();
}

被代理类【被增强类、目标对象】

/**
 * 被代理类(被增强类)
 * 主要功能:
 *      添加用户
 *      删除用户
 *      修改用户
 *      查询用户
 * 次要功能:权限校验、日志记录没必要放在这个类里,否则不满足单一原则
 */
public class UserDaoImpl implements UserDao {
    @Override
    public void addUser() {
        // System.out.println("权限检验");
        System.out.println("UserDaoImpl addUser");
        // System.out.println("日志记录");
    }

    @Override
    public void deleteUser() {
        System.out.println("UserDaoImpl deleteUser");
    }

    @Override
    public void updateUser() {
        System.out.println("UserDaoImpl updateUser");
    }

    @Override
    public void selectUser() {
        System.out.println("UserDaoImpl selectUser");
    }
}

代理类【增强类】

// 1、自定义一个代理类(增强类)实现和被代理类(被增强类)相同的接口
/**
 *  代理类(增强类)
 *      完成非必要功能:权限校验:日志记录
 */
public class UserDaoImplProxy implements UserDao {
    // 2、在代理类中声明被代理类的引用
    // 被代理类是一个私有的,创建在代理类里面,意味着可以控制被外界访问的权限
    // 如果想被外界访问,提供get、set方法
    // 如果不想被外界访问,删掉set、get方法,这就是可以控制它
    private UserDao userDao;

    /*
    public UserDao getUserDao() {
        return userDao;
    }

    public void setUserDao(UserDao userDao) {
        this.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、代理模式中的所有角色(代理对象、目标对象、目标对象的接口)等都是在编译期就确定好的
     * 2、静态代理的用途:控制真实对象的访问权限,通过代理对象控制对真实对象的使用权限
     * 3、避免创建大对象:通过使用一个代理小对象来代表一个真实的大对象,可以减少系统资源的消耗,对系统进行优化并提高运行速度
     * 4、增强真实对象的功能,这个比较简单,通过代理可以在调用真实对象的方法的前后增加额外功能
     */
public static void main(String[] args) {
    // 原始手段:
    // UserDao userDao = new UserDaoImpl();
    // userDao.addUser();
    // userDao.deleteUser();
    // userDao.updateUser();
    // userDao.selectUser();

    // 使用静态代理:
    // 实现步骤(三步、死记硬背)
    // 代理类也叫增强类,被代理类也叫被增强类
    // 1、自定义一个代理类(增强类)实现和被代理类(被增强类)相同的接口
    // 2、在代理类中声明被代理类的引用(对象)
    // 3、在代理类的方法中使用被代理类调用方法
    UserDaoImplProxy userDaoImplProxy = new UserDaoImplProxy();
    userDaoImplProxy.addUser();
    // 还存在的问题:另外三个方法不需要增强没必要写到代理类中去,这就是静态代理模式的缺点
}

特点

缺点:必须要重写被代理类接口的所有的方法(包括不需要增强的方法,增高了耦合性)

作用:增强被代理类的功能

特点:可以控制被代理类对象

装饰者设计模式

步骤

  • 定义装饰类(增强类)实现和被装饰类(被增强类)相同的接口
  • 在装饰类中声明被装饰类的引用
  • 在装饰类的方法中,使用被装饰调用原方法
public interface UserDao {
    void addUser();
    void deleteUser();
    void updateUser();
    void selectUser();
}

被装饰类【被增强类】

public class UserDaoImpl implements UserDao {
    @Override
    public void addUser() {
        System.out.println("UserDaoImpl addUser");
    }

    @Override
    public void deleteUser() {
        System.out.println("UserDaoImpl deleteUser");
    }

    @Override
    public void updateUser() {
        System.out.println("UserDaoImpl updateUser");
    }

    @Override
    public void selectUser() {
        System.out.println("UserDaoImpl selectUser");
    }
}

装饰类【增强类】

// 装饰类(增强类)
// 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、定义装饰类(增强类)实现和被装饰类(被增强类)相同的接口
 *  2、在装饰类中声明被装饰类的引用(不是对象)
 *  3、在装饰类的方法中,使用被装饰调用原方法
*/
public static void main(String[] args) {
    // 创建装饰类对象,需要传入一个被装饰类
    UserDaoWrapper userDaoWrapper = new UserDaoWrapper(new UserDaoImpl());
    userDaoWrapper.addUser();
    userDaoWrapper.updateUser();
}

特点

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

作用:在不侵入被装饰类源码的前提下,增强某个功能

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

动态代理设计模式【Proxy】

概念:基于接口的方法增强

方式一:定义类实现InvocationHandler接口【增强addUser方法】

// MyInvocationHandler是增强代理类,不是代理类,增强代码在invoke方法中【只是内部做增强】
class MyInvocationHandler implements InvocationHandler {
    // 声明一个被代理类的引用
    private UserDao userDao;

    // 通过有参把被代理类对象传进来
    public MyInvocationHandler(UserDao userDao) {
        this.userDao = userDao;
    }

    // 通过invoke方法处理增强
    @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;
    }
}

/**
* 测试:
* 动态代理模式:主要解决需要重写接口中所有方法的缺点,动态代理模式也是用来增强方法的
* Proxy类中的newProxyInstance()方法
* 动态代理模式的步骤也是固定的
*/
public static void main(String[] args) {
    // ClassLoader loader:被代理类对应的类加载器
    // Class<?>[] interfaces:被代理类所实现的所有接口
    // InvocationHandler h:一个用以增强代理类的处理器
    UserDao userDao = new UserDaoImpl();// 被代理类对象
    // 返回的Object强转之后必须是一个【接口】,如果不是一个接口会报错
    UserDao userDaoProxy = (UserDao) Proxy.newProxyInstance(
        userDao.getClass().getClassLoader(),
        userDao.getClass().getInterfaces(),
        new MyInvocationHandler(userDao));
    userDaoProxy.addUser();
    userDaoProxy.deleteUser();
    userDaoProxy.updateUser();
    userDaoProxy.selectUser();
    // 根据上述代码结果发现,只有addUser方法进行了增强
}

方式二:使用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();
}

动态代理的优点:不需要重写接口所有的方法

自定义数据库连接池

概念

反射及注解
  • 在不使用连接池的情况下,如果有100个用户要操作数据库,对应要创建100个连接对象,操作数据库完毕,还需要销毁100个连接对象,创建连接和销毁连接是非常浪费系统性能

  • 如果使用连接池,连接的创建,只在连接池初始化时才进行,当用户要操作数据库时,只需要从连接池中取出已经创建好的连接对象即可,操作数据库完毕,不需要销毁连接,只需将连接对象归还到连接池

总结:连接池提高了操作数据库的性能

自定义连接池基础版

代码实现:

1、初始化时,创建一些连接对象,并存储到LinkedList集合中

2、从连接池中取出连接

3、把连接归还连接池

配置文件(放在src目录下)
driverClass=com.mysql.jdbc.Driver
jdbcUrl=jdbc:mysql://localhost:3306/day57
user=root
password=123456
JDBC工具类
/**
 * JDBC工具类:
 *    加载驱动
 *    获取连接
 *    释放资源
 */
public class JDBCUtils {
    private static final String DRIVERCLASS;
    private static final String JDBCURL;
    private static final String USER;
    private static final String PASSWORD;

    static {
        Properties properties = new Properties();
        InputStream inputStream = JDBCUtils.class.getClassLoader().getResourceAsStream("jdbc.properties");
        try {
            properties.load(inputStream);
        } catch (IOException e) {
            e.printStackTrace();
        }
        DRIVERCLASS = properties.getProperty("driverClass");
        JDBCURL = properties.getProperty("jdbcUrl");
        USER = properties.getProperty("user");
        PASSWORD = properties.getProperty("password");
    }

    // 加载驱动
    public static void loadDriver() throws Exception {
        Class.forName(DRIVERCLASS);
    }

    // 获取连接
    public static Connection getConnection() throws Exception {
        loadDriver();
        return DriverManager.getConnection(JDBCURL, USER, PASSWORD);
    }

    // 释放资源:专门处理查询
    public static void release(Connection connection, Statement statement, ResultSet resultSet) {
        if (null != connection) {
            try {
                // 执行的增强的close方法:功能是归还连接
                connection.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
            connection = null;
        }

        if (null != statement) {
            try {
                statement.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
            statement = null;
        }

        if (null != resultSet) {
            try {
                resultSet.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
            resultSet = null;
        }
    }

    // 释放资源: 专门处理增删改
    public static void release(Connection connection , Statement statement) {
        release(connection, statement, null);
    }
}
数据库连接池
public class MyDataSource {
    int initPoolSize = 3;// 连接池中的初始连接数

    LinkedList<Connection> connections = new LinkedList<>();

    // 当连接池初始化时,创建一些连接,并存储起来
    public MyDataSource() {
        // 什么时候可以监听到对象的初始化 -> 构造器里面
        // 连接池的初始化
        createConnections();
    }

    public void createConnections() {
        for (int i = 0 ; i <= initPoolSize ; i++) {
            try {
                Connection connection = JDBCUtils.getConnection();
                // 把创建的连接对象存储起来
                /**
                 * 存储连接:数组、集合(单列,双列)
                 * 单列集合:ArrayList、LinkedList、HashSet、TreeSet
                 * ArrayList:数组,查询修改快,增删慢
                 * LinkedList:链表,查询修改慢,增删快
                 * HashSet:去重
                 * TreeSet:排序
                 * 到底是使用ArrayList还是LinkedList,取决于连接池的特性,移除连接,添加连接,应该使用LinkedList
                 */
                connections.add(connection);// 创建一个就添加一个进去
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    // 取出连接
    public Connection getConnection() {
        // 连接池中的连接够用
        if(connections.size() == 0 || connections.isEmpty()) {
            // 连接池中的连接不够用,新建一些连接存储连接池中,不能干等
            createConnections();
        }
        // 把脚标为0的连接对象移除并返回
        Connection connection = connections.remove(0);
        return connection;
    }

    // 归还连接
    public void addBack(Connection connection) {
        connections.add(connection);
    }
}
测试
public static void main(String[] args) {
    MyDataSource myDataSource = new MyDataSource();
    Connection connection = myDataSource.getConnection();
    PreparedStatement statement = null;
    ResultSet resultSet = null;
    try {
        statement = connection.prepareStatement("select * from tb_user where id = ?");
        statement.setInt(1, 2);
        resultSet = statement.executeQuery();
        User user = null;
        if(resultSet.next()) {
            int id = resultSet.getInt("id");
            String username = resultSet.getString("username");
            String password = resultSet.getString("password");
            user = new User(id, username, password);
        }
        System.out.println(user);
    } catch (Exception e) {
        e.printStackTrace();
    } finally {
        // 不能关闭连接,应该要去归还
        JDBCUtils.release(null, statement, resultSet);
        // 归还连接
        myDataSource.addBack(connection);
        // 存在问题:
        // addBack方法很鸡肋,因为是个人写的
        // 别人记得住Connection中close方法
        // 能不能通过close方法做归还连接
    }
}

存在的问题:自己定义了归还连接的方法addBack,维护困难

注意:IDEA2020.3版本在使用lombok插件时,除了需要导入依赖和开启注解开发以外,还需要做以下配置:-Djps.track.ap.dependencies=false

或者更换为lombok-1.18.16.jar的版本

反射及注解

自定义连接池优化版【装饰者设计模式】

解决问题思路:

  • 一般开发都能记住Connection类中close方法
  • 将该close方法功能改变为归还连接

代码实现:

1、连接池初始化时,创建一些连接,并存储到LinkedList中

2、加入装饰者设计模式,将普通连接对象Connection中的close方法给改变成归还连接

解决破坏单一职责原则,使用了中间类

3、获取连接,使用装饰者设计模式,返回增强的连接对象

4、归还连接的addBack就可以删除了

存在的问题:

需要重写接口中的所有方法

中间类ConnectionWrapper,专注于非close方法的调用
/**
 * 访问修饰符回顾:
 *  public:任何地方都能访问
 *  protected:只在子父类关系中
 *  默认:同包
 *  private:本类
 */
// 中间类:处理除了close以外的方法,使用普通Connection调用一次
// 装饰者设计模式的弊端就是需要重写所有方法,所以每个方法都调用了一次
public class ConnectionWrapper implements Connection {
    // protected:只能在子父类进行访问
    protected Connection connection;// 得有一个普通连接对象

    public ConnectionWrapper() {
    }

    public ConnectionWrapper(Connection connection) {
        this.connection = connection;
    }

    @Override
    public Statement createStatement() throws SQLException {
        return connection.createStatement();
    }

    @Override
    public PreparedStatement prepareStatement(String sql) throws SQLException {
        // 用普通的连接对象去调用prepareStatement方法
        return connection.prepareStatement(sql);
    }

    @Override
    public CallableStatement prepareCall(String sql) throws SQLException {
        return connection.prepareCall(sql);
    }

    @Override
    public String nativeSQL(String sql) throws SQLException {
        return connection.nativeSQL(sql);
    }

    @Override
    public void setAutoCommit(boolean autoCommit) throws SQLException {
        connection.setAutoCommit(autoCommit);
    }

    @Override
    public boolean getAutoCommit() throws SQLException {
        return connection.getAutoCommit();
    }

    @Override
    public void commit() throws SQLException {
        connection.commit();
    }

    @Override
    public void rollback() throws SQLException {
        connection.rollback();
    }

    @Override
    public void close() throws SQLException {
        connection.close();
    }

    @Override
    public boolean isClosed() throws SQLException {
        return connection.isClosed();
    }

    @Override
    public DatabaseMetaData getMetaData() throws SQLException {
        return connection.getMetaData();
    }

    @Override
    public void setReadOnly(boolean readOnly) throws SQLException {
        connection.setReadOnly(readOnly);
    }

    @Override
    public boolean isReadOnly() throws SQLException {
        return connection.isReadOnly();
    }

    @Override
    public void setCatalog(String catalog) throws SQLException {
        connection.setCatalog(catalog);
    }

    @Override
    public String getCatalog() throws SQLException {
        return connection.getCatalog();
    }

    @Override
    public void setTransactionIsolation(int level) throws SQLException {
        connection.setTransactionIsolation(level);
    }

    @Override
    public int getTransactionIsolation() throws SQLException {
        return connection.getTransactionIsolation();
    }

    @Override
    public SQLWarning getWarnings() throws SQLException {
        return connection.getWarnings();
    }

    @Override
    public void clearWarnings() throws SQLException {
        connection.clearWarnings();
    }

    @Override
    public Statement createStatement(int resultSetType, int resultSetConcurrency) throws SQLException {
        return connection.createStatement(resultSetType,resultSetConcurrency);
    }

    @Override
    public PreparedStatement prepareStatement(String sql, int resultSetType, int resultSetConcurrency) throws SQLException {
        return connection.prepareStatement(sql,resultSetType,resultSetConcurrency);
    }

    @Override
    public CallableStatement prepareCall(String sql, int resultSetType, int resultSetConcurrency) throws SQLException {
        return connection.prepareCall(sql,resultSetType,resultSetConcurrency);
    }

    @Override
    public Map<String, Class<?>> getTypeMap() throws SQLException {
        return connection.getTypeMap();
    }

    @Override
    public void setTypeMap(Map<String, Class<?>> map) throws SQLException {
        connection.setTypeMap(map);
    }

    @Override
    public void setHoldability(int holdability) throws SQLException {
        connection.setHoldability(holdability);
    }

    @Override
    public int getHoldability() throws SQLException {
        return connection.getHoldability();
    }

    @Override
    public Savepoint setSavepoint() throws SQLException {
        return connection.setSavepoint();
    }

    @Override
    public Savepoint setSavepoint(String name) throws SQLException {
        return connection.setSavepoint(name);
    }

    @Override
    public void rollback(Savepoint savepoint) throws SQLException {
        connection.rollback(savepoint);
    }

    @Override
    public void releaseSavepoint(Savepoint savepoint) throws SQLException {
        connection.releaseSavepoint(savepoint);
    }

    @Override
    public Statement createStatement(int resultSetType, int resultSetConcurrency, int resultSetHoldability) throws SQLException {
        return connection.createStatement(resultSetType,resultSetConcurrency,resultSetHoldability);
    }

    @Override
    public PreparedStatement prepareStatement(String sql, int resultSetType, int resultSetConcurrency, int resultSetHoldability) throws SQLException {
        return connection.prepareStatement(sql,resultSetType,resultSetConcurrency,resultSetHoldability);
    }

    @Override
    public CallableStatement prepareCall(String sql, int resultSetType, int resultSetConcurrency, int resultSetHoldability) throws SQLException {
        return connection.prepareCall(sql,resultSetType,resultSetConcurrency,resultSetHoldability);
    }

    @Override
    public PreparedStatement prepareStatement(String sql, int autoGeneratedKeys) throws SQLException {
        return connection.prepareStatement(sql,autoGeneratedKeys);
    }

    @Override
    public PreparedStatement prepareStatement(String sql, int[] columnIndexes) throws SQLException {
        return connection.prepareStatement(sql,columnIndexes);
    }

    @Override
    public PreparedStatement prepareStatement(String sql, String[] columnNames) throws SQLException {
        return connection.prepareStatement(sql,columnNames);
    }

    @Override
    public Clob createClob() throws SQLException {
        return connection.createClob();
    }

    @Override
    public Blob createBlob() throws SQLException {
        return connection.createBlob();
    }

    @Override
    public NClob createNClob() throws SQLException {
        return connection.createNClob();
    }

    @Override
    public SQLXML createSQLXML() throws SQLException {
        return connection.createSQLXML();
    }

    @Override
    public boolean isValid(int timeout) throws SQLException {
        return connection.isValid(timeout);
    }

    @Override
    public void setClientInfo(String name, String value) throws SQLClientInfoException {
        connection.setClientInfo(name,value);
    }

    @Override
    public void setClientInfo(Properties properties) throws SQLClientInfoException {
        connection.setClientInfo(properties);
    }

    @Override
    public String getClientInfo(String name) throws SQLException {
        return connection.getClientInfo(name);
    }

    @Override
    public Properties getClientInfo() throws SQLException {
        return connection.getClientInfo();
    }

    @Override
    public Array createArrayOf(String typeName, Object[] elements) throws SQLException {
        return connection.createArrayOf(typeName,elements);
    }

    @Override
    public Struct createStruct(String typeName, Object[] attributes) throws SQLException {
        return connection.createStruct(typeName,attributes);
    }

    @Override
    public void setSchema(String schema) throws SQLException {
        connection.setSchema(schema);
    }

    @Override
    public String getSchema() throws SQLException {
        return connection.getSchema();
    }

    @Override
    public void abort(Executor executor) throws SQLException {
        connection.abort(executor);
    }

    @Override
    public void setNetworkTimeout(Executor executor, int milliseconds) throws SQLException {
        connection.setNetworkTimeout(executor,milliseconds);
    }

    @Override
    public int getNetworkTimeout() throws SQLException {
        return connection.getNetworkTimeout();
    }

    @Override
    public <T> T unwrap(Class<T> iface) throws SQLException {
        return connection.unwrap(iface);
    }

    @Override
    public boolean isWrapperFor(Class<?> iface) throws SQLException {
        return connection.isWrapperFor(iface);
    }
}
连接装饰类MyConnectionWrapper
/**
 * 连接装饰类
 * 主要功能:增强close方法
 * close方法只有一个,而其他的非必要功能有很多很多个,破坏了单一职责原则
 * 需要将close方法和其他方法(普通连接对象调用)分开
 */
public class MyConnectionWrapper extends ConnectionWrapper {
    // 声明被代理类的引用
    // 继承之后就没必要写了
    // private Connection connection;

    private List<Connection> connectionList;

    // 根据父类再生成一个新的构造器,解决了破坏单一原则的问题
    public MyConnectionWrapper(Connection connection, List<Connection> connectionList) {
        super(connection);
        this.connectionList = connectionList;
    }

    @Override
    public void close() throws SQLException {
        // 增强close:归还连接
        // 把Connection规划给连接池(集合)
        connectionList.add(connection);
    }
}
MyDataSource
// 自定义连接池优化版,需要增强Connection里面的close方法
public class MyDataSource {
    int initPoolSize = 3;

    LinkedList<Connection> connections = new LinkedList<>();

    public MyDataSource() {
        createConnections();
    }

    public void createConnections() {
        for (int i = 3; i <= initPoolSize; i++) {
            try {
                Connection connection = JDBCUtils.getConnection();
                connections.add(connection);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    // 取出连接
    public Connection getConnection() {
        if(connections.size() == 0 || connections.isEmpty()) {
            createConnections();
        }
        // 连接池中的连接够用
        // 把脚标为0的连接对象移除并返回
        Connection connection = connections.remove(0);// 普通连接对象
        // 重点:根据普通连接对象获取一个连接代理对象(增强的连接对象)
        MyConnectionWrapper connectionWrapper = new MyConnectionWrapper(connection, connections);
        // 返回的是一个增强的连接对象
        return connectionWrapper;
    }
}
测试
public static void main(String[] args) {
    MyDataSource myDataSource = new MyDataSource();
    // 到底是使用连接对象,还是连接代理对象? -> 连接代理对象
    // 多态下,访问close成员方法:编译看左边,运行看右边
    // Connection connection = new MyConnectionWrapper()
    // 编译看左边:看Connection有没有close方法,如果有就编译通过,如果没有就会报错
    // 运行看右边:执行的时候是MyConnectionWrapper里面的close方法
    Connection connection = myDataSource.getConnection();
    PreparedStatement statement = null;
    ResultSet resultSet = null;
    try {
        // prepareStatement方法执行的是普通连接对象中的还是增强连接对象中的? -> 增强
        statement = connection.prepareStatement("select * from tb_user where id = ?");
        statement.setInt(1, 3);
        resultSet = statement.executeQuery();
        User user = null;
        if (resultSet.next()) {
            user = new User(resultSet.getInt("id"), resultSet.getString("username"), resultSet.getString("password"));
        }
        System.out.println(user);
    } catch (Exception e) {
        e.printStackTrace();
    } finally {
        // 归还连接
        JDBCUtils.release(connection, statement, resultSet);
    }
}

自定义连接池终极版【动态代理设计模式】

代码实现:

1、连接池初始化时,创建一些连接,并存储到LinkedList中

2、加入动态代理,将普通连接对象Connection中的close方法给改变成归还连接

3、获取连接,使用动态代理,返回增强的连接对象

public class MyDataSource {
    int initPoolSize = 3;

    LinkedList<Connection> connections = new LinkedList<>();

    public MyDataSource() {
        createConnections();
    }

    public void createConnections() {
        for (int i = 0 ; i <= initPoolSize ; i++) {
            try {
                Connection connection = JDBCUtils.getConnection();
                connections.add(connection);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    public Connection getConnection() {
        if(connections.size() == 0 || connections.isEmpty()) {
            createConnections();
        }
        Connection connection = connections.remove(0);// 普通连接对象
        // 原来通过装饰者设计模式返回的是一个增强的连接对象,现在使用的是动态代理
        Connection connectionProxy = (Connection) Proxy.newProxyInstance(
                connection.getClass().getClassLoader(),
                connection.getClass().getInterfaces(),
                new InvocationHandler() {
                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        // 处理增强close方法
                        String methodName = method.getName();
                        Object returnValue = null;
                        if ("close".equals(methodName)) {
                            // 如果是close方法,做增强就是归还连接
                            returnValue = connections.add(connection);
                        } else {
                            returnValue = method.invoke(connection, args);
                        }
                        return returnValue;
                    }
                });
        return connectionProxy;
    }
}

测试和上面一致

  • 总结:使用动态代理方式解决装饰者设计模式中的弊端,即装饰者设计模式必须要重写接口中的所有方法

注解

注解介绍

概念:就是一个修饰符

特点:是JDK5.0之后引入的特性,以“@注解名”形式存在

作用:

跟踪代码依赖性

执行编译时格式检查

代替已有的配置文件

JDK内置注解

@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
@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:全部类型的警告

代码演示

// java内置注解
public class Test01 {
    public static void main(String[] args) {
        // @Override : 判定子类中的方法是否是一个重写方法
        // @Deprecated : 标记一个类、方法、变量,是过时
        // 过时的可以用但是不推荐用
        Son son = new Son();
        son.method01();
        System.out.println(son.num);
    }
}

class Father {
    public void show(){
        System.out.println("Father show");
    }
}

@Deprecated
class Son extends Father{
    // Method does not override method from its superclass
    @Deprecated
    public int num = 1;
    @Override
    public void show(){
        System.out.println("Son show");
    }
    @Deprecated
    public void method01(){
        System.out.println("Son method01");
    }
}
// all压制所有警告,只需要记这一个
@SuppressWarnings( "all")
public class Test02 {
    public static void main(String[] args) {
        // @SupressWarning:压制警告
        // @SuppressWarnings("unused")
        int num = 1;
        // @SuppressWarnings(value = {"rawtypes","unused"})
        // 这里警告是集合最好给一个泛型
        List list = new ArrayList();
    }
}

注解分类

标记注解

  • 注解中没有属性
  • 比如:@Override、@Deprecated

单值注解

  • 注解中只有一个属性
  • 比如:@SuppressWarings
  • 属性中只有一个value属性时,value可以不写

完整注解

  • 注解中有多个属性

自定义注解

格式
public @interface 注解名 {
    数据类型 属性名1() default 默认值1;
    数据类型 属性名2() ;
}
基本使用
// 自定义注解MyAnnotation01
public @interface MyAnnotation01 {
    String username() default "root";
    String password() default "root123";
    int age() default 16;
    String value();
}
// 自定义注解测试
@MyAnnotation01(value = "hello world" , username = "张三")
public class Test {
    public static void main(String[] args) {
    }
}
注意事项

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

元注解

元注解就理解为注解的注解

概述

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

常用的元注解

@Target:规定自定义注解的作用区域

【枚举类】ElementType[] value():

  • TYPE:类、接口、注解、枚举
  • FIELD:成员变量
  • METHOD:成员方法
  • PARAMETER:形参
  • CONSTRUCTOR:构造器
  • LOCAL_VARIABLE:局部变量
  • ANNOTATION_TYP:注解类型
  • PACKAGE:包

@Retention:规定自定义注解的存活策略

【枚举类】RetentionPolicy value():

  • SOURCE:仅存活在源码
  • CLASS:存活在编译期
  • RUNTIME:存活在运行时
枚举
反射及注解
元注解的使用

@Target

// 自定义注解常用作用范围:类、成员方法、成员变量、形参,默认就可以作用在这些位置上,不用设置
// ElementType.TYPE:类
// ElementType.FIELD:成员变量
// ElementType.METHOD:方法
// ElementType.PARAMETER:形参
// ElementType.LOCAL_VARIABLE:局部变量
@Target(
        value =
            {
                ElementType.TYPE,
                ElementType.FIELD,
                ElementType.METHOD,
                ElementType.PARAMETER,
                ElementType.LOCAL_VARIABLE
            }
        )
public @interface MyAnnotation01 {
    String value() default "hello annotation";
}
// 元注解的使用
@MyAnnotation01
public class Demo01 {
    @MyAnnotation01
    private int num = 1;

    @MyAnnotation01
    public static void main(@MyAnnotation01 String[] args) {
        @MyAnnotation01
        int num = 1;
    }
}

@Retention

@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation01 {
    String value() default "hello annotation";
}

一般情况下,注解的存活策略是RUNTIME,什么原因?

  • 注解要起作用,必须结合反射使用,而反射是在程序运行时(RUNTIME)执行

自定义注解@MyTest

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

开发步骤:

  • 自定义注解@MyTest
  • 在测试类Test01中的方法上使用@MyTest
  • 让@MyTest注解生效
    • 获取到Test01类对应的Class对象
    • 获取Test01类中的所有方法
    • 判断方法上是否有@MyTest注解
      • 如果有,就将该方法执行
      • 如果没有,就不处理

@MyTest注解:@MyTest的存活策略必须是RUNTIME

// 标记注解,不需要设置属性
// 默认存活策略在源码阶段,需要改为运行时
@Retention(RetentionPolicy.RUNTIME)
public @interface MyTest {
    String value() default "";
}
public class Test01 {
    // 单元测试:junit test
    @Test
    public void test01() {
        System.out.println("test01");
        // 虽然,没有写main方法,但是底层还是由main方法进行驱动
        // 通过(反射 + @Test注解)起作用
    }

    // 需求:加上这个注解的方法会执行
    @MyTest
    public void test02() {
        System.out.println("test02");
    }

    @MyTest
    public void test03() {
        System.out.println("test03");
    }

    public void test04() {
        System.out.println("test04");
    }
}

测试

// 让@MyTest生效起作用
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:遍历之后数组里面的元素
    Arrays.stream(methods).forEach(method -> {
        // method就是单个方法对象
        // 3、判断方法上是否有@MyTest注解,传一个注解类
        boolean present = method.isAnnotationPresent(MyTest.class);
        if (present) {
            // 方法上有@MyTest注解,就执行方法
            try {
                // 只需要传对象就可以了
                // 单元测试的方法除了修饰符是public以外,方法不能有任何形参
                method.invoke(clazz.newInstance());
            } catch (Exception e) {
                e.printStackTrace();
            }
        } else {
            // 方法上没有@MyTest注解,不做任何处理
        }
    });
}

做不到和官方的单元测试一样右击注解就可以执行,但是原理是一样的

自定义注解@JDBCInfo

需求:使用注解@JDBCInfo替代jdbc.properties配置文件

开发步骤:

1、自定义注解@JDBCInfo

2、在JDBCUtils工具类上使用@JDBCInfo

3、在JDBCUtils工具类中静态代码块中,获取@JDBCInfo注解上的属性,并给JDBCUtils工具类中成员变量赋值

@Retention(RetentionPolicy.RUNTIME)
public @interface JDBCInfo {
    String driverClass() default "com.mysql.jdbc.Driver";
    String jdbcUrl() default "jdbc:mysql://localhost:3306/nz2002";
    String user() default "root";
    String password() default "123456";
}

在JDBCUtils工具类上使用@JDBCInfo,反射读取注解@JDBCInfo中的内容

// JDBC工具类,使用注解替换properties配置文件
@JDBCInfo
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 {
            // 类上没有@JDBCInfo注解,就从properties文件中取(过程略)
            // 没有注解相当于没给那些常量初始化,常量必须要有初始值
            DRIVERCLASS = "";
            JDBCURL = "";
            USER = "";
            PASSWORD = "";
        }
    }

    public static void loadDriver() throws Exception {
        Class.forName(DRIVERCLASS);
    }

    public static Connection getConnection() throws Exception {
        loadDriver();
        return DriverManager.getConnection(JDBCURL, USER, PASSWORD);
    }

    public static void release(Connection connection, Statement statement , ResultSet resultSet) {
        // 这里做判断是空指针的问题
        if (null != connection) {
            try {
                // 关闭连接,不是回收
                connection.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
            // connection = new Connection(); connection变量会及时回收,Connection对象没有引用也会被及时回收
            // 为了Connection对象及时被jvm回收,这里置为null
            // null在java中相当于是一个没有意义的量
            connection = null;
        }

        if (null != statement) {
            try {
                statement.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
            statement = null;
        }

        if (null != resultSet) {
            try {
                resultSet.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
            resultSet = null;
        }
    }

    public static void release(Connection connection, Statement statement) {
        release(connection, statement, null);
    }
}

测试

public static void main(String[] args) {
    Connection connection = null;
    PreparedStatement statement = null;
    ResultSet resultSet = null;
    try {
        connection = JDBCUtils.getConnection();
        statement = connection.prepareStatement("select * from tb_user where id = ?");
        statement.setInt(1, 5);
        resultSet = statement.executeQuery();
        if (resultSet.next()) {
            System.out.println(resultSet.getInt("id"));
        }
    } catch (Exception e) {
        e.printStackTrace();
    } finally {
        JDBCUtils.release(connection, statement, resultSet);
    }
}

反射、注解、设计模式综合案例

基于装饰者设计模式

开发步骤

1、自定义注解@SystemLog

  • className
  • methodName

2、定义一个UserDao接口,且在该接口使用注解@SystemLog

3、编写装饰者设计模式

  • 获取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 {
    void addUser() throws Exception;
    @SystemLog(className = "com.atmin.dao.UserDao", methodName = "deleteUser")
    void deleteUser() throws Exception;
    void updateUser() throws Exception;
}

实现类

// 主要功能:添加用户、删除用户、修改用户
// 辅助功能:日志记录
public class UserDaoImpl implements UserDao {
    @Override
    public void addUser() throws Exception {
        System.out.println("UserDaoImpl addUser");
    }
    @Override
    public void deleteUser() throws Exception {
        System.out.println("UserDaoImpl deleteUser");
    }
    @Override
    public void updateUser() throws Exception {
        System.out.println("UserDaoImpl updateUser");
    }
}

编写装饰者设计模式

// 装饰类
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");
    }

    // 日志记录
    private void printLog(String runMethodName) throws Exception {
        // 判断接口上对应的方法是否有@SystemLog注解
        // 获取UserDao接口实现子类的Class对象
        Class<? extends UserDao> sonClazz = userDao.getClass();
        // 通过实现子类的Class对象获取UserDao接口的Class对象
        Class<?>[] interfaces = sonClazz.getInterfaces();
        // 拿到UserDao接口的Class对象
        Class<?> fatherClazz = interfaces[0];
        // 通过方法名获取接口中对应的方法对象
        Method method = fatherClazz.getMethod(runMethodName);
        if (null != method) {// 非空判断,代码更加健壮
            // 判断方法上是否有@SystemLog注解
            boolean present = method.isAnnotationPresent(SystemLog.class);
            if (present) {
                /**
                 * 方法上有@SystemLog注解,打印日志
                 * SimpleDateFormat:
                 * parse():解析:把时间字符串 转换为 时间对象
                 * format():格式化:将时间对象 转换为 时间字符串
                 */
                // 创建日期格式化对象,参数就是规定了日期的格式
                SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy年MM月dd日 hh:mm:ss");
                // 格式化对象格式化当前时间对象
                String currentTimeStr = simpleDateFormat.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注解,不做任何处理
            }
        }
    }
}

测试

public static void main(String[] args) throws Exception {
    // 需求:在UserDao接口中的方法上,如果有@SystemLog注解,那么就进行日志记录
    // 如果没有@SystemLog注解,那么就不进行日志记录
    UserDao userDao = new UserDaoImpl();
    UserDaoWrapper userDaoWrapper = new UserDaoWrapper(userDao);
    userDaoWrapper.deleteUser();
    userDaoWrapper.updateUser();
    // 存在的问题
    // 装饰类中的每个方法都需要调用printLog方法,由装饰者设计模式的缺点决定的
}

基于动态代理设计模式

开发步骤

1、自定义注解@SystemLog

  • className
  • methodName

2、定义一个UserDao接口,且在该接口使用注解@SystemLog

3、动态代理

  • 获取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) throws Exception {
    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对象 -> 现在:userDao.getClass().getInterfaces()
                // 再获取到接口中的方法对象 -> 现在:Method method
                // 参数method:就是接口中的方法对象,这个method就是根据userDao.getClass().getInterfaces()而来
                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();
    userDaoProxy.deleteUser();
    userDao.updateUser();
}
上一篇:Spring 源码分析(四)容器的基础 XmlBeanFactory(系列文章基于Spring 5.0)


下一篇:Centos6.4_X64编译安装php-5.4.17、nginx-1.4.2、mysql-5.6.13