懒汉延迟加载&设计模式&反射&注解

单例

注意:尚学堂的所有源码和PPT,官网上都有。

1、

这里写图片描述
这里写图片描述
这里写图片描述
这里写图片描述

2、饿汉

类加载器加载这个类的时候就把对象给new出来,初始化静态属性instance,不管后面有没有使用这个类(要不要这个实例)也不管,上来就new好
缺点:有时候类加载了,但是后面根本就没用instance这个对象(没有调用getInstance去用对象),那就白new了,所以很多时候还是希望延迟加载。
线程安全:饿汉在创建对象的时候是在类初始化的时候立刻加载,类加载器在加载private static SingletDemo1 instance = new SingletDemo1();的时候就天然的线程安全的模式,所以这里不需要加synchronized(没有同步块效率自然就高)

/**
 * 饿汉式单例模式
 */
public class SingletonDemo1 {
    //类初始化时,立即加载这个对象(没有延迟加载的优势)。加载类时,天然的是线程安全的。
    private static SingletonDemo1 instance = new SingletonDemo1();

    private SingletonDemo1(){
    }

    //方法没有同步,调用效率高!
    public static SingletonDemo1 getInstance(){
        return instance;
    }
}
3、懒汉

这里写图片描述

如果类创建一个对象的代价很高,那就用***懒汉***的延迟加载,
如果类调用对象非常频繁,那就用饿汉

/**
 * 懒汉式单例模式
 */
public class SingletonDemo2 {
    //类初始化时,不初始化对象(延时加载,真正用的时候再创建)
    private static SingletonDemo2 instance ;

    private SingletonDemo2(){ //构造器私有化
    }

    //方法同步,调用效率低!
    public static synchronized SingletonDemo2 getInstance(){
       if(instance == null){
           instance = new SingletonDemo2();
       }
       return instance;
    }
}
下面三种尽量避免懒汉和饿汉的缺点
双重检查

这里写图片描述
面试中可以说一说,但实际中不推荐用,sonar检查也不推荐!

静态内部类

这里写图片描述
这个final加不加无所谓,想改也改不了
很多框架里面都使用了静态内部类

/**
 * 测试静态内部类实现单例模式
 * 这种方法:线程安全,调用效率高,并且实现了延时加载!
 */
public class SingletonDemo3 {
    private static class SingletonClassInstance {
        private static /*final*/ SingletonDemo3 instance = new SingletonDemo3();
    }

    private SingletonDemo3(){
    }
    
    //方法没有同步,调用效率高!
    public static SingletonDemo3 getInstance(){
        return SingletonClassInstance.instance;
    }
}
枚举实现单例

这里写图片描述
枚举类天然就是单例的
反射:就算构造器私有,也可以通过反射去调

/**
 * 测试枚举实现单例模式(没有延时加载)
 * 这种方法:线程安全,调用效率高,并且实现了延时加载!
 */
public enum SingletonDemo4 {

    //这个枚举元素、本身就是单例对象!
    INSTANCE;

    //添加自己需要的操作
    public void singletonOperation(){
    }
}

结论:记住红字部分:
这里写图片描述

测试

这里写图片描述

package cn.bjsxt.thread;

import java.io.ObjectStreamException;
import java.io.Serializable;

/**
 * 测试懒汉式单例模式(如何防止反射和发序列化漏洞)
 */
public class SingletonDemo5 implements Serializable{

    //类初始化时,不初始化对象(延时加载,真正用的时候再创建)
    private static SingletonDemo5 instance ;

    //防止反射漏洞
    private SingletonDemo5(){ //构造器私有化
        if(instance != null){
            throw new RuntimeException();
        }
    }

    //方法同步,调用效率低!
    public static synchronized SingletonDemo5 getInstance(){
       if(instance == null){
           instance = new SingletonDemo5();
       }
       return instance;
    }

    //防止反序列化漏洞
    //反序列化时,如果定义了readResolve()则直接返回此方法指定的对象,而不需要单独再创建对象
    private Object readResolve() throws ObjectStreamException{
        return instance;
    }
}

package cn.bjsxt.thread;


import java.io.*;
import java.lang.reflect.Constructor;

/**
 * 测试反射和反序列 破解单例模式
 */
public class Client2 {

    public static void main(String[] args) throws Exception{
        SingletonDemo5 s1 = SingletonDemo5.getInstance();
        SingletonDemo5 s2 = SingletonDemo5.getInstance();

        System.out.println(s1);
        System.out.println(s2);
        /**
        //通过反射的方式直接调用私有构造器
        Class<SingletonDemo5> clazz = (Class<SingletonDemo5>) Class.forName("cn.bjsxt.thread.SingletonDemo5");
        Constructor<SingletonDemo5> c = clazz.getDeclaredConstructor(null);
        c.setAccessible(true); //反射中设置一下,就可以访问私有的内容,跳过权限的检查
        SingletonDemo5 s3 = c.newInstance();
        SingletonDemo5 s4 = c.newInstance();  //这就破解了单例
        System.out.println(s3);
        System.out.println(s4);
        **/

        //通过反序列化的方式构造多个对象  (序列化到硬盘,反序列化读出来)
        //序列化
        FileOutputStream fos = new FileOutputStream("d:/a.txt");
        ObjectOutputStream oos = new ObjectOutputStream(fos); //把对象 通过输出流给fos,再通过fos写到文件
        oos.writeObject(s1);  //把s1写出去
        oos.close();
        fos.close();

        //反序列化
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream("d:/a.txt")); //暂不关注效率
        SingletonDemo5 s3 = (SingletonDemo5)ois.readObject();
        System.out.println(s3);  //s3就是通过反序列化生成了一个新的对象
    }
}

这里写图片描述

import java.util.concurrent.CountDownLatch;

/**
 * 测试多线程环境下,五种创建单例模式的效率
 */
public class Client3 {

    public static void main(String[] args) throws Exception{

        long start = System.currentTimeMillis();

        int threadNum =10;
        final CountDownLatch countDownLatch = new CountDownLatch(threadNum);

        //启10个线程,线程里面调10W次getInstance
        for(int i=0;i<10;i++){
            new Thread(new Runnable() {  //匿名内部类的方法
                public void run() {
                    for(int i=0;i<1000000;i++){
//                        Object o = SingletonDemo3.getInstance();
                        Object o = SingletonDemo4.INSTANCE;
                    }
                    //内部类不能直接使用外部的 局部变量,他们的生命周期不一样
                    countDownLatch.countDown();  //计数器一开始是10,一个线程执行完,就减1变成9了
                }
            }).start();
        }

        //main主线程需要等待,就调用await(阻塞)。阻塞本身就是一个while循环检测
        //main线程阻塞,直到计数器变为0,才会继续往下执行
        countDownLatch.await();

        //等待这10个线程执行完了main才算时间
        long end = System.currentTimeMillis();
        System.out.println("  :"+(end - start));
    }
}

结论:高并发环境下,懒汉效率最低。懒汉式比其他的高两个数量级

注解

注解是annotation 注释是comment
自定义注解光定义了没有意义,还得其他程序去使用它,解析它才有意义。。。
注解一般要经过四步才有意义:1、定义注解 2、在类中使用注解 3、写一个解析程序把注解读出来,进行一些处理。这样才有意义。。。实际当中,一些框架已经完成了解析,我们只要记住怎么用即可。。
这里写图片描述

一些内置注解:

@override,写了它如果重写方法不对,编译器就报错

这里写图片描述
@SuppressWarnings(“all”),来消除警告的,让代码看起来更清爽,偶尔用一下
要消除多个警告,用数组表示
这里写图片描述

如何自定义注解:

这里写图片描述
写自定义注解的时候要加两个元注解,也就是对注解做进一步地解释。

这里写图片描述
这里写图片描述
一般自定义注解的话,用到的就是RUNNTIME
这里写图片描述
注解里面只有一个属性的话,那默认最好叫做value(约定俗成的),而且使用的时候value就可以省略。。

例子

全部代码见高琪源代码。。

/**
 * 使用反射读取注解的信息,模拟处理注解信息的流程
 * @author 尚学堂高淇
 *
 */
public class Demo03 {
    public static void main(String[] args) {
        try {
            Class clazz = Class.forName("com.annotation.SxtStudent");

            //获得类的所有有效注解..
            Annotation[] annotations=clazz.getAnnotations();
            for (Annotation a : annotations) {   //只拿到了类注解
                System.out.println(a);
            }
            //获得类的指定的注解 , 我就只要SxtTable这个注解
            SxtTable st = (SxtTable) clazz.getAnnotation(SxtTable.class);
            System.out.println(st.value());

            //获得类的属性的注解
            Field f = clazz.getDeclaredField("studentName");
            SxtField sxtField = f.getAnnotation(SxtField.class);   //获得方法对象的注解
            System.out.println(sxtField.columnName()+"--"+sxtField.type()+"--"+sxtField.length());

            //根据获得的表名、字段的信息,拼出DDL语句,然后,使用JDBC执行这个SQL,在数据库中生成相关的表

        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

反射

配合代码看
学习反射为后面的框架打下基础,spring,hibernate,J2EE各种框架基本都用到反射。
万事万物都是对象,那类(Sdudent类,Person类)也是对象,类是java.lang.Class这个类的对象。
###类怎么加载进来
前置知识点:可以看慕课网《反射——Java高级开发必须懂的》第二章的东西。
看看什么叫类的动态加载,什么叫类的静态加载???
类的静态加载:new对象是静态加载,在编译时刻就需要加载所有可能使用的类。不管你用不用。
那如果只要有一个功能用不了,其余的都用不了,这样显然不行。 那动态加载可以解决该问题。
动态加载:在运行时刻加载。


看下图:load到code segment里面是一个一个的对象,xx.class 就是xx那个类的对象,
main运行过程之中还有更多的class被load到内存,为什么呢? 动态加载机制。。
这里写图片描述
verbose:详细的。-verbose class表示详细的输出这个类信息
static{}(静态语句块) static语句块是在load完成后被调用,只会调用一次,不管new几次。
而动态语句块(class D { 这里直接写语句 } ),动态语句块是每次new出来一个对象的时候会被调用一次。相当于是这段代码加在了每一个构造方法前面。
这里写图片描述
每一个被load进来的class文件都是一个个的class对象。
这里写图片描述

JDK里面的ClassLoader非常的多
Java最核心的类 是被bootstrap class loader load进来的。(它一般是用C 或者 汇编写出来的,就是本地操作系统语言(native language),然后负责把最核心的rt.jar load进来,你打印不出来它,因为它是最核心的ClassLoader,管理这最核心的类)

extesion class loader负责load一些jdk的扩展类,这些类一般在jre/lib/ext下(C:\Program Files (x86)\Java\jdk1.8.0_131\jre\lib\ext)
除了核心的bootstrap class loader ,其它的class loader都是用Java写的 。而且其它的class loader都是被bootstrap class loader load进来的。
顺序:bootstrap class loader load —> 其它class loader load—>其它的class
这里写图片描述

而装载我们自己类的class loader叫application class loader(看图)也叫做system classLoader

其余的 class loader:你也可以自己写 class loader,继承public abstract class ClassLoader 就可以。
所有的class loader都是从public abstract class ClassLoader继承而来的。

对象之间的关系(一个对象有另一个对象的引用),不是类之间的关系

这里写图片描述

public class TestJDKClassLoader {

    public static void main(String[] args){
        //getClassLoader会拿到一个ClassLoader对象,这里打印出来一个null值
        //因为它是最核心的ClassLoader
        /*
        System.out.println(String.class.getClassLoader());
        System.out.println(com.sun.crypto.provider.DESKeyFactory.class.getClassLoader().getClass().getName());
        System.out.println(TestJDKClassLoader.class.getClassLoader().getClass().getName());
        System.out.println(ClassLoader.getSystemClassLoader().getClass().getName());
        */
        ClassLoader classLoader = TestJDKClassLoader.class.getClassLoader();
        while(classLoader != null){
            System.out.println(classLoader.getClass().getName());
            classLoader = classLoader.getParent();
        }
    }
}

反射

这里写图片描述

T – 对象 – T t = new T(); 这里写不死 名字一变,这段代码就得改
那怎么办? 怎么实现: 知道一个类的名字,把这个类的对象new出来
首先第一步 你得加载进来(用class loader加载进来),有了class loader之后要把这个类load进来,(怎么load?用Class.forName 可以load一个class对象),然后用class.newInstance(); 拿到一个对象

反射特点:可以在运行期间动态的加载一个类进来, 动态的new一个对象出来,动态的去调用这个对象内部方法。
带来什么好处:配置文件只写类的名字就行,然后就可以动态的把这个类加载进来。
struts spring框架里面,很多类名都是写在配置文件里面的。那它们是怎么样new出来的呢,就是通过反射机制new出来的。
关于static静态块:见下面解释:
https://zhidao.baidu.com/question/751756835105797924.html
关于static方法不能被重写,见下面解释:
https://blog.csdn.net/ycb1689/article/details/17163273

public class TestReflection {
    public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException, InvocationTargetException {
        Class t = Class.forName("com.reflect.T");  //这里的t是class对象,并不是这个类的对象
        Object o = t.newInstance();    //相当于造了这个类的一个对象出来
        Method[] methods = t.getMethods();  //这是查看方法
        for (Method method : methods) {
//            System.out.println(method.getName());
            /*  打印出来有这么多
            T loaded!   ---  加载之后会调用static静态语句块,注意和构造函数的区别
            T constructed    ----  
            m1
            getString
            wait
            wait
            wait
            equals
            toString
            hashCode
            getClass
            notify
            notifyAll
            */
            //那这些方法怎么调呢? 通过method对象来调
            if(method.getName().equals("mm")){
                method.invoke(o); //invoke传的:该方法属于哪个对象,方法参数.. 这里是可变参数,参数的个数可以传多个或者0个
            }
        }
    }

}

class T{

    static {
        System.out.println("T loaded!");
    }

    public T() {
        System.out.println("T constructed!");
    }

    int i;
    String s;

    public void  m1(int i){
        this.i = i;
    }

    public String getString(){
        return s;
    }

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

总结:通过反射机制,可以了解到这个类的方方面面。。
可以通过反射的api接口,去探索运行期间的一个class的内部结构。并根据内部结构来决定方法要怎么样去调用。

慕课网反射一点知识

这个课程讲的非常好,讲师很牛逼。。。
这里写图片描述

##动态代理
这里没有马士兵的视频,看别的也一样。动态代理比静态代理用的范围更广泛。
真实的本义是:代理类的内容不需要自己定义了,全部交给工具去生成了。顾名思义:动态生成代理类。。
有以下几种方式:
这里写图片描述
这里写图片描述
重点看下JDK自带的实现,相关的类都是在JDK的反射包里面,
这里写图片描述

动态代理

代理模式用一句话来概括就是:为其他对象提供一种代理,以控制对这个对象的访问
举个例子:火车票代售点就是火车站的一个代理,可以代理火车站完成卖票的操作。但是它提供了额外的服务比如电话预约,同时代售点不能退票。所以代理可以去掉一些服务和增加一些服务。
这里写图片描述

先看静态代理

这里写图片描述

继承的方式实现代理,增加记录时间的功能,用car2(子类)来完成。
使用继承实现代理功能的叠加,代理类会无限的增加,不适合。。。

所谓聚合,把原有car传进来再调用move方法
聚合的方式实现代理,增加记录时间的功能,car3实现相同的接口(也要开车撒,但是这里是调用引用car的move方法,),然后在car3的move前后加上增加记录时间的逻辑。
用聚合方式,代理之间可以相互传递,组合。。功能可以叠加 //clp 调用 ctp的move ,ctp调用car的move

再引出动态代理

新问题?
时间和日志代理,要想用在火车上,自行车上,咋办呢。是不是要火车写一个时间代理,自动车又写一个时间代理呢?这样显然也会让代理类变得繁多。。
有没有一种方法,能动态产生代理,实现对不同类,不同方法的代理?
那看看JDK的动态代理,先看看JDK实现方式。
就是在代理类carTimeProxy和被代理类Car之间加入了一个InvocationHandler这个类(也叫事务处理器),像什么时间处理,日志处理都是在InvocationHandler中完成的。InvocationHandler只有一个invoke方法。

这里写图片描述
这里写图片描述
这里写图片描述

一句话记住:JDK动态代理可以对 实现了某些接口(Moveable接口)的 任意类car啊,火车啊,自行车啊,任意方法(接口中有很多方法,如move)产生任意的代理(不用自己写了,以前要自己写汽车的,火车的,现在直接是一个Moveable proxy就可以直接拿来用了),可以在代理中随意加入自己的业务逻辑(通过事务处理器InvocationHandler)。 现在proxy的move就是代理的move了。
动态代理的一大特点就是编译阶段没有代理类在运行时才生成代理类。


/**
 * JDK动态代理测试类
 */
public class Test {


    public static void main(String[] args){
        Car car = new Car();
        InvocationHandler h = new TimeHandler(car);
        Class<?> cls = car.getClass();
        /***
         * 参数1:被代理类的类加载器。
         * 2:实现哪些接口
         * 3、事件处理器
         *
         * 动态代理实现的思路
         * 实现功能:通过Proxy的newProxyInstance方法返回代理对象
         * 1、声明一段源码(动态产生代理)  然后把源码生成java文件,放到bin下
         *2、编译源码(JDK Compiler API),产生新的类(代理类) class文件
         *3、将这个类(class文件)load到内存当中,产生一个代理类的对象(代理对象)
         * 4、return 代理对象
         * 5、然后再测试类中定义一个InvocationHandler,专门用来处理 事务处理(写不同的业务逻辑)。
         *   产生代理的时候(也就是newProxyInstance的时候)把InvocationHandler传过去就可以了。。
         */
        //产生的代理对象,也实现了Moveable 接口
        Moveable m = (Moveable) Proxy.newProxyInstance(cls.getClassLoader(),
                                    cls.getInterfaces(),h);
        m.move();


        System.out.println(System.getProperty("user.dir"));
    }


}

写自己业务逻辑的时候,是写在invoke方法里,但是要注意的是,它也要通过invoke调用被代理类的原有的方法啊。怎么调用? 把原代理类传进去,再通过反射去调用。。。。看如下代码:

/**
 * 处理器接口。。。这里做统一流程控制
 */
public class StarHandler implements InvocationHandler {

    Star realStar;

    public StarHandler(Star realStar) {
        super();
        this.realStar = realStar;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args)
            throws Throwable {
        //调用代理类,全部到这来了,那就可以在这做统一流程控制

        Object object = null;

        //前前后后的这些println就是代理类增加的业务逻辑。。。

        System.out.println("真正的方法执行前!");
        System.out.println("面谈,签合同,预付款,订机票------------");

        if(method.getName().equals("sing")){    //比原来被代理类少了很多方法,  所以代理类是可以  增加和减少 业务逻辑的。
            object = method.invoke(realStar, args);  //被代理类realStar的方法是不能丢的,这里要传入realStar.
        }

        System.out.println("真正的方法执行后!-----------");
        System.out.println("收尾款");
        return object;
    }

}

JDK实现动态代理的局限性:它只能对实现接口的类进行代理。
CGLIB是使用继承的方式,所以不能对final修饰的类进行代理。。
这里写图片描述

动态代理的实际意义:比如想调用某个jar包中的某个类的某个方法,因为不能改源码,所以就可以采用代理模式的机制,在方法的前后加入自己的业务逻辑,这种方式也叫做AOP。。。面向切面编程。
在不改变原有类的基础上,增加一些额外的业务逻辑。。

CGLIB动态代理的使用

public class Train {

	public void move(){
		System.out.println("火车行驶中...");
	}
}
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

public class CglibProxy implements MethodInterceptor {

	private Enhancer enhancer = new Enhancer();
	
	//这是才用继承的方式,产生的代理类是父类的子类。
	public Object getProxy(Class clazz){
		//设置创建子类的类
		enhancer.setSuperclass(clazz);
		enhancer.setCallback(this);
		
		return enhancer.create();
	}
	
	/**
	 * 拦截所有目标类方法的调用
	 * obj  目标类的实例
	 * m   目标方法的反射对象
	 * args  方法的参数
	 * proxy代理类的实例
	 */
	@Override
	public Object intercept(Object obj, Method m, Object[] args,
			MethodProxy proxy) throws Throwable {
		System.out.println("日志开始...");
		//代理类调用父类的方法
		proxy.invokeSuper(obj, args);
		System.out.println("日志结束...");
		return null;
	}

}

public class Client {

	/**
	 * @param args
	 */
	public static void main(String[] args) {

		CglibProxy proxy = new CglibProxy();
		Train t = (Train)proxy.getProxy(Train.class);
		t.move();
	}

}

java字节码操作

(所谓到了字节码操作,就是运行期间去动态改变一个类)
动态代理可以用反射来做,也可以通过字节码操作 来做。。字节码操作的效率比反射要高一些。这两个可以结合使用。

这里写图片描述
这里写图片描述

这里主要看看javasit库。
AOP操作的时候,底层很多时候都可以用javasit来实现。。
这里写图片描述
这里写图片描述

JVM

###JVM运行基本原理和类加载过程
学习这个是为了更好的理解java的动态性编程,比如说反射,框架的底层,热部署等。
这里写图片描述
写在前面:
javac – 编译
java – 运行,通过它启动虚拟机,然后对类进行一个加载和执行。
静态属性,静态变量,静态域 – 都是一回事儿
静态块,静态初始化块儿 – 都是一回事儿

类加载机制有一个核心叫做 类加载器。
大概的过程:加载 – 链接 – 初始化
先看加载:(具体过程见下面的图)
字节码的本质就是一个字节数组,它可以存在于硬盘上也可以从网络上来过来的(比如服务器发过来的)
这里写图片描述
下面是链接和初始化:
链接:
常量池:每个类都有一个常量池,类名也是常量,
解析中的符号引用比较抽象,直接引用比较具体。
初始化(重点看看初始化):(初始化本质就是去调用类构造器clinit())
第三点,如果是类的静态属性初始化的时候,或者是静态块执行的时候,肯定是线程安全的。
而且,类的加载和初始化只有一次。

//类的执行顺序:先执行类加载,类加载会执行类的初始化方法,也就是把静态变量(或者叫静态域)和静态初始化块(也叫静态块)合并到类初始化方法里面。
// 这个初始化方法叫做<clinit>().  也叫做类构造器
//初始化之后,才能去new对象。

这里写图片描述

执行的过程在内存中的情况:看着程序想着内存。
几个注意的点:方法区也是堆结构。
可以通过堆中的class对象,去访问方法区中的二进制结构,从来操纵这个类的信息。 这是反射的核心的东西。
A a的时候,a是null值的。new A()就是去调用A的构造方法,new完之后会把A的对象的地址赋给栈中的a,这个时候a就不为null了。
这里写图片描述

调用类的方法必须要在类加载和初始化完成之后,才能调用。

类的加载时机:(什么时候加载)
访问final常量的时候,不会去初始化类。
这里写图片描述

 //主动引用
		new A();
		System.out.println(A.width);
//		Class.forName("com.jvm..A");


        //被动引用
//		System.out.println(A.MAX);
//		A[] as = new A[10];  //数组定义类 也不会初始化该类。
//        System.out.println(B.width);  //子类访问父类的静态域的时候 也不会初始化

类加载器

主要内容:
这里写图片描述
前面学到的东西:
这里写图片描述
马士兵那里也提到过:
内部采用的是组合的关系来处理的,不是继承。组合也可以起到代码复用的效果。
从逻辑上理解是父子关系,但是不是用继承来实现,而是用组合来实现。

bootstrap classloader是用C++来实现的。在java中获取不到,getParent()获得的是null。
其他的加载器都是用JAVA来写的。

这里写图片描述

类加载器是加载类的时候,采用的是代理模式。双亲委托机制是代理模式的一种。
双亲委托机制:就是父类加载器优先加载。先不管能不能加载,先交给父亲再说。父亲加载不了就交给儿子,父亲加载过了,儿子就不管了。。
好处就是安全。假如自己定义一个java.lang.String类,它就永远不会加载,因为加载是先交给父亲再说。最顶层的bootstrap classloader发现已经加载了rt.jar中的String类了。也就是说这些个核心类,就算你定义了,也永远都用不了。
一些服务器内部有时候其实不会采用双亲委托机制。双亲委托机制就是安全第一,但有的时候不够灵活。比如:tomcat则是子类优先加载,和JDK是相反的。
这里写图片描述

自定义类加载器

思路:给我一个类名User,我就去目录下找有没有User.class。找到之后通过IO流,把这个class文件加载到内存中。
class文件是以字节数组形式存在。具体流程如下:
这里写图片描述

/**
 * 自定义文件系统类加载器
 * @author 尚学堂高淇 www.sxt.cn
 */
public class FileSystemClassLoader extends ClassLoader {

    //com.bjsxt.test.User   --> d:/myjava/  com/bjsxt/test/User.class
    private String rootDir;  //在这个路径下去找。

    public FileSystemClassLoader(String rootDir) {
        this.rootDir = rootDir;
    }

    /**
     * @param name 把类名传进来
     * @return
     * @throws ClassNotFoundException
     */
    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        //应该要先查询有没有加载过这个类。如果已经加载,则直接返回加载好的类。如果没有,则加载新的类。
        Class<?> c = findLoadedClass(name); //protected修饰,子类和同包可以访问。

        if(c!=null){
            return c;
        }else{
            ClassLoader parent = this.getParent();
            /**
             * 若添加try catch语句则程序会处理异常,try内异常不再执行,处理后继续向下运行
                若没有try catch语句 程序会在异常处跳出来,不再运行下面部分
             */
            try {
                c = parent.loadClass(name);	   //委派给父类加载
            } catch (Exception e) {
//				e.printStackTrace();  //这里catch到了异常,但没有处理,相当于把异常吞掉了。程序还是往下执行。。
            }

            if(c!=null){
                return c;
            }else{  //没有加载,就读取指定路径下的指定文件,再转成字节数组。
                    byte[] classData = getClassData(name);
                if(classData==null){
                    throw new ClassNotFoundException();
                }else{
                    c = defineClass(name, classData, 0,classData.length);  //有了字节数组就可以加载了。
                }
            }
        }
        return c;
    }

    /**
     * 这里实际就是一些IO的操作,给一个name,返回一个字节数组
     * @param classname
     * @return
     */
    private byte[] getClassData(String classname){   //把com.bjsxt.test.User 变成  d:/myjava/  com/bjsxt/test/User.class
        String path = rootDir +"/"+ classname.replace('.', '/')+".class";

//		IOUtils,可以使用它将流中的数据转成字节数组。。没有这个IOUtils也可以自己手写。
        InputStream is = null;
        //定义一个字节输出流来输出字节数组
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        try{
            is  = new FileInputStream(path);  //从file文件中读,读到程序中来

            byte[] buffer = new byte[1024];  //
            int temp=0;
            while((temp=is.read(buffer))!=-1){   // 读一系列字节,并存到buffer中,  返回-1表示读到流的末尾了 。temp表示实际读取的字节数
                baos.write(buffer, 0, temp);  //一个字节数组一个字节数组的写。写到输出流。
            }

            return baos.toByteArray();  //最后转成字节数组
        }catch(Exception e){
            e.printStackTrace();
            return null;
        }finally{
            try {
                if(is!=null){
                    is.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
            try {
                if(baos!=null){
                    baos.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

    }


}

取反:用异或操作。。
?????

##工厂模式

getInstance
Factory 在JDK里面非常的常用。笔记见博客。

抽象工厂:网上的换皮肤的功能。 一换的话,什么菜单,按钮等等都换过来了。。。
所以,抽象工厂就是,生产了一系列产品,如果想换掉一系列的产品(或者想扩展),以及对一系列产品的生产过程进行控制,那就用抽象工厂

观察者模式

大概的结构:

这里写图片描述

举例:黄明给女朋友和丈母娘推送天气

看着下图,就对应着一般套路。。。。
在这里插入图片描述
1、目标对象就是subject、天气预报就是具体的目标对象。。。
2、女朋友和丈母娘就是观察者。 天气预报变化了,就notify观察者,观察者直接update就行

观察者模式通用代码

设计模式没什么想象力,就是理解这个模式,理解之后再去套用
就按照以下几个步骤去套:
这里写图片描述
观察者可以有很多:女朋友、丈母娘、七大姑八大姨、

代码见码云。

观察者模式具体场景代码

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值