Java 反射、代理java反射动态代理
目录
- 反射(reflect)
- 获取class对象的三种方式
- 使用反射创建对象
- 使用反射操作字段
- 使用反射调用方法
- 使用反射动态创建、操作数组
- 代理(proxy)
- 静态代理
- jdk动态代理
- cglib动态代理
- 使用建议
反射(reflect)
反射:通过类的class对象来获取类的信息,动态操作类中的字段、调用类中的方法。
获取class对象的三种方式
// Class.forName("全限定类名"),类名是String形式,编译时并不知道Class对象的泛型,所以是?,需要强转 Class<?> class1 = Class.forName("com.chy.mall.model.User"); Class<User> class2 = (Class<User>)class1; //类名.class Class<User> class3=User.class; //对象名.getClass() Class<? extends User> class4=user.getClass();使用反射创建对象
Class<User> userClass = User.class; //获取class对象 Constructor<User> constructor = userClass.getConstructor(int.class, String.class, int.class); //通过class对象获取指定的构造器,形参表要写成class对象的形式 User user = constructor.newInstance(1, "chy", 20); //传入实参创建实例使用反射操作字段
Class<User> userClass = User.class; //获取私有字段 Field nameField = userClass.getDeclaredField("name"); //取消权限检查 nameField.setAccessible(true); User user = new User(1, "zhangsan", 20); //调用方法,传入实参,即要操作的对象,获取该字段的值,返回Object String name = (String) nameField.get(user); //设置对象该字段的值 nameField.set(user, "lisi");- private成员不允许在类外操作,需要取消权限检查才能操作private成员;public成员默认不检查权限。
使用反射调用方法
Class<User> userClass = User.class; //获取带参的公有方法。后面是形参表的class对象,参数个数可变 Method getNameMethod = userClass.getMethod("setName", String.class); Method getNameMethod = userClass.getMethod("getName"); User user = new User(1, "zhangsan", 20); //调用指定对象的该方法(无参),返回值是Object类型 String name = (String) getNameMethod.invoke(user); //调用指定对象的该方法(带参),后面是实参表,参数个数可变 setNameMethod.invoke(user, "lisi");- 调用方法时需要指定形参的class对象,所以方法的形参不能使用基本类型,要使用基本类型对应的包装类型。
- getMethod()只能获取当前类的方法,不能获取从父类继承的方法,如果存在要获取从父类继承的方法的情况,可以新建一个私有方法,调用私有方法来获取
使用反射动态创建、操作数组
//动态创建数组 Object obj = Array.newInstance(String.class, 10); //数组元素类型、元素个数,返回值是Object类型 String[] arr= (String[]) obj; //有需要的话可以强转 //动态操作数组。传入数组的元素类型是无法预测的,所以参数是Object类型 Object first = Array.get(obj, 0); //获取数组指定位置(index)上的元素,返回Object Array.set(obj, 0, "zhangsan"); //设置数组指定位置上的元素反射常见的应用场景
- 开发通用框架,比如spring 从配置文件中加载配置,动态创建对象、设置属性值。
- 实现动态代理,动态创建代理对象;对原功能进行扩展、增强。
- 实现注解。
反射的优点
- 动态操作,可以根据传入的参数或从配置文件中读取的配置,动态创建对象、设置属性,十分灵活。
反射的缺点
- 开销大,使用频率低时性能还行,频繁使用时严重影响性能
- 通过反射可以操作类的私有成员,破坏了类的封装性,存在安全隐患
代理(proxy)
代理是在目标类的基础上进行扩展,增加一些额外的功能、处理,代替目标类进行工作。
代理可分为2种
- 静态代理
- 动态代理,常见的有jdk动态代理、cglib代理。aop就是使用动态代理实现的。
静态代理
//实现目标接口或继承目标类 public class UserDaoProxy implements UserDao{ private UserDao userDao; //把要代理的接口或类写作成员变量。声明为接口,则可以代理这个接口所有的实现类;声明为实现类,则只代理这个实现类 //注入目标对象 public UserDaoProxy(UserDao userDao) { this.userDao = userDao; } @Override public void addUser() { //..... //前增强 userDao.addUser(); //调用目标类的方法,如果是继承目标类,通过super来调用 //..... //后增强 } @Override public void deleteUser() { userDao.deleteUser(); //如果不需要增强,直接调用目标类的方法即可 } } UserDao userDao = new UserDaoImpl(); UserDaoProxy userDaoProxy = new UserDaoProxy(userDao); userDaoProxy.addUser();静态代理的特点
- 需要实现目标接口或者继承目标类
- 可以代理接口、类
- 可以设置每个方法的增强,很灵活;但要手动设置每个方法的增强,又很繁琐
jdk动态代理
import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; //不需要继承、实现目标接口、目标类 public class UserDaoProxyFactory { private UserDao userDao; //jdk动态代理代理的是接口,要声明为接口 //注入目标对象 public UserDaoProxyFactory(UserDao userDao) { this.userDao = userDao; } //获取代理,代理是目标接口的实例 public UserDao getProxyInstance(){ ClassLoader classLoader = userDao.getClass().getClassLoader(); //获取目标类的类加载器。 Class<?>[] interfaces = userDao.getClass().getInterfaces(); //获取目标类实现的所有接口的class对象,这也是为什么要声明为接口的原因 InvocationHandler invocationHandler = new InvocationHandler() { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { //后2个参数是目标方法、实参表 //..... //前增强 Object returnValue=method.invoke(userDao,args); //调用目标方法,传入目标对象、实参表 //.... //后增强 return returnValue; //返回目标方法的返回值,Object类型 } }; //创建代理,返回值是Object类型,需要强转 Object userDaoProxy = Proxy.newProxyInstance(classLoader, interfaces, invocationHandler); //强转为目标接口类型 return (UserDao)userDaoProxy; } } UserDaoImpl userDao = new UserDaoImpl(); //传入目标对象,创建代理,代理是目标接口类型 UserDao userDaoProxy = new UserDaoProxyFactory(userDao).getProxyInstance(); userDaoProxy.addUser();看invoke()的参数,在编译时不能确定要增强的方法,运行时根据传入的参数动态进行增强,所以叫做动态代理,静态代理则在编译时就确定了要增强的方法。
是使用jdk自带的反射机制实现的动态代理,所以叫做jdk动态代理。
动态代理、静态代理的区别
- 静态代理在编译时就已经确定了要增强的方法
- 静态代理代码侵入强,代码耦合度高,编码量大;动态代理对代码侵入小,编码量较小
jdk动态代理的特点
- 基于jdk提供的反射机制实现,无需引入第三方依赖
- 只能代理接口
- 不需要实现接口、继承目标类
- 通过代理调用的目标类的方法都会被增强,且所使用的增强完全一样,偏死板
cglib动态代理
需要添加cglib的依赖
<dependency> <groupId>cglib</groupId> <artifactId>cglib</artifactId> <version>3.3.0</version> </dependency> //需要实现MethodInterceptor接口 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 { //..... //前增强 Object returnValue = method.invoke(userDao, objects); //调用目标对象的方法 //..... //后增强 return returnValue; } //创建代理,代理是目标接口类型 public UserDao getProxyInstance(){ Enhancer en = new Enhancer(); //创建工具类对象 en.setSuperclass(userDao.getClass()); //设置基类(父类),即继承目标类 en.setCallback(this); //设置回调函数,此句代码是调用intercept()拦截目标方法,进行增强 return (UserDao) en.create(); //创建并返回代理对象。创建的对象是Object型,需要强转 } } UserDao userDao = new UserDaoImpl(); //传入目标对象,创建代理,代理是目标类型 UserDao userDaoProxy = new UserDaoProxyFactory(userDao).getProxyInstance(); userDaoProxy.addUser();
cglib动态代理的特点
- 底层使用ASM(一个短小精悍的字节码操作框架)来操作字节码,对字节码进行增强,需要引入cglib的jar包。
- 可以代理接口、类
- 需要实现MethodInterceptor接口
- 通过代理调用的目标类的方法都会被增强,且所使用的增强完全一样,偏死板
使用建议
- 如果要对不同的方法做不同的增强,用静态代理;如果对每个方法的增强都一样,用动态代理。
- 如果要代理接口,使用三种都行;如果要代理类,用静态代理、cglib动态代理。