代理模式
什么是代理模式
为其他对象提供一种代理以控制对这个对象的访问。
为什么使用代理模式
中介隔离:
在某些情况下,一个客户类不想或者不能直接引用一个委托对象,而代理类对象可以在客户类和委托对象之间起到
中介的作用
,其特征是
代理类和委托类实现相同的接口
。
开闭原则,增加功能:
代理类除了是客户类和委托类的中介之外,我们还可以通过给代理类
增加额外的功能
来扩展委托类的功能,这样做我们只需要修改代理类而不需要再修改委托类,符合代码设计的开闭原则。代理类主要负责为委托类
预处理消息
、
过滤消息
、
把消息转发给委托类
,以及事后对
返回结果的处理
等。代理类本身并不真正实现服务,而是同过调用委托类的相关方法,来提供特定的服务。真正的业务功能还是由委托类来实现,但是可以在业务功能执行的前后加入一些公共的服务。例如我们想给项目加入缓存、日志这些功能,我们就可以使用代理类来完成,而没必要打开已经封装好的委托类。
代理模式实现原理
代理模式主要包含三个角色,即抽象主题角色(Subject)、委托类角色(被代理角色,Proxied)以及代理类角色(Proxy)
抽象主题角色(Subject):
可以是接口,也可以是抽象类
委托类角色(proxied):
真实主题角色,业务逻辑的具体执行者
代理类角色(Proxy):
内部含有对真实对象RealSubject的引用,负责对真实主题角色的调用,并在真实主题角色处理前后做预处理和后处理
应用场景
事务、日志收集、权限控制、过滤器、RPC远程调用
代理模式创建的方式
静态代理
静态代理是由程序员创建或工具生成代理类的源码,再编译代理类。
所谓静态也就是在程序运行前就已经存在代理类的字节码文件,代理类和委托类的关系在运行前就确定了。
一句话,自己手写代理类就是静态代理。
-
UserDao接口
public interface UserDao {
public void save();
}
-
UserDaoImpl类
public class UserDaoImpl implements UserDao {
@Override
public void save() {
System.out.println("save");
}
}
-
UserDaoProxy代理类,也实现UserDao接口
public class UserDaoProxy implements UserDao {
private UserDao userDao = null;
public UserDaoProxy(UserDao target) {
this.userDao = target;
}
@Override
public void save() {
System.out.println("开始事务...");
userDao.save();
System.out.println("提交事务...");
}
}
测试类
public class Test {
public static void main(String[] args) {
UserDao target = new UserDaoImpl();
UserDaoProxy userDaoProxy = new UserDaoProxy(target);
userDaoProxy.save();
}
}
-
可以做到在不修改目标对象的功能前提下,对目标功能扩展.
-
缺点:
-
因为代理对象需要与目标对象实现一样的接口,所以会有很多代理类,类太多.同时,一旦接口增加方法,目标对象与代理对象都要维护.
-
动态代理
动态代理是在实现阶段不用关心代理类,而在运行阶段才指定哪一个对象。
动态代理类的源码是在程序运行期间由JVM根据反射等机制动态的生成 。
JDK动态代理
-
代理对象,不需要实现接口
-
代理对象的生成,是利用JDK的API,动态的在内存中构建代理对象(需要我们指定创建代理对象/目标对象实现的接口的类型)
-
动态代理也叫做:JDK代理,接口代理
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
/**
* 创建动态代理对象 动态代理不需要实现接口,但是需要指定接口类型
*/
public class ProxyFactory {
// 维护一个目标对象
private Object target;
public ProxyFactory(Object target) {
this.target = target;
}
// 给目标对象生成代理对象
public Object getProxyInstance() {
return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(),
new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("开始事务2");
// 执行目标对象方法
Object returnValue = method.invoke(target, args);
System.out.println("提交事务2");
return returnValue;
}
});
}
}
Test类
import com.donkee.dao.UserDao;
import com.donkee.dao.UserDaoImpl;
public class Test {
public static void main(String[] args) {
UserDao target = new UserDaoImpl();
ProxyFactory proxyFactory = new ProxyFactory(target);
UserDao proxy = (UserDao)proxyFactory.getProxyInstance();
proxy.save();
}
}
Cglib代理
Cglib是一个
强大的,高性能,高质量
的代码生成类库。它可以在运行期扩展JAVA类与实现JAVA接口。其底层实现是通过ASM字节码处理框架来
转换字节码并生成新的类
。大部分功能实际上是ASM所提供的,Cglib只是封装了ASM,简化了ASM操作,实现了运行期生成新的class。
上面的静态代理和动态代理模式都是要求目标对象是实现一个接口的目标对象,但是有时候目标对象只是一个单独的对象,并没有实现任何的接口,这个时候就可以使用以目标对象子类的方式类实现代理,这种方法就叫做:Cglib代理
Cglib代理,也叫作子类代理,它是在内存中构建一个子类对象从而实现对目标对象功能的扩展.
-
JDK的动态代理有一个限制,就是使用动态代理的对象必须实现一个或多个接口,如果想代理没有实现接口的类,就可以使用Cglib实现.
-
Cglib是一个强大的高性能的代码生成包,它可以在运行期扩展java类与实现java接口.它广泛的被许多AOP的框架使用,例如Spring AOP和synaop,为他们提供方法的interception(拦截)
-
Cglib包的底层是通过使用一个小而块的字节码处理框架ASM来转换字节码并生成新的类.不鼓励直接使用ASM,因为它要求你必须对JVM内部结构包括class文件的格式和指令集都很熟悉.
Cglib子类代理实现方法:
-
需要引入cglib的jar文件.
-
引入功能包后,就可以在内存中动态构建子类
-
代理的类不能为final,否则报错
-
目标对象的方法如果为final/static,那么就不会被拦截,即不会执行目标对象额外的业务方法.
RoleDao类
public class RoleDao {
public void save() {
System.out.println("save");
}
}
ProxyFactory代理工厂类
import java.lang.reflect.Method;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
/**
* Cglib子类代理工厂
* 对RoleDao在内存中动态构建一个子类对象
*/
public class ProxyFactory implements MethodInterceptor{
//维护目标对象
private Object target;
public ProxyFactory(Object target) {
this.target = target;
}
//给目标对象创建一个代理对象
public Object getProxyInstance(){
//1.工具类
Enhancer en = new Enhancer();
//2.设置父类
en.setSuperclass(target.getClass());
//3.设置回调函数
en.setCallback(this);
//4.创建子类(代理对象)
return en.create();
}
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
System.out.println("开始事务...");
//执行目标对象的方法
Object returnValue = method.invoke(target, args);
System.out.println("提交事务...");
return returnValue;
}
}
CglibTest测试类
import com.donkee.dao.RoleDao;
public class CglibTest {
public static void main(String[] args) {
RoleDao target = new RoleDao();
ProxyFactory proxyFactory = new ProxyFactory(target);
RoleDao proxy = (RoleDao)proxyFactory.getProxyInstance();
proxy.save();
}
}
-
如果加入容器的目标对象有实现接口,用JDK代理
-
如果目标对象没有实现接口,用Cglib代理