参考:廖雪峰——Java 反射
反射,reflection,旨在在程序运行期间拿到对应对象的所有信息,包括 class 类信息,filed 字段,method 方法,generic 泛型,annotation 注解, package 包、parameter 参数等。
简而言之,通过 Class 来获取 class 信息的方法就叫做反射 reflection。
1 Class 类
在Java 中,除了 primitive 原始 int 等基本类外,其它的类型都是 class (包括 interface)。
1.1 获取到 Class
方式一:直接通过一个 class 的静态变量 class 获取:
Class clazz = String.class;
方式二:通过实例变量(对象),通过该实例的 getClass 方法获取
String str = "xiaohui";Class clazz = str.getClass();
方式三:通过class 的全类名,调用 Class.forName() 方法获取
Class clazz = Class.forName("java.lang.Integer");
1.2 Class 与 instanceof
```java Integer i = new Integer(12);
boolean b1 = i instanceof Integer; // true, i 就是 Integer 类型 boolean b2 = i instanceof Number; // true, i Integer 是 Number 类型的子类
boolean b3 = i.getClass() == Integer.class; // true
// 下面编译报错,Error:(155, 35) java: 不可比较的类型: java.lang.Class
小结:<br />var instanceof ClassName 不仅匹配指定的类型 ClassName, 还匹配指定类型 ClassName 的子类。== 是精确匹配数据类型,不能进行子类比较(与子类比较,不能获取到 true)。<a name="HAkGm"></a>## 1.3 获取 class 信息小例`Class`类提供了以下几个方法来获取字段:- Field getField(name):根据字段名获取某个public的field(包括父类)(只能获取 pulic 修辞的)- Field getDeclaredField(name):根据字段名获取当前类的某个field(不包括父类)(包括当前类中所有的声明filed 包括 final,static,static final,无论修辞符是 private,protected,default,public,通通都能拿取)- Field[] getFields():获取所有public的field(包括父类)- Field[] getDeclaredFields():获取当前类的所有field(不包括父类)```javapublic static void main(String[] args) {System.err.println("String[] args && args.getClass() >>>>>>>>>>>>>>>>>>");Class<? extends String[]> aClass = args.getClass();System.out.println("getClass:" + aClass.getClass());System.out.println("getClasses:" + aClass.getClasses());System.out.println("isAnnotation:" + aClass.isAnnotation());System.out.println("isPrimitive:" + aClass.isPrimitive());System.out.println("isInterface:" + aClass.isInterface());System.out.println("isArray:" + aClass.isArray());System.out.println("isEnum:" + aClass.isEnum());System.out.println("isAnonymousClass:" + aClass.isAnonymousClass());System.out.println("isAssignableFrom(Object.class):" + aClass.isAssignableFrom(Object.class));System.err.println("String[].class >>>>>>>>>>>>>>>>>>");Class<String[]> strArr = String[].class;System.out.println("getClass:" + strArr.getClass());System.out.println("getClasses:" + strArr.getClasses());System.out.println("isAnnotation:" + strArr.isAnnotation());System.out.println("isPrimitive:" + strArr.isPrimitive());System.out.println("isInterface:" + strArr.isInterface());System.out.println("isArray:" + strArr.isArray());System.out.println("isEnum:" + strArr.isEnum());System.out.println("isAnonymousClass:" + strArr.isAnonymousClass());System.out.println("isAssignableFrom(Object.class):" + strArr.isAssignableFrom(Object.class));System.err.println("String.class >>>>>>>>>>>>>>>>>>");Class<String> str = String.class;System.out.println("getClass:" + str.getClass());System.out.println("getClasses:" + str.getClasses());System.out.println("isAnnotation:" + str.isAnnotation());System.out.println("isPrimitive:" + str.isPrimitive());System.out.println("isInterface:" + str.isInterface());System.out.println("isArray:" + str.isArray());System.out.println("isEnum:" + str.isEnum());System.out.println("isAnonymousClass:" + str.isAnonymousClass());System.out.println("isAssignableFrom(Object.class):" + str.isAssignableFrom(Object.class));System.err.println("int.class >>>>>>>>>>>>>>>>>>");Class<Integer> i = int.class;System.out.println("getClass:" + i.getClass());System.out.println("getClasses:" + i.getClasses());System.out.println("isAnnotation:" + i.isAnnotation());System.out.println("isPrimitive:" + i.isPrimitive());System.out.println("isInterface:" + i.isInterface());System.out.println("isArray:" + i.isArray());System.out.println("isEnum:" + i.isEnum());System.out.println("isAnonymousClass:" + i.isAnonymousClass());System.out.println("isAssignableFrom(Object.class):" + i.isAssignableFrom(Object.class));System.err.println("Integer.class >>>>>>>>>>>>>>>>>>");Class<Integer> integerClass = Integer.class;System.out.println("getClass:" + integerClass.getClass());System.out.println("getClasses:" + integerClass.getClasses());System.out.println("isAnnotation:" + integerClass.isAnnotation());System.out.println("isPrimitive:" + integerClass.isPrimitive());System.out.println("isInterface:" + integerClass.isInterface());System.out.println("isArray:" + integerClass.isArray());System.out.println("isEnum:" + integerClass.isEnum());System.out.println("isAnonymousClass:" + integerClass.isAnonymousClass());System.out.println("isAssignableFrom(Object.class):" + integerClass.isAssignableFrom(Object.class));}
运行结果:
Connected to the target VM, address: '127.0.0.1:52610', transport: 'socket'String[] args && args.getClass() >>>>>>>>>>>>>>>>>>getClass:class java.lang.ClassgetClasses:[Ljava.lang.Class;@42d3bd8bisAnnotation:falseisPrimitive:falseisInterface:falseisArray:trueisEnum:falseisAnonymousClass:falseisAssignableFrom(Object.class):falseString[].class >>>>>>>>>>>>>>>>>>getClass:class java.lang.ClassgetClasses:[Ljava.lang.Class;@26ba2a48isAnnotation:falseisPrimitive:falseisInterface:falseisArray:trueisEnum:falseisAnonymousClass:falseisAssignableFrom(Object.class):falseString.class >>>>>>>>>>>>>>>>>>getClass:class java.lang.ClassgetClasses:[Ljava.lang.Class;@5f2050f6isAnnotation:falseisPrimitive:falseisInterface:falseisArray:falseisEnum:falseisAnonymousClass:falseisAssignableFrom(Object.class):falseint.class >>>>>>>>>>>>>>>>>>getClass:class java.lang.ClassgetClasses:[Ljava.lang.Class;@3b81a1bcisAnnotation:falseisPrimitive:trueisInterface:falseisArray:falseisEnum:falseisAnonymousClass:falseisAssignableFrom(Object.class):falseInteger.class >>>>>>>>>>>>>>>>>>getClass:class java.lang.ClassgetClasses:[Ljava.lang.Class;@64616ca2isAnnotation:falseisPrimitive:falseisInterface:falseisArray:falseisEnum:falseisAnonymousClass:falseisAssignableFrom(Object.class):falseDisconnected from the target VM, address: '127.0.0.1:52610', transport: 'socket'Process finished with exit code 0
基本的测试类:
class SuperClass {private String superName;private String superGender;public String publicName;private String privateName;protected String protectedName;String defaultName;public String getSuperName() {return superName;}public void setSuperName(String superName) {this.superName = superName;}public String getSuperGender() {return superGender;}public void setSuperGender(String superGender) {this.superGender = superGender;}}class SubClassA extends SuperClass {private final String finalStr = "11";private static final String finalStaticStr = "22";private static String staticStr = "333";private String subName;private String subGender;public String subPublicName;private String subPrivateName;protected String subProtectedName;String subDefaultName;public String getSubName() {return subName;}public void setSubName(String subName) {this.subName = subName;}public String getSubGender() {return subGender;}public void setSubGender(String subGender) {this.subGender = subGender;}}
测试:
public static void testFiled(Class clazz) {System.out.println("分割线: 》》》》》》》》》》》》》》》 start");System.out.println("clazz.getSimpleName():" + clazz.getSimpleName());System.out.println("getFields:" + Arrays.asList(clazz.getFields()).stream().map(x -> x.getName()).collect(Collectors.joining(",")));System.out.println("getDeclaredFields:" + Arrays.asList(clazz.getDeclaredFields()).stream().map(x -> x.getName()).collect(Collectors.joining(",")));}public static void main(String[] args) {SubClassA subClass = new SubClassA();// SuperClass superClass = new SuperClass();testFiled(subClass.getClass());// testFiled(superClass.getClass());}
运行结果:
分割线: 》》》》》》》》》》》》》》》 startclazz.getSimpleName():SubClassAgetFields:subPublicName,publicNamegetDeclaredFields:finalStr,finalStaticStr,staticStr,subName,subGender,subPublicName,subPrivateName,subProtectedName,subDefaultName
测试final/static/fianl static 字段的访问、赋值:
@Testpublic void testFieldAccess() {try {SubClassA a = new SubClassA();Field f = null;// 访问 final、static、final static 修饰的 field,其中 final static 修饰的 field 不可再次set。// static set 后,会影响到其它值。f = SubClassA.class.getDeclaredField("finalStaticStr");f.setAccessible(true);System.out.println(f.get(a));// 当 field 为静态变量,field.set(obj,value) 与 field.set(null, value),即field 为静态变量时,set 方法的第一个参数 obj 被忽略。f.set(a, "333");System.out.println(f.get(a));} catch (NoSuchFieldException e) {e.printStackTrace();} catch (IllegalAccessException e) {e.printStackTrace();}}
final static 字段结果:
java.lang.IllegalAccessException: Can not set static final java.lang.String field com.demo.activiti7.inherit.SubClassA.finalStaticStr to java.lang.Stringat sun.reflect.UnsafeFieldAccessorImpl.throwFinalFieldIllegalAccessException(UnsafeFieldAccessorImpl.java:76)at sun.reflect.UnsafeFieldAccessorImpl.throwFinalFieldIllegalAccessException(UnsafeFieldAccessorImpl.java:80)at sun.reflect.UnsafeQualifiedStaticObjectFieldAccessorImpl.set(UnsafeQualifiedStaticObjectFieldAccessorImpl.java:77)at java.lang.reflect.Field.set(Field.java:764)at com.demo.activiti7.inherit.DemoTest.testFieldAccess(DemoTest.java:170)at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)at java.lang.reflect.Method.invoke(Method.java:498)at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:78)at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:57)at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)at org.junit.runners.ParentRunner.run(ParentRunner.java:363)at org.junit.runner.JUnitCore.run(JUnitCore.java:137)at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:68)at com.intellij.rt.execution.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:47)at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:242)at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:70)22
分析:
// getDeclaredField 方法拿取当前类的字段,无论字段的修饰符是什么,都可以拿取。// Field 的 set 方法,在可访问的情况下,final static 是不可以重新赋值的// 源码/*** Sets the field represented by this {@code Field} object on the* specified object argument to the specified new value. The new* value is automatically unwrapped if the underlying field has a* primitive type.** <p>The operation proceeds as follows:** <p>If the underlying field is static, the {@code obj} argument is* ignored; it may be null.** <p>Otherwise the underlying field is an instance field. If the* specified object argument is null, the method throws a* {@code NullPointerException}. If the specified object argument is not* an instance of the class or interface declaring the underlying* field, the method throws an {@code IllegalArgumentException}.** <p>If this {@code Field} object is enforcing Java language access control, and* the underlying field is inaccessible, the method throws an* {@code IllegalAccessException}.** <p>If the underlying field is final, the method throws an* {@code IllegalAccessException} unless {@code setAccessible(true)}* has succeeded for this {@code Field} object* and the field is non-static. Setting a final field in this way* is meaningful only during deserialization or reconstruction of* instances of classes with blank final fields, before they are* made available for access by other parts of a program. Use in* any other context may have unpredictable effects, including cases* in which other parts of a program continue to use the original* value of this field.** <p>If the underlying field is of a primitive type, an unwrapping* conversion is attempted to convert the new value to a value of* a primitive type. If this attempt fails, the method throws an* {@code IllegalArgumentException}.** <p>If, after possible unwrapping, the new value cannot be* converted to the type of the underlying field by an identity or* widening conversion, the method throws an* {@code IllegalArgumentException}.** <p>If the underlying field is static, the class that declared the* field is initialized if it has not already been initialized.** <p>The field is set to the possibly unwrapped and widened new value.** <p>If the field is hidden in the type of {@code obj},* the field's value is set according to the preceding rules.** @param obj the object whose field should be modified* @param value the new value for the field of {@code obj}* being modified** @exception IllegalAccessException if this {@code Field} object* is enforcing Java language access control and the underlying* field is either inaccessible or final.* @exception IllegalArgumentException if the specified object is not an* instance of the class or interface declaring the underlying* field (or a subclass or implementor thereof),* or if an unwrapping conversion fails.* @exception NullPointerException if the specified object is null* and the field is an instance field.* @exception ExceptionInInitializerError if the initialization provoked* by this method fails.*//**将此Field对象在指定对象参数上表示的字段设置为指定的新值。如果基础字段具有原始类型,则新值将自动解包。操作如下:如果基础字段是静态的,则obj参数将被忽略;它可以为空。否则,基础字段是实例字段。如果指定的对象参数为null,则该方法将引发NullPointerException。如果指定的对象参数不是声明基础字段的类或接口的实例,则该方法将引发IllegalArgumentException。如果此Field对象正在实施Java语言访问控制,并且基础字段不可访问,则该方法将引发IllegalAccessException。如果基础字段是最终字段,则该方法将抛出IllegalAccessException,除非已成功为此字段对象使用setAccessible(true)并且该字段是非静态的。以这种方式设置最终字段仅在反序列化或重建具有空白最终字段的类的实例时才有意义,然后才可以将它们用于程序的其他部分。在任何其他上下文中使用它可能会产生不可预测的影响,包括程序其他部分继续使用该字段的原始值的情况。如果基础字段是原始类型,则尝试展开转换以将新值转换为原始类型的值。如果此尝试失败,则该方法将引发IllegalArgumentException。如果在可能的展开之后,新值不能通过标识或扩展转换转换为基础字段的类型,则该方法将引发IllegalArgumentException。如果基础字段是静态的,则声明该字段的类将被初始化(如果尚未初始化)。该字段设置为可能已展开和扩展的新值。如果该字段以obj类型隐藏,则该字段的值将根据前面的规则进行设置。*/@CallerSensitivepublic void set(Object obj, Object value)
1.4 获取方法
过Class实例获取所有Method信息。Class类提供了以下几个方法来获取Method:
Method getMethod(name, Class...):获取某个public的Method(包括父类)Method getDeclaredMethod(name, Class...):获取当前类的某个Method(不包括父类)Method[] getMethods():获取所有public的Method(包括父类)Method[] getDeclaredMethods():获取当前类的所有Method(不包括父类)
调用静态方法
- 如果获取到的Method表示一个静态方法,调用静态方法时,由于无需指定实例对象,所以invoke方法传入的第一个参数永远为null。
调用非public方法
- 和Field类似,对于非public方法,我们虽然可以通过Class.getDeclaredMethod()获取该方法实例,但直接对其调用将得到一个IllegalAccessException。为了调用非public方法,我们通过Method.setAccessible(true)允许其调用。
- setAccessible(true)可能会失败。如果JVM运行期存在SecurityManager,那么它会根据规则进行检查,有可能阻止setAccessible(true)。例如,某个SecurityManager可能不允许对java和javax开头的package的类调用setAccessible(true),这样可以保证JVM核心库的安全。
1.5 构造方法
调用Class.newInstance()的局限是,它只能调用该类的public无参数构造方法。如果构造方法带有参数,或者不是public,就无法直接通过Class.newInstance()来调用。 为了调用任意的构造方法,Java的反射API提供了Constructor对象,它包含一个构造方法的所有信息,可以创建一个实例。Constructor对象和Method非常类似,不同之处仅在于它是一个构造方法,并且,调用结果总是返回实例;
通过Class实例获取Constructor的方法如下:
getConstructor(Class...):获取某个public的Constructor;getDeclaredConstructor(Class...):获取某个Constructor;getConstructors():获取所有public的Constructor;getDeclaredConstructors():获取所有Constructor。注意
Constructor总是当前类定义的构造方法,和父类无关,因此不存在多态的问题。调用非
public的Constructor时,必须首先通过setAccessible(true)设置允许访问。setAccessible(true)可能会失败。
小结
Constructor对象封装了构造方法的所有信息;- 通过
Class实例的方法可以获取Constructor实例:getConstructor(),getConstructors(),getDeclaredConstructor(),getDeclaredConstructors(); - 通过
Constructor实例可以创建一个实例对象:newInstance(Object... parameters); 通过设置setAccessible(true)来访问非public构造方法。
1.6 获取继承关系(包括 extends 与 implements)
获取父类的 class
- 使用 getSuperClass
获取接口:
- 使用 getInterfaces 方法
注意:
getInterfaces()只返回当前类直接实现的接口类型,并不包括其父类实现的接口类型。
1.7 判断是否是继承关系
instanceof 方式
obj instanceof Object
两个Class实例,要判断一个向上转型是否成立,可以调用isAssignableFrom()
System.out.println(Number.class.isAssignableFrom(Integer.class)); // true
小结
通过Class对象可以获取继承关系:
Class getSuperclass():获取父类类型;Class[] getInterfaces():获取当前类实现的所有接口。
通过Class对象的isAssignableFrom()方法可以判断一个向上转型是否可以实现。
System.out.println(Number.class.isAssignableFrom(Integer.class)); // true Integer 可以向上转型为Number
1.8 动态代理
Java标准库提供了一种动态代理(Dynamic Proxy)的机制:可以在运行期动态创建某个interface的实例。所谓的动态代理,是与静态代理相对的。
静态代理,就是日常的不同面向接口编写的代码,利用接口 interface inst 变量去接收一个接口的实现类impl,通过 inst 来调用相关的方法(通过接口调用)。
譬如:
// 定义接口public interface Demo {void hello(String name);}// 接口实现public class DemoImpl implement Demo {@Overridepublic void hello(String name) {System.out.println("hello " + name);}}///////////////////////////////// 创建实例,转用接口调用Demo demo = new DemoImpl();demo.hello("Bruce");
在运行期动态创建一个interface实例的方法如下:
- 定义一个
InvocationHandler实例,它负责实现接口的方法调用; - 通过
Proxy.newProxyInstance()创建interface实例,它需要3个参数:- 使用的
ClassLoader,通常就是接口类的ClassLoader; - 需要实现的接口数组,至少需要传入一个接口进去;
- 用来处理接口方法调用的
InvocationHandler实例。
- 使用的
- 将返回的
Object强制转型为接口。
完整代码
package com.demo.activiti7.inherit;import java.lang.reflect.InvocationHandler;import java.lang.reflect.Method;import java.lang.reflect.Proxy;import java.util.Objects;import org.apache.commons.lang3.StringUtils;import org.junit.Test;public class TestDynamicProxy {// 静态代理@Testpublic void testStaticProxy() {Demo demo = new DemoImpl();demo.hello("Bruce");}// 动态代理@Testpublic void testDynamic() {InvocationHandler invocationHandler = new InvocationHandler() {@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {System.out.println("proxy class:" + proxy.getClass());String name = method.getName();System.out.println("method:" + name);System.out.println("args:" + (Objects.isNull(args) ? 0 : args.length));if ("hello".equals(name)) {System.out.println("proxy invoke hello: hello " + args[0]);}return null;}};Demo demo = (Demo) Proxy.newProxyInstance(Demo.class.getClassLoader(),new Class[]{Demo.class},invocationHandler);demo.hello("Bruce Bruces");try {invocationHandler.invoke(new DemoImpl(),DemoImpl.class.getDeclaredMethod("hello", String.class),new Object[] {"Bruce Bruce"});} catch (Throwable throwable) {throwable.printStackTrace();}}}interface Demo {void hello(String name);}class DemoImpl implements Demo {@Overridepublic void hello(String name) {System.out.println("hello " + name);}}
动态代理的实质感觉就是一种组合与委托吧。JVM 帮我们生成相应的代码,通过反射调用相关代码。
动态代理实际上是JVM在运行期动态创建class字节码并加载的过程,它并没有什么黑魔法,把上面的动态代理改写为静态实现类大概长这样:
public class HelloDynamicProxy implements Hello {InvocationHandler handler;public HelloDynamicProxy(InvocationHandler handler) {this.handler = handler;}public void morning(String name) {handler.invoke(this,Hello.class.getMethod("morning", String.class),new Object[] { name });}}
其实就是JVM帮我们自动编写了一个上述类(不需要源码,可以直接生成字节码),并不存在可以直接实例化接口的黑魔法。
待(学习、确认项)
enum 枚举的类型
@Testpublic void testEnum() {System.out.println(DemoEnum.class);System.out.println(DemoEnum.class.getSimpleName());System.out.println(DemoEnum.class.getConstructors());System.out.println(DemoEnum.class.getDeclaredConstructors());System.out.println(DemoEnum.class.isEnum());}
clazz.newInstance() 的局限:只能调用无参的构造器方法,并且构造器是 public ???是的,IllegalAccessException
java.lang.IllegalAccessException: Class com.demo.activiti7.inherit.DemoTest can not access a member of class com.demo.activiti7.inherit.SubClassB with modifiers "private"at sun.reflect.Reflection.ensureMemberAccess(Reflection.java:102)at java.lang.Class.newInstance(Class.java:436)at com.demo.activiti7.inherit.DemoTest.testConstructor(DemoTest.java:163)at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)at java.lang.reflect.Method.invoke(Method.java:498)at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:78)at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:57)at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)at org.junit.runners.ParentRunner.run(ParentRunner.java:363)at org.junit.runner.JUnitCore.run(JUnitCore.java:137)at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:68)at com.intellij.rt.execution.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:47)at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:242)at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:70)
