黑马程序员——高新技术5——类加载器和动态代理

------- android培训java培训、期待与您交流! ----------

类加载器入门

类加载器就是用来将类从.class文件加载到内存中的代码。Java虚拟机主要有三个类加载器:BootStrap、ExtClassLoader、AppClassLoader。BootStrap是C++类,它位于java虚拟机内核中,它负责加载rt.jar包中的类,及ExtClassLoader和AppClassLoader两个类加载器。ExtClassLoader和AppClassLoader本身也是Java类。可以用反射的方式查看一个类的类加载器,方式如下:

public class ClassLoaderDemo {
	public static void main(String[] args) {
		//一般用户定义的类都是由AppClassLoader类加载
		System.out.println(
				ClassLoaderDemo.class.getClassLoader().getClass().getName()
				);//sun.misc.Launcher$AppClassLoader
		//System类是由BootStrap类加载器加载,BootStrap不是java类,不能被反射到
		System.out.println(
				System.class.getClassLoader()
				);//null  
	}
}
类加载器有自己的加载层次结构,如下所示:

黑马程序员——高新技术5——类加载器和动态代理_第1张图片

其中都是由上一层的类加载器去加载下一层的类。写代码验证一下此加载层次结构

public class ClassLoaderDemo {
	public static void main(String[] args) {
		ClassLoader loader=ClassLoaderDemo.class.getClassLoader();
		while(loader!=null){
			System.out.println(loader.getClass().getName());
			loader=loader.getParent();
		}
		System.out.println(loader);
	}
}

运行结果:

sun.misc.Launcher$AppClassLoader
sun.misc.Launcher$ExtClassLoader
null

如果把ClassLoaderDemo类导出成JAR包到jdk\jre\lib\ext\下,则ClassLoaderDemo类会由ExtClassLoader类加载器加载,此时重新运行上面的程序,结果会变成:

sun.misc.Launcher$ExtClassLoader
null

由此说明当AppClassLoader类加载器不会加载ExtClassLoader加载过的类。ExtClassLoader不会加载BootStrap加载过的类。

对字节码文件加密

编写一个方法可以对.class文件加密,代码如下:

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;

public class MyEncryption {
	public static void main(String[] args) {
		String sourcePath="D:\\Code\\Java\\JustForEnter\\bin\\enhance\\forfiveblog\\Attachment.class";
		String destinationPath="D:\\Code\\Java\\JustForEnter\\testData\\Attachment.class";
		encrypt(sourcePath, destinationPath);
	}
	public static void encrypt(String sourcePath,String destinationPath){
		FileInputStream fis=null;
		FileOutputStream fos=null;
		try {
			fis=new FileInputStream(sourcePath);
			fos=new FileOutputStream(destinationPath);
			int i=0;
			while((i=fis.read())!=-1){
				fos.write(i^0xFF);
			}
		} catch (IOException e) {
			throw new RuntimeException(e);
		} finally {
			try {
				if (fis != null)
					fis.close();
				if (fos != null)
					fos.close();
			} catch (IOException e) {
				throw new RuntimeException(e);
			}
		}
	}
}
用于被加密的类

public class Attachment {
	public static void main(String[] args) {
		
	}
}

当尝试运行加密后的Attachment.class文件时,会报incompatible magic value异常信息。

自定义类加载器

自定义的类加载器必须继承ClassLoader类并覆盖findClass方法,本例子中的类加载器在加载类的过程中,会自动对本上小节加密过的字节码文件进行解密。

下面是自定义类加载的类:

import java.io.ByteArrayOutputStream;
import java.io.FileInputStream;

public class MyClassLoader extends ClassLoader{
	private String classDir;
	
	public MyClassLoader(){		
	}
	
	public MyClassLoader(String classDir){
		this.classDir=classDir;
	}
	/**
	 * 根据类名加载相应的.class文件
	 */
	@SuppressWarnings("deprecation")
	@Override
	protected Class<?> findClass(String name) throws ClassNotFoundException {
		// TODO Auto-generated method stub
		String classFileName= classDir+"\\"+name+".class";
		FileInputStream fis=null;
		ByteArrayOutputStream baos=null;
		int i=0;
		try {
			fis=new FileInputStream(classFileName);
			baos=new ByteArrayOutputStream();
			while((i=fis.read())!=-1){
				baos.write(i^0xFF);
			}
			//将.class文件变为文件输入流数据,并转换成内存里的字节码对象
		    byte[] b=baos.toByteArray();
		    return defineClass(b, 0, b.length);
		} catch (Exception e) {
			throw new RuntimeException(e);
		} finally {
			try {
				if (fis != null)
					fis.close();
				if (baos != null)
					baos.close();
			} catch (Exception e) {
				throw new RuntimeException(e);
			}
		}
	}
}
下面是用于被类加载器加载的测试类Attachment

public class Attachment {
	public void showSuccess(){
		System.out.println("success");
	}
}
下面是调用自定义类加载器的类

import java.lang.reflect.Method;

public class MyClassLoaderDemo {
	public static void main(String[] args) {
		MyClassLoader classLoader=new MyClassLoader("testData");//testData是要加载的类的所在目录(相对于项目根目录)
		try {
			//用类加载器加载Attachment类并同时解密.class文件
			Class<?> attachmentClass=classLoader.findClass("Attachment");
			Object obj=(Object)attachmentClass.newInstance();
			System.out.println(obj);
			//反射Attachment类中的showSuccess()方法
			Method method= attachmentClass.getMethod("showSuccess");
			method.invoke(obj);
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
}

类加载器的顺序

问题:编写一个能打印出自己的类加载器名称和当前类加载器的父子关系链的MyServlet,正常发布后,看到打印结果为WebAppClassloader。把MyServlet.class文件打成jar包,放到ext目录中,重启tomcat,发现页面报错:找不到HttpServlet

解决方法:把servlet.jar也放到ext的目录中,问题解决了,打印结果是ExtClassLoader

结论:父级类加载器加载的类无法使用被子级类加载器加载的类,原理如下图:

代理

代理的概念

Java代理类的作用如下:

class X{
	void sayHello(){
		/*其他人写的代码,看不到*/
	}
}
class XProxy{
	void sayHello(){
		/*在其他人的代码之前执行一些操作*/
		new X().sayHello();
		/*在其他人的代码之后执行一些操作*/
	}
}

下面的XProxy类就是X类的代理,X是XProxy的目标类。XProxy替X完成一些工作,并附加自己的工作,这就是代理的含义。

AOP

系统中存在多个业务,每个业务都存在相同的方面,如下:


用伪代码描述如下:


将独立方面的代码抽出来,这就是面向方面编程(AOP:Aspect oriented program)


动态代理技术

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

JVM生成的动态类,必须实现接口

CGLIB可以生成动态类,而不需要接口

动态类

通过java.lang.reflect.Proxy类可以生成一个动态类,下面我们写一个程序演示,如何生成动态类,并用反射查看这个动态类都有哪些方法:

import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Collection;

public class ProxyDemo {
	public static void main(String[] args) {
		Class<?> clazzProxy=Proxy.getProxyClass(Collection.class.getClassLoader(),Collection.class);
		String className=clazzProxy.getSimpleName();
		System.out.println("class "+className+"{");
		//反射构造方法
		StringBuilder sb=new StringBuilder();
		Constructor[] constructors=clazzProxy.getConstructors();
		for (Constructor<?> constructor : constructors) {
			sb.append("    public ");
			sb.append(className);
			sb.append("(");
			getParameters(sb, constructor.getParameterTypes());
			sb.append("){...}");
			sb.append("\n");
		}
		//反射其他方法
		Method[] methods=clazzProxy.getMethods();
		for (Method method : methods) {
			sb.append("    public ");
			sb.append(method.getReturnType().getSimpleName());
			sb.append(" ");
			sb.append(method.getName());
			sb.append("(");
			getParameters(sb, method.getParameterTypes());
			sb.append("){...}");
			sb.append("\n");
		}
		sb.append("}");
		System.out.println(sb.toString());
	}
	public static void getParameters(StringBuilder sb,Class[] parameters){
		String typeName="";
		for (Class<?> parameter : parameters) {
			typeName=parameter.getSimpleName();
			sb.append(typeName);
			if(typeName.indexOf("Object[]")>-1){
			sb.append(" objects");
			}else if (typeName.indexOf("Object")>-1) {
				sb.append(" obj");
			}else if(typeName.indexOf("ClassLoader")>-1){
				sb.append(" loader");
			}else if(typeName.indexOf("Class[]")>-1){
				sb.append(" classes");
			}else if(typeName.indexOf("Class")>-1){
				sb.append(" clazz");
			}else if(typeName.indexOf("Collection")>-1){
				sb.append(" col");
			}else if(typeName.indexOf("long")>-1){
				sb.append(" x");
			}else if(typeName.indexOf("int")>-1){
				sb.append(" y");
			}else if(typeName.indexOf("Collection")>-1){
				sb.append(" col");
			}else if(typeName.indexOf("InvocationHandler")>-1){
				sb.append(" handler");
			}else {
				sb.append(" "+typeName.toLowerCase());
			}
			sb.append(",");
		}
		if(parameters!=null&parameters.length!=0)
		sb.deleteCharAt(sb.length()-1);
	}
}
下面是运行结果:

class $Proxy0{
    public $Proxy0(InvocationHandler handler){...}
    public boolean add(Object obj){...}
    public boolean remove(Object obj){...}
    public boolean equals(Object obj){...}
    public String toString(){...}
    public int hashCode(){...}
    public void clear(){...}
    public boolean contains(Object obj){...}
    public boolean isEmpty(){...}
    public int size(){...}
    public Object[] toArray(){...}
    public Object[] toArray(Object[] objects){...}
    public boolean addAll(Collection col){...}
    public Iterator iterator(){...}
    public boolean containsAll(Collection col){...}
    public boolean removeAll(Collection col){...}
    public boolean retainAll(Collection col){...}
    public boolean isProxyClass(Class clazz){...}
    public InvocationHandler getInvocationHandler(Object obj){...}
    public Class getProxyClass(ClassLoader loader,Class[] classes){...}
    public Object newProxyInstance(ClassLoader loader,Class[] classes,InvocationHandler handler){...}
    public void wait(long x,int y){...}
    public void wait(long x){...}
    public void wait(){...}
    public Class getClass(){...}
    public void notify(){...}
    public void notifyAll(){...}
}

下面是创建这个动态类的实例,并调用无返回值方法的代码:

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Collection;

public class ProxyDemo {
	public static void main(String[] args) throws Exception {
		
		Class<?> clazzProxy=Proxy.getProxyClass(Collection.class.getClassLoader(),Collection.class);
		Constructor<?> constructor=clazzProxy.getConstructor(InvocationHandler.class);
		class MyInvocationHandler implements InvocationHandler{

			@Override
			public Object invoke(Object proxy, Method method, Object[] args)
					throws Throwable {
				// TODO Auto-generated method stub
				return null;
			}
			
		}
		Collection<?> collection=(Collection<?>)constructor.newInstance(new MyInvocationHandler());
		collection.clear();
	}
}
这段代码运行后,没有任何结果,也不报任何异常,说明动态创建成功。
下面再用匿名内部类和newProxyInstance()方法来简化上面的代码:

public class ProxyDemo {
	public static void main(String[] args) throws Exception {
		
		Collection<?> collection=(Collection<?>)Proxy.newProxyInstance(Collection.class.getClassLoader()
				,new Class[]{Collection.class}
				,new InvocationHandler() {		
					@Override
					public Object invoke(Object proxy, Method method, Object[] args)
							throws Throwable {
						// TODO Auto-generated method stub
						return null;
					}
				});
		collection.clear();		
	}
}

InvocationHandler

下面的代码通过完善InvocationHandler匿名内部类,来使有返回值的方法可以在动态类上调用:

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.ArrayList;
import java.util.Collection;

public class ProxyDemo {
	public static void main(String[] args) throws Exception {
		
		Collection proxy=(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 {
						Object obj=method.invoke(target, args);
						return obj;
					}
				});
		proxy.add("one");
		proxy.add("two");
		proxy.add("three");
		System.out.println(proxy.size());	
	}
}
下面分析原理,看看InvocationHandler到底起了什么作用。

当代理对象调用一个方法时,例如proxy.add("one");实际上内部运行的代码原理如下:

public boolean add("one"){

return (boolean)handler.invoke(proxy,method,"one");

}
其中handler既newProxyInstance()方法的第三个参数,动态生成的代理类的构造方法的唯一参数。

method就是代理类与目标类的公共接口的Collection的add方法的反射对象。

“one”为通过动态类对象调用方法时传进来的实际参数。

由此可知InvocationHandler接口的唯一方法invoke的三个参数的作用。

	public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
		return null;
	}
proxy就是当前调用方法的代理类对象,method就是当前调用的方法的反射对象,args就是实际参数。

刚才没实现invoke方法内容时,proxy.size()之所以报错,是因为在proxy.size()方法内部返回值时,进行强制数据类型转换,而null无法转换成int,所以报异常。

将生成动态类的代码抽象成通用方法

将上一小节的代码中的targe提出来,并把其余代码抽象成一个方法,然后加入通告(advice):

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.ArrayList;
import java.util.Collection;

public class ProxyDemo {
	public static void main(String[] args) throws Exception {
		ArrayList target=new ArrayList();
		MyAdvice advice=new MyAdvice();
		Collection proxy=(Collection)getProxy(target,advice);
		proxy.add("one");
		proxy.add("two");
		proxy.add("three");
		System.out.println(proxy.size());	
	}


	private static Object getProxy(final Object target,final Advice advice) {
		return Proxy.newProxyInstance(target.getClass().getClassLoader()
				,target.getClass().getInterfaces()
				,new InvocationHandler() {
					public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
						advice.beforeMethod(proxy, method, args);
						Object obj=method.invoke(target, args);
						advice.afterMethod(proxy, method, args);
						return obj;
					}
				});
	}
}
下面是Advice接口的代码:

import java.lang.reflect.Method;

public interface Advice {
	public void beforeMethod(Object proxy, Method method, Object[] args) ;
	public void afterMethod(Object proxy, Method method, Object[] args) ;
}
下面是MyAdvice类的代码:

import java.lang.reflect.Method;

public class MyAdvice implements Advice {

	@Override
	public void beforeMethod(Object proxy, Method method, Object[] args) {

		if (args!=null) {
			System.out.println(method.getName()+"("+args[0]+")");
		}else {
			System.out.println(method.getName()+"()");
		}
	}

	@Override
	public void afterMethod(Object proxy, Method method, Object[] args) {
		if (args!=null) {
			System.out.println(method.getName()+"("+args[0]+")");
		}else {
			System.out.println(method.getName()+"()");
		}
	}
}

实现类似spring的可配置AOP框架

首先是BeanFactory类:

import java.io.IOException;
import java.io.InputStream;
import java.util.Properties;
/**
 * 负责创建目标类或代理类的实例对象,并通过配置文件实现切换
 */
public class BeanFactory {
	Properties props=new Properties();
	public BeanFactory(InputStream ips){
		try {
			props.load(ips);
		} catch (IOException e) {}
	}
	public Object getBean(String name){
		String className=props.getProperty(name);
		Object bean=null;
		//根据配置文件中的类名,反射出类的实例
		try {
			Class clazz=Class.forName(className);
			bean=clazz.newInstance();
		} catch (Exception e) {}
		//
		if(bean instanceof ProxyFactoryBean){
			ProxyFactoryBean proxyFactory=(ProxyFactoryBean)bean;
			try {
				Advice advice=(Advice)Class.forName(props.getProperty(name+".advice")).newInstance();
				Object target=Class.forName(props.getProperty(name+".target")).newInstance();
				proxyFactory.setAdvice(advice);
				proxyFactory.setTarget(target);
				bean=proxyFactory.getProxy();
			} catch (Exception e) {}	
		}
		return bean;
	}
}
然后是ProxyFactoryBean类

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

public class ProxyFactoryBean {
	
	Object target;
	Advice advice;
	
	public Object getTarget() {
		return target;
	}

	public void setTarget(Object target) {
		this.target = target;
	}

	public Advice getAdvice() {
		return advice;
	}

	public void setAdvice(Advice advice) {
		this.advice = advice;
	}
	
	public Object getProxy() {
		return Proxy.newProxyInstance(target.getClass().getClassLoader()
				,target.getClass().getInterfaces()
				,new InvocationHandler() {
					public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
						advice.beforeMethod(proxy, method, args);
						Object obj=method.invoke(target, args);
						advice.afterMethod(proxy, method, args);
						return obj;
					}
				});
	}
}
然后是使用这个AOP框架的代码:

import java.io.InputStream;

public class AOPFrameworkDemo {
	public static void main(String[] args) {
		InputStream ips=AOPFrameworkDemo.class.getResourceAsStream("config.properties");
		BeanFactory factory=new BeanFactory(ips);
		Object proxy= factory.getBean("xxx");
		System.out.println(proxy.getClass().getName());
	}
}
config.properties文件内容如下:

#xxx=java.util.ArrayList
xxx=enhance.forfiveblog.ProxyFactoryBean
xxx.advice=enhance.forfiveblog.MyAdvice
xxx.target=enhance.forfiveblog.ArrayList

MyAdvice类即为《将生成动态类的方法抽象成通用方法》小节中创建的MyAdvice类





你可能感兴趣的:(黑马程序员——高新技术5——类加载器和动态代理)