您的位置:首页 > 编程语言 > Java开发

Java反射机制应用实践

2017-05-23 16:53 274 查看
转自:https://www.ziwenxie.site/2017/03/22/java-reflection


引言

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.Cpublic 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;@Overridepublic void before() {startTime = System.nanoTime(); // 获取开始时间}@Overridepublic 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 {@Overridepublic void before() {if (...) {...} else {...}}@Overridepublic 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 optionalpublic 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 Petint 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);}}@Overridepublic 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();}}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  java 反射机制