专栏名称: 程序员大咖
为程序员提供最优质的博文、最精彩的讨论、最实用的开发资源;提供最新最全的编程学习资料:PHP、Objective-C、Java、Swift、C/C++函数库、.NET Framework类库、J2SE API等等。并不定期奉送各种福利。
目录
相关文章推荐
码农翻身  ·  年薪154W!真心建议大家冲一冲新兴领域,工 ... ·  19 小时前  
OSC开源社区  ·  李彦宏:DeepSeek让我们明白要将最优秀 ... ·  昨天  
程序员小灰  ·  如何用DeepSeek来变现?90%的人都不知道 ·  3 天前  
51好读  ›  专栏  ›  程序员大咖

Java反射机制应用实践

程序员大咖  · 公众号  · 程序员  · 2017-05-22 19:22

正文


引言

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 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 useCases = new ArrayList();

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<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()







请到「今天看啥」查看全文