Java的Class类,注解与反射
-
Class对象:
- 我们每创建一个类,经过build都会生成对应的.class文件
- 该类无法只能由虚拟机创建对象,其构造函数为private
- 当我们创建某个类的对象,ClassLoader(一个类)就会装载对应.class文件到虚拟机(仅一次)
- 该Class对象存有函数对应的Constructors(搭配反射使用),Fields(访问私有变量,getFields()只能访问公有,getDeclaredFields()可以访问所有属性),Methods(可以用于动态代理)(以指针的形式)等信息,通常用于反射
- 可以通过
ClassName.class
,className.getClass()
,Class.forName("limitName")
获取
-
为了保存class中的字段,构造器和方法,Java有定义了三种不同的类Constructor,Field,Method,同时在类对应的class文件中包含了它们的数组引用
-
classLoader
-
作用:加载.Class对象
-
分类
- BootstrapClassLoader:核心库加载,如
%JRE_HOME%\lib
- ExtClassLoader:扩展库加载,如
%JRE_HOME%\lib\ext
- AppClassLoader:程序库加载,如当前应用的classpath下的类
- CustomClassLoader:自定义类加载器
注:对于开发人员编写的类加载器来说,其父类加载器是加载此类加载器 Java 类的类加载器(正是这一条规定导致了JavaSPI机制需要破坏双亲委派机制)
- BootstrapClassLoader:核心库加载,如
-
双亲委派机制:上面四个加载器构成上下级关系,双亲委派机制优先询问上级是否加载过相应的类。如果父类加载器无法完成加载,子类才去加载
/** * Loads the class with the specified <a href="#name">binary name</a>. The * default implementation of this method searches for classes in the * following order: * * <ol> * * <li><p> Invoke {@link #findLoadedClass(String)} to check if the class * has already been loaded. </p></li> * * <li><p> Invoke the {@link #loadClass(String) <tt>loadClass</tt>} method * on the parent class loader. If the parent is <tt>null</tt> the class * loader built-in to the virtual machine is used, instead. </p></li> * * <li><p> Invoke the {@link #findClass(String)} method to find the * class. </p></li> * * </ol> * * <p> If the class was found using the above steps, and the * <tt>resolve</tt> flag is true, this method will then invoke the {@link * #resolveClass(Class)} method on the resulting <tt>Class</tt> object. * * <p> Subclasses of <tt>ClassLoader</tt> are encouraged to override {@link * #findClass(String)}, rather than this method. </p> * * <p> Unless overridden, this method synchronizes on the result of * {@link #getClassLoadingLock <tt>getClassLoadingLock</tt>} method * during the entire class loading process. * * @param name * The <a href="#name">binary name</a> of the class * * @param resolve * If <tt>true</tt> then resolve the class * * @return The resulting <tt>Class</tt> object * * @throws ClassNotFoundException * If the class could not be found */ 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; } }
具体流程:
-
通过
findLoadedClass
查找是否该类是否已经被加载过(其底层是native的) -
如果没加载过,有父类则先尝试调用父类进行加载
-
如果没加载过,没有父类则直接尝试用BootstrapClassLoader加载
/** * Returns a class loaded by the bootstrap class loader; * or return null if not found. */ private Class<?> findBootstrapClassOrNull(String name) { if (!checkName(name)) return null; return findBootstrapClass(name); }
-
-
好处:
- 避免重复加载
- 安全
-
-
为什么要使用反射:因为在代码运行过程中,我们可能需要知道运行的类的相关信息并对其进行操作
以动态反射为例,假如我们要给某个类添加日志操作,显然我们可以直接在对应的类中添加相关的打印日志信息的操作,显然这个方法不仅繁琐而且不符合设计模式对于解耦的追求。通过反射,即可将日志动作与运行的类分离。
package org.example; import org.omg.CORBA.portable.InvokeHandler; import redis.clients.jedis.Jedis; import java.lang.reflect.*; /** * Hello world! * */ interface DemoInf{ public void doWork(); } class Demo implements DemoInf{ public void doWork(){ System.out.println("Do something~"); } } public class App { public static void main( String[] args ) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException { Demo demo = new Demo(); //这里是唯一创建的实例,而不是接口 Class<?> proxyClass = Proxy.getProxyClass(DemoInf.class.getClassLoader(), DemoInf.class); Constructor<?> constructor = proxyClass.getConstructor(InvocationHandler.class); DemoInf DemoInf = (DemoInf)constructor.newInstance(new InvocationHandler() { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("before methods"); method.invoke(demo, args); //通过Method类的invoke方法调用,实际的Demo对象 System.out.println("after methods"); return null; } }); DemoInf.doWork(); } }
-
四个元注解
@Target
:指定注解的使用范围,相应的参数可以通过ElementType配置@Retention
:指定注解的保留策略,源码,.Class文件中或是运行时,通过RetentionPolicy类配置@Documented
:是否将注解包含在javadoc中@Inherited
:可以被子类继承@Repeatable
:注解是否可以重复使用,比如某个类上重复添加某个注解
-
为什么要使用注解
注解可以看做是对类或者他的方法和字段的额外信息,通过注解我们可以在不改变原代码和逻辑的情况下在源代码中嵌入补充信息。如,通过@Test注解实现对测试类的运行(类似Junit)
-
定义
package com.example.readfile.annotation; import java.lang.annotation.*; @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface Test { String value(); }
-
使用
package com.example.readfile.service.impl; import com.example.readfile.annotation.Test; import com.example.readfile.service.List; public class UnorderList implements List { @Test("addContent") @Override public String addContent(String content) { System.out.println("have a test:"+content); return null; } }
-
读取
@Test void annotationTest() throws InvocationTargetException, IllegalAccessException, InstantiationException { Method[] methods = UnorderList.class.getMethods(); for (Method method : methods) { com.example.readfile.annotation.Test annotation = method.getAnnotation(com.example.readfile.annotation.Test.class); if (annotation!=null){ System.out.println(annotation.value()); method.invoke(UnorderList.class.newInstance(),"annotationTest"); //Java中执行方法,隐式地规定了需要指明是哪个对象执行方法 } } }
注:函数上面那个@Test是模拟的Junit中的@Test
-