黑马程序员—JAVA高新技术之类加载器、动态代理

类加载器和动态代理

 

类加载器:

1.java虚拟机中可以安装多个类加载器,系统默认3个主要类加载器,每个类负责加载特定位置的类:
    BootStrap,ExtClassLoader,AppClassLoader
    2.类加载器也是java类,因为其他java类的类加载器本身也要被类加载器加载,因此必须有一个类加载器不是
    java类,这正是BootStrap
    3.java虚拟机中的所有类加载器采用父子关系的树形结构进行组织,在实例化每个类加载器对象时,需要为其指
    定一个父级类加载器对象或默认采用系统类加载器为其父级类加载器。
    BootStrap--->JRE/lib/rt.jar
    ExtClassLoader--->JRE/lib/ext/*.jar 。 */
    AppClassLoader--->CLASSPATH指定的所有jar或目录

    *类加载器的委托机制
    java虚拟机加载类的3种方式:
    1.首先当前线程的类加载器去加载线程中的第一个类。
    2.如果类A中引用了类B,java虚拟机将使用加载类A的类加载器来加载类B。
    3.还可以直接调用ClassLoader.loadClass()方法来指定某个类加载器去加载某个类。//ClassLoader位于java.lang包下。
    每个类加载器加载类时,先委托给其上级类加载器,其上级类加载器再委托给上级类加载器。让最顶层的加载器去加载类,
    当父类加载器没有加载到类,回到下一级加载器中找,若找不到,再向下一级,直到回到发起者类加载器,还加载不了,
    则抛ClassNotFoundException.
    自定义的类加载器必须继承抽象类ClassLoader

 

java的类加载器分类:

1:引导类加载器:BootStrap,是JVM内置的用本地代码编写的类加载器,作用是可以加载jre\lib\rt.jar里面的类文件

2:扩展类加载器:sun.misc.Launcher$ExtClassLoader,作用是可以加载jre\lib\ext\*.jar里面的类文件

3:系统类加载器:sun.misc.Launcher$AppClassLoader,作用是可以加载由classpath配置的空间里的类文件

4:自定义类加载器:CustomClassLoader,作用是可以加载自定义空间里的类文件

知道了类加载的流程后,我们就可以自己编写自定义类加载器,用来加载自定义空间的类文件了。java.lang包里有一个抽象类ClassLoader,用于给扩展类及其下属继承,这个ClassLoader实现了loadClass(name)方法的具体操作,但没有实现findClass(name)方法的具体操作。以下是我查看ClassLoader源码的关于实现loadClass方法的部分:

{
Class c;
c = findLoadedClass(name);//先判断该类是否已加载
if(c==null){
try{
if(parent != null){
c = parent.loadClass(name,false);//有上级时,先委托上级加载器加载,形成层层向上委托
}else{
c = findBootstrapClass0(name);//没上级了,即到了jvm内核的引导类加载器,让该引导类加载器加载
}
}
catch(ClassNotFindException e){
c = find(name);//当上述委托加载都失败后,用自己的加载方法加载
}
}
}

    由于每级类加载器的loadClass方法都是调用上级的loadClass方法,所以自定义类加载器的loadClass方法只需要继承ClassLoader的loadClass方法就行了,该方法实现了先将加载操作逐级向上委托直到Bootstrap,并会在捕获到上级的未找到异常后调用自己的findClass(name)方法。这样自定义类加载器就只需要实现自己的findClass(name)方法就可以了。我的自定义类加载器代码如下:

import java.io.*;

public class Myclassloader extends ClassLoader{
protected Class<?> findClass(String name) throws ClassNotFoundException {
  
String classdir = "F:\\customclass";//指定自定义类加载器的作用空间
String classpath = classdir + "\\" + name.substring(name.lastIndexOf('.')+1) + ".class";//得到目标类文件的路径
byte[] buf = new byte[1024];
byte[] classbinary;
int len;
try {
InputStream is = new FileInputStream(classpath);
ByteArrayOutputStream os = new ByteArrayOutputStream();
while((len = is.read(buf))!=-1){//读取目标类文件的内容
os.write(buf, 0, len);
}
classbinary = os.toByteArray();
Class<?> clazz = defineClass(classbinary,0,classbinary.length);//通过得到的类文件内容定义类对象
return clazz;
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return null;
}
}

自定义类的使用代码如下:

public class ClassLoaderTest {
public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException {  
Myclassloader loader = new Myclassloader();//得到自定义类加载器
Object test = loader.loadClass("SC").newInstance();//用自定义类加载器加载目标类,该目标类位于自定义类加载器的作用空间内
System.out.println("自定义类加载器加载的类对象的类加载器为:"+test.getClass().getClassLoader().getClass().getName());
}

}

执行的结果为:自定义类加载器加载的类对象的类加载器为:com.itheima.bbs.blog_8.Myclassloader,表明使用自定义类加载器成功。

 

 

 

 

动态代理

 

张老师形象比喻:

武汉人从武汉的代理商手中买联想电脑和直接跑到北京传智播客旁边来找联想总部买电脑,你觉得最终的主体业务目标有什么区别吗?基本上一样吧,都解决了核心问题,但是,一点区别都没有吗?从代理商那里买真的一点好处都没有吗?

 

程序中的代理

    编写一个与目标类具有相同接口的代理类,代理类的每个方法调用目标类的相同方法,并在调用方法时加上系统功能的代码。如果采用工厂模式和配置文件的方式进行管理,则不需要修改客户端程序,在配置文件中配置是使用目标类、还是代理类,这样以后很容易切换,譬如,想要日志功能时就配置代理类,否则配置目标类,这样,增加系统功能很容易,以后运行一段时间后,又想去掉系统功能也很容易。

 

动态代理技术:

    要为系统中的各种接口的类增加代理功能,那将需要太多的代理类,全部采用静态代理方式,将是一件非常麻烦的事情!写成百上千个代理类,是不是太累!

JVM可以在运行期动态生成出类的字节码,这种动态生成的类往往被用作代理类,即动态代理类。

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

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

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

1.在调用目标方法之前

2.在调用目标方法之后

3.在调用目标方法前后

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

利用代码说明:

Class proxy{

void sayHello(){

……….

try{

target.sayHello();

}catch(Exception e){

………..

}

………….

}

}


    交叉业务的编程问题即为面向方面的编程(Aspect oriented program,简称AOP),AOP的目标就是要使交叉业务模块化,可以采用将切面代码移动到原始方法的周围,这与直接在方法中编写切面代码的运行效果是一样的。
1。JVM可以在运行期动态生成出类的字节码,这种动态生成的类往往被用作代理类,即动态代理类。
2。JVM生成的动态类必须实现一个或多个接口,所以,JVM生成的动态类只能用做具有相同接口的目标类的代理。
3。CGLIB库(还不是标准,只是一个开源的东西。)可以动态生成一个类的子类,一个类的子类也可以用作该类的代理,所以,如果要为一个没有实现接口的类生成动态代理类,那么可以使用CGLIB库。
4。代理类的各个方法中通常除了要调用目标的相应方法和对外返回目标返回的结果外,还可以在代理方法中的
如下4个位置加上系统功能代码:
    a.在调用目标方法之前
    b.在调用目标方法之后
    c.在调用目标方法前后
    d.在处理目标方法异常的catch块中。
    用于了解代理的一段代码:
public static void main(String[] args) throws Exception{
//获取代理类的字节码
Class clazzProxy1 = Proxy.getProxyClass(Collection.class.getClassLoader(),Collection.class);
//创建动态类实例对象
Constructor constructor = clazzProxy1.getConstructor(InvocationHandler.class);
//利用匿名内部类创建InvocationHandler对象,创建一个动态类实例对象,此处创建的是Collection对象
Collection proxy = (Collection) constructor.newInstance(new InvocationHandler(){
private ArrayList list = new ArrayList();
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
long startTime = System.currentTimeMillis();
Object retVal = method.invoke(list, args);
long endTime = System.currentTimeMillis();
System.out.println(retVal+"time="+(endTime-startTime));
//返回值为调用相应方法的返回值,例如调用list的add方法,返回boolean值。
return retVal;
}
});
//利用代理类创建的对象调用其相应的方法,在调用方法时,如下add方法,则会调用上面的匿名内部类
//InvocationHandler类内部实现的invoke方法,各参数的意义:
//proxy:就是动态创建的那个类对象proxy;
//method:就是proxy调用的add方法;
//args:就是add方法要添加的参数;
proxy.add("lihuoming");
proxy.add("bixiangdong");
//在代理类中的调用模式
/*Class Proxy${
add(Object object){
return invocationhandler.invode((Object proxy,Method method,Object[] args);
}
}*/
System.out.println(proxy);
//proxy调用size()方法,其实是通过handler对象调用的invoke方法。与上面的add方法基本思想相同
System.out.println(proxy.size());
/*另外一种直接创建动态类实例的方法,不需要或去代理类的字节码便可以直接创建对象。
* 此处用的是代理类Proxy的另外一个静态方法,
* Proxy.newProxyInstance(loader, interfaces[], invocationhandler)
Collection proxy2= (Collection)Proxy.newProxyInstance(
Collection.class.getClassLoader(), 
new InvocationHandler(){
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
return null;
}
});
*/
}


用于对代理的封装,使得其更具扩展性。真正的动态代理是如何实现的
public interface Advice {
void beforeMethod(Method method);
void afterMethod(Method method);
}

/*此类实现了Advice接口,用于实现给代理类中的invoke添加功能用的。
 * beforeMethod中的method就是指代理类中InvocationHandler的invoke方法,afterMethod亦是如此。 
 */
public class MyAdvice implements Advice{
private long startTime;
private long endTime;
//此处接收method参数,主要是可以使用method添加一些自己的操作。
public void afterMethod(Method method) {
startTime = System.currentTimeMillis();
}
public void beforeMethod(Method method) {

endTime = System.currentTimeMillis();
System.out.println("time="+(endTime-startTime));
}
}

public class RealProxy {
public static void main(String[] args) throws Exception{
final ArrayList target = new ArrayList();
Collection proxy2 = (Collection)getProxy(target,new MyAdvice());

proxy2.add("lihuoming");

proxy2.add("bixiangdong");

System.out.println(proxy2);

System.out.println(proxy2.size());
}
/*为了实现更高的扩展性,将所有的硬编码,都换为可变的,实时性的。根据所传的参数来获取相应的代理类。
*参数:Object target:将要被代理的类的对象,通过此对象获取将被代理的类的信息。
*参数:Advice advice:Advice为一个接口,目的是为了满足所有实现了Advice接口的子类。以便实现自己的添加功能。 
*/
public static Object getProxy(final Object target,final Advice advice) {
Object objProxy = 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 objProxy;

}
}

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值