类加载器
①类加载器是加载类的工具,它的作用是将硬盘上的.class文件加进内存,并对之进行一些处理。
②java虚拟机中可安装多个类加载器,系统默认三个主要的类加载器,每个类加载器负责加载特定的类:
BootStrap xitClassLoader AppClassLoader
③类加载器也是java类,因为其他是java类的类加载器也要被类加载器加载,显然必须有第一个类加载器不是java类, 这正是:BootStrap,它是由C++编写的。
④java虚拟机中的所有类加载器采用具有父子关系的树形结构进行组织,在实例化每个类加载器对象时,需要为其指定一个父级类加载器对象,或者默认采用系统类加载器为其父级类加载器。
①类加载器是加载类的工具,它的作用是将硬盘上的.class文件加进内存,并对之进行一些处理。
②java虚拟机中可安装多个类加载器,系统默认三个主要的类加载器,每个类加载器负责加载特定的类:
BootStrap xitClassLoader AppClassLoader
③类加载器也是java类,因为其他是java类的类加载器也要被类加载器加载,显然必须有第一个类加载器不是java类, 这正是:BootStrap,它是由C++编写的。
④java虚拟机中的所有类加载器采用具有父子关系的树形结构进行组织,在实例化每个类加载器对象时,需要为其指定一个父级类加载器对象,或者默认采用系统类加载器为其父级类加载器。
类加载器之间的父子关系和管辖范围图:
|---Bootstrap ClassLoader ->JRE/lib/rt.jar
|---Extension ClassLoader ->JRE/lib/ext/.jar
|---ApplicationClassLoader ->ClassPath指定的所有jar或目录。
|--MyClassLoader ->指定的特殊目录
|--itcastClassLoader->指定的特殊目录
|---Bootstrap ClassLoader ->JRE/lib/rt.jar
|---Extension ClassLoader ->JRE/lib/ext/.jar
|---ApplicationClassLoader ->ClassPath指定的所有jar或目录。
|--MyClassLoader ->指定的特殊目录
|--itcastClassLoader->指定的特殊目录
System.out.println(ClassLoaderTest.class.getClassLoader().getClass().getName());
System.out.println(System.class.getClassLoader());
ClassLoader loader=ClassLoaderTest.class.getClassLoader();
while(loader!=null){
System.out.println(loader.getClass().getName());
loader=loader.getParent();
}
System.out.println(loader);
类加载器的委托机制---分为两个过程
①类加载器的选择:当java虚拟机要加载一个类时,到底派出那个类加载器去加载?
(1).首先当前线程的类加载器去加载线程中的第一个类。
(2).如果类A中引用了B类,java虚拟机将使用类A的加载器来加载类B
(3).还可以直接调用ClassLoader.loadClass()方法来指定某个类加载器去加载某个类。
②类加载器怎样加载:每个类加载器加载类时,又先委托给器上级类加载器。当所有祖宗类加载器没有加载到类,回到发起者类加载器,还加载不了,则抛出ClassNotFoundException,不再去找发起者类加载器的儿子,因为没有getChild方法;即使有,那么有多个儿子,到底要找那个一个呢?
思考:能否自己写一个java.lang.System类?
通常不可以,因为根据类加载器的委托机制,每次都会先委托给上级寻找,先找到的是Jdk中的java.lang.System,而不是自己写的 java.lang.System类;但可以自己写一个类加载器,撇开器委托机制,不给它指定上级,来加载自己的类。
注意:需要说明一下 Java 虚拟机是如何判定两个Java类是相同的。Java虚拟机不仅要看类的全名是否相同,还要看加载此类的类加载器是否一样。只有两者都相同的情况,才认为两个类是相同的。即便是同样的字节代码,被不同的类加载器加载之后所得到的类,也是不同的。
注意:需要说明一下 Java 虚拟机是如何判定两个Java类是相同的。Java虚拟机不仅要看类的全名是否相同,还要看加载此类的类加载器是否一样。只有两者都相同的情况,才认为两个类是相同的。即便是同样的字节代码,被不同的类加载器加载之后所得到的类,也是不同的。
编写自己的类加载器,模板方法设计模式:
父类->loadClass/findClass/得到class文件的转换成字节码用父类的defineClass方法。
子类:(自己干)
子类2:(自己干)
总体的流程已经在父类定义好,实现的具体细节有一部分父类不清楚,留给子类完成。
父类->loadClass/findClass/得到class文件的转换成字节码用父类的defineClass方法。
子类:(自己干)
子类2:(自己干)
总体的流程已经在父类定义好,实现的具体细节有一部分父类不清楚,留给子类完成。
知识讲解:
①自定义的类加载器必须继承ClassLoader
②loadClass方法与findClass方法:只需重新findClass方法,就会跳出委托机制。
③defineClass方法。
编程步骤:
①编写一个队文件内容进行监督加密的程序
②编写了一个自己的类加载器,可实现对加密的类进行撞在和解码。
③编写一个程序调用类加载器加载类,在源程序中不能用该类定义引用变量,因为编程器无法识别这个类。程序中可以除了使用ClassLoader.load方法外,还可以使用使用设置线程的上下文加载器或者系统加载器,然后再使用Class.forName.
①自定义的类加载器必须继承ClassLoader
②loadClass方法与findClass方法:只需重新findClass方法,就会跳出委托机制。
③defineClass方法。
编程步骤:
①编写一个队文件内容进行监督加密的程序
②编写了一个自己的类加载器,可实现对加密的类进行撞在和解码。
③编写一个程序调用类加载器加载类,在源程序中不能用该类定义引用变量,因为编程器无法识别这个类。程序中可以除了使用ClassLoader.load方法外,还可以使用使用设置线程的上下文加载器或者系统加载器,然后再使用Class.forName.
//要加载的测试类代码:
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);
}
//这时候不用关闭,谁创建资源,谁关闭资源。
}
}
//调用自己写的类加载器
Class clazz = new MyClassLoader("itcastlib").loadClass("ClassLoaderAttachments");
Date d = (Date)clazz.newInstance();
System.out.println(d.toString());
关于类加载器的一个高级问题
当把我们写好的MyServlet作为jar包导入的Tomcat使用的JDK的jre/lib/ext中时,这时的MyServlet的类加载器由原来tomcat的WebAppClassLoader变成了ExtClassLoader,在加载TMyServlet时,又用到了HttpServlet,ExtClassLoader找不到就不会报错此时,只需将tomcat中包含HttpServlet的servlet-api.jar也导入ext文件夹下即可。
import java.io.IOException;
import java.io.PrintWriter;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class MyServlet extends HttpServlet {
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
response.setContentType("text/html");
PrintWriter out = response.getWriter();
ClassLoader loader = this.getClass().getClassLoader();
while(loader!=null){
out.println(loader.getClass().getName()+"<br>");
loader=loader.getParent();
}
out.close();
}
}
代理类与AOP
代理的概念与作用
代理模式简单示例
//目标类:
class X
{
void sayHello()
{
System.out.println("hello java");
}
}
// 代理类:
XProxy
{
void sayHello()
{
starttime;
X.sayHello();
endTime;
}
}
程序中的代理
①要为已存在的多个具有相同接口的目标类的各个方法增加一系列的系统功能,例如,异常处理,日志、计算方法等的运行时间、事务管理等等,你如何做?
②编写一个与目标类具有相同接口的代理类,代理类的每个方法调用目标类的相同方法,并在调用方法时加上系统功能的代码。
③如果采用工厂模式和配置文件的方式进行管理,则不需要修改客户端程序,在配置文件中配置是使用目标类还是代理类,这样以后很容易切换。譬如,想要日志功能时就配置代理类,否则配置目标类,这样增加系统功能很容易。以后运行一段时间后,又想去掉系统功能也很容易。
代理分为两种:静态代理和动态代理。
<AOP>
①系统中存在交叉业务,一个交叉业务就是要切入到系统中的一个方法。
②交叉业务的房产问题即为面向方面的编程(AOP,Aspect oriented program),AOP的目标就是要使交叉业务模块化,可以采用将切面代码移动到原始方法的周围,这与直接在方法中编程切面代码的运行效果是一样的。
③使用代理技术正好可以解决这种问题,代理是实现AOP功能的核心和关键技术。
动态代理技术(JVM动态代理和CGLIB库动态代理)
①要为系统中的各种接口的类增加代理功能,那将需要太多的代理类,全部采用静态代理方式,将是一件非常麻烦的事情。(引出动态代理)
②JVM可以在运行期动态生成出类的字节码,这种动态生成的类往往被用作 代理类 ,即动态代理类。
③JVM生成的动态类必须具有一个或多个接口,所以,JVM生成的动态类(Proxy)只能用作具有相同接口的目标类的代理。
④CGLIB库(一个开源库)可以动态生成一个类的子类,一个类的子类也可以用作该类的代理,所以,如果要为一个没有实现接口的 类生成动态代理类,可以使用CGLIB库。
代理类的各个方法中通除了要调用目标的相应方法和对外返回目标返回的结果外,还可以在代理方法中的如下 四个位置加上系统功能代码:
①在调用目标方法之前
②在调用目标方法之后
③在调用目标方法之前或之后
④在处理目标方法异常的catch块中。
①要为系统中的各种接口的类增加代理功能,那将需要太多的代理类,全部采用静态代理方式,将是一件非常麻烦的事情。(引出动态代理)
②JVM可以在运行期动态生成出类的字节码,这种动态生成的类往往被用作 代理类 ,即动态代理类。
③JVM生成的动态类必须具有一个或多个接口,所以,JVM生成的动态类(Proxy)只能用作具有相同接口的目标类的代理。
④CGLIB库(一个开源库)可以动态生成一个类的子类,一个类的子类也可以用作该类的代理,所以,如果要为一个没有实现接口的 类生成动态代理类,可以使用CGLIB库。
代理类的各个方法中通除了要调用目标的相应方法和对外返回目标返回的结果外,还可以在代理方法中的如下 四个位置加上系统功能代码:
①在调用目标方法之前
②在调用目标方法之后
③在调用目标方法之前或之后
④在处理目标方法异常的catch块中。
创建JVM动态代理方法:
方法一:获取动态类字节码(Class)--->用字节码获取Constructor---->用Constructor创建动态类实例
方法一:获取动态类字节码(Class)--->用字节码获取Constructor---->用Constructor创建动态类实例
//使用Proxy获取动态类字节码(以Collection为例)
Class clazzProxy1 = Proxy.getProxyClass(Collection.class.getClassLoader(), Collection.class);
System.out.println(clazzProxy1.getName());
打印clazzProxy1的所有构造方法和成员方法。
[java] view plaincopy
System.out.println("----begin constructors list-------");
/*
* $Proxy0
* $Proxy0(InvocationHandler ,int)
*/
printConstructors(clazzProxy1);
System.out.println("----begin methods list-------");
printMethods(clazzProxy1);<span style="font-size:14px;"> </span>
public static void printMethods(Class clazzProxy1) {
Method[] methods = clazzProxy1.getMethods();
for(Method method:methods){
String name = method.getName();
StringBuilder sBuilder = new StringBuilder(name);
sBuilder.append("(");
Class[] clazzParams = method.getParameterTypes();
for(Class clazzParam:clazzParams){
sBuilder.append(clazzParam.getName()).append(",");
}
if(clazzParams!=null &&clazzParams.length!=0)
sBuilder.deleteCharAt(sBuilder.length()-1);
sBuilder.append(")");
System.out.println(sBuilder);
}
}
public static void printConstructors(Class clazzProxy1) {
Constructor[] constructors=clazzProxy1.getConstructors();
for(Constructor constructor:constructors){
String name = constructor.getName();
StringBuilder sBuilder = new StringBuilder(name);
sBuilder.append("(");
Class[] clazzParams = constructor.getParameterTypes();
for(Class clazzParam:clazzParams){
sBuilder.append(clazzParam.getName()).append(",");
}
if(clazzParams!=null &&clazzParams.length!=0)
sBuilder.deleteCharAt(sBuilder.length()-1);
sBuilder.append(")");
System.out.println(sBuilder);
}
//获取Constructor
Constructor constructor =clazzProxy1.getConstructor(InvocationHandler.class);
//用Constructor创建代理类实例:因为需要用到InvocationHandler接口类型的参数,可以先定义一个内部实现类,
//也可以直接用匿名内部类在参数列表上实现接口。
class MyInvocationHandler1 implements InvocationHandler{
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
return null;
}
}
Collection proxy1 = (Collection)constructor.newInstance(new MyInvocationHandler1());
System.out.println(proxy1.toString());
[java] view plaincopy
Collection proxy2 = (Collection)constructor.newInstance(new InvocationHandler(){
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
return null;
}
});
//此时,proxy1的打印值为null,调用其无返回值的方法没有报错,调用其有返回值的方法则报空指针异常,因为它总是调///用InvocationHandler的invoke方法,而这个方法此时总是返回null。
方法二:直接用Proxy的静态方法newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h)
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 {
long startTime = System.currentTimeMillis();
Object retVal = method.invoke(target, args);
long endTime = System.currentTimeMillis();
System.out.println(method.getName()+" running time:"+(endTime - startTime));
return retVal;
}
});
proxy3.add("123");
proxy3.add("453");
proxy3.add("789");
System.out.println(proxy3.size());
注意:如果InvocationHandler的invoke方法只是Eclipse自动生成的代码的话,那现在得到的代理类实例,也只是一个骨架,没有代理的目标,就像一个代理某个产品的专卖店,只是装饰好了门面,没有摆放产品。
提示:在invoke前和后的代码,因为跟其目标没有多大关系,可专门封装在一个类中;另外,在invoke中也可以对args参数进行过滤(比如如果是args传入的是字符串,可以实现敏感信息过滤)。
猜想分析动态生成的类的内部代码
[java] view plaincopy
class Proxy${
add(Object obj){
return handler.invoke(Object proxy, Method method, Object[] args);
}
}
int size()
{
return handler.invoke(this,this.getClass().getMethod("size"),null);
}
注意:Proxy动态代理类在调用方法时,从Ojbect继承的方法 的 hashCode、equals 和 toString三个方法会委托给InvocationHandler,其他从Object 继承的方法都不委托。这既是为什么proxy1.getClass().getName()返回的是 Proxy$ 而不是ArrayList的原因。
将动态生成的类成为目标的代理
动态代理类的骨架用Proxy.newProxyInstance()方法就能生成,执行要再传入两个参数:要代理的目标target和增加的系统功能的封装对象(advice),就能让动态生成的类成为目标的代理啦。
//建立增加的系统功能封装类
import java.lang.reflect.Method;
public interface Advice {
void beforeMethod(Method method);
void afterMethod(Method method);
}
[java] view plaincopy
import java.lang.reflect.Method;
public class MyAdvice implements Advice {
long startTime=0;
@Override
public void beforeMethod(Method method) {
// TODO Auto-generated method stub
System.out.println("方法开始啦");
startTime = System.currentTimeMillis();
}
@Override
public void afterMethod(Method method) {
// TODO Auto-generated method stub
System.out.println("方法结束啦");
long endTime = System.currentTimeMillis();
System.out.println(method.getName()+" running time:"+(endTime - startTime));
}
}
//建立目标:(以Collection为例)
final ArrayList target = new ArrayList();
//成为目标的代理
[java] view plaincopy
public static Object getProxy(final Object target,final Advice advice) {
Object proxy3 =Proxy.newProxyInstance(
target.getClass().getClassLoader(),
target.getClass().getInterfaces(),//注意,这里参数是Class数组
new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
advice.beforeMethod(method);
Object retVal = method.invoke(target, args);
advice.afterMethod(method);
// return method.invoke(proxy, args);//这就变成了死循环。
return retVal;
}
});
return proxy3;
}
注意:为了能让InvocationHandler匿名内部类访问到targe和Advice,需要将其设为final。你不仅可以用Collection指向这个代理,所有它实现的接口引用都可以指向它~。它的父类是java.lang.reflect.Proxy。
实现AOP功能的封装和配置
功能描述:建立一个BeanFactory,可以根据配置文件中的bean名称到底一个JavaBean,如果bean的名字的是“ProxyFactoryBean”则返回ProxyFactoryBean根据配置文件中的".advice"和“.target”提供的目标和Advice返回的代理类。
//ProxyFactoryBean类代码
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import cn.itcast.day3.Advice;
public class ProxyFactoryBean {
private Object target;
private 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() {
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.beforeMethod(method);
Object retVal = method.invoke(target, args);
advice.afterMethod(method);
return retVal;
}
});
return proxy;
}
}
//BeanFacotry代码:
import java.io.IOException;
import java.io.InputStream;
import java.util.Properties;
import cn.itcast.day3.Advice;
public class BeanFactory {
Properties prop =new Properties();
public BeanFactory(InputStream inStream){
try {
prop.load(inStream);
} catch (IOException e) {
e.printStackTrace();
}
}
public Object getBean(String name){
String className = prop.getProperty(name);
Class clazz=null;
Object bean=null;
try {
clazz = Class.forName(className);
bean = clazz.newInstance();
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
if(bean instanceof ProxyFactoryBean){
Object proxy = null;
try {
ProxyFactoryBean proxyFactoryBean = (ProxyFactoryBean)bean;
Advice advice = (Advice) Class.forName(prop.getProperty(className+".advice")).newInstance();
Object target =Class.forName(prop.getProperty(className+".target")).newInstance();
proxyFactoryBean.setAdvice(advice);
proxyFactoryBean.setTarget(target);
proxy = proxyFactoryBean.getProxy();
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return proxy;
}else{
return bean;
}
}
}
//配置文件内容:
xxx=java.util.ArrayList
#xxx=cn.itcast.day3.aopframework.ProxyFactoryBean
xxx.advice=cn.itcast.day3.MyAdvice
xxx.target=java.util.ArrayList
//框架测试类:
import java.io.InputStream;
public class AopFrameworkTest {
public static void main(String[] args) {
InputStream ips =AopFrameworkTest.class.getResourceAsStream("config.properties");
BeanFactory beanFactory = new BeanFactory(ips);
Object bean =beanFactory.getBean("xxx");
System.out.println(bean.getClass().getName());
}
}
//注意: Spring的两大核心技术----AOP和代理,上边的代码即实现了一个简单的AOP框架。