l Java提供了一个Proxy类,调用它的newInstance方法可以生成某个对象的代理对象,使用该方法生成代理对象时,需要三个参数:
• 1.生成代理对象使用哪个类装载器
• 2.生成哪个对象的代理对象,通过接口指定
• 3.生成的代理对象的方法里干什么事,由开发人员编写handler接口的实现来指定。
备注:
• Proxy类负责创建代理对象时,如果指定了handler(处理器),那么不管用户调用代理对象的什么方法,该方法都是调用处理器的invoke方法。(这相当于invoke方法拦截到了代理对象的方法调用)。
• 由于invoke方法被调用需要三个参数:代理对象、方法、方法的参数,因此不管代理对象哪个方法调用处理器的invoke方法,都必须把自己所在的对象、自己(调用invoke方法的方法)、方法的参数传递进来。
用途:
• 开发人员通过invoke方法的参数,还可以在拦截的同时,知道用户调用的是什么方法,因此利用这两个特性,就可以实现一些特殊需求,例如:拦截用户的访问请求,以检查用户是否有访问权限、动态为某个对象添加额外的功能。
静态代理
包装设计模式的写法可以实现代理, 我们称之为静态代理
方法: 写一个类去代理某个对象
1. 代理类要和被代理对象实现同样的接口
2. 代理类一定要持有一个被代理对象
3. 代理类在实现接口所有的方法时,大部分方法都依靠被代理对象实现,有些功能,需要懂一些手脚
动态代理
分析静态代理缺点:由于实现同样的接口,要实现所有不需要代理的方法,很麻烦,这个时候我们就想能不能自己生成个类,jdk提供了一种机制,能够动态地生成一个类,不需要写一行代码,要求我们说明这个类要实现哪些接口,目的就是为了让其知道要覆盖什么方法。
用法:Proxy.getProxyClass(loader,interfaces);
这个方法就能够动态生成一个类,传入类加载器与共同实现的接口就能返回一个类,这个类没有无参构造函数,因此不能够用生成的class文件.newInstance方法来获取实例对象,由于newInstance方法默认会调用无参构造函数,但是实际上动态生成的类中存在一个有一个参数的构造函数,这个参数的类型为InvocationHandler(接口),实例这个接口要覆盖一个invoke方法。
例:
public class DaoProxy {
// 代理谁 被代理对象
private Dao dao;
//这个仅仅是个例子,user应该在session中,这里就在实例化的时候让调用者传过来就得了
private User user;
public DaoProxy(Dao dao, User user) {
super();
this.dao = dao;
this.user = user;
}
//返回值为Dao,因为我们要创建的这个类实现了这个接口,所以就是他的子类
public Dao createDaoProxy() {
try {
// 通过 工具类 Proxy的getProxyClass方法 动态生成代理类
//this.dao为被代理对象.getClass().getClassLoader()获取类加载器
//this.dao.getClass().getInterfaces()获取被代理对象所实现的接口
Class proxyClass = Proxy.getProxyClass(this.dao.getClass()
.getClassLoader(), this.dao.getClass().getInterfaces());
// 代理生成一个类后要实例化对象
// 看一下有什么构造函数,实验结论为有一个有参的构造函数 参数类型是InvocationHandler
Constructor constructor = proxyClass.getConstructor(InvocationHandler.class);
//由于实现了dao接口,所以返回值一定是个Dao,这里也应用了策略模式,我帮你生成一个类,你要给我 实现一个接口
Dao dao = (Dao) constructor.newInstance(new InvocationHandler() {
@Override//实例InvocationHandler接口需要覆盖invoke方法
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
// 当前代理对象的任何一个方法被调用了 都会触发invoke方法
// 例子:判断一下被调用的方法名是什么,如果是delete 就检查权限
if("delete".equals(method.getName())) {
//这里就简单做了,如果用户名不为manager就告诉其没有权限
if(!"manager".equals(user.getUsername())) {
System.out.println("没有删除的权限");
return null;
}
}
// 对于大部分方法 不需要包装就调用被代理对象同样的方法
// 用方法参数的method的invoke方法执行该方法,DaoProxy.this.dao意思为被代理对象 (DaoProxy的dao)
//args为方法的参数,我们不去管代理代理对象的方法参数是什么,还用这个参数就行了,将方法的 返回值进行返回就行了
Object value = method.invoke(DaoProxy.this.dao, args);
return value;
}
});
return dao;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
例:由于拦截器的统一全站乱码用到了代理,这里就进行演示
Proxy.newProxyInstance(loader, interfaces, InvocationHandler);方法直接就将动态生成的类进行实例化
//这里直接在这个类上实现InvocationHandler,直接在类里覆盖接口的方法,这样写看着清楚
class RequestProxy implements InvocationHandler {
// 被代理对象
private HttpServletRequest request;
public RequestProxy(HttpServletRequest request) {
super();
this.request = request;
}
// 实现一个方法用于创建代理
public HttpServletRequest createRequestProxy() {
HttpServletRequest requestProxy = (HttpServletRequest) Proxy.newProxyInstance(
this.request.getClass().getClassLoader()
, this.request.getClass().getInterfaces()
, this);
// this指向RequestProxy类对象,RequestProxy类又实现了InvocationHandler接口,所以this就是InvocationHandler
return requestProxy;
}
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
// 处理程序
// 代理 getParameter
if("getParameter".equals(method.getName())) {
String value = (String) method.invoke(this.request, args);
if(value==null || "post".equals(this.request.getMethod())) {
return value;
}
// 是get方式提交
return new String(value.getBytes("iso-8859-1"),"utf-8");
}
//HttpServletRequest的其他方法不变
return method.invoke(this.request, args);
}
}
类加载器
类加载器负责将 .class 文件(可能在磁盘上, 也可能在网络上) 加载到内存中, 并为之生成对应的 java.lang.Class 对象当JVM 启动时,会形成由三个类加载器组成的初始类加载器层次结构
类加载器之间的父子关系和管辖范围图
bootstrap classloader
l bootstrap classloader:引导(也称为原始)类加载器,它负责加载Java的核心类。这个加载器的是非常特殊的,它实际上不是java.lang.ClassLoader的子类,而是由JVM自身实现的。可以通过执行以下代码来获得bootstrapclassloader加载了那些核心类库:
URL[]urls=sun.misc.Launcher.getBootstrapClassPath().getURLs();
for (int i = 0; i < urls.length; i++) {
System.out.println(urls[i].toExternalForm());
}
l 因为JVM在启动的时候就自动加载它们,所以不需要在系统属性CLASSPATH中指定这些类库
extension classloader
l extension classloader -扩展类加载器,它负责加载JRE的扩展目录(JAVA_HOME/jre/lib/ext或者由java.ext.dirs系统属性指定的)中的JAR包。这为引入除Java核心类以外的新功能提供了一个标准机制。因为默认的扩展目录对所有从同一个JRE中启动的JVM都是通用的,所以放入这个目录的JAR类包对所有的JVM和system classloader都是可见的。
system classloader
l system classloader - 系统(也称为应用)类加载器,它负责在JVM被启动时,加载来自在命令java中的-classpath或者java.class.path系统属性或者CLASSPATH操作系统属性所指定的JAR类包和类路径。
l 可以通过静态方法ClassLoader.getSystemClassLoader()找到该类加载器。如果没有特别指定,则用户自定义的任何类加载器都将该类加载器作为它的父加载器。
父类委托机制
l classloader 加载类用的是父类委托机制。
l 父类委托机制:先让parent(父)类加载器 寻找,只有在parent找不到的时候才从自己的类路径中去寻找。
l 类加载还采用了cache机制:如果cache中保存了这个Class就直接返回它,如果没有才从文件中读取和转换成Class,并存入cache,这就是为什么修改了Class但是必须重新启动JVM才能生效,并且类只加载一次的原因。