Java反射机制是一个很是强大的功能,在不少大型项目好比Spring, Mybatis中均可以看见反射的身影。经过反射机制咱们能够在运行期间获取对象的类型信息,利用这一特性咱们能够实现工厂模式和代理模式等设计模式,同时也能够解决Java泛型擦除等使人苦恼的问题。本文咱们就从实际应用的角度出发,来应用一下Java的反射机制。java
p.s: 本文须要读者对反射机制的API有必定程度的了解,若是以前没有接触过的话,建议先看一下官方文档的Quick Start。git
在应用反射机制以前,首先咱们先来看一下如何获取一个对象对应的反射类Class
,在Java中咱们有三种方法能够获取一个对象的反射类。github
在Java中,每个Object
都有一个getClass()
方法,经过getClass方法咱们能够获取到这个对象对应的反射类:编程
String s = "http://www.ziwenxie.site"; Class<?> c = s.getClass();
咱们也能够调用Class
类的静态方法forName()
:设计模式
Class<?> c = Class.forName("java.lang.String");
或者咱们也能够直接使用.class
:安全
Class<?> c = String.class;
在文章开头咱们就提到反射的一大好处就是能够容许咱们在运行期间获取对象的类型信息,下面咱们经过一个例子来具体看一下。oracle
首先咱们在typeinfo.interfacea
包下面新建一个接口A
:app
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()
方法就能够触发对象的相关方法:less
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<String> 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<Class<? extends Pet>>
,咱们须要统计出这个集合类中每种具体的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<Individual> { 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<Class<? extends Pet>> 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<Pet> arrayList(int size) { ArrayList<Pet> result = new ArrayList<Pet>(); 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<Class<? extends Pet>> allTypes = Collections.unmodifiableList( Arrays.asList(Pet.class, Dog.class, Cat.class, Mutt.class, EgyptianMau.class)); private static final List<Class<? extends Pet>> types = allTypes.subList( allTypes.indexOf(Mutt.class), allTypes.size()); public List<Class<? extends Pet>> getTypes() { return types; } }
整体的逻辑已经完成了,最后咱们实现用来统计集合中相关Pet
类个数的TypeCounter
类。解释一下isAssignalbeFrom()
方法,它能够判断一个反射类是某个反射类的子类或者间接子类。而getSuperclass()
顾名思义就是获得某个反射类的父类了。
public class TypeCounter extends HashMap<Class<?>, 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<Class<?>, 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 class Pets { public static final PetCreator creator = new LiteralPetCreator(); public static Pet randomPet() { return creator.randomPet(); } public static Pet[] createArray(int size) { return creator.createArray(size); } public static ArrayList<Pet> arrayList(int size) { return creator.arrayList(size); } }
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); }
GitHub: https://github.com/ziwenxie
Blog: https://www.ziwenxie.site
本文为做者原创,转载请声明博客出处:)