代理模式:
代理模式是常用的Java设计模式,它的特征是代理类与委托类有同样的接口,代理类主要负责为委托类预处理消息,代理类与委托类之间通常会存在关联关系,一个代理类的对象与一个委托类的对象关联,代理类的对象本身并不真正实现服务,而是通过调用委托类的对象的相关方法,来提供特定的服务,简单的说就是,我们在访问实际对象时,是通过代理对象来访问的.代理模式就是在访问实际对象时,引入一定程度的间接性,因为这种间接性,可以附加多种用途.
代理模式结构图(图片来自<<大话设计模式>>)
静态代理:
静态代理:由程序员创建或特定工具自动生成源代码,也就是在编译时就已经将接口,被代理类,代理类等确定下来,在程序运行之前,代理类.class文件已经生成.
静态代理简单实现:
1.首先创建一个接口,这个接口是代理类和被代理类公共的接口,它们有一个共同的实现方法.
1 public interface Person { 2 // 上交班费 3 void giveMoney(); 4 }
2.创建两个实现类
1 public class Student implements Person { 2 private String name; 3 public Student(String name){ 4 this.name = name; 5 } 6 7 public void giveMoney() { 8 System.out.println("student: "+name+" giveMoney"); 9 } 10 }
1 public class StudentProxy implements Person { 2 //被代理的学生 3 Student student; 4 public StudentProxy(Person student){ 5 // 只代理学生对象 6 if(student.getClass() == Student.class){ 7 this.student = (Student)student; 8 } 9 } 10 // 实现被代理类的方法 11 public void giveMoney() { 12 student.giveMoney(); 13 } 14 }
3.测试类
1 public class Test { 2 public static void main(String[] args) { 3 //被代理对象 4 Person student = new Student("张三"); 5 //生成代理对象,并将张三传给代理对象 6 Person monitor = new StudentProxy(student); 7 //代理对象调用方法,间接调用被代理对象的方法 8 monitor.giveMoney(); 9 } 10 }
4.测试结果
这里并没有直接通过张三(被代理对象)来执行上交班费的行为,而是通过班长(代理对象)来代理执行了。这就是代理模式.
代理模式最主要的就是有一个公共接口(Person),一个具体的类(Student),一个代理类(StudentsProxy),代理类持有具体类的实例,代为执行具体类实例方法。上面说到,代理模式就是在访问实际对象时引入一定程度的间接性,因为这种间接性,可以附加多种用途。这里的间接性就是指不直接调用实际对象的方法,那么我们在代理过程中就可以加上一些其他用途。
1 public class StudentProxy implements Person { 2 //被代理的学生 3 Student student; 4 public StudentProxy(Person student){ 5 // 只代理学生对象 6 if(student.getClass() == Student.class){ 7 this.student = (Student)student; 8 } 9 } 10 // 实现被代理类的方法 11 public void giveMoney() { 12 System.out.println("在执行被代理类方法之前执行"); 13 student.giveMoney(); 14 } 15 }
可以看到,只需要在代理类中执行被代理方法之前,执行其他操作就可以了。这种操作,也是使用代理模式的一个很大的优点。最直白的就是在Spring中的面向切面编程(AOP),我们能在一个切点之前执行一些操作,在一个切点之后执行一些操作,这个切点就是一个个方法。这些方法所在类肯定就是被代理了,在代理过程中切入了一些其他操作。
动态代理:
代理类在程序运行时创建的代理方式被称为动态代理。动态代理,代理类并不是在Java代码中定义的,而是在运行时根据我们在Java代码中的“指示”动态生成的。相比于静态代理, 动态代理的优势在于可以很方便的对代理类的函数进行统一的处理,而不用修改每个代理类中的方法。
动态代理的实现:
在java的java.lang.reflect包下提供了一个Proxy类和一个InvocationHandler接口,通过这个类和这个接口可以生成JDK动态代理类和动态代理对象。
1.创建一个接口
1 public interface Person { 2 void giveMoney(); 3 }
2.创建一个需要被代理的实际类
public class Student implements Person { private String name; public Student(String name) { this.name = name; } public void giveMoney() { try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("student "+name +" giveMoney"); } }
3定义一个检测方法执行时间的工具类
1 public class MonitorUtil { 2 private static ThreadLocal<Long> t1 = new ThreadLocal<Long>(); 3 public static void start(){ 4 t1.set(System.currentTimeMillis()); 5 } 6 //结束打印时耗时 7 public static void finish(String methodName){ 8 long finishTime = System.currentTimeMillis(); 9 System.out.println(methodName+"方法耗时:"+(finishTime - t1.get())+"ms"); 10 } 11 }
4.创建StudentInvocationHandler类,实现InvocationHandler接口,这个类中持有一个被代理对象的实例target。InvocationHandler中有一个invoke方法,所有执行代理对象的方法都会被替换成执行invoke方法。再在invoke方法中执行被代理对象target的相应方法.
1 public class StudentInvocationHandler<T> implements InvocationHandler { 2 // invocationHandler持有的被代理对象 3 T target; 4 5 public StudentInvocationHandler(T target) { 6 this.target = target; 7 } 8 9 /** 10 * 11 * @param proxy 代表动态代理对象 12 * @param method 代表正在执行的方法 13 * @param args 代表调用目标方法时传入的实参 14 * @return 15 * @throws Throwable 16 */ 17 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { 18 System.out.println("代理执行"+method.getName()+"方法"); 19 //代理过程插入检测方法,计算方法耗时 20 MonitorUtil.start(); 21 Object result = method.invoke(target,args); 22 MonitorUtil.finish(method.getName()); 23 return result; 24 } 25 }
5.创建测试类:
1 public class Test { 2 public static void main(String[] args) { 3 //创建一个实例对象,这个对象是被代理的对象 4 Person student = new Student("张三"); 5 //创建一个与代理对象相关联的invocationHandler 6 InvocationHandler stuHandler = new StudentInvocationHandler<Person>(student); 7 //创建一个代理对象stuProxy来代理student,代理对象的每个执行方法都会替换执行InvocationHandler的invoke()方法 8 Person stuProxy = (Person) Proxy.newProxyInstance(Person.class.getClassLoader(),new Class[]{Person.class},stuHandler); 9 //代理执行方法 10 stuProxy.giveMoney(); 11 } 12 }
6.上面也有说到所有执行代理对象的方法都会被替换成执行invoke方法,也就是说,最后执行的是StuInvocationHandler中的invoke方法.
7.总结:
动态代理的优势在于可以很方便的对代理类的函数进行统一的处理,而不用修改每个代理类中的方法。是因为所有被代理执行的方法,都是通过在InvocationHandler中的invoke方法调用的,所以我们只要在invoke方法中统一处理,就可以对所有被代理的方法进行相同的操作了,动态代理的过程中,我们并没有实际看到代理类,也没有很清晰地的看到代理类的具体样子,因为动态代理中被代理对象和代理对象是通过InvocationHandler来完成的代理过程的
Java动态代理只能对接口进行代理,Java的继承机制注定了这些动态代理类们无法实现对class的动态代理。
静态代理和动态代理的区别:
静态代理:编译时将增强代码植入class文件,因为是编译期进行的增强,所以代码运行时效率比动态代理高,
动态代理:运行时生成代理类并加载,效率比静态代理要低,spring中使用了JDK动态代理和cglib两种动态代理的方式来实现代理类的生成.
CGlib代理:
CGLIB(Code Generation Library)是一个开源项目!是一个强大的,高性能,高质量的Code生成类库,它可以在运行期扩展Java类与实现Java接口。
CGLIB是一个强大的高性能的代码生成包。它广泛的被许多AOP的框架使用,例如Spring AOP为他们提供方法的interception(拦截)。CGLIB包的底层是通过使用一个小而快的字节码处理框架ASM,来转换字节码并生成新的类。
CGLIB是无法代理final修饰的方法的.
CGlib的简单实现:
1.创建一个业务类,这个类没有实现任何接口.
1 public class HelloService { 2 3 public HelloService() { 4 System.out.println("HelloService构造"); 5 } 6 7 /** 8 * 该方法不能被子类覆盖,Cglib是无法代理final修饰的方法的 9 */ 10 final public String sayOthers(String name) { 11 System.out.println("HelloService:sayOthers>>"+name); 12 return null; 13 } 14 15 public void sayHello() { 16 System.out.println("HelloService:sayHello"); 17 } 18 }
2.自定义一个MethodInterceptor方法实现MethodInterceptor接口;
1 public class MyMethodInterceptor implements MethodInterceptor{ 2 3 /** 4 * sub:cglib生成的代理对象 5 * method:被代理对象方法 6 * objects:方法入参 7 * methodProxy: 代理方法 8 */ 9 @Override 10 public Object intercept(Object sub, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable { 11 System.out.println("======插入前置通知======"); 12 Object object = methodProxy.invokeSuper(sub, objects); 13 System.out.println("======插入后者通知======"); 14 return object; 15 } 16 }
3.测试类:
1 public class Client { 2 public static void main(String[] args) { 3 // 代理类class文件存入本地磁盘方便我们反编译查看源码 4 System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "D:\code"); 5 // 通过CGLIB动态代理获取代理对象的过程 6 Enhancer enhancer = new Enhancer(); 7 // 设置enhancer对象的父类 8 enhancer.setSuperclass(HelloService.class); 9 // 设置enhancer的回调对象 10 enhancer.setCallback(new MyMethodInterceptor()); 11 // 创建代理对象 12 HelloService proxy= (HelloService)enhancer.create(); 13 // 通过代理对象调用目标方法 14 proxy.sayHello(); 15 } 16 }
4.运行结果:
5.总结:
实现CGLIB动态代理必须实现MethodInterceptor(方法拦截器)接口,这个接口只有一个intercept()方法,这个方法有4个参数:
1)obj表示增强的对象,即实现这个接口类的一个对象;
2)method表示要被拦截的方法;
3)args表示要被拦截方法的参数;
4)proxy表示要触发父类的方法对象;
在上面的Client代码中,通过Enhancer.create()方法创建代理对象
JDK动态代理和CGlib代理的区别:
JDK动态代理是利用反射机制生成一个实现代理接口的匿名类,在调用具体方法前调用InvokeHandler来处理.
CGlib动态代理是利用asm开源包,对代理对象类的class文件加载进来,通过修改其字节码生成子类来处理.
AOP的实现:
1.如果目标对象实现了接口,默认情况下会采用JDK动态代理实现AOP
2.如果目标对象实现了接口,可以强制使用CGlib实现AOP,实现方法
1.添加CGlib库,SPRING_HOME/cglib/*.jar
2,在spring配置文件中加入<aop:aspectj-autoproxy proxy-target-class="true"/>
3.如果目标对象没有实现接口,必须采用CGlib库,Spring会自动在JDK动态代理和CGlib之间转换.