JAVA基础加强(张孝祥)_类加载器、分析代理类的作用与原理及AOP概念、分析JVM动态生成的类、实现类似Spring的可配置的AOP框架

1、类加载器

·简要介绍什么是类加载器,和类加载器的作用

·Java虚拟机中可以安装多个类加载器,系统默认三个主要类加载器,每个类负责加载特定位置的类:BootStrap,ExtClassLoader,AppClassLoader

·类加载器也是Java类,因为其他是Java的类加载器本身也要被类加载器加载,显然必须有第一个加载器不是Java类,这正是BootStrap

·Java虚拟机中的所有类加载器采用具有树形结构进行组织,在实例化每个类装载器对象时,需要为其指定一个父类级的装载器对像或者采用系统默认类装载器为其父类加载

·类加载器之间的父子关系和管辖范围:

 

注意点:用Eclipse的打包工具将ClassLoaderTest输出成jre/lib/ext目录下的shanhw.jar包,在用Eclipse中运行这个类,运行结果显示为ExeClassLoader。此时环境状态是classpath目录有ClassLoaderTest.class,ext/shanhw.jar包中也有ClassLoarderTest.class,这时候,我们就需要了解类加载的具体过程和原理了

类加载器的委托机制:

·当Java虚拟机要加载一个类时,到底派出那个类加载器去加载呢?1、首先在当前线程的类加载器去在加载线程中的第一个类 2、如果类中A引用了类B,Java虚拟机将使用装载A的装载器来装载B 3、还有可以直接调用ClassLoader.loadClass();方法来指定某个类加载器去加载某个类

· 每个类加载器加载时,优先委托给其上级类加载器;

2、编写自己的类加载器

自定义类加载器,例子:写了三个类,1、MyClassLoaderTest.java 2、MyClassLoader.java 3、ClassLoaderAttachment.java

MyClassLoaderTest.java

复制代码
 1 package com.shanhw.javaEnhance.thirteenthDay;
 2 
 3 import java.util.Date;
 4 
 5 
 6 /**    
 7  * 自定义类加载器
 8  */
 9 public class MyClassLoaderTest {
10 
11     public static void main(String[] args) throws Exception {
12         String path = "E:\\Kingsoft Cloud\\Workspaces\\TestExample\\shanhwlib\\ClassLoaderAttachment.class";
13         Class<?> clzz = new MyClassLoader(path).loadClass("com.shanhw.javaEnhance.thirteenthDay.ClassLoaderAttachment");
14         Date d = (Date)clzz.newInstance();
15         System.out.println(d);
16     }
17 
18 }
复制代码

MyClassLoader.java

复制代码
 1 package com.shanhw.javaEnhance.thirteenthDay;
 2 
 3 import java.io.ByteArrayOutputStream;
 4 import java.io.FileInputStream;
 5 import java.io.InputStream;
 6 import java.io.OutputStream;
 7 
 8 public class MyClassLoader extends ClassLoader{
 9 
10     private static void cypher(InputStream is,OutputStream os) throws Exception{
11         int i = 0;
12         while((i = is.read()) != -1){
13             os.write(i ^ 0xff);
14         }
15     }
16     
17     private String classDir;
18     
19     public MyClassLoader(String classDir){
20         this.classDir = classDir;
21     }
22 
23     @SuppressWarnings("deprecation")
24     @Override
25     protected Class<?> findClass(String name) throws ClassNotFoundException {
26         try {
27             
28             FileInputStream fis = new FileInputStream(classDir);
29             ByteArrayOutputStream baos = new ByteArrayOutputStream();
30             cypher(fis,baos);
31             fis.close();
32             byte[] bytes = baos.toByteArray();
33             return defineClass(bytes, 0,bytes.length);
34         } catch (Exception e) {
35             throw new RuntimeException("运行时异常");
36         }
37     }
38 }
复制代码

ClassLoaderAttachment.java

复制代码
 1 package com.shanhw.javaEnhance.thirteenthDay;
 2 
 3 import java.util.Date;
 4 
 5 public class ClassLoaderAttachment extends Date {
 6     /**
 7      * 
 8      */
 9     private static final long serialVersionUID = 1L;
10 
11     public String toString(){
12         return "Hello Houwang Shan!";
13     }
14 } 
复制代码

3、分析代理类的作用与原理及AOP概念

·生活中的代理:武汉人从武汉代理商手中买联想电脑和直接跑到北京联想总部买电脑,你觉得最终业务的主体业务目标有什么区别吗?基本上一样,解决了核心问题,从代理商那里买,是不是更方便一些?

·程序中的代理:1、要为已经存在的多个具有相同接口的目标类的各个方法增加一些功能,例如:异常处理、日志、计算方法的运行时间、事物管理等等,你准备如何做?2、编写一个目标类具有相同接口的代理类,代理类的每个方法调用目标类的相同方法,并在调用方法时加上系统功能代码、如图:

 

3、如果采用工厂模式和配置文件的方式进行管理,则不需要修改客户端程序,在配置文件中配置是使用目标类、还是代理类,这样以后很容易切换,譬如,想要日志功能时,就配置代理类,否则配置目标类,这样,增加系统功能很容易,以后运行一段时间后,又想去掉系统功能也很容易

·AOP

1、系统中存在交叉业务,一个交叉业务就是要切入到系统的一个方面,(安全、事物、日志等要贯穿到好多的模块中,所以它们就是交叉业务)如下所示:

2、用具体的程序代码描述交叉业务

3、交叉业务的编码问题即为面向方面的编程(Aspect Oriented Program 简称AOP),AOP的目标就是使用交叉业务模块儿化。可以采用将切面代码移动到原始方法的周围,这与直接在方法中编写切面代码运行效果是一样的,如下所示:

4、使用代理技术正好可以解决这种问题,代理是实现AOP功能的核心和关键技术

·动态代理技术

1、要为系统中的各种接口的类增加代理功能,那将需要太多的代理类,全部采用静态代理的方式,将是一件非常麻烦的事情,写成成百上千的代理类,是不是太累了

2、JVM可以在运行期动态生成类的字节码,这种动态生成的类,往往被用作代理类,即动态代理

3、JVM生成动态类必须实现一个或者多个接口,所以JVM生成的动态类只能用作具有相同接口的目标类的代理

4、CGLIB库可以动态生成一个类的子类,一个类的子类也可以用作该类的代理,所以,如果要为一个没有实现接口的类生成动态代理,那么可以使用CGLIB库

5、代理类的各个方法中通常除了要调用目标的相应方法和对外返回目标返回的结果外,还可以在代理方法中的如下四个位置加上系统功能代码:

******1、在调用目标方法之前 2、在调用目标方法之后 3、在调用目标方法前后 4、在处理目标方法异常的catch块中

·分析JVM动态生成的类

·1、创建实现了Collection接口的动态类和查看其名称,分析Proxy.getProxyClass方法的各个参数

·2、编码列出动态类中的所有构造方法和参数签名

·3、编码列出动态类中的所有方法和参数签名

·4、创建动态类的实例对象

  1、用反射获得动态方法

  2、编写一个简单的InvocationHandle类

  3、调用构造方法创建动态类的实例对象,并将编写的InvocationHandler类的实例对象传进去

  4、打印创建的对象和调用的对象的没有返回值和getClass方法,演示调用其他返回值的方法报告了异常

  5、将创建动态类的实例对象的代理改成匿名内部类的形式编写,锻炼习惯匿名内部类

1、例子:ProxyTest.java

复制代码
 1 package com.shanhw.javaEnhance.thirteenthDay;
 2 
 3 import java.lang.reflect.Constructor;
 4 import java.lang.reflect.InvocationHandler;
 5 import java.lang.reflect.Method;
 6 import java.lang.reflect.Proxy;
 7 import java.util.ArrayList;
 8 import java.util.Collection;
 9 
10 public class ProxyTest {
11     
12     /**
13      * 
14      */
15     public static void main(String[] args) throws Exception{
16         Class clazzProxy = Proxy.getProxyClass(Collection.class.getClassLoader(), Collection.class);
17         System.out.println("----------Constructors:----------");
18         Constructor[] constructors = clazzProxy.getConstructors();
19         for(Constructor constructor:constructors){
20             String consName = constructor.getName(); 
21             StringBuilder str = new StringBuilder(consName);
22             str.append('(');
23             Class[] clazzParas = constructor.getParameterTypes();
24             for(Class clazzPara : clazzParas){
25                 str.append(clazzPara.getName()).append(',');
26             }
27             if(clazzParas.length != 0){
28                 str.deleteCharAt(str.length()-1);
29             }
30             str.append(')');
31             System.out.println(str);
32         }
33         System.out.println("----------Methods:----------");
34         Method[] methods = clazzProxy.getMethods();
35         for(Method method:methods){
36             String consName = method.getName(); 
37             StringBuilder str = new StringBuilder(consName);
38             str.append('(');
39             Class[] clazzParas = method.getParameterTypes();
40             for(Class clazzPara : clazzParas){
41                 str.append(clazzPara.getName()).append(',');
42             }
43             if(clazzParas.length != 0){
44                 str.deleteCharAt(str.length()-1);
45             }
46             str.append(')');
47             System.out.println(str);
48         }
49         System.out.println("----------创建实例对象:----------");
50         Constructor constructor = clazzProxy.getConstructor(InvocationHandler.class);
51         
52         Collection collection = (Collection)constructor.newInstance(new InvocationHandler(){
53 
54             @Override
55             public Object invoke(Object proxy, Method method, Object[] args)
56                     throws Throwable {
57                 return null;
58             }});
59         Collection proxy = (Collection) Proxy.newProxyInstance(Collection.class.getClassLoader(),
60                 new Class[]{Collection.class},
61                 new InvocationHandler(){
62                     ArrayList arrayList = new ArrayList();
63                     @Override
64                     public Object invoke(Object proxy, Method method,
65                             Object[] args) throws Throwable {
66                         return method.invoke(arrayList, args);
67                     }
68                 });
69         proxy.add("a");
70         proxy.add("a");
71         proxy.add("a");
72         System.out.println(proxy.size());
73     }
74 }
复制代码

 内部的原理:猜想分析动态生成的类的内部代码?

·动态生成的类实现了Collection接口(可以实现若干接口),生成的类有Collection接口中的所有方法和一个如下接受InvocationHandler参数的构造方法

·构造方法接受一个InvocationHandler对象,接受对象干什么了呢?该方法内部类的代码会是怎么样的呢?

·实现的Collection接口中的各个方法的代码又是怎么样的呢?InvocationHandler接口中定义的invoke方法接受的三个参数是什么意思呢?

·让动态生成的类,成为目标类的代理

1、分析动态代理的工作原理图:

 

2、怎样将目标类传进去?

****1、在InvocationHandler实现类中创建目标类的实例对象,可以看运行效果和加入日志代码,但是没有实际意义

****2、为InvocationHandler实现类注入目标类的实例对象,不能采用匿名内部类的形式了

****3、让匿名的InvocationHandler实现访问外面方法中的目标类实例对象的final类型的引用变量

3、将创建代理的过程改为一种更优雅的方式,eclipse重构出一个getProxy方法绑定接受目标同时返回代理对象,让调用者更懒惰,更方便,调用者甚至不用接触任何代理的API

4、把系统功能代码模块化,即将切面代码也改为通过参数形式提供,怎样把要执行的系统功能代码从参数形式提供?

****1、把要执行的代码装到一个对象的某个方法里,然后把这个对象作为参数传递,接收者只要调用这个对象的方法,即等于执行外界提供的代码。

****2、为bind方法增加一个Advice参数。

·实现类似Spring的可配置的AOP框架

(实现思路):1、工厂类BeanFactory负责创建目标类或代理类的实例对象,并通过配置文件实现切换,其getBean方法根据参数字符串返回一个相应的实例对象,如果参数字符串在配置文件中对应的类名不是ProxyFactoryBean,则直接返回该类的实例对象,否则,返回该类实例对象的getProxy方法返回的对象

2、BeanFactory的构造方法接收代表配置文件的输入流对象,配置文件格式如下:xxx = java.util.ArrayList #xxx = cn.shanhw.ProxyFactoryBean xxx.target=java.util.ArrayList xxx = advice.shanhw.MyAdvice

·ProxyFactoryBean充当封装生成动态代理的工厂,需要为工厂类提供哪些配置参数信息:1、目标 2、通知

·编写客户端应用 1、编写实现Advice接口的类和配置文件中进行配置 2、调用BeanFactory获取对象

posted on 2013-10-27 01:57  heartstage  阅读(539)  评论(0编辑  收藏  举报

导航