分享

盘点Java框架常用的3大底层技术!

 hlhq1 2019-08-22

作者:陌北有棵树,Java人,架构师社区合伙人!

本文所介绍的三个Java底层技术,有着逐渐递进的特点,Java注解中使用了JDK动态代理,而JDK动态代理中运用了Java反射。

Java注解

当我们阅读框架源码时,会看到其中包含着大量的注解,注解被广泛使用的原因在于,可以生成一些通用的“模板化”代码,来避免重复性的工作。使用注解的工作模式是,通过注解来描述我们的意图,然后用注解解析工具对注解进行解析。

【一】实验:自定义注解

首先我们通过 @interface关键字定义一个注解@Tree,定义注解时,需要定义两个内容:元注解,注解属性。

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Tree {

String name() default 'tree';
}

元注解:

可以看到我在上面还添加了@Target和@Retention,这个是元注解,也就是添加到注解之上的注解,元注解有5种:

  • @Retention:声明注解的的存活时间

    RetentionPolicy.SOURCE 注解只在源码阶段保留,在编译器进行编译时它将被丢弃忽视。

    RetentionPolicy.CLASS 注解只被保留到编译进行的时候,它并不会被加载到 JVM 中。

    RetentionPolicy.RUNTIME 注解可以保留到程序运行的时候,它会被加载进入到 JVM 中,所以在程序运行时可以获取到它们。

  • @Target:声明注解运用的地方

    ElementType.ANNOTATION_TYPE 应用到注解

    ElementType.CONSTRUCTOR 应用到构造方法

    ElementType.FIELD 应用到属性

    ElementType.LOCAL_VARIABLE 应用到局部变量

    ElementType.METHOD 应用到方法

    ElementType.PACKAGE 应用到包

    ElementType.PARAMETER 应用到方法内的参数

    ElementType.TYPE 应用到类型(类、接口、枚举)

  • @Documented:将注解中的元素包含到 Javadoc 中

  • @Inherited:使用了这个注解的子类,就继承了该注解

  • @Repeatable:Java1.8新增特性,应用于注解的值可以取多个的场景

注解属性:

可以类比为普通类中的成员变量,注解中只有成员变量没有方法,在使用该注解时为该属性赋值,也可以在定义时赋默认值。

【注解处理器】

在注解处理器中,我们可以为注解定义逻辑,例如在下面的例子中,就是调用AnnotationClient类中所有方法,把@Tree中name属性值注入到方法中。

public class TreeProcessor {
public void parseMethod(final Class<?> clazz) throws Exception {
final Object obj = clazz.getConstructor(new Class[] {}).newInstance(new Object[] {});
final Method[] methods = clazz.getDeclaredMethods();
for (final Method method : methods) {
final Tree my = method.getAnnotation(Tree.class);
if (null != my) {
method.invoke(obj, my.name());
}
}
}
}

接下来做一下测试:

public class AnnotationClient {

@Tree
public static void sayHello(final String name) {
System.out.println('==>> Hi, ' + name + ' [sayHello]');
}

@Tree(name = 'Someone')
public static void sayHelloToSomeone(final String name) {
System.out.println('==>> Hi, ' + name + ' [sayHelloToSomeone]');
}

public static void main(final String[] args) throws Exception {
final TreeProcessor treeProcessor = new TreeProcessor();
treeProcessor.parseMethod(AnnotationClient.class);
}

}

【二】深入理解注解

如果换一个角度理解注解:它的本质是一个继承了Annotation接口的接口

当我们通过getAnnotation()方法获取一个注解的时候,JDK会通过动态代理生成注解的代理类$Proxy1,这个代理类代理了Tree中的所有方法,其实本质上还是通过反射来实现的,但是我们逐步递进的分析,先研究注解,下一步研究JDK动态代理,最后才能到达反射。

JAVA 中专门用于处理注解的 Handler:

sun.reflect.annotation.AnnotationInvocationHandler

AnnotationInvocationHandler有如下几个属性:

private final Class<? extends Annotation> type;
private final Map<String, Object> memberValues;
private transient volatile Method[] memberMethods = null;

其中memberValues在初始化后,key是注解的属性值,value是我们为属性的赋值,可能你已经忘了我们在程序中是怎么做的了 : @Tree(name = 'Someone')

所有动态代理类生成的方法,都会走这个invoke()方法。

而这个invoke方法做的事情,概括起来就是:通过方法名获取属性值。

public Object invoke(Object var1, Method var2, Object[] var3) {
String var4 = var2.getName();
Class[] var5 = var2.getParameterTypes();
if (var4.equals('equals') && var5.length == 1 && var5[0] == Object.class) {
return this.equalsImpl(var3[0]);
} else if (var5.length != 0) {
throw new AssertionError('Too many parameters for an annotation method');
} else {
byte var7 = -1;
switch(var4.hashCode()) {
case -1776922004:
if (var4.equals('toString')) {
var7 = 0;
}
break;
case 147696667:
if (var4.equals('hashCode')) {
var7 = 1;
}
break;
case 1444986633:
if (var4.equals('annotationType')) {
var7 = 2;
}
}

switch(var7) {
case 0:
return this.toStringImpl();
case 1:
return this.hashCodeImpl();
case 2:
return this.type;
default:
Object var6 = this.memberValues.get(var4);
if (var6 == null) {
throw new IncompleteAnnotationException(this.type, var4);
} else if (var6 instanceof ExceptionProxy) {
throw ((ExceptionProxy)var6).generateException();
} else {
if (var6.getClass().isArray() && Array.getLength(var6) != 0) {
var6 = this.cloneArray(var6);
}
return var6;
}
}
}
}

从代码中我们可以看到,如果匹配为toString(),hashCode(),annotationType,会专门返回相应的实现,否则就返回memberValues对应key中相应的value,这样看起来还是比较清晰的。

于是,在分析完注解的实现后,我们同时也有了下一步的研究目标:JDK动态代理的实现。

【三】servlet 3.0引入的新注解

此篇文章的缘起,是由于在阅读SpringBoot源码时看到了@HandlesTypes注解,这是Tomcat的SCI机制用到的一个注解,被它标明的类需要作为参数值传入到onStartup方法。这是Servlet3.0新增的特性,所以在这里也列举一下Servlet3.0中新增的一些注解:

  • HandlesTypes –该注解用来表示一组传递给ServletContainerInitializer的应用类。

  • HttpConstraint – 该注解代表所有HTTP方法的应用请求的安全约束,和ServletSecurity注释中定义的HttpMethodConstraint安全约束不同。

  • HttpMethodConstraint – 指明不同类型请求的安全约束,和ServletSecurity 注解中描述HTTP协议方法类型的注释不同。

  • MultipartConfig –该注解标注在Servlet上面,表示该Servlet希望处理的请求的 MIME 类型是 multipart/form-data。

  • ServletSecurity 该注解标注在Servlet继承类上面,强制该HTTP协议请求遵循安全约束。

  • WebFilter – 该注解用来声明一个Server过滤器;

  • WebInitParam – 该注解用来声明Servlet或是过滤器的中的初始化参数,通常配合 @WebServlet 或者 @WebFilter 使用。

  • WebListener –该注解为Web应用程序上下文中不同类型的事件声明监听器。

  • WebServlet –该注解用来声明一个Servlet的配置。

JDK动态代理

【一】实验:实现基于JDK的动态代理

【第一步】定义一个接口和实现类

public interface IPerson {
void sayHello();
}
@Service
public class PersonServiceImpl implements IPerson {

@Override
public void sayHello() {
System.out.println('Hello~~~');
}
}
public class PersonInvocationHandler implements InvocationHandler {

private Object target;

public PersonInvocationHandler(Object target){
this.target = target;
}


@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println('------前置逻辑-------------');
// 执行相应的目标方法
Object result = method.invoke(target,args);
System.out.println('------后置逻辑-------------');
return result;
}
}

【第二步】两种使用JDK动态代理的方式

方式一:分为五个步骤
  1. 实现InvocationHandler接口

  2. 获得动态代理类:Proxy.getProxyClass

  3. 获得代理类的构造方法:getConstructor(InvocationHandler.class)

  4. 获得代理对象,传入自定义的InvocationHandler

  5. 通过代理对象调用目标方法

方式二 - 提供了一种封装好的方法:
java.lang.reflect.Proxy#newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
三个参数代表的含义分别是:

ClassLoader loader:接口的类加载器

Class<?>[] interfaces:接口(可以是一组接口)

InvocationHandler h:自定义的InvocationHandler

下面是两种方式的示例:

public class DynamicProxyClient {

public static void main(String[] args) throws Exception{
// 方式一
System.getProperties().put('sun.misc.ProxyGenerator.saveGeneratedFiles', 'true');
Class proxyClazz = Proxy.getProxyClass(IPerson.class.getClassLoader(),IPerson.class);
Constructor constructor = proxyClazz.getConstructor(InvocationHandler.class);
IPerson iPerson = (IPerson) constructor.newInstance(new PersonInvocationHandler(new PersonServiceImpl()));
iPerson.sayHello();

// 方式二
IPerson proxyInstance = (IPerson) Proxy.newProxyInstance(IPerson.class.getClassLoader(), new Class[]{IPerson.class}, new PersonInvocationHandler(new PersonServiceImpl())); proxyInstance.sayHello();
}
}

综上所述,基于JDK的动态代理的实现方式:实现InvocationHandler接口,重写invoke()方法。

【二】源码分析

首先要说明一点,JDK动态代理只能代理接口,因为代理类本身已经继承了Proxy,而Java不允许多重继承,但是允许实现多个接口。

我们先来看那个封装好的newProxyInstance()方法源码:

public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
throws IllegalArgumentException
{
Objects.requireNonNull(h);

final Class<?>[] intfs = interfaces.clone();
final SecurityManager sm = System.getSecurityManager();
if (sm != null) {
checkProxyAccess(Reflection.getCallerClass(), loader, intfs);
}

// 生成代理类
Class<?> cl = getProxyClass0(loader, intfs);
try {
if (sm != null) {
checkNewProxyPermission(Reflection.getCallerClass(), cl);
}
// 获取代理类的构造方法,其中constructorParams是InvocationHandler.class
final Constructor<?> cons = cl.getConstructor(constructorParams);
final InvocationHandler ih = h;
if (!Modifier.isPublic(cl.getModifiers())) {
AccessController.doPrivileged(new PrivilegedAction<Void>() {
public Void run() {
cons.setAccessible(true);
return null;
}
});
}
// 返回代理类
return cons.newInstance(new Object[]{h});
} catch (IllegalAccessException|InstantiationException e) {
throw new InternalError(e.toString(), e);
} catch (InvocationTargetException e) {
Throwable t = e.getCause();
if (t instanceof RuntimeException) {
throw (RuntimeException) t;
} else {
throw new InternalError(t.toString(), t);
}
} catch (NoSuchMethodException e) {
throw new InternalError(e.toString(), e);
}
}
 private static final class ProxyClassFactory
implements BiFunction<ClassLoader, Class<?>[], Class<?>>
{
// 组合成为代理类名字
private static final String proxyClassNamePrefix = '$Proxy';
private static final AtomicLong nextUniqueNumber = new AtomicLong();

@Override
public Class<?> apply(ClassLoader loader, Class<?>[] interfaces) {

Map<Class<?>, Boolean> interfaceSet = new IdentityHashMap<>(interfaces.length);
for (Class<?> intf : interfaces) {

Class<?> interfaceClass = null;
try {
interfaceClass = Class.forName(intf.getName(), false, loader);
} catch (ClassNotFoundException e) {
}
if (interfaceClass != intf) {
throw new IllegalArgumentException(
intf + ' is not visible from class loader');
}

if (!interfaceClass.isInterface()) {
throw new IllegalArgumentException(
interfaceClass.getName() + ' is not an interface');
}
/*
* Verify that this interface is not a duplicate.
*/
if (interfaceSet.put(interfaceClass, Boolean.TRUE) != null) {
throw new IllegalArgumentException(
'repeated interface: ' + interfaceClass.getName());
}
}

String proxyPkg = null; // package to define proxy class in
int accessFlags = Modifier.PUBLIC | Modifier.FINAL;

for (Class<?> intf : interfaces) {
int flags = intf.getModifiers();
if (!Modifier.isPublic(flags)) {
accessFlags = Modifier.FINAL;
String name = intf.getName();
int n = name.lastIndexOf('.');
String pkg = ((n == -1) ? '' : name.substring(0, n + 1));
if (proxyPkg == null) {
proxyPkg = pkg;
} else if (!pkg.equals(proxyPkg)) {
throw new IllegalArgumentException(
'non-public interfaces from different packages');
}
}
}

if (proxyPkg == null) {
proxyPkg = ReflectUtil.PROXY_PACKAGE + '.';
}

long num = nextUniqueNumber.getAndIncrement();
String proxyName = proxyPkg + proxyClassNamePrefix + num;

// 生成代理类文件
byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
proxyName, interfaces, accessFlags);
try {
// native方法,返回代理类
return defineClass0(loader, proxyName,
proxyClassFile, 0, proxyClassFile.length);
} catch (ClassFormatError e) {

throw new IllegalArgumentException(e.toString());
}
}
}

依稀记得,当年看《深入理解Java虚拟机时》,第七章有这样一段话,当时懵懵懂懂的我只是把它背了下来,

在类加载的加载阶段,虚拟机完成三件事情:

  1. 通过一个类的全限定名来获取定义此类的二进制字节流

  2. 将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构

  3. 在内存中生成一个代表这个类的 java.lang.Class 对象,作为方法区这个类的各种数据访问入口

由于虚拟机规范对这3点要求并不具体,所以实际的实现是非常灵活的,关于第1点,获取类的二进制字节流(class字节码)就有很多途径:

  • 从ZIP包获取,这是JAR、EAR、WAR等格式的基础

  • 从网络中获取,典型的应用是 Applet

  • 运行时计算生成,这种场景使用最多的是动态代理技术,在 java.lang.reflect.Proxy 类中,就是用了 ProxyGenerator.generateProxyClass 来为特定接口生成形式为 *$Proxy 的代理类的二进制字节流

  • 由其它文件生成,典型应用是JSP,即由JSP文件生成对应的Class类

  • 从数据库中获取等等

其中的“运行时计算生成 - ProxyGenerator.generateProxyClass”不就在这里吗~~

public static byte[] generateProxyClass(final String var0, Class<?>[] var1, int var2) {
ProxyGenerator var3 = new ProxyGenerator(var0, var1, var2);
final byte[] var4 = var3.generateClassFile();
if (saveGeneratedFiles) {
AccessController.doPrivileged(new PrivilegedAction<Void>() {
public Void run() {
try {
int var1 = var0.lastIndexOf(46);
Path var2;
if (var1 > 0) {
Path var3 = Paths.get(var0.substring(0, var1).replace('.', File.separatorChar));
Files.createDirectories(var3);
var2 = var3.resolve(var0.substring(var1 + 1, var0.length()) + '.class');
} else {
var2 = Paths.get(var0 + '.class');
}

Files.write(var2, var4, new OpenOption[0]);
return null;
} catch (IOException var4x) {
throw new InternalError('I/O exception saving generated file: ' + var4x);
}
}
});
}

return var4;
}

如果继续深入研究,下面的这些方法,基本都是一些IO文件写入的操作,就不粘贴源码了。

sun.misc.ProxyGenerator#generateClassFile
sun.misc.ProxyGenerator#addProxyMethod
......

至此,基于JDK的动态代理的分析就到此为止,其本质上是基于反射机制,在运行时创建代理类。所以下一步的研究对象,理所当然的就是Java的反射机制了。

Java反射

【一】简介

  • 允许运行时的Java程序获取自身信息,同时操作类或对象的内部属性,最通俗易懂的解释,就是让你根据一个String来得到你要的实体对象

  • 功能:

    1. 运行时判断对象所属类

    2. 运行时构造类的对象

    3. 运行时判断类的属性和方法

    4. 运行时调用任意一个类的方法

下面是相关的四个类,包含了我们定义一个类所能用到的所有属性和方法。

  • Class

  • Field

  • Method

  • Constructor

【二】常用方法

  1. 获取Class对象

    • Class类中的静态方法forName

    • 直接获取某一个对象的 class

    • 调用某个对象的 getClass() 方法

  2. 反射获取类的属性

    • getFields():Field[] - 只能获取public的字段,包括父类的

    • **getDeclaredFields():Field[] **- 只能获取自己声明的各种字段,包括public,protected,private

  3. 获取方法:

    • getMethod(String,Class<?>...):Method - 根据方法名和参数获取方法

    • getDeclaredMethods():Method[]

  4. invoke():

    调用包装在当前Method对象中的方法

    Method和NativeMethodAccessorImpl类中的invoke()方法:

    public Object invoke(Object obj, Object... args)
    throws IllegalAccessException, IllegalArgumentException,
    InvocationTargetException
    {
    if (!override) {
    if (!Reflection.quickCheckMemberAccess(clazz, modifiers)) {
    Class<?> caller = Reflection.getCallerClass();
    checkAccess(caller, clazz, obj, modifiers);
    }
    }
    MethodAccessor ma = methodAccessor;
    if (ma == null) {
    ma = acquireMethodAccessor();
    }
    return ma.invoke(obj, args);
    }
    public Object invoke(Object var1, Object[] var2) throws IllegalArgumentException, InvocationTargetException {
    if (++this.numInvocations > ReflectionFactory.inflationThreshold() && !ReflectUtil.isVMAnonymousClass(this.method.getDeclaringClass())) {
    MethodAccessorImpl var3 = (MethodAccessorImpl)(new MethodAccessorGenerator()).generateMethod(this.method.getDeclaringClass(), this.method.getName(), this.method.getParameterTypes(), this.method.getReturnType(), this.method.getExceptionTypes(), this.method.getModifiers());
    this.parent.setDelegate(var3);
    }
    return invoke0(this.method, var1, var2);
    }

在上述方法中,有两点是比较重要的:

  1. 根据Class对象获取Method方法时,参数:方法名和参数的CLass类型

  2. 调用method.invoke(obj, args)

【三】原理探究

如果想要理解反射,就首先要对Java类加载的方式有了解。

我们知道,我们写的Java文件会被编译成.class文件(字节码文件),才能在JVM中执行,是通过类加载器进行加载,

java.lang.ClassLoader#loadClass(java.lang.String, boolean)是加载一个类的核心方法,

protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
synchronized (getClassLoadingLock(name)) {
// First, check if the class has already been loaded
Class<?> c = findLoadedClass(name);
if (c == null) {
long t0 = System.nanoTime();
try {
if (parent != null) {
c = parent.loadClass(name, false);
} else {
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
// ClassNotFoundException thrown if class not found
// from the non-null parent class loader
}

if (c == null) {
// If still not found, then invoke findClass in order
// to find the class.
long t1 = System.nanoTime();
c = findClass(name);

// this is the defining class loader; record the stats
sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
sun.misc.PerfCounter.getFindClasses().increment();
}
}
if (resolve) {
resolveClass(c);
}
return c;
}
}

主要逻辑分为3步

  1. 检查是否已加载,如果已加载则直接返回

  2. 遵循双亲委派模式,先调用父类加载器加载器

  3. 如果父类加载器加载失败,调用findClass加载

当我们打开findClass方法时,会不会有些诧异,直接抛了个异常,这是因为ClassLoader是一个抽象类,是无法通过new创建一个对象的,也就是说,你无法通过ClassLoader类直接加载.class文件,只能写一个子类继承ClassLoader,然后覆盖findClass方法,定义自己的加载逻辑。

  protected Class<?> findClass(String name) throws ClassNotFoundException {
throw new ClassNotFoundException(name);
}

在自定义加载逻辑的时候,通常的做法是,读取一个字节数组byte[] b,然后调用ClassLoader的defineClass()方法,于是返回了一个Class对象

private native Class<?> defineClass0(String name, byte[] b, int off, int len,
ProtectionDomain pd);

private native Class<?> defineClass1(String name, byte[] b, int off, int len,
ProtectionDomain pd, String source);

private native Class<?> defineClass2(String name, java.nio.ByteBuffer b,
int off, int len, ProtectionDomain pd,
String source);

Class类的内部类 ReflectionData,里面的内容与xxx.class文件中的内容映射,作用就是缓存从JVM中读取类的如下属性数据。同时由于一个类中包含的内容信息量过大,所以被拆成了四个类,也就是上面提到的- Class、Field、Method、Constructor

 private static class ReflectionData<T> {
volatile Field[] declaredFields;
volatile Field[] publicFields;
volatile Method[] declaredMethods;
volatile Method[] publicMethods;
volatile Constructor<T>[] declaredConstructors;
volatile Constructor<T>[] publicConstructors;
// Intermediate results for getFields and getMethods
volatile Field[] declaredPublicFields;
volatile Method[] declaredPublicMethods;
volatile Class<?>[] interfaces;

// Value of classRedefinedCount when we created this ReflectionData instance
final int redefinedCount;

ReflectionData(int redefinedCount) {
this.redefinedCount = redefinedCount;
}
}

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多