------- android培训、java培训、期待与您交流! ----------
类加载器
类加载器是加载类的工具,它的作用是将硬盘上的 .class 文件加进内存,并对之进行一些处理。java 虚拟机中可安装多个类加载器,系统默认三个主要的类加载器,每个类加载器负责加载特定的类:
BootStrap ExtClassLoader AppClassLoader
类加载器也是 java 类,因为其他是 java 类的类加载器也要被类加载器加载,显然必须有第一个类加载器不是 java 类 , 这正是: BootStrap ,它是由 C++ 编写的。
java 虚拟机中的所有类加载器采用具有父子关系的树形结构进行组织,在实例化每个类加载器对象时,需要为其指定一个父级类加载器对象,或者默认采用系统类加载器为其父级类加载器。
类加载器之间的父子关系和管辖范围图
|---Bootstrap ClassLoader ->JRE/lib/rt.jar
|---Extension ClassLoader ->JRE/lib/ext/*.jar
|---ApplicationClassLoader ->ClassPath指定的所有jar或目录。
|--MyClassLoader ->指定的特殊目录(自定义类加载器)
|--itcastClassLoader->指定的特殊目录(自定义类加载器)
类加载器的委托机制
(A)类加载器的选择:当java虚拟机要加载一个类时,到底派出那个类加载器去加载?
(1).首先当前线程的类加载器去加载线程中的第一个类。
(2).如果类A中引用了B类,java虚拟机将使用类A的加载器来加载类B
(3).还可以直接调用ClassLoader.loadClass()方法来指定某个类加载器去加载某个类。
(B)类加载器怎样加载:每个类加载器加载类时,又先委托给器上级类加载器。当所有祖宗类加载器没有加载到类,回到发起者类加载器,还加载不了,则抛出
ClassNotFoundException, 不再去找发起者类加载器的儿子,因为没有getChild方法;即使有,那么有多个儿子,到底要找那个一个呢?
思考:能否自己写一个java.lang.System类?
通常不可以,因为根据类加载器的委托机制,每次都会先委托给上级寻找,先找到的是Jdk中的java.lang.System,而不是自己写的 java.lang.System类;但可以自己写一个类加载器,撇开器委托机制,不给它指定上级,来加载自己的类。
注意:需要说明一下Java虚拟机是如何判定两个Java类是相同的。Java 虚拟机不仅要看类的全名是否相同,还要看加载此类的类加载器是否一样。只有两者都相同的情况,才认为两个类是相同的。即便是同样的字节代码,被不同的类加载器加载之后所得到的类,也是不同的。
编写自己的类加载器
知识讲解:
(1).自定义的类加载器必须继承ClassLoader
(2)loadClass方法与findClass方法:只需重新findClass方法,就会跳出委托机制。
(3)defineClass方法。
编程步骤:
(1).编写一个队文件内容进行监督加密的程序
(2).编写了一个自己的类加载器,可实现对加密的类进行撞在和解码。
(3)编写一个程序调用类加载器加载类,在源程序中不能用该类定义引用变量,因为编程器无法识别这个类。程序中可以除了使用C
lassLoader.load方法外,还可以使用使用设置线程的上下文加载器或者系统加载器,然后再使用Class.forName.
A---要加载的测试类代码:
import java.util.Date;
public class ClassLoaderAttachments extends Date {
@Override
public String toString() {
return "hello,java";
}
}
注意:这里最好继承一个类,以便于获取其实例时,定义父类的引用,而不是它自己的引用。
B---编写自己的类加载器代码:(里边包含main方法,以便于对类加密解密)
import java.io.ByteArrayOutputStream;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
public class MyClassLoader extends ClassLoader {
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);
System.out.println("xxxxx");
fis.close();
fos.close();
}
private String classDir;
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
// TODO Auto-generated method stub
String classFileName = classDir+"\\"+name+".class";
try {
FileInputStream fis = new FileInputStream(classFileName);
ByteArrayOutputStream bos = new ByteArrayOutputStream();
cypher(fis,bos);
byte[] b = bos.toByteArray();
return defineClass(b, 0, b.length);
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println("aaaa");
return super.findClass(name);
}
public MyClassLoader(){}
public MyClassLoader(String classDir){
this.classDir = classDir;
}
public static void cypher(InputStream ips,OutputStream ops) throws Exception{
int b = -1;
while((b=ips.read())!=-1){
ops.write(b^0xff);
}
}
C--调用自己写的类加载器
Class clazz = new MyClassLoader("itcastlib").loadClass("ClassLoaderAttachments");
Date d = (Date)clazz.newInstance();
System.out.println(d.toString());
类加载器的一个高级问题
编写一个能打印出自己的类加载器名称和当前类加载器的父子结构关系链的MyServlet,正常发布后,看到打印结果为WebAppClassloader。
把MyServlet.class文件打jar包,放到ext目录中,重启tomcat,发现找不到HttpServlet的错误。
把servlet.jar也放到ext目录中,问题解决了,打印的结果是ExtclassLoader 。
父级类加载器加载的类无法引用只能被子级类加载器加载的类
模板方法设计模式:
父类->loadClass/findClass/得到class文件的转换成字节码用父类的defineClass方法。
子类:(自己干)
子类2:(自己干)
总体的流程已经在父类定义好,实现的具体细节有一部分父类不清楚,留给子类完成。
代理与AOP
<代理模式>简单示例
目标类:
class X {
void sayHello(){
System.out.println("hello java");
}
}
代理类:
XProxy{
void sayHello(){
starttime;
X.sayHello();
endTime;
}
}
程序中的代理
(1)要为已存在的多个具有相同接口的目标类的各个方法增加一系列的系统功能,例如,异常处理,日志、计算方法等的运行时间、事务管理等等,你如何做?
(2)编写一个与目标类具有相同接口的代理类,代理类的每个方法调用目标类的相同方法,并在调用方法时加上系统功能的代码。
(3)如果采用工厂模式和配置文件的方式进行管理,则不需要修改客户端程序,在配置文件中配置是使用目标类还是代理类,这样以后很容易切换。譬如,想要日志功能时就配置代理类,否则配置目标类,这样增加系统功能很容易。以后运行一段时间后,又想去掉系统功能也很容易。
3.代理分为两种:静态代理和动态代理
AOP
系统中存在交叉业务,一个交叉业务就是要切入到系统中的一个方面.
交叉业务的编程问题即为面向方面的编程(Aspect oriented program ,简称AOP)AOP的目标就是要使交叉业务模块化。
可以采用将切面代码移动到原始方法的周围,这与直接在方法中编写切面代码的运行效果是一样的.
使用代理技术正好可以解决这种问题,代理是实现AOP功能的核心和关键技术。
动态代理技术
JVM可以在运行期动态生成出类的字节码,这种动态生成的类往往被用作代理类,即动态代理类。
JVM生成的动态类必须实现一个或多个接口,所以,JVM生成的动态类只能用作具有相同接口的目标类的代理。
CGLIB库可以动态生成一个类的子类,一个类的子类也可以用作该类的代理,所以,如果要为一个没有实现接口的类生成动态代理类,那么可以使用CGLIB库。
代理类的各个方法中通常除了要调用目标的相应方法和对外返回目标返回的结果外,还可以在代理方法中的如下四个位置加上系统功能代码:
1.在调用目标方法之前
2.在调用目标方法之后
3.在调用目标方法前后
4.在处理目标方法异常的catch块中
创建JVM动态代理方法:
方法一:获取动态类字节码(Class)--->用字节码获取Constructor---->用Constructor创建动态类实例
方法二:直接用Proxy的静态方法newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)
注意:如果InvocationHandler的invoke方法只是Eclipse自动生成的代码的话,那现在得到的代理类实例,也只是一个骨架,没有代理的目标,就像一个代理某个产品的专卖店,只是装饰好了门面,没有摆放产品。
在invoke前和后的代码,因为跟其目标没有多大关系,可专门封装在一个类中;另外,在invoke中也可以对args参数进行过滤(比如如果是args传入的是字符串,可以实现敏感信息过滤)。
Proxy动态代理类在调用方法时,从Ojbect继承的方法 的 hashCode、equals 和 toString三个方法会委托给InvocationHandler,其他从Object 继承的方法都不委托。
实现AOP功能的封装和配置
功能描述:建立一个BeanFactory,可以根据配置文件中的bean名称到底一个JavaBean,如果bean的名字的是“ProxyFactoryBean”则返回ProxyFactoryBean根据配置文件中的".advice"和“.target”提供的目标和Advice返回的代理类。
1.ProxyFactoryBean类代码
package cn.itcast.day3.aopframework;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import cn.itcast.day3.Advice;
public class ProxyFactoryBean {
private Advice advice;
private Object target;
public Object getProxy(){
Object proxy = Proxy.newProxyInstance(
target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
new InvocationHandler(){
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
advice.startMethod(method);
Object retVal = method.invoke(target, args);
advice.beforeMethod(method);
/*long endTime = System.currentTimeMillis();
System.out.println(method.getName()+":"+(endTime-beginTime));*/
return retVal;
}
});
return proxy;
}
public Advice getAdvice() {
return advice;
}
public void setAdvice(Advice advice) {
this.advice = advice;
}
public Object getTarget() {
return target;
}
public void setTarget(Object target) {
this.target = target;
}
}
2.BeanFacotry代码:
package cn.itcast.day3.aopframework;
import java.io.IOException;
import java.io.InputStream;
import java.util.Properties;
import cn.itcast.day3.Advice;
public class BeanFactory {
Properties props = new Properties();
public BeanFactory(InputStream is){
try {
props.load(is);
} catch (IOException e) {
e.printStackTrace();
}
}
public Object getBean(String name){
Object bean = null;
try {
bean = Class.forName(props.getProperty(name)).newInstance();
} catch (Exception e1) {
e1.printStackTrace();
}
if(bean instanceof ProxyFactoryBean ){
Object proxy = null;
try {
Advice advice = (Advice) Class.forName(
props.getProperty(name + ".advice")).newInstance();
Object target = Class.forName(
props.getProperty(name + ".target")).newInstance();
ProxyFactoryBean pfb = (ProxyFactoryBean) bean;
pfb.setAdvice(advice);
pfb.setTarget(target);
proxy = ((ProxyFactoryBean) bean).getProxy();
} catch (Exception e) {
e.printStackTrace();
}
return proxy;
}
return bean;
}
}
3.配置文件内容:
xxx=java.util.ArrayList
#xxx=cn.itcast.day3.aopframework.ProxyFactoryBean
xxx.target=java.util.ArrayList
xxx.advice=cn.itcast.day3.MyAdvice
4.框架测试类:
package cn.itcast.day3.aopframework;
import java.io.InputStream;
public class AopFrameworkTest {
public static void main(String[] args) {
// TODO Auto-generated method stub
InputStream is = AopFrameworkTest.class.getResourceAsStream("config.properties");
Object bean = new BeanFactory(is).getBean("xxx");
System.out.println(bean.getClass().getName());
}
}
Spring的两大核心技术----AOP和代理,上边的代码即实现了一个简单的AOP框架。