Java代理

代理的作用在于不修改目标代码的情况下,可以控制对目标的访问,可以在其前后加上自己的业务处理代码,甚至阻止对目标方法的访问,有点类似于过滤器和拦截器的作用。比如要添加用户信息和删除用户信息必须具有特殊的权限,那么我们可以将添加用户和删除用户信息的方法放在一个类中,然后给其写一个代理类,添加和删除之前先对其进行权限校验,校验通过后再调用添加和删除的方法,否则不调用相依的方法。外部添加和删除用户时调用代理类的对象,而不是原始的用户操作对象,这样可以达到在不修改原始用户操作类代码的情况下,对其添加业务校验代码。

Java中的代理分为静态代理和动态代理。静态代理比较简单,不够灵活,适用面比较窄,实际的应用中使用的基本都是动态代理。Java中的动态代理实现包括JDK原生的动态代理和CGLIB动态代理。JDK动态代理必须要有接口,而CGLIB可以不要接口。

1、静态代理

先写一个接口:

package cn.com.huixin.staticproxy;

public interface IUserDao {
	
	public void addUser(String username);

	public void deleteUser(int userid);

	public void updateUser(String username);

}

这就是一个普通的接口,代码很简单,不解释。

再写一个该接口的实现类:

package cn.com.huixin.staticproxy;

public class UserDao implements IUserDao {

	public void addUser(String username) {
		System.out.println("UserDao.addUser()......");
	}

	public void deleteUser(int userid) {
		System.out.println("UserDao.deleteUser()......");
	}

	public void updateUser(String username) {
		System.out.println("UserDao.updateUser()......");
	}

}

代码中没什么复杂逻辑,不解释。重点在下边的代理类:

package cn.com.huixin.staticproxy;

/**
 * 代理了也实现了被代理的接口(IUserDao)
 */
public class UserProxy implements IUserDao {

	private IUserDao userDao;
	
	public UserProxy(IUserDao userDao) {
		this.userDao = userDao;
	}
	
	public void addUser(String username) {
		checkSecurity();
		userDao.addUser(username);
	}

	public void deleteUser(int userid) {
		checkSecurity();
		userDao.deleteUser(userid);
	}

	public void updateUser(String username) {
		checkSecurity();
		userDao.updateUser(username);
	}
	
	/**
	 * 安全检查
	 */
	private void checkSecurity() {
		System.out.println("UserProxy.checkSecurity()......");
	}

}

该代理类也实现了IUserDao接口,也就是代理类要实现和被代理类同样的接口才能实现代理的目的,而且代理类中要注入被代理接口的对象,以便能够在代理类中进行调用。该代理类中增加了安全检查的方法checkSecurity(),在调用被代理对象的方法之前都会先调用该方法,这样就起到了前置业务处理的作用。如果校验不通过,可以不调用被代理对象的方法。当然如果有需要后置处理的业务方法,也可以放在被代理对象方法之后调用。

下来写一个类测试一下:

package cn.com.huixin.staticproxy;

public class Client {

	public static void main(String[] args) {
		// 注意此处new的是代理类
		IUserDao userDao = new UserProxy(new UserDao());
		
		userDao.addUser("zhangsan");
		userDao.deleteUser(1);
	}

}

运行结果如下:

UserProxy.checkSecurity()......
UserDao.addUser()......
UserProxy.checkSecurity()......
UserDao.deleteUser()......

打印出了UserProxy.checkSecurity()......,说明代理类起作用了。

2、JDK动态代理

上边的静态代理灵活性受限,要代理哪个类,必须手动写一个目标接口的实现类,要代理多少个目标接口,就得写多少个代理类。动态代理比较灵活,只需要些一个代理类即可。

接口和实现类仍然使用静态代理中的IUserDao.java和UserDao.java。下边添加一个动态代理类:

package cn.com.huixin.dynamicproxy;

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

public class SecurityHandle implements InvocationHandler {

	// 代理的目标类对象
	private Object target;
	
	/**
	 * 查找目标的代理对象
	 * @param target 代理的目标类对象
	 * @return 代理对象
	 */
	public Object findProxy(Object target) {
		this.target = target;
		return Proxy.newProxyInstance(target.getClass().getClassLoader(), 
				target.getClass().getInterfaces(), this);
	}
	
	/**
	 * 调用目标方法
	 * @param proxy 调用方法的代理实例
	 * @param method 代理的方法
	 * @param args 目标方法的参数
	 */
	public Object invoke(Object proxy, Method method, Object[] args)
			throws Throwable {
		checkSecurity();
		Object ret = method.invoke(target, args);
		return ret;
	}
	
	private void checkSecurity() {
		System.out.println("检查安全性......");
	}

}

该代理类要实现InvocationHandler接口,将代理的目标对象传入,然后利用反射的方式调用目标对象的方法。在调用目标对象的方法之前或者之后可以加入自己的业务处理代码。findProxy方法用于生成目标的代理对象,invoke用于调用目标对象的方法。其中,第19行的Proxy.newProxyInstance()方法的第一参数为目标类的类加载器,第二个参数为目标类实现的接口列表,第三个参数为InvocationHandler类型的代理类对象,当前这个类就是,所以传的是this。

接下来写一个测试类:

package cn.com.huixin.dynamicproxy;

import cn.com.huixin.staticproxy.IUserDao;
import cn.com.huixin.staticproxy.UserDao;

public class Client {

	public static void main(String[] args) {
		SecurityHandle handle = new SecurityHandle();
		IUserDao userDao = (IUserDao)handle.findProxy(new UserDao());
		userDao.addUser("lisi");
		userDao.deleteUser(2);
	}

}

先new一个代理类对象,然后调用findProxy方法生成目标类的代理对象,后边调用的实际上是代理对象的方法。运行结果如下:

检查安全性......
UserDao.addUser()......
检查安全性......
UserDao.deleteUser()......

打印出了“检查安全性......”,说明代理方法生效。

3、CBLIB动态代理

JDK动态代理需要有接口,没有接口的类需要做动态代理的话则只能用CGLIB了。CGLIB动态代理需要导入cglib和asm的jar包,我使用的是cglib-3.2.12.jar和asm-7.1.jar。

现在的机票大部分都由航空公司放在各自的官方渠道卖了,但也会有一部分由机票代理商去卖,比如携程等等,用户从机票代理商的渠道来购票,但最终还是得由航空公司来出票。下边就以此为例来说CGLIB动态代理。首先创建一个代表航空公司的类AirlineCompany:

package cn.com.huixin.cglibproxy;

/**
 * 航空公司
 */
public class AirlineCompany {
	/**
	 * 卖票
	 * @return true表示出票成功, false表示出票失败
	 */
	public boolean saleTickets() {
		System.out.println("航空公司出票");
		return true;
	}
}

该类比较简单,里边就一个卖票的方法。再创建一个代表机票代理商的类PlaneTicketAgent:

package cn.com.huixin.cglibproxy;

import java.lang.reflect.Method;

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

/**
 * 机票代理商
 */
public class PlaneTicketAgent implements MethodInterceptor {

	/**
	 * 拦截被代理对象中除final之外的所有方法.
	 * 所有生成的代理方法(除过final修饰的方法)都调用此方法而不是原始方法, 
	 * 原始方法可以由使用方法对象的普通反射调用, 或者使用MethodProxy对象调用(这种方式更快).
	 * 之所以final修饰的方法不能被cglib代理, 是因为cglib采用extends被代理对象的方式来重写相应的方法, 
	 * 以此来拦截目标方法并在目标方法前后加上自己的业务处理代码. 而final修饰的方式是不能被重写的.
	 * @param proxyObj 代理对象
	 * @param method 被拦截的方法
	 * @param param 方法参数
	 * @param methodProxy 代理方法
	 * @return 与代理方法的签名兼容的返回值
	 */
	@Override
	public Object intercept(Object proxyObj, Method method, Object[] param, MethodProxy methodProxy) throws Throwable {
		// 可以在此处添加前置业务处理代码
		System.out.println("由机票代理商来卖票");
		Object result = methodProxy.invokeSuper(proxyObj, param);
		// 可以在此处添加后置业务处理代码
		if ((boolean) result) {
			System.out.println("已顺利出票");
		} else {
			System.out.println("出票遇到一点麻烦");
		}
		return result;
	}

}

方法上已经做了详细的注释。该代理类实现了MethodInterceptor接口。第29行的methodProxy.invokeSuper()方法用来调用指定对象的原始方法(或者父类方法,因为cglib通过继承目标类的方式来实现代理,所以父类方法即目标类的方法,也就是原始方法)。该方法的第一个参数为增强的代理对象,必须是作为第一个参数传递给MethodInterceptor的对象。第二个参数为传递给被拦截方法的参数,只要类型兼容,就可以替换其他参数数组。返回的是与代理方法的签名兼容的任何值,返回void的方法将忽略此值。

写一个测试类:

package cn.com.huixin.cglibproxy;

import net.sf.cglib.proxy.Enhancer;

public class CglibProxyTest {

	public static void main(String[] args) {
		// 创建Enhancer实例,也就是cglib中的一个class generator
		Enhancer enhancer = new Enhancer();
		// 设置目标类
		enhancer.setSuperclass(AirlineCompany.class);
		// 设置拦截对象
		enhancer.setCallback(new PlaneTicketAgent());
		// 生成代理类并返回代理类的实例
		AirlineCompany airlineCompany = (AirlineCompany) enhancer.create();
		boolean result = airlineCompany.saleTickets();
		if (result) {
			System.out.println("出票成功");
		} else {
			System.out.println("出票失败");
		}
	}

}

运行的结果为:

由机票代理商来卖票
航空公司出票
已顺利出票
出票成功

打印出了“由机票代理商来卖票”、“已顺利出票”,说明代理类已经生效。

  • 1
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值