Java基础加强 四

 

类加载器

Java虚拟机中可以安装多个类加载器,每个类加载器负责加载特定位置的类,系统默认三个主要类加载器:BootStrap,ExtClassLoader,AppClassLaoder,其中BootStrap加载jre/lib/rt.jar中的类,ExtClassLoader加载jre/lib/ext/*.jar中的类(ext中一般存放的是自己编写的类产生的jar),AppClassLaoder加载classpath指定的jar或运行目录下的类

类加载器也是类,因为是java类的类加载器本身也要被类加载器加载,显然必须有第一个类加载器不是java类,它是运行在java内核中的,这就是BootStrap。

AppClassLaoder的父类是ExtClassLoader,ExtClassLoader的父类是BootStrap,自己编写的类加载器应继承ClassLaoder类

类加载器的委托机制:

首先当前线程的类加载器去加载线程中的第一个类。(Thread类中有一个setContextClassLoader(ClassLoader cl)方法用来设置线程的类加载器),当线程运行时用到类时,该类加载器就去加载那个类。

如果类A中引用类B,java虚拟机将使用加载类A的类加载器去加载类B

还可以直接调用loadClass方法来指定某个类加载器去加载这个类

每个类加载类时,又先委托给其上级类加载器,然后再委托给它的上级类加载器,直到顶层BootStrap,从BootStrap开始检查是否能够加载,不能的话向则下传递,当所有的父(祖宗)类加载器没有加载到类,并且回到发起者本身也不能加载的话,则抛出ClassNotFoundException,不是传递给发起者的子类。

同样某一类加载器和其父类加载器都能加载某一个类时,该类会被父(祖宗)类加载器加载,而不会交到这个类加载器中

 

编写自己的类加载器,加载特定目录下的类,而不用其他的类加载器加载

编写原理:自定义的类加载器必须继承ClassLoader类,该类中的loadClass中规定好了加载类时逐级向父类加载器传递,并逐级向下检查的机制,所以一般不覆盖此方法。而是覆盖findClass方法,此方法规定了所定义的类加载器本身应做的工作;defineClass方法规定了将class文件转换成字节码的功能,也不用去覆盖。

编写自己的类加载器并且带有解密方法,使得被自己加密过的class文件,被解密并正常加载。从而使得外人无法加载使用被自己加密的class文件。

因此首先编写一个对文件内容进行简单加密的程序,大致程序如下:

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

              // TODO Auto-generated method stub

              String srcPath = args[0];

              String destDir = args[1];//填相对路径

              FileInputStream fis = new FileInputStream(srcPath);

              String destFileName = srcPath.substring(srcPath.lastIndexOf('\\')+1);

              String destPath = destDir + "\\" + destFileName;

              FileOutputStream fos = new FileOutputStream(destPath);

              cypher(fis,fos);

              fis.close();

              fos.close();

       }

      

 

       private static void cypher(InputStream ips ,OutputStream ops) throws Exception{

              int b = -1;

              while((b=ips.read())!=-1){

                     ops.write(b ^ 0xff);//与运算加密

              }

       }

 

写一个类加载器,对加密过的类解密和加载:

public class MyClassLoader extends ClassLoader{

  

   private String classDir;

  

   public MyClassLoader(){

       }

      

public MyClassLoader(String classDir){

              this.classDir = classDir;

}

  

   protected Class<?> findClass(String name) throws ClassNotFoundException {

              String classFileName = classDir + "\\" + name.substring(name.lastIndexOf('.')+1) + ".class";//处理带包名的类

              try {

                     FileInputStream fis = new FileInputStream(classFileName);

                     ByteArrayOutputStream  bos = new ByteArrayOutputStream();

                     cypher(fis,bos);

                     fis.close();

                     byte[] bytes = bos.toByteArray();

                     return defineClass(bytes, 0, bytes.length);

              } catch (Exception e) {

                     // TODO Auto-generated catch block

                     e.printStackTrace();

              }

              return null;

       }

}

 

 

当一个类继承某个类时,当加载这个类时也要加载它所继承的类,如:

public class MyServlet extends HttpServlet{

   …….;

}

MyServlet 类被WebappClassLaoder加载,但把MyServlet打包成jar放到ext目录中后,再运行,发现找不到类HttpServlet了,因为如果类A中引用类B,java虚拟机将使用加载类A的类加载器去加载类B,此时加载MyServlet的是ExtClassLaoder,而ExtClassLaoder无法加载HttpServlet,把HttpServlet所在的servlet.jar放到ext目录下,问题解决,所用的类加载器是ExtClassLaoder.

 

代理 proxy

例如:原方法:

class X{

void method(){……;}

    }

使用代理后:void methodY{

                 …..;//增加的代理功能

                 x.method();

                 ……;

}

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

系统中存在交叉业务,即在不同类的方法中存在着相同的代码块,从横向看这些存在于不同类的方法中的代码块形成了一个面,一个交叉业务就是要切入到系统的一个面,从而交叉业务的编程即为面向方面的编程(Aspect oriented program,简称AOP),AOP的目标就是是交叉业务模块化。可以采用将切面代码移动到原始方法的周围,这个与直接在方法中编写切面代码的运行效果是一致的。用代理就是实现AOP的关键。

动态代理技术

倘若为系统中所有的类增加代理功能,那需要太多的代理类,手工编写的话会很麻烦,因此JVM可以在运行期间动态生成类的字节码,这种动态生成的类往往被用作代理类,即动态代理类

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

CGLIB库可以动态生成一个类的子类一个类的子类也可以用作该类的代理,所以如果要为一个没有实现接口的类生成动态代理类,那么可以使用CGLIB库。

 

JVM动态生成的类

Proxy中一个

public static Class<?> getProxyClass(ClassLoader loader, Class<?>... interfaces) throws IllegalArgumentException方法

该方法的返回值为Class字节码,即产生一个类,方法的第二个方法指明了新类实现的接口,第一个参数为新类的类加载器。如:

Class clazzProxy1 = Proxy.getProxyClass(Collection.class.getClassLoader(), Collection.class);

System.out.println(clazzProxy1.getName());

产生代理对象:

Constructor constructor = clazzProxy.getConstructor(InvocationHandler.class);

/*该动态类的构造方法需要接受一个InvocationHandler的参数,一般的代理类都要实现这个接口*/

              class MyInvocationHander implements InvocationHandler{

                     public Object invoke(Object proxy, Method method, Object[] args)

                                   throws Throwable {

                            // TODO Auto-generated method stub

                            return null;

                     }

              }

 

Collection proxy = (Collection)constructor.newInstance(new MyInvocationHander ());

或:

Collection proxy = (Collection)constructor.newInstance(new InvocationHandler(){

 

                     public Object invoke(Object proxy, Method method, Object[] args)

                                   throws Throwable {

                            return null;

                     }

                    

              });

 

不用另产生动态类而直接产生代理对象:

Collection proxy =( Collection) Proxy.newProxyInstance(

// newProxyInstance方法接受三个参数

                            Collection.getClass().getClassLoader(),

                            new Class[]{Collection .class}

                            new InvocationHandler(){

                              public Object invoke(Object proxy, Method method, Object[] args)

                                    throws Throwable {

                              return null;

                           }

                            }

                     };

但是以上的proxy只能调用返回值为void的方法

 

添加代理作用的目标,即修改InvocationHandler的实现类

Collection proxy =( Collection) Proxy.newProxyInstance(

// newProxyInstance方法接受三个参数

                            Collection.getClass().getClassLoader(),

                            new Class[]{Collection .class}

                            new InvocationHandler(){

                  ArrayList target = new ArrayList();

                 //代理作用的目标类

                              public Object invoke(Object proxy, Method method, Object[] args)

                                    throws Throwable {

                  long beginTime = System.currentTimeMillis();//功能代码

                                          Object retVal = method.invoke(target, args);

                                          long endTime = System.currentTimeMillis();//功能代码

    System.out.println(method.getName() + " running time of " + (endTime - beginTime));

//功能代码

                                          return retVal;

                           }

                            }

                     };

proxy.add("zxx");

proxy.add("lhm");

proxy.add("bxd");

System.out.println(proxy.size());

/*此时的proxy可以调用所有方法,每调用一次方法,就会调用InvocationHandler 实现类中的invoke方法*/

InvocationHandler中的invoke方法的参数:

程序在调用方法时,涉及到三个要素:以proxy.add("zxx");为例,有对象(proxy);方法(add);参数("zxx"),此三者正是InvocationHandler中的invoke方法的参数:第一个为代理对象,第二个参数为目标方法,第三个参数为目标方法的参数(Class)。Object retVal = method.invoke(target, args);就是调用目标类上的目标方法,注意返回值赋值给Object类型的引用,Invoke方法的返回值也应为此。

根据上面的原理,被代理包装的目标方法的参数和返回值就可以在invoke方法中修改了。

 

proxy 调用getClass()方法时不会调用InvocationHandler中的invoke方法,而是有自己的调用方式,从Object类继承的方法除了hashCode,toString,equals方法调用时需调用InvocationHandler中的invoke方法外,其他的方法都有自己的调用方式。

 

invoke方法中的目标类(target)及功能代码应该是可以供调用者修改的,而不是硬生生的写在invoke方法中:

private static Object getProxy(final Object target,final Advice advice) {

//Advice接口作为一个契约,其实现类中含具体的功能代码

//该方法会根据返回一个代理

              Object proxy3 = Proxy.newProxyInstance(

                            target.getClass().getClassLoader(),

                            target.getClass().getInterfaces(),

                            new InvocationHandler(){

                           

                                   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 proxy;

       }

 

//Adivce接口

import java.lang.reflect.Method;

 

public interface Advice {

       void beforeMethod(Method method);

       void afterMethod(Method method);

}

 

//Adivce接口的具体实现类

import java.lang.reflect.Method;

 

public class MyAdvice implements Advice {

       long beginTime = 0;

       public void afterMethod(Method method) {

              long endTime = System.currentTimeMillis();

       System.out.println(method.getName() + " running time of " + (endTime - beginTime));

       }

 

public void beforeMethod(Method method) {

              beginTime = System.currentTimeMillis();

       }

 

}

 

使用:final ArrayList target = new ArrayList();                

         Collection proxy = (Collection)getProxy(target,new MyAdvice());

      //根据目标类进行强制转换

 

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值