编程语言有多种分类角度,其中一种就是动态类型语言和静态类型语言,简单区分就是静态类型语言在编译期进行类型检查,而动态类型语言在运行时进行类型检查。注意,这和动态语言、静态语言区分角度不同。
一般来说,Java 属于静态类型语言,但因为提供了类似反射等机制,也具备了部分动态类型语言的能力。今天就来介绍一下反射机制。
什么是反射
反射机制指的是程序在运行时自省(introspect,即能够获取自身信息)的能力。在 Java 中,只要给定类的完全限定名,就可以通过反射直接操作类或对象。它主要有以下几个作用:
- 在运行时判断任意一个对象所属的类;
- 在运行时判断任意一个类具有的成员变量和方法;
- 在运行时调用任意一个对象的方法;
- 在运行时构造任意一个类的对象;
可以看到,反射提高了程序的灵活性和扩展性。但同时降低了代码的可读性和维护性;又因为反射涉及到动态类型,无法执行某些虚拟机优化,所以代码的执行性能也降低;另外,可以访问任意成员变量和方法,也破坏了封装性。一般来说,在业务代码中应尽量避免使用反射,但必须能理解中间件或框架中的反射代码。
反射的应用场景非常多。例如,对象序列化,动态代理,JDBC 的 Class.forName(),RPC 框架,Spring 的 IOC/DI。
Class 类
Java 的 Class 类是反射机制的基础,包含了被装入到 JVM 中的类(包括类和接口)的信息。每个类(型)都有一个 Class 对象,也就是说每当编写并编译了一个新类,就会产生一个 Class 对象,被保存在一个同名的 .class 文件中。
所有的类都是在对其第一次使用时,动态加载到 JVM 中的。当运行程序时,类加载器会首先检查这个类的 class 对象是否已经加载,如果没有加载,类加载器就会根据类名查找 .class 文件,并将其 Class 对象载入。
获取某一个类所对应的 Class 对象有三种方法:
1). 根据对象的 getClass() 方法获取:
User user = new User();
Class c = user.getClass();
2). 根据 Class 的静态方法 forName() 获取:
Class c = Class.forName("com.timber.User");
3). 根据类名 .class 获取:
Class c = User.class;
对于基本类型来说,它们的包装类型拥有一个名为 TYPE 的 final 静态字段,指向该基本类型对应的 Class 对象。例如,Integer.TYPE 指向 int.class。
对于数组类型来说,可以使用 类名 + [].class 来访问 Class 对象。例如,int[].class。
使用反射创建对象
在 Java 中创建对象主要有四种方式:
- 通过
new关键字创建; - 使用反射;
- 使用
clone方法; - 使用反序列化;
使用反射
Java 中使用反射创建对象主要有两种方法:
1). 使用 Class 类的 newInstance 方法
方法一:
Class<?> userClass = Class.forName("com.timber.User"); // 给定类的完全限定名
User user = (User) userClass.newInstance();
方法二:
User user = User.class.newInstance();
2). 使用 Constructor 类的 newInstance 方法
Constructor<User> constructor = User.class.getConstructor();
User user = constructor.newInstance();
Constructor<?> constructor = User.class.getConstructor();
User user = (User) constructor.newInstance();
事实上,Class 的 newInstance 方法内部就是调用 Constructor 的 newInstance 方法。如下为 Class 类的源码:
@CallerSensitive
public T newInstance() throws InstantiationException, IllegalAccessException {
Constructor<T> tmpConstructor = cachedConstructor;
try {
return tmpConstructor.newInstance((Object[])null);
} catch (InvocationTargetException e) {
Unsafe.getUnsafe().throwException(e.getTargetException());
// Not reached
return null;
}
}
使用反射功能
在得到 Class 对象后,可以正式使用反射功能了。除了使用 newInstance() 生成类的实例,还有以下几项:
1). 使用 isInstance(Object o) 来判断一个对象是否该类的实例,等同于 instanceof 关键字:
Class c = User.class.newInstance();
boolean b = c.isInstance(user);
2). 使用 Array.newInstance(Class c, int size) 来构造该类型的数组:
int[] arr = Array.newInstance(int[].class, 3);
3). 使用 getFields()/getConstructors()/getMethods() 来访问该类的成员。需要注意,方法名中带 Declared 的不会返回父类的成员,但是会返回私有成员;而不带 Declared 的则相反。
Method[] methods = c.getMethods();
Method[] methods = c.getDeclaredMethods();
当获得类成员之后,可以进一步做如下操作:
- 使用
Constructor/Field/Method.setAccessible()来修改访问限制 - 使用
Constructor.newInstance(Object[])来生成该类的实例 - 使用
Field.set/get(Object)来访问字段值 - 使用
Method.invoke(Object, Object[])来调用方法
实例:在泛型为 Integer 的 ArrayList 中存放一个 String 类型的对象:
public void test() {
try {
List<Integer> list = new ArrayList<>();
Class<?> listClass = list.getClass();
Method method = listClass.getMethod("add", Object.class);
method.invoke(list, "test");
System.out.println(list.get(0));
} catch (Exception e) {
e.printStackTrace();
}
}
反射调用的开销
在前面的方法中,Class.forName 会调用本地方法,Class.getMethod() 会遍历该类的共有方法,如果找不到,还会遍历父类的私有方法,所以它们的操作都很费时。另外,Method.getMethods() 等方法还会返回查找结果的一份拷贝。
在实践中,应该避免在热点代码中使用 Method.getMethods() 和 Method.getDeclaredMethods() 方法。并且往往会缓存 Class.forName() 和 Method.getMethod() 的结果。
在反射调用时会带来不少性能开销,主要原因有三个:
- 由于
Method.invoke是一个变长参数方法,在调用时会生成一个Object数组 - 由于
Object数组不能存储基本类型,所以会对基本类型进行自动装箱、拆箱 - 如果拥有多个不同的反射调用,就会对应多个
GeneratedMethodAccessor,可能由于JVM调用点的类型profile无法同时记住多个类,而没有被内联
静态代理
代理是基本的设计模式之一。它可以通过访问代理对象来完成对目标对象的访问,在不修改原对象的情况下扩充其功能。可以分为静态代理和动态代理两种。
静态代理,就是代理类由程序员自己编写,代理模式中的所有对象在编译期就已经确定。下面是一个简单的例子,首先定义一个接口和其实现:
public interface UserService {
void say();
}
public class UserServiceImpl implements UserService {
@Override
public void say() {
System.out.println("目标对象");
}
}
这就是代理模式中的目标对象和目标对象的接口,接下来定义代理对象:
public class UserServiceProxy implements UserService {
private UserService target;
public UserServiceProxy(UserService target) {
this.target = target;
}
@Override
public void say() {
System.out.println("调用目标对象之前");
target.say();
System.out.println("调用目标对象之后");
}
}
上面就是一个代理类,它同样实现了目标对象的接口,并重写了 say 方法。下面是一个测试类:
public class ProxyTest {
public static void main(String[] args) {
// 目标对象
UserService target = new UserServiceImpl();
// 代理对象
UserService proxy = new UserServiceProxy(target);
proxy.say();
}
}
// 调用目标对象之前
// 目标对象
// 调用目标对象之后
静态代理也存在一些局限,例如,需要程序员手写很多代码,并且当需要代理的类中方法比较多,或者同时需要代理多个对象时,实现会很复杂。
动态代理
动态代理中的代理类是在运行期动态生成的。在 Java 中,动态代理有两种方式:
1). JDK 接口 + 反射方式
这种方式主要通过 java.lang.reflect 包中的 Proxy 类和 InvocationHandler 接口实现。由于是 JDK 本身支持,可能比 cglib 更加可靠,代码实现也比较简单。
但是它有一个限制,就是代理对象必须实现一个或多个接口。
2). cglib 继承 + asm 方式
cglib 是一个第三方代码生成类库,通过在运行时动态生成一个子类对象来实现,它底层通过一个小而快字节码处理框架 asm,转换字节码来生成新的类。这种方式无需实现接口,达到了代理类无侵入。但是无法代理 final 类和父类的 static/final 方法。
cglib 虽然性能较高,但是它需要对 JVM 内部结构包括 Class 文件格式和指令集很熟悉,所以不鼓励使用。
Java 的动态代理最主要的用途就是应用在各种框架中。例如,RPC 框架,Spring 中的 AOP,Servlet 的过滤器、拦截器。像 Mybatis 的分页插件,Spring AOP 中类似日志、事务、权限、性能监控等都用到了动态代理,以在不同模块的特定阶段实现某些功能。
两种实现方法
1). JDK 动态代理
目标对象的接口和实现和静态代理中一样,下面定义调用处理器类,它需要实现 IovacationHandler 接口:
public class JDKInvocationHandler implements InvocationHandler {
private Object target;
// 传入目标对象
public JDKInvocationHandler(Object target) {
super();
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("调用前:" + method.getName());
// 将请求转发给目标对象,并传入相应的参数
Object result = method.invoke(target, args);
System.out.println("调用后:" + method.getName());
return result;
}
public Object getProxy() {
// 传入类加载器、希望该代理类实现的接口类数组、调用处理器,构造代理类
return Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(),
target.getClass().getInterfaces(),
this);
}
}
下面是测试类:
public class JDKProxyTest {
public static void main(String[] args) {
UserService target = new UserServiceImpl();
JDKInvocationHandler handler = new JDKInvocationHandler(target);
UserService proxy = (UserService) handler.getProxy();
proxy.say();
}
}
// 调用前:say
// 目标对象
// 调用后:say
2). Cglib 动态代理
目标对象和之前的一致,下面定义代理类:
public class CglibProxy implements MethodInterceptor {
private Enhancer enhancer = new Enhancer();
public Object getProxy(Class c) {
enhancer.setSuperclass(c);
enhancer.setCallback(this);
return enhancer.create();
}
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
System.out.println("打开事务");
Object retVal = methodProxy.invokeSuper(o, objects);
System.out.println("关闭事务");
return retVal;
}
}
下面是测试类:
public class CglibTest {
public static void main(String[] args) {
CglibProxy proxy = new CglibProxy();
UserServiceImpl proxyImpl = (UserServiceImpl) proxy.getProxy(UserServiceImpl.class);
proxyImpl.say();
}
}
// 打开事务
// 目标对象
// 关闭事务