黑马程序员__JAVA基础加强__高新技术(四)

时间:2022-01-16 15:42:13

------------android培训java培训、java学习型技术博客、期待与您交流! ------------

1.类加载器

    定义与作用:与普通程序不同的是,Java程序(class文件)并不是本地的可执行程序。当运行Java程序时,首先运行JVM(Java虚拟机),然后再把Java class加载到JVM里头运行,负责加载Java class的这部分就叫做Class Loader。 

    Java虚拟机中可以安装多个类加载器,系统默认三个主要类加载器,每个类负责加载特定位置的类:BootStrap,ExtClassLoader,AppClassLoader 

    类加载器也是Java类,因为其他是java类的类加载器本身也要被类加载器加载,显然必须有第一个类加载器不是不是java类,这正是BootStrap。

    BootStrap:用本地代码实现的(C++的一段二进制代码),它负责加载核心JavaClass(即所有java.*开头的类);

    ExtClassLoader:负责加载扩展的Javaclass(例如所有javax.*开头的类和存放在JRE的ext目录下的类);

    AppClassLoader:负责加载应用程序自身的类;

    注:ExtClassLoader与AppClassLoader它们都是用Java语言编写的,由Bootstrap加载的

    Java虚拟机中的所有类装载器采用具有父子关系的树形结构进行组织,在实例化每个类装载器对象时,需要为其指定一个父级类装载器对象或者默认采用系统类装载器为其父级类加载。

黑马程序员__JAVA基础加强__高新技术(四) 

    类加载器的委托机制

    当Java虚拟机要加载一个类时,到底派出哪个类加载器去加载呢?
    1.首先当前线程的类加载器去加载线程中的第一个类。
    2.如果类A中引用了类B,Java虚拟机将使用加载类A的类装载器来加载类B。
    3.还可以直接调用ClassLoader.loadClass()方法来指定某个类加载器去加载某个类。

    每个类加载器加载类时,又先委托给其上级类加载器。
    1.当所有祖宗类加载器没有加载到类,回到发起者类加载器,还加载不了,则抛ClassNotFoundException,不是再去找发起者类加载器的儿子,因为没有getChild方法,即使有,那有多个儿子,找哪一个呢?
    2.对着类加载器的层次结构图和委托加载原理,解释先前将ClassLoaderTest输出成jre/lib/ext目录下的itcast.jar包中后,运行结果为ExtClassLoader的原因。

    每个ClassLoader本身只能分别加载特定位置和目录中的类,但它们可以委托其他的类装载器去加载类,这就是类加载器的委托模式。类装载器一级级委托到BootStrap类加载器,当BootStrap无法加载当前所要加载的类时,然后才一级级回退到子孙类装载器去进行真正的加载。当回退到最初的类装载器时,如果它自己也不能完成类的装载,那就应报告ClassNotFoundException异常。

    面试细节:有一道面试,能不能自己写个类叫java.lang.System,为了不让我们写System类,类加载采用委托机制,这样可以保证爸爸们优先,也就是总是使用爸爸们能找到的类,这样总是使用java系统提供的System。

    编写自己的类加载器

    知识讲解:
    1.自定义的类加载器的必须继承ClassLoader

    2.loadClass方法(直接继承,省去委托机制的编写)与findClass方法(覆盖这个就行了)
    3.defineClass方法
    编程步骤:
    1.编写一个对文件内容进行简单加密的程序。
    2.编写了一个自己的类装载器,可实现对加密过的类进行装载和解密。
    3.编写一个程序调用类加载器加载类,在源程序中不能用该类名定义引用变量,因为编译器无法识别这个类。程序中可以除了使用ClassLoader.load方法之外,还可以使用设置线程的上下文类加载器或者系统类加载器,然后再使用Class.forName。
    实验步骤:
    1.对不带包名的class文件进行加密,加密结果存放到另外一个目录,例如: java MyClassLoader MyTest.class F:\itcast
    2.运行加载类的程序,结果能够被正常加载,但打印出来的类装载器名称为AppClassLoader:java MyClassLoader MyTest F:\itcast
    3.用加密后的类文件替换CLASSPATH环境下的类文件,再执行上一步操作就出问题了,错误说明是AppClassLoader类装载器装载失败。
    4.删除CLASSPATH环境下的类文件,再执行上一步操作就没问题了。

代码示例:

import java.io.*;
import java.lang.reflect.*;
public class MyClassLoader extends ClassLoader
{
private String path = null;
public MyClassLoader(String path) throws Exception//检查文件是否存在
{
File f = new File(path);
if(!f.isDirectory())
{
throw new RuntimeException(path + " is not a directory");
}
this.path = path;
}

public Class findClass(String name) //throws Exception //为什么不能抛出
{
try
{
File f = new File(path,name.substring(name.lastIndexOf('.')+1) + ".class");
FileInputStream fis = new FileInputStream(f);
ByteArrayOutputStream bos = new ByteArrayOutputStream();
cypher(fis,bos);
byte [] buf = bos.toByteArray();
fis.close();
bos.close();
return defineClass(name,buf,0,buf.length);
}catch(Exception e)
{
throw new ClassNotFoundException(name + " is not found!");
}
return null;
}


public static void cypher(InputStream istream,OutputStream ostream) throws Exception
{
//下面这段代码可能遇到255的字节,当成byte就成了-1
/*byte b = 0;
while((b = (byte)istream.read()) != -1)
{
ostream.write(b ^ 0xff);
}*/

int b = 0;
while((b = istream.read()) != -1)
{
ostream.write(((byte)b) ^ 0xff);
}
}

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

//下面省略了错误检查
if(!args[0].endsWith("class"))
{
ClassLoader loader = new MyClassLoader(args[1]);
Class cls = loader.loadClass(args[0]);

/*
让自定义类继承Date类
System.out.println(cls.getClassLoader().getClass().getName());
java.util.Date d = (java.util.Date)cls.newInstance();
System.out.println(d.toString());
*/

//Method m = cls.getMethod("test",null);//在jdk1.5中报警告,为什么?
Method m = cls.getMethod("test");
//m.invoke(cls.newInstance(),null);
m.invoke(cls.newInstance());
//((Test)cls.newInstance()).test();
return;
}
else
{
FileInputStream fis = new FileInputStream(args[0]);
File f = new File(args[1], new File(args[0]).getName());//不用检查目录最后是否有目录分割符
FileOutputStream fos = new FileOutputStream(f);
cypher(fis,fos);
fis.close();
fos.close();
}
}
}

//类加载器不能加载这种非public的类
/*
Exception in thread "main" java.lang.IllegalAccessException: Class MyClassLoader
can not access a member of class MyTest with modifiers ""
*/
/*
class MyTest
{
public void test()
{
System.out.println("hello,www.it315.org");
}
}
*/


2.代理 

    要为已存在的多个具有相同接口的目标类的各个方法增加一些系统功能,例如,异常处理、日志、计算方法的运行时间、事务管理、等等,你准备如何做?
    编写一个与目标类具有相同接口的代理类,代理类的每个方法调用目标类的相同方法,并在调用方法时加上系统功能的代码。 (参看下页的原理图)
    如果采用工厂模式和配置文件的方式进行管理,则不需要修改客户端程序,在配置文件中配置是使用目标类、还是代理类,这样以后很容易切换,譬如,想要日志功能时就配置代理类,否则配置目标类,这样,增加系统功能很容易,以后运行一段时间后,又想去掉系统功能也很容易。
黑马程序员__JAVA基础加强__高新技术(四)

    面向方面编程(AOP)

    系统中存在交叉业务,一个交叉业务就是要切入到系统中的一个方面,如下所示:
                         安全       事务         日志
    StudentService  ------|----------|------------|-------------
    CourseService   ------|----------|------------|-------------
    MiscService     ------|----------|------------|-------------


    用具体的程序代码描述交叉业务:
    method1         method2          method3
    {                      {                       {
    ------------------------------------------------------切面
    ....            ....              ......
    ------------------------------------------------------切面
    }                       }                       }


    交叉业务的编程问题即为面向方面的编程(Aspect oriented program ,简称AOP),AOP的目标就是要使交叉业
务模块化。可以采用将切面代码移动到原始方法的周围,这与直接在方法中编写切面代码的运行效果是一样的,
    如下所示:


    ------------------------------------------------------切面
    func1         func2            func3
    {             {                {
    ....            ....              ......
    }             }                }
    ------------------------------------------------------切面

    使用代理技术正好可以解决这种问题,代理是实现AOP功能的核心和关键技术。

    安全,事务,日志等功能要贯穿到好多个模块中,所以,它们就是交叉业务

    重要原则:不要把供货商暴露给你的客户

    动态代理

    为什么要出现动态代理:要为系统中的各种接口的类增加代理功能,那将需要太多的代理类,全部采用静态代理方式,将是一件非常麻烦的事情!写成百上千个代理类,是不是太累,JVM可以在运行期动态生成出类的字节码,这种动态生成的类往往被用作代理类,即动态代理类

    注意:JVM生成的动态类必须实现一个或多个接口,所以,JVM生成的动态类只能用作具有相同接口的目标类的代理。

    如果一个类没有实现接口,可以用CGLIB库动态生成一个类的子类,用这个子类来做这个类的代理类。

    代理类的各个方法中通常除了要调用目标的相应方法和对外返回目标返回的结果外,还可以在代理方法中的如下四个位置加上系统功能代码:

        1.在调用目标方法之前

        2.在调用目标方法之后

        3.在调用目标方法前后

        4.在处理目标方法异常的catch块中

利用InvocationHandler接口创建代理类代码示例:

Class clazzProxy1 = Proxy.getProxyClass(Collection.class.getClassLoader(), Collection.class);//创建代理类
Constructor constructor = clazzProxy1.getConstructor(InvocationHandler.class);//获取代理类的构造方法



class MyInvocationHandler1 implements InvocationHandler//内部类,实现InvocationHandler接口
{
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
return null;
}
}



//代理类通过反射调用方法,参数为内部类的实例对象
Collection proxy1 = (Collection)constructor.newInstance(new MyInvocationHandler1());
System.out.println(proxy1.toString());


//运用匿名内部类
Collection proxy2 = (Collection)constructor.newInstance(new InvocationHandler()
{
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
return null;
}
});

//用Proxy的静态方法newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)

Collection proxy3=(Collection)Proxy.newProxyInstance(
Collection.class.getClassLoader(),
new Class[]{Collection.class},
new InvocationHandler(){
ArrayList target= new ArrayList();
public Object invoke(Object proxy, Method method,
Object[] args) throws Throwable {
long startTime = System.currentTimeMillis();
Object retVal = method.invoke(target, args);
long endTime = System.currentTimeMillis();
System.out.println(method.getName()+" running time:"+(endTime - startTime));
return retVal;
}

});


动态代理类框架: 

//事务接口
public interface Advice {
void beforeMethod(Method method);//方法执行之前执行的方法
void afterMethod(Method method);//方法执行之后执行的方法
}

//实现事务接口
public class MyAdvice implements Advice {
long startTime=0;
@Override
public void beforeMethod(Method method) { //覆盖父类方法
startTime = System.currentTimeMillis();
}

@Override
public void afterMethod(Method method) { //覆盖父类方法
long endTime = System.currentTimeMillis();
System.out.println(method.getName()+" running time:"+(endTime - startTime));
}
}

//创建代理框架
public static Object getProxy(final Object target,final Advice advice) {//传入目标类与事务类
Object proxy3 =Proxy.newProxyInstance(
target.getClass().getClassLoader(), //类加载器
target.getClass().getInterfaces(),//实现的接口
new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
advice.beforeMethod(method);//执行事务类方法
Object retVal = method.invoke(target, args);//执行目标类的方法
advice.afterMethod(method);//执行事务类方法
return retVal;
}
});
return proxy3;
}


个人总结

    类加载器就是java中各个类的字节码进行内存的一个途径,JAVA系统有三个类加载器,要明确这三个各个不同的作用。代理里面应该要重点掌握动态代理,作用就是给目标类加上一些事务处理,比如说运行时间等等,要掌握写一个动态代理类的框架。