Java语言是把Java源文件编译成后缀为class的字节码文件,然后再通过ClassLoader机制把这些文件加载到内存中,最后生成实例执行的,这是Java处理的基本机制。
1.1 Class对象
Java使用一个元类(MetaClass)来描述加载到内存中的类数据,这就是Class类,它是一个描述类的类对象。
Class类是Java的反射入口,只有在获取一个类的描述对象后才能动态的加载、调用,一般来说,获取一个Class对象有三种途径。
- 类属性方式:如String.class。
- 对象的getClass方法:如new String().getClass()。
- forName方法加载,如Class.forName("java.lang.String")。
获取到Class对象后,就可以通过getAnnotations()获取注解,通过getMethods获取方法,通过getConstructors()获取构造函数等,为后续反射代码铺平道路。
1.2 适当选择getDeclaredXXX和getXXX
Java的Class类提供了很多的getDeclaredXXX方法和getXXX方法,例如getDeclaredMethod和getMethod成对出现,getDecaredConstructors和getConstructors也是成对出现,那么这两者的区别是哪些?
public class Foo {
void doStuff() {
}
public static void main(String[] args) throws NoSuchMethodException {
String methodName="doStuff";
Method declaredMethod = Foo.class.getDeclaredMethod(methodName);
Method method = Foo.class.getMethod(methodName);
}
}
返回结果是method没有找到doStuff结果。
getXXX方法获取的是所有public访问级别的方法,包括在父类继承的方法,而getDeclaredXXX方法获取的是自身类的所有方法(不受限于访问权限)。
1.3 反射访问属性或方法时,将Accessible设置为true
Java中通过反射执行一个方法的过程如下:
public class Foo {
private void doStuff() {
System.out.println("hello world");
}
}
public class TestFoo {
@Test
public void test() throws Exception {
String methodName = "doStuff";
Method declaredMethod = Foo.class.getDeclaredMethod(methodName);
//由开发者决定是否要逃避安全体系的检查(是否可以快速获取)
System.out.println(declaredMethod.isAccessible());
if(declaredMethod.isAccessible()){
declaredMethod.setAccessible(true);
}
declaredMethod.invoke(new Foo());
}
}
实际上,在通过反射执行方法时,必须在invoke之前检查Accessible属性。但是方法对象的Accessible属性并不是用来决定是否可以访问的。
public class Foo {
public void doStuff() {
System.out.println("hello world");
}
public static void main(String[] args) throws NoSuchMethodException {
String methodName = "doStuff";
Method declaredMethod = Foo.class.getDeclaredMethod(methodName);
System.out.println(declaredMethod.isAccessible());
}
}
实际上,即使反射的方法访问修饰符为public,isAccessible()
的最终结果依旧是false。而且即使为false,还是可以使用invoke()方法执行。
故Accessible的属性并不是我们语法层次理解的访问权限,而是指是否更加容易获得,是否进行安全检查。
我们知道,动态修改一个类或者执行方法都会受到java安全体系的制约,而安全处理是非常消耗资源的(性能非常低),因此对于运行期要执行的方法或要修改的属性提供了Accessible
可选项:由开发者来决定是否逃避安全体系的检查。
在源码java.lang.reflect.AccessibleObject#isAccessible
中,该方法默认返回值为false。
AccessibleObject是Field、Method、Constructor的父类,决定其是否可以快速访问而不进行访问控制检查。在AccessibleObject中是以override变量保存该值的,但是具体是否快速执行是在Method类的invoke方法中决定的。
public Object invoke(Object obj, Object... args)
throws IllegalAccessException, IllegalArgumentException,
InvocationTargetException{
//检查是否可以快速获取,其值是父类的AccessibleObject的override变量
if (!override) {
//不能快速获取,要进行安全检查
if (!Reflection.quickCheckMemberAccess(clazz, modifiers)) {
Class<?> caller = Reflection.getCallerClass();
checkAccess(caller, clazz, obj, modifiers);
}
}
MethodAccessor ma = methodAccessor; // read volatile
if (ma == null) {
ma = acquireMethodAccessor();
}
//直接执行方法
return ma.invoke(obj, args);
}
Accessible属性只是用来判断是否需要进行安全检查的,如果不需要则直接执行,这样可以大幅度地提升系统性能(当然了,由于取消了安全检查,也可以运行private方法,获取private私有属性)。
1.4 使用forName动态的加载类
- 静态加载:编译时期加载的类。
- 动态加载:运行时期加载的类。
一般new对象便是静态加载,静态加载的不足在于:在编译的时刻就会把所有的类都加载,无论该类是否使用。
而forName可以看做是动态加载,在运行期间才会检查该类是否存在。
Class c1= Class.forName("com.javatest.Excel");
Officeable e=(Officeable)c1.newInstance();
而加载一个类即表示要初始化该类的static变量,特别是static块,在这里我们可以做大量的工作,比如注册自己,初始化环境,这才是动态加载的意义所在。
例如源码中:com.mysql.cj.jdbc.Driver
public class Driver extends NonRegisteringDriver implements java.sql.Driver {
//静态代码块
static {
try {
//把自己注册到DriverManager中
java.sql.DriverManager.registerDriver(new Driver());
} catch (SQLException E) {
throw new RuntimeException("Can't register driver!");
}
}
public Driver() throws SQLException {
// Required for Class.forName().newInstance()
}
}
而实际上我们一般使用Class.forName("com.mysql.jdbc.Driver")
来将Diver类加载到内存中。
需要注意的是,forName只是加载类,并不执行任何代码,也不保证由此产生一个实例对象。之所以运行static代码,那是由类加载机制决定的,而不是forName方法决定的。
1.5 反射在动态代理中的使用
1.6 反射在装饰模式中的使用
装饰模式的定义是“动态地给一个对象添加一些额外的职责。就增加功能来说,装饰模式相比生成子类更加灵活。”不过,使用Java的动态代理也可以实现装饰模式的效果,而且其更加灵活,适应性更强。
装饰器模式.png装饰模式的特点是:功能增强,接口不变。
构建类型:
public interface Animal {
void doStuff();
}
public class Rat implements Animal {
@Override
public void doStuff() {
System.out.println("这是一只小老鼠!");
}
}
装饰类型:
//定义某种能力
public interface Feature {
//加载能力
void load();
}
public class FlyFeature implements Feature {
@Override
public void load() {
System.out.println("增加一只翅膀...");
}
}
装饰者模式:实现了装饰类和被装饰类的解耦。
public class DecorateAnimal implements Animal {
//被包装的动作
private Animal animal;
//使用哪个包装器
private Class<? extends Feature> clz;
public DecorateAnimal(Animal animal, Class<? extends Feature> clz) {
this.animal = animal;
this.clz = clz;
}
@Override
public void doStuff() {
//代理模式
Feature proxy = (Feature)Proxy.newProxyInstance(getClass().getClassLoader(), clz.getInterfaces(), (p, m, args) -> {
//实现InvocationHandler接口
Object obj = null;
//设置增强方法(Feature的接口类型为public)
if (Modifier.isPublic(m.getModifiers())) {
obj = m.invoke(clz.newInstance(), args);
}
//执行animal方法
animal.doStuff();
return obj;
});
//执行代码方法
proxy.load();
}
}
一个装饰类型必然是抽象构建(Component)的子类型,它必须实现doStuff,此处doStuff方法委托了动态代理执行。
此处代码是一个通用的装饰模式,只需要定义被装饰的类以及装饰类即可,装饰行为又动态代理实现,实现了装饰类和被装饰类的完全解耦,提供了系统的可扩展性。
1.7 反射在模板模式中的使用
模式方法模式(Template Method Pattern)的定义是:定义一个操作中的算法骨架,将一些步骤延迟到子类中,使子类不改变一个算法的结构即可重定义该算法的某些特定步骤。
public abstract class AbsPopulator {
public final void dataInitialing(){
//TODO 父类共有逻辑
//子类特有方法
doInit();
}
//需要每个业务自己实现
protected abstract void doInit();
}
而反射如何在模板方法模式中使用呢?
若doInit()方法中逻辑比较繁杂(例如初始化一张User表需要很多的操作,比如先建表,然后筛选数据,之后插入,最后校验),但是将其全部放入doInit()方法非常庞大(即使提取出多个方法承担不同的责任,代码可读性依旧非常差)。
可以使模板方法实现对一批固定规则的基本方法的调用。
public abstract class AbsPopulator {
public final void dataInitialing() {
//TODO 父类共有逻辑
Method[] methods = getClass().getMethods();
Arrays.stream(methods).
filter(this::isInitDataMethod).
forEach(method -> {
try {
method.setAccessible(true);
method.invoke(this);
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
});
}
private boolean isInitDataMethod(Method method) {
return method.getName().startsWith("init") //init开头
&& Modifier.isPublic(method.getModifiers()) //public修饰
&& method.getReturnType().equals(void.class) //返回值是void
&& !method.isVarArgs() //输入参数为空
&& !Modifier.isAbstract(method.getModifiers()); //不是抽象方法
}
}
子类方法
public class UserPopulator extends AbsPopulator {
public void initUser() {
System.out.println("初始化User");
}
public void initPassword() {
System.out.println("初始化Password");
}
public void initJob() {
System.out.println("初始化job");
}
public static void main(String[] args) {
AbsPopulator userPopulator = new UserPopulator();
userPopulator.dataInitialing();
}
}
UserPopulator类中的方法只要符合基本方法鉴别器条件即会被模板方法调用,方法的数据量也不再受父类的约束,实现了子类灵活定义基本方法,父类批量调用的功能,并且缩减了子类的代码量。
网友评论