第一节 课程概述
- 反射机制的讲解
- 单例设计模式有7种,我们只讲5种
- 单例模式可以通过反射攻击,我们如何防止反射攻击
- 23个设计模式,分为三种:创建型模式,结构型模式,行为型模式
- 工厂模式,抽象工厂模式,单例模式,原型模式,适配器模式,装饰器模式,代理模式,外观模式,策略模式,模板方法模式,观察者模式
第二节与第三节 什么是反射机制
- 什么是反射?
反射机制其实是正在运行的程序,动态获取类的信息。
我们先有java源代码,编译之后成为class文件(字节码文件),反射能够拿到字节码文件拿到类的属性,方法,实例化对象。
- 反射机制可以获取类的属性,方法,实例化对象。
- 反射可以不使用new,直接创建对象。
- Class.forName(“类的完整路径”)的作用是反射拿到字节码文件。
- newInstance:表示使用反射机制创建对象,默认返回的是Object类型的,可以进行强转。
- 通过反射创建对象的时候,也会走构造函数。
- 反射的应用场景:
(1)JDBC连接,连接数据库驱动的时候
(2)Spring框架,Spring的IOC就是使用反射机制+Dom4j
(3)Hibernate和Mybatis框架
(4)还有很多。。。。自己查 - 注意:
(1)若new对象的时候,构造函数中有异常,那么该对象无法成功创建。 - 获取反射类中的方法:
Class<?> forName = Class.forName("XXXXX.XXXX.XXX");
Method[] methods = forName.getMethods();
这时候获取到的方法,包括继承的方法
- 获取类的属性
Class<?> forName = Class.forName("XXXXX.XXXX.XXX");
// 获取所有属性,包括私有
Field[] fields = forName.getDeclaredFields();
- 反射创建对象的时候,若构造函数私有了且不在同一个类下用反射创建对象,则无法创建,报错。若执意想要访问,调用其setAccessable(true),这样子就可以操作私有的了。
这时候取到的属性包括私有的属性,如果只是用getFields获取到的属性就是公有的。有权限不同的限制。 - 通过反射调用公有(public)的有参构造
Class<?> forName = Class.forName("XXXXX.XXXX.XXX");
Construct<?> constructor = forName.getConstructor(参数的类型的字节码文件如: String.class);
constructor.newInstance("构造函数的实参"); // 此时已经创建对象成功,是一个有参构造
- 通过反射调用私有的有参数构造
Class<?> forName = Class.forName("XXXXX.XXXX.XXX");
Construct<?> constructor = forName.getDeclaredConstructor(参数的类型的字节码文件如: String.class);
constructor.setAccessable(true);
constructor.newInstance("构造函数的实参"); // 此时已经创建对象成功,是一个有参构造
方法名称 | 作用 |
getFields() | 获取该类的公有的成员方法 |
getDeclaredFields() | 获取该类的所有的字段(public,default,protected,private) |
getMethods() | 获取所有的方法,包括其父类的方法,所有的类都有一个Object父类 |
getDeclaredMethods() | 获取当前类的方法,不包括父类的方法 |
setAccessible() | 是否允许访问私有成员,私有方法, 如果为true,表示允许,如果为false(默认)表示不允许 |
getReturnType() | 获取该类的返回值 |
getParameterTypes() | 获取传入的参数 |
- 如何防止被反射攻击?
(1)将方法设置为私有,但是通过accessable也可以拿到,因此我们将其设置成单例的,这样反射就获取不到。
- 简单举例:
package com.xiyou.fanshe;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
/**
* 反射的测试类
*/
public class TestRefect {
public static void main(String[] args) {
try {
Class<?> forName = Class.forName("com.xiyou.fanshe.User");
// 获取所有的属性
Field[] declaredFields = forName.getDeclaredFields();
// 只能获取public的属性
// Field[] declaredFields = forName.getFields();
for (Field field : declaredFields) {
System.out.println(field.getName());
}
System.out.println("##########下面测试获取方法getMethods##############");
// 获取所有的方法,包括父类的方法,任何类都继承了Object
Method[] methods = forName.getMethods();
for (Method method : methods) {
System.out.println(method.getName());
}
System.out.println("##########下面测试获取方法getDeclaredMethods##############");
// 只获取了当前类的方法,不包括父类的方法
Method[] declaredMethods = forName.getDeclaredMethods();
for (Method method : declaredMethods) {
System.out.println(method.getName());
}
System.out.println("##########下面测试初始化对象##############");
User user = (User)forName.newInstance();
// 获取私有的成员age
Field privateAge = forName.getDeclaredField("privateAge");
// 设置其私有访问权限是true,否则无法操作,报错
privateAge.setAccessible(true);
// 给私有成员赋值
privateAge.set(user, 20);
System.out.println("通过反射取到的user的age的值是: " + user.getPrivateAge());
} catch (Exception e) {
e.printStackTrace();
}
}
}
运行结果:
publicName
privateAge
defaultName
protectedName
##########下面测试获取方法getMethods##############
getPrivateAge
getDefaultName
setDefaultName
getPublicName
setPublicName
setPrivateAge
setProtectedName
getProtectedName
wait
wait
wait
equals
toString
hashCode
getClass
notify
notifyAll
##########下面测试获取方法getDeclaredMethods##############
getPrivateAge
getDefaultName
setDefaultName
getPublicName
setPublicName
setPrivateAge
setProtectedName
getProtectedName
##########下面测试初始化对象##############
通过反射取到的user的age的值是: 20
第四节 设计模式概述
- 设计模式总共有23种设计模式,我们这里只讲解重要的。
- 设计模式方便维护,扩展性提高,减少冗余代码,降低耦合度,增强可读性。
- 23种设计模式分为3类:
(1)创建型模式(5种):工厂方法模式,抽象工厂模式,单例模式,原型模式,建造者模式
(2)结构型模式(7种):适配器模式,装饰器模式,代理模式,外观模式,组合模式,桥接模式,享元模式
(3)行为型模式(11种):策略模式,模板方法模式,观察者模式,迭代子模式,责任链模式,命令模式,备忘录模式,状态模式,访问者模式,中介者模式,解释器模式
第五节 什么是单例
- 什么是单例设计模式?
保证一个类只有一个实例,并且提供一个访问该全局访问点。保证对象的唯一性。
- 设计模式的应用场景?
Servlet(单例),Struts(有单例的模式,默认多例的),SpringMVC(默认单例),Spring(默认单例),连接池,线程池,枚举,常量(自己封装的)
- 单例模式的好处?
(1)节约内存,只有一个实例。 重复利用,方便管理。
- 单例模式的缺点?
(1)线程安全问题,单例模式线程不安全
- 单例有几种创建方式?
单例总共有7种创建方式,我们这里只讲解两种。
我们主要讲的五种单例模式的创建方式为:
(1)饿汉模式:类初始化的时候,就会立即加载该对象,线程天生安全,调用效率高
(2)懒汉模式:类初始化的时候,不会初始化该对象,真正需要使用的时候才会创建该对象,具备了懒加载功能,线程不安全。
(3)静态内部方式:结合了懒汉式和饿汉式各自的优点,真正需要的时候才会加载,且加载类是线程安全的。
(4)枚举单例:使用枚举实现单例模式;优点:实现简单,调用效率高,枚举本身就是单例,由JVM从根本上提供保障。避免了通过反射和序列化的漏洞,缺点就是没有延迟加载。
(5)双重检测锁方式,因为JVN本质重排序的原因,可能会初始化多次,不推荐使用。
第六节 使用饿汉模式创建单例
- 类初始化的时候,就会创建对象,天生线程安全,调用率比较高,如果在不适用对象的时候,会浪费内存。
- 构造函数私有化,则无法new对象。但是反射是可以创建出来的。
- 例子:
/**
* 饿汉式创建单例模式
*/
public class SingletonDemo1 {
// 上来就直接创建对象,这就是饿汉
// 类初始化的时候就加载该对象
private static SingletonDemo1 singletonDemon1 = new SingletonDemo1();
/**
* 构造函数私有化
*/
private SingletonDemo1(){
System.out.println("饿汉模式的初始化");
}
/**
* 得到对象的方法
* @return
*/
public static SingletonDemo1 getInstance(){
return singletonDemon1;
}
public static void main(String[] args) {
SingletonDemo1 singletonDemo1 = SingletonDemo1.getInstance();
SingletonDemo1 singletonDemo12 = SingletonDemo1.getInstance();
System.out.println(singletonDemo1 == singletonDemo12);
}
}
输出结果是:
饿汉模式的初始化
true
第七节 使用懒汉模式创建单例
- 类初始化的时候不会创建该对象,真正需要的时候,才会创建,天生线程不安全,需要自己解决线程安全的问题。所以效率低。
- 简单例子:
package com.xiyou.mayi.thread5.danli.lanhan;
/**
* 懒汉模式
* 只有在用到的时候才会创建对象
*/
public class SingletonDemo2 {
// 类初始化的时候,不会初始化该对象,真正需要的时候才会创建
private static SingletonDemo2 singletonDemo2;
/**
* 私有化其构造函数
*/
private SingletonDemo2(){
System.out.println("SingletonDemo2的构造函数");
}
/**
* 加锁是因为懒汉模式会造成线程不安全
* @return
*/
public static synchronized SingletonDemo2 getInstance(){
if (singletonDemo2 == null) {
// 本类中可以调用私有的方法
singletonDemo2 = new SingletonDemo2();
}
return singletonDemo2;
}
public static void main(String[] args) {
SingletonDemo2 singletonDemo1 = SingletonDemo2.getInstance();
SingletonDemo2 singletonDemo12 = SingletonDemo2.getInstance();
System.out.println(singletonDemo1 == singletonDemo12);
}
}
结果是:
SingletonDemo2的构造函数
true
第八节和第九节 使用枚举方式创建
- 我们推荐用枚举方式创建单例。天生具备JVM保障单例,可以防止反射攻击。
- 简单的枚举
package com.xiyou.mayi.thread5.danli.meiju;
/**
* 用户枚举类
* 天生就是单例模式,只调用一次构造
*/
public enum UserEnum {
HTTP_200(200, "ok"),
HTTP_500(500, "error");
private Integer code;
private String name;
public String getName() {
return name;
}
/**
* 构造函数
* 枚举的构造不能写public权限修饰符
* @param code
* @param name
*/
UserEnum(Integer code, String name) {
this.code = code;
this.name = name;
}
public void setName(String name) {
this.name = name;
}
public Integer getCode() {
return code;
}
public void setCode(Integer code) {
this.code = code;
}
}
- 枚举的构造函数只会执行一次。
- 代码测试:
/**
* 枚举法创建单例模式
*/
public class SingletonDemo3 {
public static SingletonDemo3 getInstance(){
return SingletonEnum.INSTANCE.getInstance();
}
private static enum SingletonEnum{
INSTANCE;
// 枚举天生单例
private SingletonDemo3 singletonDemo3;
private SingletonEnum(){
System.out.println("枚举的构造");
singletonDemo3 = new SingletonDemo3();
}
public SingletonDemo3 getInstance() {
return singletonDemo3;
}
}
}
第十节 使用双重检验锁创建
- 不推荐使用,因为会有重排序机制,有可能初始化多次。即使加了volatile(可以禁止重排序)也不是很安全。该方法不推荐使用。
- 重排序只有在多线程问题中才会发生线程安全问题。
代码:
/**
* 双重锁检验创建单例模式
*/
public class SingletonDemo4 {
// volatile禁止重排序
private static volatile SingletonDemo4 singletonDemo4;
/**
* 私有的构造
*/
private SingletonDemo4 () {
}
/**
* 加锁然后两次检验singletonDemo4是否为空
* 还是不安全,即使用volatile禁止重排序
* @return
*/
public static SingletonDemo4 getInstance(){
if (singletonDemo4 == null) {
synchronized (SingletonDemo4.class){
if (singletonDemo4 == null) {
singletonDemo4 = new SingletonDemo4();
}
}
}
return singletonDemo4;
}
}
注意:
- 如何防止反射再次创建对象?
利用一个标志位进行判断
代码如下:
private static boolean flag = false;
private SingletonDemo04() {
if (flag == false) {
flag = !flag;
} else {
throw new RuntimeException("单例模式被侵犯!");
}
}
public static void main(String[] args) {
}