类加载器
类加载器负责将 .class 文件(可能在磁盘上,也可能在网络上)加载到内存中,并为之生成对应的 java.lang.Class对象。
Java中,不同的类,可能由不同的类加载器加载。
当 JVM 启动时,会形成由三个类加载器组成的初始类加载器层次结构:
bootstrap classloader:
引导(也称为原始)类加载器,它负责加载Java的核心类。这个加载器的是非常特殊的,它实际上不是 java.lang.ClassLoader的子类,而是由JVM自身实现的。可以通过执行以下代码来获得bootstrap classloader加载了那些核心类库:
URL[] urls=sun.misc.Launcher.getBootstrapClassPath().getURLs(); for (int i = 0; i < urls.length; i++) { System.out.println(urls[i].toExternalForm()); } |
因为JVM在启动的时候就自动加载它们,所以不需要在系统属性CLASSPATH中指定这些类库
extension classloader
扩展类加载器,它负责加载JRE的扩展目录(JAVA_HOME/jre/lib/ext或者由java.ext.dirs系统属性指定的)中的JAR包。这为引入除Java核心类以外的新功能提供了一个标准机制。因为默认的扩展目录对所有从同一个JRE中启动的JVM都是通用的,所以放入这个目录的JAR类包对所有的JVM和system classloader都是可见的。
system classloader
系统(也称为应用)类加载器,它负责在JVM被启动时,加载来自在命令java中的-classpath或者java.class.path系统属性或者CLASSPATH操作系统属性所指定的JAR类包和类路径。
可以通过静态方法ClassLoader.getSystemClassLoader()找到该类加载器。如果没有特别指定,则用户自定义的任何类加载器都将该类加载器作为它的父加载器。
全盘负责委托机制
classloader 加载类用的是全盘负责委托机制。
全盘负责:即是当一个classloader加载一个Class的时候,这个Class所依赖的和引用的其它Class通常也由这个classloader负责载入。
委托机制:先让parent(父)类加载器寻找,只有在parent找不到的时候才从自己的类路径中去寻找。
类加载还采用了cache机制:如果cache中保存了这个Class就直接返回它,如果没有才从文件中读取和转换成Class,并存入cache,这就是为什么修改了Class但是必须重新启动JVM才能生效,并且类只加载一次的原因。
自定义类加载器
1、 自定义的类加载器必须继承ClassLoader。
2、 ClassLoader中有loadClass()和findclass()两个方法,loadClass()方法实现了委托机制,findClass()方法辅负责自己加载类,因此我们只需重写findClass()方法
3、 defineClass()方法。通过findClass()得到class文件中的二进制数据后,用findClass()方法将其转换为字节码。
练习:编写一个对class文件进行加密和解密的自定义类加载器
import java.io.ByteArrayOutputStream;
import java.io.FileInputStream;
import java.io.InputStream;
import java.io.OutputStream;
public class MyClassLoader extends ClassLoader{
//加载的类所在的目录
private String srcDir;
public MyClassLoader(){
}
public MyClassLoader(String srcDir){
this.srcDir = srcDir;
}
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException{
String className = srcDir + "/"+name+".class";
FileInputStream fis=null;
try{
fis = new FileInputStream(className);
ByteArrayOutputStream bos = new ByteArrayOutputStream();
cypher(fis,bos);
fis.close();
byte[] bytes = bos.toByteArray();
System.out.println("zhising");
Class clazz =defineClass(bytes,0,bytes.length);
System.out.println("zhising");
return clazz;
}catch(Exception e)
{
return super.findClass(name);
}
}
public static void cypher(InputStream is,OutputStream os)throws Exception
{
int b=-1;
while((byte)(b=is.read())!=-1)
{
os.write(b^0Xff);
}
}
}
测试类:
mport java.io.FileInputStream;
import java.io.FileOutputStream;
public class ClassLoaderTest {
public static void main(String[] args) throws Exception {
//对文件进行加密并保存
String srcPath = "G:/MyEclipse WorkSpac/JavaEnhance/bin/ClassLoaderAttribute.class";
String destDir = "G:/MyEclipse WorkSpac/JavaEnhance/src/properties";
String destPath = destDir.concat("/"+srcPath.substring(srcPath.lastIndexOf("/")+1));
FileInputStream fis = new FileInputStream(srcPath);
FileOutputStream fos = new FileOutputStream(destPath);
MyClassLoader.cypher(fis,fos);
fis.close();
fos.close();
//解密.class文件,并建立新的对象
String srcPath1 = "G:/MyEclipse WorkSpac/JavaEnhance/src/properties";
Class claClazz = new MyClassLoader(srcPath1).loadClass("ClassLoaderAttribute");
System.out.println(claClazz.newInstance());
代理:
应用场合:有多个已经开发完成的类,他们具有相同的接口,现在要为这些类增加一些其他的功能又不能改变这些类,这时候我们只能编写一个与这些目标类具有相同接口的代理类,代理类的每个方法调用目标类的相同方法,并且在调用时增加上需要的功能代码,这就是代理。
代理对象:代理类所生成的对象。
目标对象:代理类所代理的那个类生成的对象。
代理的实现原理图:
AOP
系统中存在交叉业务,一个交叉业务就是要切入到系统中的一个方面,如下所示:
安全 事务 日志
StudentService ------|----------|------------|-------------
CourseService ------|----------|------------|-------------
MiscService ------|----------|------------|-------------
StudentService、CourseService、MiscService这些服务都需要安全的功能,也就是说安全功能贯穿到这服务中,也就是说安全功能与这些服务有交叉
安全,事务,日志等功能要贯穿到好多个模块中,所以,它们就是交叉业务
用具体的程序代码描述交叉业务:
method1 method2 method3
{ { {
------------------------------------------------------切面 安全功能代码
.... .... ......
------------------------------------------------------切面 日志功能代码
} } }
交叉业务的编程问题即为面向方面的编程(Aspect oriented program ,简称AOP),AOP的目标就是要使交叉业务模块化。可以采用将切面代码移动到原始方法的周围,这与直接在方法中编写切面代码的运行效果是一样的,如下所示:
------------------------------------------------------切面
func1 func2 func3
{ { {
.... .... ......
} } }
------------------------------------------------------切面
使用代理技术正好可以解决这种问题,代理是实现AOP功能的核心和关键技术。
静态代理的缺点:(1)如果接口加一个方法,代理类和目标类都要做个实现,这就增加了代码的复杂度。
(2)要为系统中的各种接口的类增加代理功能,那将需要太多的代理类,全部采用静态代理方式,将是一件非常麻烦的事情
动态代理:
(1) JVM可以在运行期动态生成出类的字节码,这种动态生成的类往往被用作代理类,即动态代理类。
(2) JVM生成的动态类必须实现一个或多个接口,所以,JVM生成的动态类只能用作具有相同接口的目标类的代理。
(3) CGLIB库可以动态生成一个类的子类,一个类的子类也可以用作该类的代理,所以,如果要为一个没有实现接口的类生成动态代理类,那么可以使用CGLIB库。
(4) 代理类的各个方法中通常除了要调用目标的相应方法和对外返回目标返回的结果外,还可以在代理方法中的如下四个位置加上系统功能代码:
1.在调用目标方法之前
2.在调用目标方法之后
3.在调用目标方法前后
4.在处理目标方法异常的catch块中
JAVA 自带的动态代理是基于java.lang.reflect.Proxy、java.lang.reflect.InvocationHandler两个类来完成的,使用JAVA 反射机制。
(1) Proxy.getProxyClass(ClassLoadercl,Class<?>…interface),动态的生成一个类的字节码,由于字节码都有getClassLoader()方法,而且都是由类加载器加载到内存的,所以必须为其指定classLoader(通常为接口参数的类加载器),而且必须要指定它所代理的类所实现或者继承的接口,即要代理的目标对象的归属接口数组,从这份动态生成的字节码中,我们可以得到一个带有InvasionHandler类型的参数的构造函数,可以用该构造函数创建一个代理对象。
以下代码创建一个代理类,去代理那些实现了Collection接口的类
//动态生成代理类的字节码
Class clazz = Proxy.getProxyClass(Collection.class.getClassLoader(), Collection.class);
/*从字节码的得到代理类的构造方法(含有一个InvocationHandler参数)
* 用该构造方法创建对象,必须要有一个InvocationHandler
* InvocatinHandler是一个接口,不能直接创建对象,只能创建其子类对象,并实现其中的抽象方法
*/
Collection proxy = (Collection) clazz.getConstructor(InvocationHandler.class).newInstance(new InvocationHandler(){
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
return method.invoke(目标对象,args);
}});
以下代码也能起到同样的效果:
Collection proxy2 = (Collection)Proxy.newProxyInstance(
Collection.class.getClassLoader(),
new Class[]{Collection.class},
new InvocationHandler(){
@Override
public Object invoke(Object proxy, Method method,
Object[] args) throws Throwable {
return method.invoke(目标对象,args);
}
}
);
第二种方式更加直接简便,并且隐藏了代理$Proxy0 对象的结构。
JDK 的动态代理会动态的创建一个$Proxy0的类,这个类继承了Proxy并且实现了要代理的
目标对象的接口,但你不要试图在JDK 中查找这个类,因为它是动态生成的。$Proxy0 的结构大致如下所示:
public final class $Proxy0 extends Proxy implements 目标对象的接口1,接口2,…{
InvocationHandlerhandler;
//构造方法
Public$Proxy0(InvocationHandler h){
This.handler=handler;
……
}
}
总结思考:让jvm创建动态类及其实例对象,需要给它提供三个方面信息:
(1)生成的类中有哪些方法,通过让其实现哪些接口的方式进行告知;
(2)产生的类字节码必须有个一个关联的类加载器对象;
(3)生成的类中的方法的代码是怎样的,也得由我们提供。把我们的代码写在一个约定好了接口对象的方法中,把对象传给它,它调用我的方法,即相当于插入了我的代码。提供执行代码的对象就是那个InvocationHandler对象,它是在创建动态类的实例对象的构造方法时传递进去的。在上面的InvocationHandler对象的invoke方法中加一点代码,就可以看到这些代码被调用运行了。
动态代理类的内部实现:
$Proxy0 implements interfaceX
{
InvocationHandler handler;
public $Proxy0(InvocationHandler handler)
{
this.handler = handler;
}
//生成的Collection接口中的方法的运行原理
int methodX()
{
return handler.invoke(this, this.getClass().getMethod("methodX"),null);
}
void methodY (){
handler.invoke(this, this.getClass().getMethod("methodY"),null);
}
boolean methodZ(Object obj){
return handler.invoke(this, this.getClass().getMethod("methodZ "),obj);
}
}
InvocationHandler的作用:InvocationHandler起到一个中介的作用,当客户使用代理对象调用某一个方法时,代理对象会把他转交给InvocationHandler对象,让其代理,InvocationHandler对象会调用其invoke()方法,invoke方法再去调用目标对象的相同方法,以及实现附加功能的模块,这就完成了代理。这里需要说一下invoke()方法的三个参数:
Object obj 代理对象,也就是指出哪个对象调用了InvocationHandler对象的invoke方法
Method method 方法,指出调用了哪个方法
Object[]args 接收method方法的参数
明白了InvocationHandler的作用也就明白了动态代理的工作原理。动态代理的工作原理图如下:
动态代理的小例子:
代理接口:
interface Action
{
void doSomething();
}
代理接口的实现类
class Eat implements Action
{
@Override
public void doSomething(){
System.out.println( "eat!!!");
}
}
回调类
class myInvocationHandler implements InvocationHandler
{
private Object target;
public myInvocationHandler(Object target)
{
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[]args)
throws Throwable {
System.out.println("BeforeEat");
Object retVal = method.invoke(target, args);
System.out.println("AfterEat");
return retVal;
}
}
测试代码:
Eat eatObj = new Eat();
Object proxy = Proxy.newProxyInstance(
Action.class.getClassLoader(),
/*这里必须用Eat.class.getInterfaces(),而不能使用Action.class.getInterfaces()
* 因为getInterfaces()方法返回的是当前类所实现的接口
*/
Eat.class.getInterfaces(),
new myInvocationHandler(eatObj)
);
/*
* 只能强制转换为Action,而不能强制转换为Eat
* 也就是说代理对象不能强制转换为目标对象的类型
* 因为两者之间没有继承或者实现关系
* 而代理对象却实现了代理接口,所以能强制转换为Action
*/
((Action)proxy).doSomething();
实现类似Spring的可配置的AOP框架
(1) 工厂类BeanFactory负责创建目标类或代理类的实例对象,并通过配置文件实现切换。其getBean方法根据字符串参数(从配置文件读取得到)所指定的类名返回一个相应实例对象,如果字符串 参数对应的类名不是ProxyFactoryBean,则直接返回该类的实例对象,如果是ProxyFactoryBean,则通过ProxyFactoryBean的getProxy方法返回的配置文件所指定的目标类的代理对象。
实现:a:从输入流中得到配置文件信息,并根据配置文件中的类名建立一个实例
B:如果该实例不是ProxyFactoryBean类的实例对象,返回该实例对象,如果是,则调用ProxyFactoryBean类中的getProxy()方法得到一个代理对象并返回。
(2) BeanFactory的构造方法接收代表配置文件的输入流对象,配置文件格式如下:
#xxx=java.util.ArrayList
xxx=AOPFramwork.ProxyFactoryBean
xxx.target=java.util.ArrayList
xxx.advice=AOPFramwork.MyAdvice
(3)ProxyFacotryBean充当封装生成动态代理的工厂,需要为工厂类提供哪些配置参数信息?
(a)目标类,即指定代理类为哪个类代理。(b)通告,一个接口,其中的抽象方法规定了代理类相对于目标类所添加的那些附加功能的规则,实现它的子类必须实现它的抽象方法,即按照这些抽象方法的约定去实现附加功能,使得这些附加功能即使是交叉业务,也能够实现模块化。
实现:1、作为一个JavaBean,它必须有一个无参的构造方法
2、 getProxy()方法得到代理类的实例对象时,必须加载代理类,在加载代理类时,必须为其制定目标类和通告,这两部分将作为ProxyFacotryBean的私有变量,传递到类中,以便于getProxy方法使用。
(4)编写客户端应用:
编写实现Advice接口的类和在配置文件中进行配置
调用BeanFactory获取对象
该框架应该有的类:
(1) BeanFactory充当创建目标类或代理类的实例对象的工厂
(2) ProxyFacotryBean充当生成动态代理的工厂
(3) Advice充当制定附加功能的添加规则,MyAdvice作为其实现类,按照其规则(定义的抽象方法)实现所需添加的系统功能。
具体实现:
1、 工厂类BeanFactory
ackage AOPFramwork;
import java.io.IOException;
import java.io.InputStream;
import java.util.Properties;
public class BeanFactory {
private Properties properties = new Properties();
public BeanFactory(InputStream is)
{
try {
properties.load(is);
} catch (IOException e) {
e.printStackTrace();
}
}
public Object getBean(String key)
{
Object bean = null;
try {
Class clazz = Class.forName((String)properties.get(key));
bean = clazz.newInstance();
} catch (Exception e) {
e.printStackTrace();
}
Object target= null;
MyAdvice advice = null;
if(bean instanceof ProxyFactoryBean)
{
ProxyFactoryBean proxyFactoryBean = (ProxyFactoryBean)bean;
try {
advice = (MyAdvice)Class.forName((String)properties.get(key+".advice")).newInstance();
target = Class.forName((String)properties.get(key+".target")).newInstance();
} catch (Exception e) {
e.printStackTrace();
}
proxyFactoryBean.setAdvice(advice);
proxyFactoryBean.setTarget(target);
return proxyFactoryBean.getProxy();
}
return bean;
}
}
2、 动态代理类的工厂类ProxyFacotryBean
package AOPFramwork;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class ProxyFactoryBean {
private Object target;
private MyAdvice advice;
public void setTarget(Object target) {
this.target = target;
}
public void setAdvice(MyAdvice advice) {
this.advice = advice;
}
public ProxyFactoryBean(){
}
public Object getProxy() {
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();
Object retVal = method.invoke(target, args);
advice.afterMethod();
return retVal;
}
});
return proxy;
}
}
3、 通告Advice
package AOPFramwork;
public interface Advice {
void beforeMethod();
void afterMethod();
}
//通告的实现类
package AOPFramwork;
public class MyAdvice implements Advice {
@Override
public void beforeMethod() {
System.out.println("before method!");
}
@Override
public void afterMethod() {
System.out.println("after method!");
}
}
4、 客户端测试类
package AOPFramwork;
import java.io.InputStream;
import java.util.Collection;
public class AOPFramworkTest {
public static void main(String[] args) throws Exception {
InputStream is = AOPFramworkTest.class.getResourceAsStream("config.properties");
BeanFactory beanFactory = new BeanFactory(is);
Collection bean = (Collection)beanFactory.getBean("xxx");
System.out.println(bean.getClass().getName());
bean.add("afa");
}
}