Java 代理
代理的概念
代理是使用一个更强大的类(在原类的基础上进行功能扩展)来代替原来的类进行工作。
比如在使用UserDao时,还想做一些事务处理、日志记录等其它操作,这些操作不属于UserDao(持久层、操作数据库)的范畴,不能封装到UserDao中。
这时就可以使用代理来对原来的类进行增强。代理类在原有类的基础上进行了扩展,保留了原有类所有的功能,并添加了其他功能,更加强大。
被代理的类(UserImpl类)叫做目标类,实现代理的类(比如UserProxy)叫做代理类。
代理(proxy)分为2种:
- 静态代理
- 动态代理 常用的有jdk动态代理、cglib代理
原来的接口、实现类
public interface UserDao { public void addUser(); public void deleteUser(); }
public class UserDaoImpl implements UserDao { @Override public void addUser() { System.out.println("正在添加用户..."); } @Override public void deleteUser() { System.out.println("正在删除用户..."); } }
静态代理
代理类:
public class UserDaoProxy implements UserDao{ //把要代理的接口或类写成成员变量。声明为接口,可以代理这个接口所有的实现类;声明为实现类,则只能代理这个实现类。 private UserDao userDao; //注入目标类的对象。 public UserDaoProxy(UserDao userDao) { this.userDao = userDao; } @Override public void addUser() { //前增强 System.out.println("正在执行前增强..."); //调用目标类的方法 userDao.addUser(); //后增强 System.out.println("正在执行后增强..."); } @Override public void deleteUser() { //如果不需要增强,直接调用目标类的方法即可 userDao.deleteUser(); } }
在编译时就已确定要增强的方法,以及方法的参数表(参数类型、个数),
代理对象中增强的方法、参数表已经固定了,不会变化,是静态的,所以叫做静态代理。
使用:
//创建目标类对象 UserDao userDao = new UserDaoImpl(); //创建代理。 UserDaoProxy userDaoProxy = new UserDaoProxy(userDao); //不再使用目标类对象,使用的是代理 userDaoProxy.addUser();
静态代理的特点
- 代理类需要 implements 目标类的接口,因为要重写目标类的方法。就是说目标类必须要有父接口。
- 十分灵活,可以单独设置每个方法的增强
- 很繁琐,因为需要手动设置每个方法的增强
JDK动态代理
代理类:
package com.chy.proxy; import com.chy.dao.UserDao; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; public class UserDaoProxyFactory { //将目标对象作为成员变量,需要声明为目标接口,不能声明为目标类。 private UserDao userDao; //注入目标对象 public UserDaoProxyFactory(UserDao userDao) { this.userDao = userDao; } //获取代理。代理是目标接口的实例。 public UserDao getProxyInstance(){ //获取目标类的类加载器。 ClassLoader classLoader = userDao.getClass().getClassLoader(); //获取目标类实现的所有接口的class对象。 Class<?>[] interfaces = userDao.getClass().getInterfaces(); //创建InvocationHandler接口的实例。此处使用匿名内部类来创建。 InvocationHandler invocationHandler = new InvocationHandler() { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { //前增强 System.out.println("正在执行前增强..."); //调用目标类中对应的方法,需传入目标对象 Object returnValue=method.invoke(userDao,args); //后增强 System.out.println("正在执行后增强..."); return returnValue; } }; //使用Proxy类的静态方法创建实例,这个实例就是目标对象的代理。返回值是Object类型,需要强转。 Object userDaoProxy = Proxy.newProxyInstance(classLoader, interfaces, invocationHandler); //强转为目标接口类型 return (UserDao)userDaoProxy; } }
看invoke()的参数,要增强的方法、参数表都不确定,需要动态传入,根据传入的参数来确定,所以叫做动态代理。
(虽然代理对象是Obejct,但成员变量已经声明为接口,实际是接口类型的)
使用:
//创建目标对象 UserDaoImpl userDao = new UserDaoImpl(); //传入目标对象,创建代理。代理是目标接口类型。 UserDao userDaoProxy = new UserDaoProxyFactory(userDao).getProxyInstance(); //通过代理对象来调用方法 userDaoProxy.addUser();
JDK动态代理的特点
- 是使用jdk中的反射实现的动态代理,不像cglib一样要使用第三方的包,所以叫做jdk动态代理。
- 代理类不需要实现接口,但目标类必须要有父接口。可代理该接口下所有的实现类。
- 通过代理调用的目标类的方法都会被增强,且所使用的增强完全一样,有点死板。
- 代理对象不是新类型的,而是属于该接口类型。
CGLIB动态代理
如果单独使用cglib,需要导入cglib.jar、cglib的依赖包asm.jar。
如果使用maven,则会自动导入依赖的asm.jar。
如果使用spring,在spring的核心包spring-core.jar中已经内嵌了cglib所需的包,不必再导包。
代理类:
public class UserDaoProxyFactory implements MethodInterceptor { //目标对象 private UserDao userDao; //传入目标对象 public UserDaoProxyFactory(UserDao target) { this.userDao = target; } //拦截目标方法,进行增强 @Override public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable { //前增强 System.out.println("前增强..."); //调用目标对象的方法 Object returnValue = method.invoke(userDao, objects); //后增强 System.out.println("后增强..."); //返回目标方法的返回值,Object类型 return returnValue; } //创建代理对象,接口类型 public UserDao getProxyInstance(){ //创建工具类对象 Enhancer en = new Enhancer(); //设置基类(父类),即继承目标类 en.setSuperclass(userDao.getClass()); //设置回调函数。此句代码是调用intercept()拦截目标方法,进行增强 en.setCallback(this); //创建并返回代理对象。创建的对象是Object型,需要强转。 return (UserDao) en.create(); } }
看intercept()的参数,要增强的方法、参数都是动态传入。
目标对象可以使用接口类型,可代理此接口的所有实现类;也可使用某个具体的类(可以把上面的接口UserDao换为具体类UserDaoImpl),只能代理这个类。就是说目标类可以不实现接口。
使用:
//创建目标对象 UserDao userDao = new UserDaoImpl(); //创建代理对象,需传入目标对象 UserDao userDaoProxy = new UserDaoProxyFactory(userDao).getProxyInstance(); //通过代理调用方法 userDaoProxy.addUser();
CGLIB动态代理的特点
- 目标类实不实现接口都可以
- 目标类不能用final修饰,因为代理类要继承目标类;目标类中的方法不能使用final(要重写,前后增强)、static(要求是实例方法)修饰。
- 通过代理调用的目标类方法都会被增强
静态代理、jdk动态代理只能增强接口(代理接口的所有实现类),cglib动态代理既可增强接口,又可只增强某个具体的类。
静态代理的代理对象是代理类类型,jdk代理的代理对象是接口类型,cglib代理的代理对象是接口类型 | 目标类类型。
如果目标类实现了接口,使用三种代理都行;如果目标类没有实现接口,只能使用cglib代理。
如果要对方法做不同的增强,用静态代理;如果对每个方法的增强都一样,用动态代理。
如果前后增强的代码要复用,可以封装成函数,调用函数即可。