代理的作用在于不修改目标代码的情况下,可以控制对目标的访问,可以在其前后加上自己的业务处理代码,甚至阻止对目标方法的访问,有点类似于过滤器和拦截器的作用。比如要添加用户信息和删除用户信息必须具有特殊的权限,那么我们可以将添加用户和删除用户信息的方法放在一个类中,然后给其写一个代理类,添加和删除之前先对其进行权限校验,校验通过后再调用添加和删除的方法,否则不调用相依的方法。外部添加和删除用户时调用代理类的对象,而不是原始的用户操作对象,这样可以达到在不修改原始用户操作类代码的情况下,对其添加业务校验代码。
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("出票失败");
}
}
}
运行的结果为:
由机票代理商来卖票
航空公司出票
已顺利出票
出票成功
打印出了“由机票代理商来卖票”、“已顺利出票”,说明代理类已经生效。