Java反射机制应用实践
Java反射机制是一个非常强大的功能,在很多大型项目比如Spring, Mybatis中都可以看见反射的身影。通过反射机制我们可以在运行期间获取对象的类型信息,利用这一特性我们可以实现工厂模式和代理模式等设计模式,同时也可以解决Java泛型擦除等令人苦恼的问题。本文我们就从实际应用的角度出发,来应用一下Java的反射机制。
-
作者:ziwenxie
来源:segmentfault|
2017-05-17 15:28
-
移动端
收藏
分享
引言
Java反射机制是一个非常强大的功能,在很多大型项目比如Spring, Mybatis中都可以看见反射的身影。通过反射机制我们可以在运行期间获取对象的类型信息,利用这一特性我们可以实现工厂模式和代理模式等设计模式,同时也可以解决Java泛型擦除等令人苦恼的问题。本文我们就从实际应用的角度出发,来应用一下Java的反射机制。
反射基础
p.s: 本文需要读者对反射机制的API有一定程度的了解,如果之前没有接触过的话,建议先看一下官方文档的Quick Start。
在应用反射机制之前,首先我们先来看一下如何获取一个对象对应的反射类Class,在Java中我们有三种方法可以获取一个对象的反射类。
通过getClass方法
在Java中,每一个Object都有一个getClass()方法,通过getClass方法我们可以获取到这个对象对应的反射类:
- String s = "http://www.ziwenxie.site";
-
- Class> c = s.getClass();
通过forName方法
我们也可以调用Class类的静态方法forName():
- Class> c = Class.forName("java.lang.String");
使用.class
或者我们也可以直接使用.class:
- Class> c = String.class;
获取类型信息
在文章开头我们就提到反射的一大好处就是可以允许我们在运行期间获取对象的类型信息,下面我们通过一个例子来具体看一下。
首先我们在typeinfo.interfacea包下面新建一个接口A:
- package typeinfo.interfacea;
-
- public interface A { void f(); }
接着我们在typeinfo.packageaccess包下面新建一个类C,类C实现了接口A,并且我们还另外创建了几个用于测试的方法,注意下面几个方法的权限都是不同的。
- package typeinfo.packageaccess;
-
- import typeinfo.interfacea.A;
-
- class C implements A {
- public void f() { System.out.println("public C.f()"); }
- public void g() { System.out.println("public C.g()"); }
- protected void v () { System.out.println("protected C.v()"); }
- void u() { System.out.println("package C.u()"); }
- private void w() { System.out.println("private C.w()"); }
-
- }
-
- public class HiddenC {
- public static A makeA() { return new C(); }
- }
在callHiddenMethod()方法中我们用到了几个新的API,其中getDeclaredMethod()根据方法名用于获取Class类指代对象自己声明的某个方法,然后我们通过调用invoke()方法就可以触发对象的相关方法:
- package typeinfo;
-
- import typeinfo.interfacea.A;
- import typeinfo.packageaccess.HiddenC;
-
- import java.lang.reflect.Method;
-
- public class HiddenImplementation {
- public static void main(String[] args) throws Exception {
- A a = HiddenC.makeA();
- a.f();
- System.out.println(a.getClass().getName());
- // Oops! Reflection still allows us to call g():
- callHiddenMethod(a, "g");
- // And even methods that are less accessible!
- callHiddenMethod(a, "u");
- callHiddenMethod(a, "v");
- callHiddenMethod(a, "w");
- }
-
- static void callHiddenMethod(Object a, String methodName) throws Exception {
- Method g = a.getClass().getDeclaredMethod(methodName);
- g.setAccessible(true);
- g.invoke(a);
- }
- }
从输出结果我们可以看出来,不管是public,default,protect还是private方法,通过反射类我们都可以自由调用。当然这里我们只是为了显示反射的强大威力,在实际开发中这种技巧还是不提倡。
- public C.f()
- typeinfo.packageaccess.C
- public C.g()
- package C.u()
- protected C.v()
- private C.w()
上面我们只是测试了Method对象,感兴趣的读者在熟悉了反射的API之后,不妨测试一下Filed,这里我们就不重复了。
利用动态代理实现面向切面编程
AOP是Spring提供的一个强大特性之一,AOP的意思是面向切面编程,就是说要分离和业务不相关的代码,当我们需要新增相关的事务的时候,我们不想对业务本身做修改。面向切面编程和面向对象变成相比到底有什么好处呢,我们通过一个例子来看一下,对于新手来说,常常会写出下面这样的代码:
- public class Example1 {
- public void execute() {
- // 记录日志
- Logger logger = Logger.getLog(...);
- // 进行性能统计
- PerformanceUtil.startTimer(...);
- // 权限检查
- if (!user.hasPrevilege()) {
- // 抛出异常
- }
- // 执行真正的业务
- executeTransaction();
- PerformanceUtil.endTimer();
- }
- }
虽然我们上面真正要执行的业务只有executeTransaction(),但是日志,性能,权限相关的代码差不多要将真正的业务代码掩盖了。而且以后如果我们还有一个Example2,它同样需要实现相同的日志,性能,权限代码。这样当以后我们需要新增相关的逻辑检查的时候,我们需要所有Example进行重构,这显然不符合面向对象的一个基本原则-封装变化。
上面这个场景利用模板方法和装饰器模式都可以解决,在Spring中是通过动态代理来实现的,下面我们通过一个例子来模拟一下Spring中的AOP实现。
我们要实现的业务是,统计员工工资的时候程序所执行的时间以及检查用户的权限。首先我们先来实现Salary类,它里面包含一些实现统计员工工资的业务逻辑:
- public interface SalaryInterface {
- public void doSalary();
- }
-
- public class Salary implements SalaryInterface {
- public void doSalary() {
- ...
- }
- }
通过InvocationHandler我们来实现动态代理,以后当我们调用obj的相关方法之前,都会通过invoke方法进行代理,而不会直接调用obj方法。
- public class SimpleProxy implements InvocationHandler {
- private Object obj;
- private Object advice;
-
- // 绑定代理对象
- public Object bind(Object obj, Advice advice) {
- this.obj = obj;
- this.advice = advice;
- return Proxy.newProxyInstance(obj.getClass().getClassLoader(),
- obj.getClass().getInterfaces(), this)
- }
-
- // 实现代理
- public Object invoke(Object proxy, Method method, Object[] args) throws Throwalbe {
- Object result = null;
- try {
- advice.before();
- result = method.invoke(obj, args);
- advice.after();
- } catch(Exception e) {
- e.printStackTrace();
- }
- return result
- }
- }
模拟Spring中的Advice接口:
- public interface Advice {
- public void before();
- public void after();
- }
实现TimeAdvice用于统计程序的执行时间:
- public class TimeAdvice implements Advice {
- long startTime;
- long endTime;
-
- @Override
- public void before() {
- startTime = System.nanoTime(); // 获取开始时间
- }
-
- @Override
- public void after() {
- endTime = System.nanoTime(); // 获取结束时间
- }
- }
客户端调用代码如下:
- public class Client {
- public static void main(String[] args) {
- SimpleProxy = new SimpleProxy();
- SalaryInterface salaryInterface =
- (SalaryInterface) simpleProxy.bind(new Salary(), new TimeAdvice());
- salaryInterface.doSalary();
- }
- }
如果我们现在需要新增权限控制,我们来实现ControlAdvie类:
- public class ControlAdvice implements Advice {
- @Override
- public void before() {
- if (...) {
- ...
- } else {
- ...
- }
- }
-
- @Override
- public void after() {
- ...
- }
- }
而我们客户端的代码只需要改成simpleProxy.bind(new Salary(), new ControlAdvie)就行了,而SimpleProxy本身不需要做任何的修改。
与注解相结合
在单元测试框架比如Junit中反射机制也得到了广泛的应用,即通过注解的方式。下面我们简单地来了解一下如何通过反射机制来获取相关方法的注解信息,比如说我们有下面这样一个业务场景,当用户在修改自己密码的时候,为了保证密码的安全性,我们要求用户的新密码要满足一些条件,比如说至少要包含一个非数字字符,不能与以前的密码相同之类的条件等。
- import java.lang.annotation.*
-
- @Target(ElementType.METHOD)
- @Retention(RetentionPolicy.RUNTIME)
- public @interface UserCase {
- public int id();
- public String description() default "no description";
- }
下面是我们检测密码的工具类的实现:
- public class PasswordUtils {
- @UserCase(id=47, description="Password must contain at least one numeric")
- public boolean validatePassword(String password) {
- return (password.matches("\\w*\\d\\w*"));
- }
-
- @UserCase(id=48)
- public String encryptPassword(String password) {
- return new StringBuilder(password).reverse().toString();
- }
-
- @UserCase(id=49, description="New passwords can't equal previously used ones")
- public boolean checkForNewPassword(List prevPasswords, String password) {
- return !prevPasswords.contains(password);
- }
- }
利用反射我们可以写出更加清晰的测试代码,其中getDeclaredMethods()方法可以获取相关对象自己声明的相关方法,而getAnnotation()则可以获取Method对象的指定注解。
- public class UseCaseTracker {
- public static void trackUseCases(List<Integer> useCases, Class> cl) {
- for(Method m : cl.getDeclaredMethods()) {
- UseCase uc = m.getAnnotation(UseCase.class);
- if(uc != null) {
- System.out.println("Found Use Case: " + uc.id() + " " + uc.description());
- useCases.remove(new Integer(uc.id()));
- }
- }
-
- for(int i : useCases) {
- System.out.println("Warning: Missing use case-" + i);
- }
- }
-
- public static void main(String[] args) {
- List<Integer> useCases = new ArrayList<Integer>();
- Collections.addAll(useCases, 47, 48, 49, 50);
- trackUseCases(userCases, PasswordUtils.class);
- }
- }
解决泛型擦除
现在有下面这样一个业务场景,我们有一个泛型集合类List>,我们需要统计出这个集合类中每种具体的Pet有多少个。由于Java的泛型擦除,注意类似List extends Pet>的做法肯定是不行的,因为编译器做了静态类型检查之后,到了运行期间JVM会将集合中的对象都视为Pet,但是并不会知道Pet代表的究竟是Cat还是Dog,所以到了运行期间对象的类型信息其实全部丢失了。p.s: 关于泛型擦除,我在上一篇文章里面有详细解释,感兴趣的朋友可以看一看。
为了实现我们上面的例子,我们先来定义几个类:
- public class Pet extends Individual {
- public Pet(String name) { super(name); }
- public Pet() { super(); }
- }
-
- public class Cat extends Pet {
- public Cat(String name) { super(name); }
- public Cat() { super(); }
- }
-
- public class Dog extends Pet {
- public Dog(String name) { super(name); }
- public Dog() { super(); }
- }
-
- public class EgyptianMau extends Cat {
- public EgyptianMau(String name) { super(name); }
- public EgyptianMau() { super(); }
- }
-
- public class Mutt extends Dog {
- public Mutt(String name) { super(name); }
- public Mutt() { super(); }
- }
上面的Pet类继承自Individual,Individual类的的实现稍微复杂一点,我们实现了Comparable接口,重新自定义了类的比较规则,如果不是很明白的话,也没有关系,我们已经将它抽象出来了,所以不理解实现原理也没有关系。
- public class Individual implements Comparable {
- private static long counter = 0;
- private final long id = counter++;
- private String name; // name is optional
-
- public Individual(String name) { this.name = name; }
-
- public Individual() {}
-
- public String toString() {
- return getClass().getSimpleName() + (name == null ? "" : " " + name);
- }
-
- public long id() { return id; }
-
- public boolean equals(Object o) {
- return o instanceof Individual && id == ((Individual)o).id;
- }
-
- public int hashCode() {
- int result = 17;
- if (name != null) {
- result = 37 * result + name.hashCode();
- }
- result = 37 * result + (int) id;
- return result;
- }
-
- public int compareTo(Individual arg) {
- // Compare by class name first:
- String first = getClass().getSimpleName();
- String argFirst = arg.getClass().getSimpleName();
- int firstCompare = first.compareTo(argFirst);
- if (firstCompare != 0) {
- return firstCompare;
- }
-
- if (name != null && arg.name != null) {
- int secendCompare = name.compareTo(arg.name);
- if (secendCompare != 0) {
- return secendCompare;
- }
- }
-
- return (arg.id < id ? -1 : (arg.id == id ? 0 : 1));
- }
- }
下面创建了一个抽象类PetCreator,以后我们通过调用arrayList()方法便可以直接获取相关Pet类的集合。这里使用到了我们上面没有提及的newInstance()方法,它会返回Class类所真正指代的类的实例,这是什么意思呢?比如说声明new Dog().getClass().newInstance()和直接new Dog()是等价的。
- public abstract class PetCreator {
- private Random rand = new Random(47);
-
- // The List of the different getTypes of Pet to create:
- public abstract List> getTypes();
-
- public Pet randomPet() {
- // Create one random Pet
- int n = rand.nextInt(getTypes().size());
-
- try {
- return getTypes().get(n).newInstance();
- } catch (InstantiationException e) {
- throw new RuntimeException(e);
- } catch (IllegalAccessException e) {
- throw new RuntimeException(e);
- }
- }
-
- public Pet[] createArray(int size) {
- Pet[] result = new Pet[size];
-
- for (int i = 0; i < size; i++) {
- result[i] = randomPet();
- }
- return result;
- }
-
- public ArrayList arrayList(int size) {
- ArrayList result = new ArrayList();
- Collections.addAll(result, createArray(size));
- return result;
- }
- }
接下来我们来实现上面这一个抽象类,解释一下下面的代码,在下面的代码中,我们声明了两个集合类,allTypes和types,其中allTypes中包含了我们呢上面所声明的所有类,但是我们具体的类型实际上只有两种即Mutt和EgypianMau,所以我们真正需要new出来的宠物只是types中所包含的类型,以后我们通过调用getTypes()便可以得到types中所包含的所有类型。
- public class LiteralPetCreator extends PetCreator {
- @SuppressWarnings("unchecked")
- public static final List> allTypes = Collections.unmodifiableList(
- Arrays.asList(Pet.class, Dog.class, Cat.class, Mutt.class, EgyptianMau.class));
-
- private static final List> types = allTypes.subList(
- allTypes.indexOf(Mutt.class), allTypes.size());
-
- public List> getTypes() {
- return types;
- }
- }
总体的逻辑已经完成了,最后我们实现用来统计集合中相关Pet类个数的TypeCounter类。解释一下isAssignalbeFrom()方法,它可以判断一个反射类是某个反射类的子类或者间接子类。而getSuperclass()顾名思义就是得到某个反射类的父类了。
- public class TypeCounter extends HashMap, Integer> {
- private Class> baseType;
-
- public TypeCounter(Class> baseType) {
- this.baseType = baseType;
- }
-
- public void count(Object obj) {
- Class> type = obj.getClass();
- if (!baseType.isAssignableFrom(type)) {
- throw new RuntimeException(
- obj + " incorrect type " + type + ", should be type or subtype of " + baseType);
- }
- countClass(type);
- }
-
- private void countClass(Class> type) {
- Integer quantity = get(type);
- put(type, quantity == null ? 1 : quantity + 1);
- Class> superClass = type.getSuperclass();
- if (superClass != null && baseType.isAssignableFrom(superClass)) {
- countClass(superClass);
- }
- }
-
- @Override
- public String toString() {
- StringBuilder result = new StringBuilder("{");
-
- for (Map.Entry, Integer> pair : entrySet()) {
- result.append(pair.getKey().getSimpleName());
- result.append("=");
- result.append(pair.getValue());
- result.append(", ");
- }
-
- result.delete(result.length() - 2, result.length());
- result.append("} ");
- return result.toString();
- }
- }
测试代码如下:
- public static void main(String[] args) {
- TypeCounter counter = new TypeCounter(Pet.class);
- for (Pet pet : Pets.createArray(20)) {
- System.out.println(pet.getClass().getSimpleName() + " ");
- counter.count(pet);
- }
- System.out.println(counter);
- }
References
THINKING IN JAVA