Java的代理和CGLib的比较及使用

代理模式是我们写代码过程中经常使用的模式,其通常由两部分构成,即代理类和委托类,他们对外暴露同样的业务方法(即实现相同的接口)。委托类是实际进行业务操作的类,代理类中会封装一个委托类实例,对业务方法的处理就是转调委托类实例相应方法。实际使用中,用户得到的是代理类实例,那为什么要在委托类上封装一层呢?我们先看一个实际生活的例子吧。

我们以教育局为例吧,目前假设教育局就局长一人,局长有义务向社会公众提供教育政策的解答,这就是局长对外提供的“业务方法”。每天局长接到好多电话(即方法调用)。但统计发现,这些电话很大部分打错了,或这个人根本言语不清,局长的精力都耗费在这些没有意义的电话上了。最后,局长没法了,就找个秘书吧,秘书的责任也是提供教育政策的解答,公众热线现在全部就转给秘书了。秘书接到电话后,先判断这个人言语表达还可以并且没有打错才转接给局长处理,否则就直接挂断。这样局长的精力全部能去处理正事,无意义的电话全被秘书过滤掉了。

上述例子就是代理模式在实际的典型应用,例子中,秘书是代理类,局长就是委托类。代理类会进行一些与实际业务相关的小的业务点的操作,如资格审查,信息记录等等,然后根据结果确定是否转调委托类。

从实际编写代码的角度,代理模式分为静态代理和动态代理。

【静态代理】

静态代理是指,代理类是由程序员写好的,并手动编译为class文件进行使用。我们直接看个例子吧:

package cn.test;

/**
 * 服务接口
 */
public interface OrderService {
	
	public boolean placeOrder(String...goods);

}


 

package cn.test;

/**
 * 服务的实现类!实际业务处理的类,代理中的委托类。
 */
public class OrderServiceImpl implements OrderService {

	@Override
	public boolean placeOrder(String... goods) {
		
		System.out.println("开始下订单, 请等待....");
		System.out.println("订单已生效....");
		return true;
	}

}


 

package cn.test;

/**
 * 订单服务的静态代理类!
 */
public class OrderServiceStaticProxy implements OrderService{
	
	private OrderServiceImpl orderService;
	
	public OrderServiceStaticProxy(OrderServiceImpl orderService) {
		
		this.orderService = orderService;
	}

	@Override
	public boolean placeOrder(String... goods) {
		
		System.out.println("用户权限校验....");
		System.out.println("订单有效性校验....");
		return orderService.placeOrder(goods);
	}

}


 

package cn.test;

/**
 * 静态代理测试类
 */
public class OrderServiceTest {

	public static void main(String[] args) {
		
		// 用户最终使用的订单服务类为代理类
		OrderService orderService = new OrderServiceStaticProxy(new OrderServiceImpl());
		orderService.placeOrder("apple");
	}

}


测试输出为:

用户权限校验....
订单有效性校验....
开始下订单, 请等待....
订单已生效....


上面就是一个订单服务静态代理的应用,静态代理使用十分简单方便,其代理类也是程序员手动书写出来并编译运行的。但从上例我们也可以看出静态代理的缺点:静态代理只能面型某一个确定的服务接口或服务实现,如果我们的系统有几百个服务类,现在要统一添加代理,则我们的系统就要额外添加几百个静态的代理类,这会导致系统类特别多不易管理。以后我们每当向系统中添加一个新的服务类,还得记得为他编写一个代理类,这点也很难实现。因此,Java同时还提供了动态代理,意如其名,就是代理类对象是在运行期动态生成的,这个和静态代理在本质上是有分别的。

【动态代理】

关于动态代理,我们先直接看一个例子吧:

 

package cn.test;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

/**
 * 带权限校验的动态代理生成器类
 */
public class ChkUserProxy implements InvocationHandler {

	/**
	 * 委托类对象
	 */
	private Object target;
	
	/**
	 * 绑定委托类的方法,同时会返回一个代理类对象。这个代理类会兼具权限检查的功能。
	 * @param target
	 * @return
	 */
	public Object bind(Object target){
		
		this.target = target;
		Object proxy = Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), this);
		
		// 将动态生成的代理类对象返回给用户即可
		return proxy;
	}
	
	@Override
	public Object invoke(Object proxy, Method method, Object[] args)
			throws Throwable {
		
		System.out.println("用户权限校验....");
		// 权限校验完成后,调用委托类中的方法即可
		return method.invoke(target, args);
	}
	
	

}


我们接着用订单服务类来测试我们的动态代理:

package cn.test;

/**
 * 动态代理测试类
 */
public class OrderServiceTest {

	public static void main(String[] args) {
		
		OrderService orderService = (OrderService)new ChkUserProxy().bind(new OrderServiceImpl());
		orderService.placeOrder("apple");
	}

}

 

用户权限校验....
开始下订单, 请等待....
订单已生效....


由输出可以看出,代理生效。动态代理使用了Java在java.lang.reflect包中提供的接口InvocationHandler 和 类Proxy来实现的。上例中,我们通过实现InvocationHandler接口来实现用户校验的动态代理生成器,然后通过Proxy类来生成最终的动态代理对象。这种方式比静态代理的优势就在于:通过实现InvocationHandler的动态代理生成器可以为不同的委托类对象生成动态代理对象。这些动态代理对象公共的地方在于,都在委托类业务方法调用前增加了用户权限校验的功能。这就有点“面向切面”编程的概念了。我们这里的切面是业务方法调用前,我们在这个切面引入的逻辑是用户权限校验。

动态代理有个缺陷是:委托类必须实现某个接口,否则不能为这个委托类生成动态代理类。利用CGLib(Code Generation Lib)去生成代理类对象可以解决这个缺陷。

【CGLib】

从上例可以看出,Java本身动态代理是基于接口去生成的。而CGLib生成代理是基于类去生成的,即直接采用继承的方式生成委托类的子类,然后覆写相应的业务方法。我们继续上例子:

package cn.test;

/**
 * 订单服务类,没有实现任何接口。
 */
public class OrderService2 {

	public boolean placeOrder(String... goods) {
		
		System.out.println("开始下订单, 请等待....");
		System.out.println("订单已生效....");
		return true;
	}
}


 

package cn.test;

import java.lang.reflect.Method;

import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

/**
 * 利用CGLib实现的动态代理对象生成器
 */
public class ChkUserCGLib implements MethodInterceptor {

	// 委托类对象
	private Object target;
	
	/**
	 * 创建并返回委托类对象的代理类对象
	 */
	public Object getInstance(Object target){
		
		this.target = target;
		Enhancer enhancer = new Enhancer();
		// 设置最后生成的代理类的父类
		enhancer.setSuperclass(target.getClass());
		// 设置切面回调
		enhancer.setCallback(this);
		
		return enhancer.create();
	}
	
	@Override
	public Object intercept(Object proxy, Method method, Object[] args,
			MethodProxy methodProxy) throws Throwable {
		
		System.out.println("用户权限校验....");
		// 权限校验完成后,调用委托类中的方法即可
		method.invoke(target, args);
		return null;
	}

}


 

package cn.test;

/**
 * CGLib生成代理的测试类
 */
public class OrderServiceTest {

	public static void main(String[] args) {
		
		OrderService2 orderService = (OrderService2)new ChkUserCGLib().getInstance(new OrderService2());
		orderService.placeOrder("apple");
	}

}


输出为:

用户权限校验....
开始下订单, 请等待....
订单已生效....


由输出看,我们通过CGLib可以顺利地生成没有实现任何接口的类对象的动态代理对象。因为CGLib是基于继承的方式去生成委托类的代理类对象,所以对于final来修改的类无法利用CGLib生成动态代理对象。注意,利用CGLib去开发时,需要CGLib和ASM相关的jar包,CGLib是基于ASM的,对ASM的接口进行了更好的封装,易用性更强,我的资源中有下载。

 

综上,就是使用代理相关的3种方式。代理模式在各种框架中被广泛使用,如Hibernate,Spring等。AOP(面向切面编程)也是动态代理的经典应用。

最后说一下代理和反射的一些联系吧,这两者是完全不同的概念,代理是一种设计模式,反射是Java提供的运行时调用对象方法的一种机制,动态代理中使用了反射这种技术。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值