第一节 课程概述

  • 反射机制的讲解
  • 单例设计模式有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) {

	}