类加载器:把class文件加载到内存形成字节码,其本身也是类
系统默认三个类加载器: BootStrap(最高层由c++实现,不需要类加载器,JRE/lib/rt.jar)
ExtClassLoader(JRE/lib/ext/*.jar)
ExtClassLoader(JRE/lib/ext/*.jar)
AppClassLoader(classpath指定所有jar或目录)
MyClassLoader(自己定义类加载器)
MyClassLoader(自己定义类加载器)
Java虚拟机中的所有类装载器采用具有父子关系的树形结构进行组织,在实例化每个类装载器对象时,需要为其指定一个父级类装载器对象或者默认采用系统类装载器为其父级类加载
<span style="white-space:pre"> </span>//打印AppClassLoader
System.out.println(Demo.class.getClassLoader().getClass().getName());
//打印ExtClassLoader
System.out.println(Demo.class.getClassLoader().getParent().getClass().getName());
//打印null,实际就是BootStrap,第一个类加载器不是java类,由c++完成
System.out.println(Demo.class.getClassLoader().getParent().getClass().getParent());
委托机制
类加载器采用委托机制,即先委托自己的上一级加载,上一级也委托他的上一级,第一层加载失败再交给下一级,直至返回自身,如果自身加载失败则停止加载并抛出ClassNotFoundException
1、首先当前线程的类加载器去加载线程中的第一个类
2、如果类A中引用了类B,Java虚拟机将使用加载类A的类装载器来加载类B
3、还可以直接调用ClassLoader.loadClass()方法来指定某个类加载器去加载某个类
编写自己的类加载器(模板设计方法)
<span style="white-space:pre"> </span>在eclipse中的某个Java文件上右键-->export-->打包为xxx.jar文件放到jre/lib/ext目录下
Demo.class.getClassLoader().getClass().getName()//打印ExtClassLoader
//证明了委托机制是先由父类加载器加载
当Java虚拟机要加载一个类时,到底派出哪个类加载器去加载呢?
1、首先当前线程的类加载器去加载线程中的第一个类
2、如果类A中引用了类B,Java虚拟机将使用加载类A的类装载器来加载类B
3、还可以直接调用ClassLoader.loadClass()方法来指定某个类加载器去加载某个类
编写自己的类加载器(模板设计方法)
1、自定义的类加载器的必须继承ClassLoader
2、loadClass方法与findClass方法:loadClass即确定的功能,先调用父类的loadclass方法,在使用子类复写的的findclass方法
3、defineClass方法:将一个 byte 数组转换为 Class 类的实例
2、loadClass方法与findClass方法:loadClass即确定的功能,先调用父类的loadclass方法,在使用子类复写的的findclass方法
3、defineClass方法:将一个 byte 数组转换为 Class 类的实例
import java.io.*;
import java.util.Date;
public class ClassLoaderDemo{
public static void main(String[] args)throws Exception{
Class clazz = new MyClassLoader().loadClass("Demo");//通过自定义的类加载器返回class对象
//这种方法是错误的,因为这里使用到Demo,类加载器AppClassLoader就会去加载这个类
//而我们是通过自己的方式来加载这个类的
//Demo demo = (Demo)clazz.newInstance();
Date date = (Date)clazz.newInstance();
System.out.println(date);
System.out.println(new Demo());
}
class MyClassLoader extends ClassLoader{ //自定义类加载器
FileInputStream fis ;
ByteArrayOutputStream bos ;
byte[] outbyte;
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException{//只需实现findClass方法
String filename = "E:\\new\\"+name+".class";//实际就是MyclassLoader管理的目录
try{
fis = new FileInputStream(filename);
bos = new ByteArrayOutputStream();//字节数组输出流
int readbyte = 0;
while((readbyte=fis.read())!=-1){
readbyte = readbyte^0xff//对读取的数据操作(解密),自定义类加载器的目的
bos.write(readbyte);
}
outbyte = bos.toByteArray() ;
fis.close();
bos.close();
}catch(Exception e){}
System.out.println("MyClassLoader");
return defineClass(name,outbyte,0,outbyte.length);//调用父类的defineClass方法,返回一个class
}
}
class Demo extends Date{//定义用于被记载的类,继承Date
public String toString(){
return "Demo";
}
}
重要总结:因为类加载器的委托机制,父加载器会先搜索自己管辖目录下的.class文件,同时子类加载器并没有覆盖loadClass方法,所以想使用自己编写的类加载器,就必须把父类加载器目录下的class文件删除(AppClassLoader管理的classpath和ExtClassLoader管理的(JRE/lib/ext)
tomcat使用自己的类加载器加载class
1、编写一个能打印出自己的类加载器名称和当前类加载器的父子结构关系链的MyServlet,正常发布后,看到打印结果为WebAppClassloader。
2、把MyServlet.class文件打jar包,放到ext目录中,重启tomcat,发现找不到HttpServlet的错误。ExtclassLoader加载MyServlet.class,同时MyServlet.class中用到了HttpServlet.class。虽然tomcat自定义的类加载器可以加载HttpServlet.class,由于委托机制,父类级加载器找不到并不交给子类,也不知道该交给哪个子类(前提为不是有子类级加载器发起的),直接报错
3、把servlet.jar也放到ext目录中,问题解决了,打印的结果是ExtclassLoader
第一种情况:WebAppClassLoader加载MyServlet.class,MyServlet.class使用了HttpServlet.class,
同样由WebAppClassLoader加载HttpServlet.class
第二种情况:ExtclassLoader加载了Servlet.class,同样也由ExtclassLoader加载HttpServlet.class
================================
代理=======================================
代理架构与AOP
编写一个与目标类具有相同接口的代理类,代理类的每个方法调用目标类的相同方法,并在调用方法时加上系统功能的代码
交叉业务的编程问题即为面向方面的编程(Aspect oriented program ,简称AOP),AOP的目标就是要使交叉业务模块化。
可以采用将切面代码移动到原始方法的周围,这与直接在方法中编写切面代码的运行效果是一样的,不同对象的不同方法中需要添加相同的功能,对这个相同功能的实现就是切面编程
使用代理技术正好可以解决这种问题,代理是实现AOP功能的核心和关键技术
编写一个与目标类具有相同接口的代理类,代理类的每个方法调用目标类的相同方法,并在调用方法时加上系统功能的代码
交叉业务的编程问题即为面向方面的编程(Aspect oriented program ,简称AOP),AOP的目标就是要使交叉业务模块化。
可以采用将切面代码移动到原始方法的周围,这与直接在方法中编写切面代码的运行效果是一样的,不同对象的不同方法中需要添加相同的功能,对这个相同功能的实现就是切面编程
使用代理技术正好可以解决这种问题,代理是实现AOP功能的核心和关键技术
动态代理技术
(1)JVM可以在运行期动态生成出类的字节码,这种动态生成的类往往被用作代理类,即动态代理类。
(2)JVM生成的动态类必须实现一个或多个接口,所以,JVM生成的动态类只能用作具有相同接口的目标类的代理。
(3)CGLIB库可以动态生成一个类的子类,一个类的子类也可以用作该类的代理,所以,如果要为一个没有实现接口的类生成动态代理类,那么可以使用CGLIB库
(1)JVM可以在运行期动态生成出类的字节码,这种动态生成的类往往被用作代理类,即动态代理类。
(2)JVM生成的动态类必须实现一个或多个接口,所以,JVM生成的动态类只能用作具有相同接口的目标类的代理。
(3)CGLIB库可以动态生成一个类的子类,一个类的子类也可以用作该类的代理,所以,如果要为一个没有实现接口的类生成动态代理类,那么可以使用CGLIB库
代理类的各个方法中通常除了要调用目标的相应方法和对外返回目标返回的结果外,还可以在代理方法中的如下四个位置加上系统功能代码:
1.在调用目标方法之前
2.在调用目标方法之后
3.在调用目标方法前后
4.在处理目标方法异常的catch块中
1.在调用目标方法之前
2.在调用目标方法之后
3.在调用目标方法前后
4.在处理目标方法异常的catch块中
分析JVM动态生成的类
//动态代理类,继承Collection接口,类加载器使用和接口一样的
Class clazzProxy = Proxy.getProxyClass(Collection.class.getClassLoader(),Collection.class);
System.out.println(clazzProxy.getName());//打印proxy$0
//打印构造方法列表
Constructor[] constructors = clazzProxy.getConstructors();//得到所有的构造方法
for(Constructor constructor:constructors){
String name = constructor.getName();
StringBuilder sb = new StringBuilder(name);
sb.append('(');
Class[] clazzParams = constructor.getParameterTypes();//得到方法中的参数
for(Class clazzParam:clazzParams){
sb.append(clazzParam.getName());
sb.append(',');
}
if(clazzParams!=null&&clazzParam.length==0){
sb.deleteCharAt(sb.length()-1);
sb.append(')');
}
System.out.println(sb);
· //打印方法列表
Method[] methods = clazzProxy.getMethods();
for(Method method:methods){
String name = method.getName();
StringBuilder sb = new StringBuilder(name);
sb.append('(');
Class[] clazzParams = method.getParameterTypes();
for(Class clazzParam:clazzParams){
sb.append(clazzParam.getName());
sb.append(',');
}
if(clazzParams!=null&&clazzParam.length==0){
sb.deleteCharAt(sb.length()-1);
sb.append(')');
System.out.println(sb);
}
<span style="white-space: pre; "> </span>结果:动态类只有一个构造方法:$Proxy0(java.lang.reflect.InvocationHandler)
方法列表为:从接口继承的方法和从Object类继承的方法<strong>
</strong>
创建动态类的实例对象:
三个条件:(1)该动态类实现的接口(2)传递类加载器(3)InvocationHandler对象
1、通过反射得到构造函数,创建对象
1、通过反射得到构造函数,创建对象
Constructor constructor = clazzProxy.getConstructor(InvocationHandler.class);
class myInvocationHandler1 implements InvocationHandler{//实现InvocationHandler接口的类
public Object invoke(Object proxy,Method method,Object[] args){//实现接口的方法
return null;
}
}
//参数为自定义类的对象,创建的对象为其继承接口的类型
Collection proxy1 = (Collection)constructor.newInstance(new myInvocationHandler1());
proxy.clear();//Collection的方法
2、通过匿名内部类创建对象
Collection proxy2 = (Collection)constructor.newInstance(new InvocationHandler(){
public Object invoke(Object proxy,Method method,Object[] args)throws Throwable{
return null;
}
});
3、通过 Proxy的静态方法newProxyInstance
Collection proxy3 = (Collection) Proxy.newProxyInstance(
Collection.class.getClassLoader(),
new Class[]{Collection.class},//实现接口的数组(可变参数必须是最后一个)
new InvocationHandler(){
public Object invoke(Object proxy,Method method,Object[] args)throws Throwable{
return null;
}
});
分析动态生成的类的内部代码
动态生成的类实现了Collection接口(可以实现若干接口),生成的类有Collection接口中的所有方法和接受InvocationHandler参数的构造方法
1.构造方法接受一个InvocationHandler对象:
class proxy$0{
InvocationHandler handler;
proxy$0(InvocationHandler handler){
this.handler = handler; //接受此对象,内部使用handler.invoke()方法
}
}
2.实现Collection接口的动态类中的各个方法的代码又是怎样的呢?InvocationHandler接口中定义的invoke方法接受的三个参数又是什么意思?
Client程序调用objProxy.add(“abc”)方法时,涉及三要素:objProxy对象、 add方法、 “abc”参数
Class Proxy$ { | | |
add(Object object) { | | |
return handler.invoke(Object proxy, Method method, Object[] args);
}
}
3.动态代理类中size方法的实现原理:
int size(){
return handler.invoke(this,this.getClass.getMethod("size"),null)
}
为什么动态类的实例对象的getClass()方法返回了正确结果(Proxy$0)而没有调用invoke()返回ArrayList呢?
调用代理对象的从Object类继承的hashCode, equals, 或toString这几个方法时,代理对象将调用请求转发给InvocationHandler对象,对于其他方法,则不转发调用请求。
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{
//ArrayList target = new ArrayList();//在这定义,每次定义都产生一个target
long btime=System.curentTimeMillis();
//实际调用目标类的方法,知道目标类有此方法是因为他们实现了相同的接口
Object value = method.invoke(target,args);
long etime=System.curentTimeMillis();
System.out.println(""+(etime-btime))
return value;
}
});
proxy3.add("xxx");//new InvocationHandler().invoke(proxy3,add,"xxx");
proxy3.add("yyy");
proxy3.size();
让动态生成的类成为目标类的代理
1、将目标类作为参数传递:
为InvocationHandler实现类注入目标类的实例对象,不能采用匿名内部类的形式了。让匿名的InvocationHandler实现类访问外面方法中的目标类实例对象的final类型的引用变量
2、将系统功能代码模块化,即将切面代码也改为通过参数形式提供:
把要执行的代码装到一个对象的某个方法里,然后把这个对象作为参数传递,接收者只要调用这个对象的方法,即等于执行了外界提供的代码
3、eclipse重构出一个getProxy方法绑定接收目标同时返回代理对象
<span style="white-space:pre"> </span>class ProxyTest{
public Object getProxy(final Object target,final Advice advice){//Advice advice参数调用即实现功能接口
Object proxy = 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 value = method.invoke(target,args);//调用目标类的方法
advice.afterMethod(method);
return value;
}
});
return proxy;
}
}
public interface Advice{ //定义一个通行契约,想实现系统的功能,必须实现此接口
void beforeMethod(Method method);
void afterMethod(Method method);
}
class MyAdvice implements Advice{//此类实现了Advice接口,实现了具体的方法
long btime,etime;
public void beforeMethod(Method method){//实现方法
btime = System.curentTimeMillis();
System.out.println(method.getName+btime);
}
public void afterMethod(Method method){
etime = System.curentTimeMillis();
System.out.println(method.getName+etime);
}
}
proxy = getProxy(new ArrayList(),new MyAdvice());//生成动态代理类,参数:目标类,功能类
proxy.add("xxx");