Java基础加强-3
***************************************************************************
类加载器
l Java虚拟机中可以安装多个类加载器,系统默认三个主要类加载器,每个类负责加载特定位置的类:BootStrap,ExtClassLoader,AppClassLoader
l 类加载器也是Java类,因为其他是java类的类加载器本身也要被类加载器加载,显然必须有第一个类加载器不是不是java类,这正是BootStrap。
l Java虚拟机中的所有类装载器采用具有父子关系的树形结构进行组织,在实例化每个类装载器对象时,需要为其指定一个父级类装载器对象或者默认采用系统类装载器为其父级类加载。
1. 如下代码说明放置在不同位置的类确实由不同的类加载器加载的:
System.out.println(ClassLoaderTest.class.getClassLoader().getClass().getName());
//将上面语句的测试类改为System则抛NullPointerException,这两个类存放位置不同
System.out.println(System.class.getClassLoader().getClass().getName());
改为System.out.println(System.class.getClassLoader());打印的结果为null。
2.用下面的代码让查看类加载器的层次结构关系
ClassLoaderloader = ClassLoaderTest.class.getClassLoader();
//打印出当前的类装载器,及该类装载器的各级父类装载器
while(loader !=null){
System.out.println(loader.getClass().getName());
loader =loader.getParent();
}
【类加载器的委托机制】:
l 当Java虚拟机要加载一个类时,到底派出哪个类加载器去加载呢?
Ø 首先当前线程的类加载器去加载线程中的第一个类。
Ø 如果类A中引用了类B,Java虚拟机将使用加载类A的类装载器来加载类B。
Ø 还可以直接调用ClassLoader.loadClass()方法来指定某个类加载器去加载某个类。
l 每个类加载器加载类时,又先委托给其上级类加载器。
Ø 当所有祖宗类加载器没有加载到类,回到发起者类加载器,还加载不了,则抛ClassNotFoundException,不是再去找发起者类加载器的儿子,因为没有getChild方法,即使有,那有多个儿子,找哪一个呢?
Ø 对着类加载器的层次结构图和委托加载原理,解释先前将ClassLoaderTest输出成jre/lib/ext目录下的itcast.jar包中后,运行结果为ExtClassLoader的原因。
----------------------------------------------------------------------------------------------------------------------
每个ClassLoader本身只能分别加载特定位置和目录中的类,但它们可以委托其他的类装载器去加载类,这就是类加载器的委托模式。类装载器一级级委托到BootStrap类加载器,当BootStrap无法加载当前所要加载的类时,然后才一级级回退到子孙类装载器去进行真正的加载。当回退到最初的类装载器时,如果它自己也不能完成类的装载,那就应报告ClassNotFoundException异常。
把先前编写的类加入到jdk的rt.jar中,会有怎样的效果呢?不行!!!看来是不能随意将自己的class文件加入进rt.jar文件中的。
----------------------------------------------------------------------------------------------------------------------
【编写自己的类加载器】:
l 知识讲解:
Ø 自定义的类加载器的必须继承ClassLoader
Ø loadClass方法委托父类加载器去加载,父类无法加载时调用findClass方法
Ø defineClass方法
l 编程步骤:
Ø 编写一个对文件内容进行简单加密的程序。
Ø 编写了一个自己的类装载器,可实现对加密过的类进行装载和解密。
Ø 编写一个程序调用类加载器加载类,在源程序中不能用该类名定义引用变量,因为编译器无法识别这个类。程序中可以除了使用ClassLoader.load方法之外,还可以使用设置线程的上下文类加载器或者系统类加载器,然后再使用Class.forName。
l 实验步骤:
Ø 对不带包名的class文件进行加密,加密结果存放到另外一个目录,例如:java MyClassLoader MyTest.class F:\itcast
Ø 运行加载类的程序,结果能够被正常加载,但打印出来的类装载器名称为AppClassLoader:java MyClassLoaderMyTest F:\itcast
Ø 用加密后的类文件替换CLASSPATH环境下的类文件,再执行上一步操作就出问题了,错误说明是AppClassLoader类装载器装载失败。
Ø 删除CLASSPATH环境下的类文件,再执行上一步操作就没问题了。
代码示例:
import java.io.*;
import java.lang.reflect.*;
public class MyClassLoader extends ClassLoader
{
private String path = null;
publicMyClassLoader(String path) throws Exception{//检查文件是否存在
File f = newFile(path);
if(!f.isDirectory()){
throw newRuntimeException(path + " is not a directory");
}
this.path =path;
}
public ClassfindClass(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();
returndefineClass(name,buf,0,buf.length);
}catch(Exceptione){
throw newClassNotFoundException(name + " is not found!");
}
return null;
}
public static voidcypher(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 voidmain(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());
*/
//Methodm = 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 ofclass MyTest with modifiers ""
* // *
class MyTest
{
public void test()
{
System.out.println("hello,www.it315.org");
}
}
* /
代理技术
【AOP面向方面编程】
l 系统中存在交叉业务,一个交叉业务就是要切入到系统中的一个方面,如下所示:
安全 事务 日志
StudentService ------|----------|------------|-------------
CourseService ------|----------|------------|-------------
MiscService ------|----------|------------|-------------
l 用具体的程序代码描述交叉业务:
method1 method2 method3
{ { {
------------------------------------------------------切面
.... .... ......
------------------------------------------------------切面
} } }
l 交叉业务的编程问题即为面向方面的编程(Aspect oriented program ,简称AOP),AOP的目标就是要使交叉业务模块化。可以采用将切面代码移动到原始方法的周围,这与直接在方法中编写切面代码的运行效果是一样的,如下所示:
------------------------------------------------------切面
func1 func2 func3
{ { {
.... .... ......
} } }
------------------------------------------------------切面
l 使用代理技术正好可以解决这种问题,代理是实现AOP功能的核心和关键技术。
【动态代理技术】:
l 要为系统中的各种接口的类增加代理功能,那将需要太多的代理类,全部采用静态代理方式,将是一件非常麻烦的事情!写成百上千个代理类,是不是太累!
l JVM可以在运行期动态生成出类的字节码,这种动态生成的类往往被用作代理类,即动态代理类。
l JVM生成的动态类必须实现一个或多个接口,所以,JVM生成的动态类只能用作具有相同接口的目标类的代理。
l CGLIB库可以动态生成一个类的子类,一个类的子类也可以用作该类的代理,所以,如果要为一个没有实现接口的类生成动态代理类,那么可以使用CGLIB库。
l 代理类的各个方法中通常除了要调用目标的相应方法和对外返回目标返回的结果外,还可以在代理方法中的如下四个位置加上系统功能代码:
Ø 1.在调用目标方法之前
Ø 2.在调用目标方法之后
Ø 3.在调用目标方法前后
Ø 4.在处理目标方法异常的catch块中
l 创建实现了Collection接口的动态类和查看其名称,分析Proxy.getProxyClass方法的各个参数。
l 编码列出动态类中的所有构造方法和参数签名
l 编码列出动态类中的所有方法和参数签名
Class proxyCls1 =Proxy.getProxyClass(Collection.class.getClassLoader(), Collection.class);
System.out.println(proxyCls1.getName());
System.out.println("-------------constructorslist-------------");
Constructor[] cons =proxyCls1.getConstructors();
for (Constructorconstructor : cons) {
Class[] paraTypes= constructor.getParameterTypes();
System.out.print(constructor.getName()+"(");
for (int i =0;i<paraTypes.length;i++) {
if(i>0)
System.out.print(","+paraTypes[i].getName());
else
System.out.print(paraTypes[i]);
}
System.out.println(")");
}
System.out.println();
System.out.println("-------------methodslist-------------");
Method[] methods =proxyCls1.getMethods();
for (Method method :methods) {
Class[] paraTypes= method.getParameterTypes();
System.out.print(method.getName()+"(");
for (int i =0;i<paraTypes.length;i++) {
if(i>0)
System.out.print(","+paraTypes[i].getName());
else
System.out.print(paraTypes[i]);
}
System.out.println(")");
}
l 创建动态类的实例对象
Ø 用反射获得构造方法
Ø 编写一个最简单的InvocationHandler类
Ø 调用构造方法创建动态类的实例对象,并将编写的InvocationHandler类的实例对象传进去
Ø 打印创建的对象和调用对象的没有返回值的方法和getClass方法,演示调用其他有返回值的方法报告了异常。
Ø 将创建动态类的实例对象的代理改成匿名内部类的形式。
l 总结思考:让jvm创建动态类及其实例对象,需要给它提供哪些信息?
Ø 三个方面:
• 生成的类中有哪些方法,通过让其实现哪些接口的方式进行告知;
• 产生的类字节码必须有个一个关联的类加载器对象;
• 生成的类中的方法的代码是怎样的,也得由我们提供。把我们的代码写在一个约定好了接口对象的方法中,把对象传给它,它调用我的方法,即相当于插入了我的代码。提供执行代码的对象就是那个InvocationHandler对象,它是在创建动态类的实例对象的构造方法时传递进去的。在上面的InvocationHandler对象的invoke方法中加一点代码,就可以看到这些代码被调用运行了。
l 用Proxy.newInstance方法直接一步就创建出代理对象。
Collection c =(Collection)Proxy.newProxyInstance(Collection.class.getClassLoader(),
newClass[]{Collection.class},
newInvocationHandler(){
ArrayListalist = new ArrayList();
publicObject invoke(Object proxy,Method method,Object[] args)throws Throwable{
longstart = System.currentTimeMillis();
ObjectretVal = method.invoke(alist,args);
longend = System.currentTimeMillis();
System.out.println(method.getName()+" running time is:"+(end-start));
returnretVal;
}
}
);
【动态代理的工作原理】:
Client程序调用objProxy.add(“abc”)方法,涉及三点:objProxy对象、add方法、“abc”参数
Class Proxy$ {
add(Object object) {
return handler.invoke(Object proxy, Method method, Object[] args);
}
}
调用调用代理对象的从Object类继承的hashCode, equals, 或toString这几个方法时,代理对象将调用请求转发给InvocationHandler对象,对于其他方法,则不转发调用请求。
原理图:
l 怎样将目标类传进去?
Ø 直接在InvocationHandler实现类中创建目标类的实例对象,可以看运行效果和加入日志代码,但没有实际意义。
Ø 为InvocationHandler实现类注入目标类的实例对象,不能采用匿名内部类的形式了。
Ø 让匿名的InvocationHandler实现类访问外面方法中的目标类实例对象的final类型的引用变量。
l 将创建代理的过程改为一种更优雅的方式,eclipse重构出一个getProxy方法绑定接收目标同时返回代理对象,让调用者更懒惰,更方便,调用者甚至不用接触任何代理的API。
l 将系统功能代码模块化,即将切面代码也改为通过参数形式提供,怎样把要执行的系统功能代码以参数形式提供?
Ø 把要执行的代码装到一个对象的某个方法里,然后把这个对象作为参数传递,接收者只要调用这个对象的方法,即等于执行了外界提供的代码!
Ø 为bind方法增加一个Advice参数。
代码示例:
Main函数:
ArrayList arr = new ArrayList();
Collection cc = (Collection)getProxy(arr,new MyAdvice());
---------------------------------------------------------------------
public static ObjectgetProxy(final Object target,final Advice advice){
Object obj =Proxy.newProxyInstance(
target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
newInvocationHandler(){
publicObject invoke(Object target_e,Method method,Object[] args)throws Throwable{
advice.beforeMethod(method);
ObjectretVal = method.invoke(target, args);
advice.afterMethod(method);
returnretVal;
}
}
);
return obj;
}
----------------------------------------------------------------------------
public class MyAdvice implements Advice {
long start = 0;
@Override
public voidbeforeMethod(Method method) {
start =System.currentTimeMillis();
System.out.println("start from time: "+start);
}
@Override
public voidafterMethod(Method method) {
long end =System.currentTimeMillis();
System.out.println("end from time: "+end);
System.out.println(method.getName()+" running time is:"+(end-start));
}
}
【实现AOP功能的封装与配置】:
l 工厂类BeanFactory负责创建目标类或代理类的实例对象,并通过配置文件实现切换。其getBean方法根据参数字符串返回一个相应的实例对象,如果参数字符串在配置文件中对应的类名不是ProxyFactoryBean,则直接返回该类的实例对象,否则,返回该类实例对象的getProxy方法返回的对象。
l BeanFactory的构造方法接收代表配置文件的输入流对象,配置文件格式如下:
#xxx=java.util.ArrayList
xxx=cn.itcast.ProxyFactoryBean
xxx.target=java.util.ArrayList
xxx.advice=cn.itcast.MyAdvice
l ProxyFacotryBean充当封装生成动态代理的工厂,需要为工厂类提供哪些配置参数信息?
Ø 目标
Ø 通知
l 编写客户端应用:
Ø 编写实现Advice接口的类和在配置文件中进行配置
Ø 调用BeanFactory获取对象
---------------------------------------------------------------------
public class BeanFactory {
private Properties props =new Properties();
private InputStream ips =null;
publicBeanFactory(InputStream ips){
this.ips = ips;
try {
props.load(this.ips);
} catch (IOExceptione) {
e.printStackTrace();
}
}
public ObjectgetBean(String beanName){
Object obj = null;
try {
String className =props.getProperty(beanName);
Class cls =Class.forName(className);
obj =cls.newInstance();
if(obj instanceofProxyFactoryBean){
StringtargetName = props.getProperty(beanName+".target");
StringadviceName = props.getProperty(beanName+".advice");
ProxyFactoryBean proxyFactoryBean = (ProxyFactoryBean)obj;
proxyFactoryBean.setTarget(Class.forName(targetName).newInstance()); proxyFactoryBean.setAdvice((Advice)Class.forName(adviceName).newInstance());
obj =proxyFactoryBean.getProxy();
}
} catch (Exception e){
}
return obj;
}
}
---------------------------------------------------------------------
public class ProxyFactoryBean {
private Object target =null;
private Advice advice =null;
publicProxyFactoryBean(){}
public Object getTarget(){
return target;
}
public voidsetTarget(Object target) {
this.target = target;
}
public Advice getAdvice(){
return advice;
}
public voidsetAdvice(Advice advice) {
this.advice = advice;
}
public Object getProxy(){
Object obj =Proxy.newProxyInstance(
target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
newInvocationHandler(){
public Object invoke(Objecttarget_e,Method method,Object[] args)throws Throwable{
advice.beforeMethod(method);
ObjectretVal = method.invoke(target, args);
advice.afterMethod(method);
returnretVal;
}
}
);
return obj;
}
}
---------------------------------------------------------------------
配置文件:config.properties
xxx=java.util.ArrayList
#xxx=cn.itcast.day3.FactoryBeanDemo.ProxyFactoryBean
xxx.target=java.util.ArrayList
xxx.advice=cn.itcast.day3.MyAdvice
---------------------------------------------------------------------
public static void main(String[] args){
// TODO Auto-generatedmethod stub
InputStream ips =Test.class.getResourceAsStream("config.properties");
BeanFactorybeanFactory = new BeanFactory(ips);
Object obj =beanFactory.getBean("xxx");
System.out.println(obj.getClass().getName());
Collection c =(Collection)obj;
c.add("11111");
c.add("22222");
System.out.println(c.toString());
}