一.代理
一个角色代表另一个角色来完成某些特定的功能,如我们生活中的各种代理商。
代理是实现AOP(AspectOriented Program面向方面的编程)功能的核心和关键技术。
代理概念:
为某个对象提供一个代理,以控制对这个对象的访问。代理类和委托类有共同的父类或父接口,这样在任何使用委托类对象的地方都可以用代理对象替代。代理类负责请求的预处理、过滤、将请求分派给委托类处理、以及委托类执行完请求后的后续处理。
什么时候用代理呢?
为已存在的多个具有相同接口的目标类的各个方法增加一些系统功能的时候。如:异常处理、日志、计算机方法的运行时间、事务管理等。
注意:采用工厂模式和配置文件的方式进行管理不需要修改客户端程序,在配置文件中配置是使用目标类还是代理类,很容易切换,方便增删系统功能。
二.代理架构图
原来的时候客户端是直接调用目标类的,出现代理后是调用代理类。代理类与目标类实现了同一个接口,也就是说对外有相同的方法。客户端是用接口来进行引用。
三.静态代理
由程序员创建或工具生成代理类的源码,再编译代理类。所谓静态也就是在程序运行前就已经存在代理类的字节码文件(也就是.class文件),代理类和委托类的关系在运行前就确定了。
静态代理代码示例:
/*
*定义一个 人类接口
*/
public interface Person {
//吃东西方法
public void eat();
//睡觉方法
public void sleep();
}
/*
*委托类(目标类)
*/
public class PersonImpl implements Person{
@Override
public void eat() {
System.out.println("吃饭。。。。。。");
}
@Override
public void sleep() {
System.out.println("睡觉。。。。。。");
}
}
/*
*代理类
*/
public class PersonProxy implements Person{
private PersonImpl personImpl;
/*
*覆盖默认构造函数
*/
PersonProxy(PersonImpl personImpl){
this.personImpl = personImpl;
}
@Override
public void eat() {
//事务处理之前
System.out.println("吃饭前。。。洗手");
//调用委托类方法
personImpl.eat();
//事务处理之后
System.out.println("吃完饭。。。散步");
}
@Override
public void sleep() {
//事务处理之前
System.out.println("睡觉前。。。喝杯牛奶");
//调用委托类方法
personImpl.sleep();
//事务处理之后
System.out.println("睡觉醒来。。。");
}
}
/*
*测试类
*/
public class TestPerson {
public static void main(String[] args) {
PersonImpl personImpl = new PersonImpl();
PersonProxy personProxy = new PersonProxy(personImpl);
personProxy.eat();
personProxy.sleep();
}
}
从以上代码可以发现每一个代理类只能为一个接口服务,这样的话程序开发中必然会产生过多的代理。而且,所有的代理操作只有调用的方法不一样,这就出现了重复代码。解决这一问题最好的做法是可以通过一个代理类完成全部的代理功能,那么此时就必须使用动态代理完成。
静态代理的优点与缺点
优点:业务类只需要关注业务逻辑本身,保证了业务类的重用性。这是代理的共有优点。
缺点:
1.代理对象的一个接口只服务于一种类型的对象,如果要代理的方法很多,势必要为每一种方法都进行代理,静态代理在程序规模稍大时就无法胜任了。
2. 如果接口增加一个方法,除了所有实现类需要实现这个方法外,所有代理类也需要实现此方法。增加了代码维护的复杂度。
四.动态代理
与静态代理类相比较,动态代理类的字节码是在程序运行时由Java反射机制动态生成,不需要程序员手动编写它的源代码。动态代理类不仅简化了编程工作,而且提高了软件系统的可扩展性,因为Java 反射机制可以生成任意类型的动态代理类。
为什么要用动态代理技术呢?
因为如果要为系统中的各种接口的类增加代理功能,那将需要太多的代理类,全部采用静态代理方式会很麻烦。
动态代理代码示例:
/*
*真实对象接口
*/
import java.lang.reflect.Method;
public interface Advice {
void beforeMethod(Method method);
void afterMethod(Method method);
}
import java.lang.reflect.Method;
public class MyAdvice implements Advice{
long beginTime;
long endTime;
public void beforeMethod(Method method) {
System.out.println("程序开始运行");
beginTime = System.currentTimeMillis();
}
public void afterMethod(Method method) {
System.out.println("程序运行结束");
endTime = System.currentTimeMillis();
System.out.println(method.getName() + "运行时间" + (endTime - beginTime)+"\n");
}
}
/*创建动态类*/
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 ProxyTest1 {
public static void main(String[] args) throws Exception {
// 创建动态类与创建实例对象一体
final ArrayList target = new ArrayList();
Collection proxy = (Collection) getProxy(target, new MyAdvice());
proxy.add("aaa");
proxy.add("bbb");
// System.out.println("个数:"+proxy.size());
}
// 把目标抽取成一个参数
private static Object getProxy(final Object target, final Advice advice) {
Object proxy = Proxy.newProxyInstance(target.getClass()
.getClassLoader(), target.getClass().getInterfaces(),
new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method,
Object[] args) throws Throwable {
advice.beforeMethod(method);
Object retVal = method.invoke(target, args);
advice.afterMethod(method);
return retVal;
}
});
return proxy;
}
}
注意:JVM生成的动态类必须实现一个或多个接口,所以,JVM生成的动态类只能用作具有相同接口的目标类的代理,如果这些类并没有实现接口,则不能使用JDK代理,这就要使用CGLIB动态代理了。
CGLIB动态代理
JDK的动态代理机制只能代理实现了接口的类,而不能实现接口的类就不能实现JDK的动态代理,cglib是针对类来实现代理的,他的原理是对指定的目标类生成一个子类,并覆盖其中方法实现增强,但因为采用的是继承,所以不能对final修饰的类进行代理。
注意:
代理类的各个方法中通常处理要调用目标的相应方法和对外返回目标返回的结果外,还可以再代理方法的如下四个位置加上系统功能代码:
1)在调用目标方法之前
2)在调用目标方法之后
3)在调用目标方法前后
4)在处理目标方法异常的catch块中
JVM动态生成类的三种方法:
1.
(1)Proxy.getProxyClass(interface加载器)
(2)获得含参数的构造方法.getConstructor(InvocationHandler.class)
(3)创建MyInvocationHandler类实现InvocationHandler接口,并覆写invoke()方法;
2.
使用匿名内部类:new InvocationHandler{
覆写invoke();
}
3.
newProxyInstance方法,直接将类与对象直接创建
Proxy.newProxyInstance(
target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
new InvocationHandler(){
覆写invoke();
})
动态代理的优点:动态代理与静态代理相比较,最大的好处是接口中声明的所有方法都被转移到调用处理器一个集中的方法中处理(InvocationHandler.invoke)。这样,在接口方法数量比较多的时候,我们可以进行灵活处理,而不需要像静态代理那样每一个方法进行中转。
五.代理模式
代理模式的作用是:
为其他对象提供一种代理以控制对这个对象的访问。在某些情况下,一个客户不想或者不能直接引用另一个对象,而代理对象可以在客户端和目标对象之间起到中介的作用。
代理模式一般涉及到三个角色:
1.抽象角色:声明真实对象和代理对象的共同接口;
2.代理角色:代理对象角色内部含有对真实对象的引用,从而可以操作真实对象,同时代理对象提供与真实对象相同的接口以便在任何时刻都能代替真实对象。同时代理对象可以在执行真实对象操作时,附加其他的操作,相当于对真实对象进行封装。
3.真实角色:代理角色所代表的真实对象,是我们最终要引用的对象。
总结:
动态代理类的源码是在程序运行期间由JVM根据反射等机制动态的生成,所以不存在代理类的字节码文件。代理类和委托类的关系是在程序运行时确定。
java.lang.reflect包中的Proxy类和InvocationHandler接口提供了生成动态代理类的能力。
每个类一定要有一个类加载器来加载,配置文件的加载往往用的就是类加载器。
代理类可以用来隐藏对外的原始代码体现,只提供方法即可,提高了安全性。
JVM生成的动态类必须实现一个或多个接口,所以,JVM生成的动态类只能用作具备相同接口的目标类的代理。如果要为一个没有实现接口的类生成动态代理类,那么可以使用CGLIB库。
JVM可以在运行期动态生成出类的字节码,这种动态生成的类往往被用作代理类,即动态代理。