目录
学习笔记
ClassLoader类加载器
在Java语言里面提供有一个系统的环境变量:CLASSPATH,这个环境的属性的主要作用是在JVM进程启动的时候进行类的加载路径的定义,在JVM里面可以通过类加载器而后进行指定路径中类的加载,也就是说找到类加载器,就意味着找到类的来源。
系统类的加载器
如果说现在要想获得类的加载器,那么一定要通过ClassLoade来获得,而要想获得ClassLoader类的对象则必须利用Class类(反射的根源)实现,方法:public ClassLoader getClassLoader(),当获取了ClassLoader之后还可以获取其父类的ClassLoader类对象:public final ClassLoader getParent()。
范例:观察类加载器
package cn.ren.demo;
class Message {}
public class JavaAPIDemo {
public static void main(String[] args) throws Exception {
Class<?> clazz = Message.class;
System.out.println(clazz.getClassLoader()); // 获取当前类的加载器
System.out.println(clazz.getClassLoader().getParent()); // 获取父类加载器
System.out.println(clazz.getClassLoader().getParent().getParent()); // 获取祖父类加载器
}
}
从JDK1.8之后的版本提供有一个“PlatformClassLoader”类加载器,而在JDK1.8及以前的版本里面提供的是“ExtClassLoader”,因为在JDK的安装目录里面提供有ext的目录,开发者可以将*.class文件拷贝到此目录里面,这样就可以直接执行的了,但是这种操作并不安全。为了与系统类加载器与应用类加载器之间保持设计的平衡,提供有平台类加载器。
当你获得了类加载器之后就可以利用类加载器来实现类的反射加载处理。
自定义ClassLoader处理类
清楚了类加载器的功能后,就可以根据自身的需要来实现自定义的类加载器。但是要注意自定义类类加载器的顺序是在所有系统类加载器的最后。系统类之中的类加载器都是根据CLASSPATH中来的,而有了自定义类加载器就可以有开发者任意指派加载路径。
1、随意编写程序类,并且将这个类保存在磁盘上
package cn.ren.util;
public class Message {
public void send() {
System.out.println("ren");
}
}
2、将此类拷贝到D盘上并进行编译处理,并且不打包:javac Message.java,此时并没有进行打包处理,所以无法通过CLASSPATH正常加载。
3、 自定一个类加载器,并且继承字ClassLoader类。在ClassLoader类里面提供有一个字节转换为类结构的方法:
定义类转换:protected final Class<?> defineClass(String name, byte[] b, int off, int len) throws ClassFormatError
package cn.ren.util;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
public class RENClassLoader extends ClassLoader {
private static final String MESSAGE_CLASS_PATH = "D:" + File.separator + "Message.class" ;
/**
* 进行指定类的加载
* @param className 类的完整名称 “包.类”
* @return 返回一个指定类的class对象
* @throws Exception 如果类文件不存在则无法加载
*/
public Class<?> loadData(String className) throws Exception {
byte [] data = this.loadClassData() ; // 读取二进制数据文件
if (data != null) {
return super.defineClass(className, data, 0, data.length) ;
}
return null ;
}
private byte [] loadClassData() throws Exception { // 通过文件进行类的加载
InputStream input = null ;
ByteArrayOutputStream bos = null ; // 将数据加载到内存流之中。内存流有个重要的特点,返回全部数据
byte data [] = null ;
try {
bos = new ByteArrayOutputStream() ; // 实例化内存流
input = new FileInputStream(new File(MESSAGE_CLASS_PATH)) ; // 文件流加载
input.transferTo(bos) ; // 读取数据
data = bos.toByteArray() ; // 将所有读取到的字节数据取出
} catch (Exception e) {
e.printStackTrace();
} finally {
if (input != null) {
input.close();
}
if (bos != null) {
bos.close();
}
}
return data ;
}
}
4、编写测试类实现类加载控制
package cn.ren.demo;
import java.lang.reflect.Method;
import cn.ren.util.RENClassLoader;
class Message {}
public class JavaAPIDemo {
public static void main(String[] args) throws Exception {
RENClassLoader classloader = new RENClassLoader() ; // 实例化自定义类的加载器
Class<?> cls = classloader.loadData("cn.ren.util.Message") ; // 进行类的加载
Object obj = cls.getDeclaredConstructor().newInstance() ;
Method method = cls.getDeclaredMethod("send") ;
method.invoke(obj) ;
}
}
如果在以后结合到网络程序开发的话,就可以通过一个远程的服务器确定类的功能。
5、观察当前的Message类加载器的情况。
package cn.ren.demo;
import cn.ren.util.RENClassLoader;
class Message {}
public class JavaAPIDemo {
public static void main(String[] args) throws Exception {
RENClassLoader classloader = new RENClassLoader() ; // 实例化自定义类的加载器
Class<?> cls = classloader.loadData("cn.ren.util.Message") ; // 进行类的加载
System.out.println(cls.getClassLoader());
System.out.println(cls.getClassLoader().getParent());
System.out.println(cls.getClassLoader().getParent().getParent());
}
}
自定义加载器都是最后执行的。
如果你现在定义了一个类,这个类的名字为:java.lang.String,并且利用了自定义的加载器加载处理,这个类将不会被加载,Java之中针对于类加载器提供有双亲加载机制,如果加载的程序类是由系统提供的类则会由系统类进行加载,如果开发者定义的类于系统类名称相同,为了保证系统的安全性绝对不会加载。
反射与代理设计
代理设计模式是在程序开发之中使用最多的设计模式,代理设计的核心是有真实业务实现类与代理业务实现类,并且代理类要完成比真实业务更多的操作。
传统代理设计的弊端
所有的代理设计模式如果按照设计要求来讲,必须是基于接口的设计,也就是说需要首先定义出核心接口的组成,下面模拟一个消息发送的代理操作结构。
范例:传统代理设计
package cn.ren.demo;
interface IMessage { // 传统代理设计必须有接口
public void send(); // 业务方法
}
class MessageReal implements IMessage {
public void send() {
System.out.println("【发送消息】 ren57");
}
}
class MessageProxy implements IMessage {// 代理类
private IMessage message ; // 代理对象,一定是业务接口实例
public MessageProxy(IMessage message) {
this.message = message ;
}
@Override
public void send() {
if (this.connect()) {
this.message.send();
this.close();
}
}
public boolean connect() {
System.out.println("【消息代理】进行消息发送通道的连接");
return true ;
}
public void close() {
System.out.println("【消息代理】关闭消息通道");
}
}
public class JavaAPIDemo {
public static void main(String[] args) throws Exception {
IMessage msg = new MessageProxy(new MessageReal()) ;
msg.send();
}
}
以上的操作代码是一个标准的代理设计,但是进一步思考会发现客户端的接口与具体的子类耦合问题(主类需要知道太多内容),这个设计并不好,从实际的开发角度讲应该在引入工厂设计模式处理。
以上的代理设计模式为静态代理设计,这种静态代理设计的特点在于:一个代理类只为一个接口服务。那么如果现在准备出了3000个业务接口,按照此种做法就需要编写3000个代理类,并且这3000个代理类操作形式一样。
所以现在要解决的问题在于:如何让一个代理类满足所有的业务接口操作要求。
动态代理设计模式
通过静态代理设计模式的缺陷可以发现,最好的做法是为所有功能一致的业务操作接口提供统一的代理处理操作,这样就可以通过动态代理机制来实现,但是在动态代理机制里面需要考虑到如下几点问题:
- 代理不管是动态代理类还是静态代理类都一定要接收真实业务实现子类对象;
- 由于动态代理类不在与某一个具体的接口进行捆绑,所以应该可以获取类的接口信息;
在进行动态代理的实现过程之中,首先需要关注的就是一个InvocationHandler接口,这个接口规定了代理方法的执行。
public interface InvocationHandler {
/**
* 代理方法调用,代理主题类里面执行的方法最终都是此方法
*
* @param proxy 要代理的对象
* @param method 要执行的接口方法名称
* @param args 传递的参数
* @return 某一个方法的返回值
* @throws Throwable 方法调用时出现的错误,继续向上抛出。
*/
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable;
}
在進行动态代理设计的时候动态对象的创建是由JVM底层完成的,此时主要依靠的时java.lang.reflect.Proxy程序类,这个程序类之中只提供有一个核心方法:
- 代理对象:public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)
| - ClassLoader loader: 获取当前真实主体类的ClassLoader;
|- Class<?> [] interfaces :代理时围绕接口进行的,所以一定要获取真实主题类的接口信息;
|- InvocationHandler h :代理处理的方法;
范例:实现动态代理机制
package cn.ren.demo;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class JavaAPIDemo {
public static void main(String[] args) throws Exception {
IMessage msg = (IMessage) new RENProxy().bind(new MessageReal()) ;
msg.send();
}
}
class RENProxy implements InvocationHandler {
private Object target ; // 保存真实业务主题对象
/**
* 进行真实业务对象与代理业务对象绑定处理
* @param target 真实业务对象
* @return Proxy生成的代理业务对象
*/
public Object bind(Object target) {
this.target = target ;
return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), this) ;
}
public boolean connect() {
System.out.println("【消息代理】进行消息发送通道的连接");
return true ;
}
public void close() {
System.out.println("【消息代理】关闭消息通道");
}
public Object invoke(Object pro, Method method, Object[] args) throws Throwable {
System.out.println("************【执行方法】" + method);
Object returnData = null ;
if (this.connect()) {
returnData = method.invoke(this.target,args) ;
this.close();
}
return returnData ;
}
}
interface IMessage { // 传统代理设计必须有接口
public void send(); // 业务方法
}
class MessageReal implements IMessage {
@Override
public void send() {
System.out.println("【发送消息】 ren57");
}
}
通过观察系统中提供的Proxy.newIProxyInstance()方法,会发现该方法会使用大量的底层机制来进行代理对象的动态创建,所有的代理类是复合所有相关功能需求的操作功能类,它不再代表具体的接口,这样处理的时候就必须依赖于类加载器与接口进行代理对象的伪造。
没人再用静态代理,所有的代理必须是动态代理。
CGLIB实现代理设计模式
从Java的官方来讲已经明确了如果要想实现代理设计模式,一定是基于接口的应用,所在在官方给出的Proxy类创建代理对象时都需要传递该对象所有的接口信息。
roxy.newProxyInstance(target.getClass().getClassLoader(), arget.getClass().getInterfaces(), this) ;
但是这个时候有一部分的开发者就认为不应该强迫性的基于接口来实现代理设计,所以开发者就开发出了一个CGLIB的开发包,利用这个开发包就可以实现基于类的代理设计模式。
1、CGLIB是一个第三方的程序包,需要单独在Eclipse中进行配置,现在假设程序包的路径:d:\jar-lib\ ... .jar,那么需要打开Eclipse的项目属性安装第三方开发包。
2、编写程序类,该类不实现任何接口。
class Message{
public void send() {
System.out.println("【发送消息】 ren57");
}
}
3、利用CGLIB编写代理类,但是这个代理类需要明确此时相当于使用了类的形式实现了代理设计的处理,所以该代理设计需要通过来生成代理对象,定义一个代理类。
class RENProxy implements MethodInterceptor { // 拦截器配置
private Object target ;
public RENProxy(Object target) {
this.target = target ;
}
@Override
public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throw Exception {
Object returnData = null ;
if (this.connect()) {
returnData = method.invoke(this.target, args) ;
this.close();
}
return returnData ;
}
public boolean connect() {
System.out.println("【消息代理】进行消息发送通道的连接");
return true ;
}
public void close() {
System.out.println("【消息代理】关闭消息通道");
}
}
4、此时如果要想创建代理类对象则必须进行一系列的CGLIB处理。
public class JavaAPIDemo {
public static void main(String[] args) throws Exception {
IMessage realObject = new Message() ; // 真实主题对象
Enhancer enhancer = new Enhancer() ; //负责代理操作的程序类
enhancer.setSuperClass(realObject.getClass()) ; // 假定一个父类
enhancer.setCallback(new RENProxy(realObject)) ; //设置代理类
Message proxyObject = (Message) enhancer.create() ; // 创建代理对象
proxyObject.send();
}
}
在进行代理设计模式定义的时候,除了可以使用接口之外,也可以不受接口的限制而是基于类的代理设计但是从正常的设计角度来讲,建议基于接口的设计比较合理。
反射与Annotation
从JDK1.5之后Java开发提供了Annotation技术支持,这种技术为项目的编写带来了新的模型,而后经过十多年的发展,Annotation的技术得到了广泛的应用,并且已经在所有的项目开发之中都会存在。
获取Annotation信息
在进行类或方法定义的时候都可以使用一系列的Annotation进行声明,如果要想获取这些Annotation那么就可可以直接通过反射来完成。在java.lang.reflect里面有一个AccessibleObject类,在这个类中提供有或取Annotation类的方法:
获取全部Annotation:public Annotation[] getAnnotations() ;
获取指定Annotation: public <T extends Annotation> T getAnnotation(Class<T> annotationClass) ;
范例:定义一个接口并且在接口上使用Annotation
package cn.ren.demo;
import java.io.Serializable;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
public class JavaAPIDemo {
public static void main(String[] args) throws Exception {
{ // 获取接口上的Annotation信息
Annotation annotations [] = IMessage.class.getAnnotations() ; // 获取接口上的全部信息
for ( Annotation temp : annotations ) {
System.out.println(temp);
}
}
System.out.println("-------------------------------------------------");
{ // 获取MessageImpl子类上的Annotation
Annotation annotations [] = MessageImpl.class.getAnnotations() ; // 获取接口上的全部信息
for ( Annotation temp : annotations ) {
System.out.println(temp);
}
}
System.out.println("--------------------------------------------------");
{ // 获取MessageImpl.send()方法上的Annotation
Method method = MessageImpl.class.getDeclaredMethod("send", String.class) ;
Annotation annotations [] = method.getAnnotations() ; // 获取接口上的全部信息
for ( Annotation temp : annotations ) {
System.out.println(temp);
}
}
}
}
@FunctionalInterface
@Deprecated(since="1.0")
interface IMessage { // 有两个Annotation
public void send(String msg) ;
}
@SuppressWarnings("serial") // 无法在程序执行的时候获取
class MessageImpl implements IMessage, Serializable {
@Override // 无法在程序运行时获取
public void send(String msg) {
System.out.println("【消息发送】 " + msg);
}
}
不同的Annotation有它存在的范围,下面对比两个Annotation:
@FunctionalInterface(运行时) | @SuppressWarnings(源代码时) |
|
|
此时发现@FunctionalInterface()是运行时生效的Annotation,所以当程序执行的时候可以获取此Annotation,而“@SuppressWarnings()”是在源代码编写的时候有效。而在“RetentionPolicy”枚举类中还有一个class的定义,指的是在类定义的时候生效。
自定义Annotation
现在已经清楚了Annotation的获取,以及Annotation的运行策略,但是最为关键性的因素是,如何可以实现自定义的Annotation,为此在Java里面提供有新的语法,使用“@interface”来定义Annotation。
范例:自定义Annotation
package cn.ren.demo;
import java.lang.annotation.Annotation;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.reflect.Method;
@Retention(RetentionPolicy.RUNTIME) // 定义Annotation的运行策略
@interface DefaultAnnotation { // 自定义Annotation
public String title () ; // 获取数据
public String url() default "www.ren.cn" ; // 获取数据,默认值
}
class Message {
@DefaultAnnotation(title = "REN")
public void send(String msg) {
System.out.println("【消息发送】" + msg);
}
}
public class JavaAPIDemo {
public static void main(String[] args) throws Exception {
Method method = Message.class.getDeclaredMethod("send", String.class) ;
DefaultAnnotation annotation = method.getAnnotation(DefaultAnnotation.class ) ;
System.out.println(annotation.title()); // 调用Annotation中的方法
System.out.println(annotation.url());
String msg = annotation.title() + "(" + annotation.url() + ")" ; // 消息内容
method.invoke(Message.class.getDeclaredConstructor().newInstance(), msg) ;
}
}
使用Annotation之后的最大特点是可以结合反射机制实现程序的处理。
工厂设计模式与Annotation整合
现在已经清楚Annotation的整体作用,但是在开发之中Annotation到底在开发之中能做那些事情呢?下面结合工厂设计模式来应用Annotation。
package cn.ren.demo;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class JavaAPIDemo {
public static void main(String[] args) throws Exception {
MessageService messageService = new MessageService() ;
messageService.send("ren");
}
}
@Retention(RetentionPolicy.RUNTIME)
@interface UseMessage {
public Class<?> clazz() ;
}
@UseMessage(clazz=MessageImpl.class) // 利用Annotation实现类的使用,以后换子类时Annotation更换就完成了
class MessageService {
private IMessage message ;
public MessageService() {
UseMessage use = MessageService.class.getAnnotation(UseMessage.class) ;
this.message = (IMessage) Factory.getInstance(use.clazz()) ; // 直接是通过Annotation获取
}
public void send(String msg) {
this.message.send(msg);
}
}
class Factory {
private Factory() {}
@SuppressWarnings("unchecked")
public static <T> T getInstance(Class<T> clazz) { // 直接返回一个实例化的操作对象
try {
return (T) new MessageProxy().bind((clazz.getDeclaredConstructor().newInstance())) ;
} catch (Exception e) {
e.printStackTrace();
return null ;
}
}
}
class MessageProxy implements InvocationHandler {
private Object target ;
public Object bind(Object target) {
this.target = target ;
return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), this) ;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object returnData = null;
try {
if (this.connect()) {
returnData = method.invoke(this.target, args);
} else {
throw new Exception("【ERROR】消息无法发送");
}
} finally {
this.close();
}
return returnData;
}
public boolean connect() {
System.out.println("【代理】发送通道建立");
return true ;
}
public void close() {
System.out.println("【代理】发送通道关闭");
}
}
interface IMessage {
public void send(String msg) ;
}
class MessageImpl implements IMessage {
@Override
public void send(String msg) {
System.out.println("【消息发送】" + msg);
}
}
由于Annotation存在,所以对于面向接口的编程配置处理将可以利用Annotation的属性完成控制,从而使得整体代码变得简洁。