类加载概念和类加载时机
类加载概念
在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();
}