经过上一篇对于单例模式的心得分享,今天来聊一聊第二种设计模式 ,关于代理模式的个人理解。代理你可以把它想象成中间人、中介等等,不需要你亲自去处理你想做的事,把你想要的告诉他们,让他们去帮你实现,完成后再把最终的结果通知你。
总的来说代理模式分为两种:一种是静态代理,一种是动态代理。
那么首先来说一下静态代理:
静态代理
静态代理在使用的时候,需要定义接口(Interface) 或者是父类(Parent Class),被代理的对象与代理对象一起实现相同的接口或者是继承相同的父类。
下面举个案例:
接口
/**
* 代理接口
* @author lyr
* @date 2017年11月27日
*/
public interface IUserDao {
void save();
}
目标对象(需求方,俗称客户方)
/**
* 目标对象实现接口
* @author lyr
* @date 2017年11月27日
*/
public class UserDao implements IUserDao {
public void save() {
System.out.println("数据正在保存。。。。。");
System.out.println("数据保存成功!");
}
}
/**
* 代理对象,静态代理
* @author lyr
* @date 2017年11月27日
*/
public class UserDaoProxy implements IUserDao{
//接收保存目标对象
private IUserDao iUserDao;
public UserDaoProxy(IUserDao iUserDao) {
this.iUserDao = iUserDao;
}
public void save() {
System.out.println("开启事务...");
iUserDao.save(); //执行目标对象方法
System.out.println("提交事务...");
}
}
/**
* 测试类
* @author lyr
* @date 2017年11月27日
*/
public class TestProxy {
public static void main(String[] args) {
//目标对象(客户)
UserDao userDao = new UserDao();
//代理对象(中间人),把目标对象传递给代理对象,建立代理关系
UserDaoProxy proxy = new UserDaoProxy(userDao);
//执行代理方法
proxy.save();
}
}
静态代理的优缺点:
优点:能够在不修改目标对象的功能的前提下,对目标对象进行扩展;
缺点:目标对象要与代理对象实现一样的接口,会产生过多的代理类,并且一旦接口进行修改变动或者增加方法,那么目标对象与代理对象都需要进行维护。
动态代理
针对静态代理中出现的问题,可以通过动态代理来解决掉。那么动态代理有哪些特别的地方呢?其实在动态代理方面,有两种选择(JDK动态代理和CGLib动态代理)。
1.1JDK动态代理
JDK动态代理的几个特点:
(1)代理对象不需要实现接口;
(2)代理对象的生成,是利用JDK的API,动态的在内存中构建代理对象。(需要我们指定创建代理对象/目标对象实现的接口的类型);
(3)JDK代理也叫接口代理。
JDK生成代理对象的API
代理类所在包 :java.lang.reflect.Proxy
先看下代码实现的例子:
/**
* 创建动态代理对象
* 动态代理不需要实现接口。但是需要指定接口类型
* @author lyr
* @date 2017年11月27日
*/
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() {
public Object invoke(Object proxy, Method method, Object[] args) throws Exception{
System.out.println("开始事务...");
//执行目标对象方法
Object returnValue = method.invoke(target, args);
System.out.println("提交事务...");
return returnValue;
}
}
);
}
}
测试代码
//目标对象
IUserDao iUserDao = new UserDao();
//为目标对象创建代理对象
IUserDao target = (IUserDao) new ProxyFactory(iUserDao).getProxyInstance();
//执行代理方法
target.save();
通过上面的例子可以看到,JDK动态代理在代理对象不需要实现接口,但是目标对象一定要实现接口,否则就不能实现动态代理。这搞啥子嘛,问题还是有,只不过少了。当然,还有另外一种动态代理方式:CGLib动态代理。
1.2 CGLib动态代理
CGLib动态代理,也叫做子类代理,它是在内存中构建一个子类对象从而实现对目标对象功能的扩展。
(1)JDK动态代理有一个限制,就是动态代理的对象必须要实现一个或多个接口,否则就需要考虑CGLib动态代理来实现;
(2)CGLib是一个强大的高性能的代码生成包,可以在运行期扩展java类与实现java接口。这一点在Spring AOP中可以见到,可以对方法进行很好的拦截;
(3)CGLib的底层是通过使用一个小而快的字节码处理框架ASM来转换字节码并生成新的类,这里不鼓励直接去使用ASM,这点要求你必须要对JVM的内部结构包括class文件的格式和指令集都很熟悉才可以。
其实这里还有需要注意的是:(1)代理的类不能是final修饰的,否则会直接报错;(2)目标对象中的方法如果有是final或者static修饰的,就不会被拦截。
在使用CGLib动态代理的时候,你需要引入它的jar包才能使用它,但是目前Spring的核心包里面已经包括CGLib的相关功能(Spring的范围有点广),所以只需要引入spring-core-4.2.2.jar就行了。接下来,下面的相关代码:
先看目标对象,没有实现任何接口
/**
* 目标对象,没有实现任何接口
* @author lyr
* @date 2017年11月27日
*/
public class UsersDao {
public void save(){
System.out.println("正在保存数据...");
System.out.println("数据保存成功!");
}
}
再来看下对应的CGLib子类代理工厂
/**
* CGLib子类代理工厂,对UserDao在内存中动态的构建一个子类对象
* @author lyr
* @date 2017年11月27日
*/
public class ProxyFactorys implements MethodInterceptor{
private Object target;
public ProxyFactorys(Object target) {
this.target = target;
}
public Object getProxyInstance(){
//这个作为工具类,它允许为非接口类型创建一个Java代理,动态的创建给定类型的子类并且拦截所有的方法
Enhancer en = new Enhancer();
en.setSuperclass(target.getClass());
en.setCallback(this);
return en.create();
}
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;
}
}
最终的测试
/**
* 测试类
* @author lyr
* @date 2017年11月27日
*/
public class TestProxy {
public static void main(String[] args) {
UsersDao userDao = new UsersDao();
UsersDao target = (UsersDao) new ProxyFactorys(userDao).getProxyInstance();
target.save();
}
}
到这里你可以发现,它和JDK代理是不是也有点相似之处,当然这两种动态代理要根据具体的情况进行使用,一般来说,在Spring的AOP里面,如果加入的容器的目标对象有实现接口,就用JDK动态代理,否则的话,就用CGLib动态代理。
代理模式就说这么多吧,感谢各位, 下一篇再见!