分析代理类的作用与原理及AOP概念代理的概念与作用·生活中的代理 -武汉人从武汉的代理商手中买联想电脑和直接跑到北京传智播客旁边来找联想总部买电脑,你觉得最终的主体业务目标有什么区别吗?基本上一样吧,都解决类核心问题,但是,一点区别都没有吗?从代理商那里买真的一点好处都没有吗?·程序中的代理 -要为已存在的多个具有相同接口的目标类的各个方法增加一些系统功能,例如,异常处理、日志、计算方法的运行时间、事务管理、等等,你准备如何做? -编写一个与目标类具有相同接口的代理类,代理类的每个方法调用目标类的相同方法,并在调用方法时加上系统功能的代码。代理架构图:
-如果采用工厂模式和配置文件的方式进行管理,则不需要修改客户端程序,在配置文件中配置是使用目标类、还是代理类,这样以后很容易切换,譬如,想要日志功能时就配置代理类,否则配置目标类,这样,增加系统功能很容易,以后运行一段时间后,又想去掉系统功能也很容易。
AOP(Aspect Oriented program,简称AOP)即为面向方面的编程·系统中存在交叉业务,一个交叉业务就是要切入到系统中的一个方面,如下所示:{安全、事务、日志等功能要贯穿到好多个模块中,所以,它们就是交叉业务。} 安全 事务 日志 StudentService--|------|------|------CourseService---|------|------|------ MiscService-----|------|------|------·用具体的程序代码描述交叉业务员:method1 method2 method3{ { {-----------------------------------------切面... ... ...-----------------------------------------切面} } }·交叉业务的编程问题即为面向方面的编程(Aspect Oriented program,简称AOP),AOP的目标就是要使交叉业务模块化。可以采用将切面代码移动到原始方法的周围,这与直接在方法中编写切面代码的运行效果是一样的,如下所示:-----------------------------------------切面func1 func2 func3{ { {... ... ...} } }-----------------------------------------切面·使用代理技术正好可以解决这种问题,代理是实现AOP功能的核心和关键技术。
动态代理技术·要为系统中的各种接口的类增加代理功能,那将需要太多的代理类,全部采用静态代理方式,将是一件非常麻烦的事情!写成百上千个代理类,是不是太累?·JVM可以在运行期动态生成出类的字节码,这种动态生成类往往被用作代理类,即动态代理类。·JVM生成的动态类必须实现一个或多个接口,所以,JVM生成的动态类只能用作具有相同接口的目标类的代理。·CGLIB库可以动态生成一个类的子类,一个类的子类也可以用作该类的代理,所以,如果要为一个没有实现接口的类生成动态代理类,那么可以使用CGLIB库。·代理类的各个方法中通常除了要调用目标的相应方法和对外返回目标返回的结果外,还可以在代理方法中的如下四个位置加上系统功能代码: -1.在调用目标方法之前 -2.在调用目标方法之后 -3.在调用目标方法前后 -4在处理目标方法异常的catch块中示例代码:void sayHello(){ ....... try{ target.sayHello(); }catch(Exception e){ ...... } ........}
分析JVM动态生成的类·创建实现类Collection接口的动态类和查看其名称,分析Proxy.getProxyClass方法的各个参数。·编码列出动态类中的所有构造方法和参数签名·编码列出动态类中的所有方法和参数签名·创建动态类的实例对象 -用反射获得构造方法 -编写一个最简单的InvocationHandler类 -调用构造方法创建动态类的实例对象,并将编写的InvocationHandler类的实例对象传递进去 -打印创建的对象和调用对象的没有返回值的方法和getClass方法,演示调用其他有返回值的方法报告了异常。 -将创建动态类的实例对象的代理改成匿名内部类的形式编写,锻炼大家习惯匿名内部类。·总结思考:让jvm创建动态类及其实例对象,需要给它提供哪些信息? -三个方面: ·生成的类中有哪些方法,通过让其他实现哪些接口的方式进行告知; ·产生的类字节码必须有一个关联的类加载器对象; ·生成的类中的方法的代码是怎样的,也得由我们提供,把我们的代码写在一个约定好了接口对象的方法中,把对象传递给它,它调 用我的方法,即相当于插入了我们的代码。提供执行代码的对象就是那个InvocationHandler对象的invoke方法中加一点代码,就可 以看到这些代码被调用运行了。
·用Proxy.newInstance方法直接一步就创建出代理对象。以上编程示例的代码:package lqq.heima.day3;
import java.lang.reflect.Constructor;import java.lang.reflect.InvocationHandler;import java.lang.reflect.Method;import java.lang.reflect.Proxy;import java.util.ArrayList;import java.util.Collection;
public class ProxyTest {
/** * @param args */ @SuppressWarnings({"unchecked","unused"}) public static void main(String[] args) throws Exception { // TODO Auto-generated method stub Class clazzProxy = Proxy.getProxyClass(Collection.class.getClassLoader(), Collection.class); System.out.println(clazzProxy.getName()); System.out.println("------------begin constructors list--------------"); /*$Proxy0() $Proxy0(InvocationHandler,int)*/ Constructor[] constructors = clazzProxy.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.toString()); } System.out.println("------------begin methods list--------------"); Method[] methods = clazzProxy.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.toString()); } System.out.println("------------begin create instance object--------------"); //Object obj = clazzProxy.newInstance();//此处不可以调用该方法,因为这个类没有默认的构造方法 //1.采用内部类的方式得到Collection的代理实例 Constructor constructor = clazzProxy.getConstructor(InvocationHandler.class); class MyInvocationHandler implements InvocationHandler{
@Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { // TODO Auto-generated method stub return null; } } Collection proxy = (Collection) constructor.newInstance(new MyInvocationHandler()); System.out.println(proxy);//此处打印的结果是null是因为proxy.toString()返回的结果是null System.out.println(proxy.toString());//此处打印的结果是null proxy.clear();//调用没有返回值的方法时没有问题。 //proxy.size();//调用有返回值的方法的时候报告错误:Exception in thread "main" java.lang.NullPointerException //2.采用匿名的内部类的方式(即直接new 接口InvocationHandler(){}的形式)得到Collection的代理实例 Collection proxy1 = (Collection) constructor.newInstance(new InvocationHandler(){
@Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { // TODO Auto-generated method stub return null; } }); //3.用Proxy.newInstance方法直接一步就创建出代理对象Collection的代理实例。 Collection proxy2 = (Collection) Proxy.newProxyInstance( Collection.class.getClassLoader(), new Class[]{Collection.class}, new InvocationHandler(){ ArrayList target = new ArrayList();
@Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { // TODO Auto-generated method stub long beginTime = System.currentTimeMillis(); Object retVal = method.invoke(target, args); long endTime = System.currentTimeMillis(); System.out.println(method.getName()+" 方法调用共耗时:" +(endTime -beginTime)+" 毫秒"); return retVal; } }); proxy2.add("zxx"); proxy2.add("lhm"); proxy2.add("bxd"); System.out.println(proxy2.size()); }
}
面试题:StringBuffer与StringBuilder的区别。答:StringBuilder是单线程的:一个可变的字符序列。此类提供一个与 StringBuffer 兼容的 API,但不保证同步。该类被设计用作 StringBuffer 的一个简易替换,用在字符串缓冲区被单个线程使用的时候(这种情况很普遍)。如果可能,建议优先采用该类,因为在大多数实现中,它比 StringBuffer 要快。StringBuffer是线程安全的: 线程安全的可变字符序列。一个类似于 String 的字符串缓冲区,但不能修改。虽然在任意时间点上它都包含某种特定的字符序列,但通过某些方法调用可以改变该序列的长度和内容。 可将字符串缓冲区安全地用于多个线程。可以在必要时对这些方法进行同步,因此任意特定实例上的所有操作就好像是以串行顺序发生的,该顺序与所涉及的每个线程进行的方法调用顺序一致。
猜想分析动态生成的类的内部代码·动态生成的类实现了Collection接口(可以实现若干接口),生成的类有Collection接口中的所有方法和一个如下接受InvocationHandler参数的构造方法。·构造方法接受一个InvocationHandler对象,接受对象了要干什么用呢?该方法内部的代码会是怎样的呢?示意代码:Class $Proxy0{ InvocationHandler handler; public $Proxy0(InvocationHandler handler) { this.handler = handler; }}·实现的Collection接口中的各个方法的代码又是怎样的呢?InvocationHandler接口中定义的invoke方法接受的三个参数又是什么意思?图解说明如下:Client程序调用objProxy.add("abc")方法时,涉及三要素:objProxy对象、add方法、"abc"参数Class Proxy${ add(Object object){ return handler.invoke(Object proxy,Method method,Object[] args); }}·分析先前但因动态类的实例对象时,结果为什么会是null呢?调用有基本类型答:在调用代理对象的方法的时候,会将方法的参数传递给handler中的invoke方法,在invoke方法中我们会找到对应的目标来调用目标对象的invoke方法的并得到目标对象方法的返回值,依次会返回给handler中的invoke方法->最后返回给代理对象的方法。注意:在代理实例上的 java.lang.Object 中声明的 hashCode、equals 或 toString 方法的调用将按照与编码和指派接口方法调用相同的方式进行编码,并被指派到调用处理程序的 invoke 方法,如上所述。传递到 invoke 的 Method 对象的声明类是 java.lang.Object。代理类不重写从 java.lang.Object 继承的代理实例的其他公共方法,所以这些方法的调用行为与其对 java.lang.Object 实例的操作一样。