管理中心

Java反射学习和反射的应用场景干货都在这里

写在前面:Java反射是我们做项目中必备的技能,本篇文章将重新学习反射的基本用法、反射的应用场景等。

一、Java反射定义

JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法;这种动态获取的以及动态调用对象的方法的功能称为Java的反射机制


二、Java反射机制实现

1、Class对象获取

在一个JVM中,一种类,只会有一个类对象存在。


public class Person {

    private String name;

    private int age;

    private String address;

}    

// 省略get、set方法

private void setCardId(String id) {

        System.out.println(id);

    }

获取类对象有3种方式


Class.forName()

Person.class

new Person().getClass()

//第一种方式 通过对象getClass方法

Person person = new Person();

Class<?> class1 = person.getClass();

//第二种方式 通过类的class属性

class1 = Person.class;

try {

    //第三种方式 通过Class类的静态方法——forName()来实现

    class1 = Class.forName("com.sl.Person");

} catch (ClassNotFoundException e) {

    e.printStackTrace();

}

1

2

3

4

5

6

7

8

9

10

11

第三种方式这里也通过捕获异常,因为我们传的这个字符串可能不合法,字符串合法命名是类的命名空间和类的名称组成。


2、获取class对象的摘要信息

以下例子将判断Class对象是否是基础类型、是否是集合类、是否是注解类、是否是接口类、是否是枚举类、是否是匿名内部类、是否被某个注解类修饰、获取class的包信息、获取class类名、获取class访问权限、内部类、外部类。


获取Person的Class对象


Class class1 = Person.class;

1

判断是否是基础类型


 boolean isPrimitive = class1.isPrimitive();

1

判断是否是集合类


  boolean isArray = class1.isArray();

1

判断是否是注解类


boolean isAnnotation = class1.isAnnotation();

1

判断是否是接口类


boolean isInterface = class1.isInterface();

1

判断是否是枚举类


boolean isEnum = class1.isEnum();

1

判断是否是匿名内部类


boolean isAnonymousClass = class1.isAnonymousClass();

1

判断是否被某个注解类修饰


   boolean isAnnotationPresent = class1.isAnnotationPresent(Deprecated.class);

1

获取class名字 包含包名路径


 String className = class1.getName();

1

获取class的包信息


  Package aPackage = class1.getPackage();

获取class类名


String simpleName = class1.getSimpleName();

1

获取class访问权限


 int modifiers = class1.getModifiers();

1

内部类


 Class<?>[] declaredClasses = class1.getDeclaredClasses();

1

外部类


   Class<?> declaringClass = class1.getDeclaringClass();

1

3、获取class对象的属性、方法、构造函数等

获取class对象的属性

Field[] fields = class1.getFields();

Field[] declaredFields = class1.getDeclaredFields();

1

2

getFields和getDeclaredFields的区别:

getFields 只能获取public的,包括从父类继承来的字段。

getDeclaredField 可以获取本类所有的字段,包括private的,但是不能获取继承来的字段。 (注: 这里只能获取到private的字段,但并不能访问该private字段的值,除非加上setAccessible(true))。


Field常用方法field.set()举例:

field.set()方法获取属性并修改。


 Person person = new Person();

 person.setName("张三");

 Field field = person.getClass().getDeclaredField("name");

 field.setAccessible(true);

 field.set(person,"李四");

 System.out.println(person.getName());

1

2

3

4

5

6

获取class对象的方法

获取class对象的所有声明方法


Method[] methods = class1.getDeclaredMethods();

1

获取class对象的所有方法 包括父类的方法


Method[] allMethods = class1.getMethods();

1

如何调用类的私有方法


先在测试类中编写一个测试的私有方法


private void setCardId(String id) {

        System.out.println(id);

    }

1

2

3

正常的调用类的方法都是通过类.方法调用,所以我们调用私有方法也需要得到类的实例。

首先通过 getDeclaredMethod方法获取到这个私有方法,第一个参数是方法名,第二个参数是参数类型。

然后通过invoke方法执行,invoke需要两个参数一个是类的实例,一个是方法参数。


 Class[] str = {String.class};

 Method method= class1.getDeclaredMethod("setCardId", str);

 method.setAccessible(true);

 setName.invoke(person,"123333333");

1

2

3

4

获取class对象的父类

获取class对象的父类


Class parentClass = class1.getSuperclass();

1

获取class对象的所有接口


Class<?>[] interfaceClasses = class1.getInterfaces();

1

获取class对象构造函数

获取class对象的所有声明构造函数,但是不能获取父类的构造函数


Constructor<?>[] allConstructors = class1.getDeclaredConstructors();

1

获取class对象public构造函数,但是不能获取私有的构造函数


Constructor[] constructors = class1.getConstructors();

1

获取类中特定的构造方法并创建对象


step1:可以通过getDeclaredConstructor()方法传参获取特定参数类型的构造方法,这里要进行异常捕获,因为可能不存在对应的构造方法。


Class[] clz = {String.class,int.class};

Constructor declaredConstructor = class1.getDeclaredConstructor(clz);

1

2

step2:调用newInstance()方法创建对象


 Person person1 = (Person) declaredConstructor.newInstance("赵云", 25);

1

获取类的泛型类型

在平常写代码时会遇到这样的写法 parameterizedType.getActualTypeArguments()[0],这样写是什么意思呢。

getClass().getGenericSuperclass()返回表示此 Class 所表示的实体(类、接口、基本类型或 void)的直接超类的 Type,然后将其转ParameterizedType。

getActualTypeArguments()返回表示此类型实际类型参数的 Type 对象的数组。

[0]就是这个数组中第一个了,简而言之就是获得超类的泛型参数的实际类型。


Type type = class1.getGenericSuperclass();

        Type[] genericInterfaces = class1.getGenericInterfaces();

        Class<?> componentType = null;

        String typeName = type.getTypeName();

        if (type instanceof ParameterizedType) {

            Type[] actualTypeArguments = ((ParameterizedType) type).getActualTypeArguments();

            if (actualTypeArguments != null && actualTypeArguments.length > 0) {

                componentType = (Class<?>) actualTypeArguments[0];

            }

        }  else if (type instanceof GenericArrayType) {

            // 表示一种元素类型是参数化类型或者类型变量的数组类型

            componentType = (Class<?>) ((GenericArrayType) type).getGenericComponentType();

        }else {

            componentType = (Class<?>) type;

        }

这篇文章讲的很详细:Java反射获取实际泛型类型参数


三、反射的应用场景

1、动态代理

利用反射机制在运行时创建代理类(method.invoke)。

接口、被代理类不变,我们构建一个handler类来实现InvocationHandler接口。


下面我们看具体事例。创建接口、实现类、代理类

//接口

public interface Anmail {


    void name();

}

//实现类

public class Cat implements Anmail {

    @Override

    public void name() {

        System.out.println("小猫咪");

    }

}


//代理类

public class ProxyHandler implements InvocationHandler {


    private Object target;


    public ProxyHandler(Object target) {

        this.target = target;

    }

    @Override

    public Object invoke(Object object, Method method, Object[] args) throws Throwable {

        System.out.println("Before invoke "  + method.getName());

        Object invoke = method.invoke(target, args);

        System.out.println("After invoke " + method.getName());

        return invoke;

    }


    public static void main(String[] args) {

        Cat cat = new Cat();

        ProxyHandler proxyHandler = new ProxyHandler(cat);

        Anmail proxy = (Anmail)(Proxy.newProxyInstance(cat.getClass().getClassLoader(), cat.getClass().getInterfaces(), proxyHandler));

        proxy.name();

    }

}


方法newProxyInstance就会动态产生代理类,并且返回给我们一个实例,实现了Anmail 接口。这个方法需要三个参数,第一个ClassLoader并不重要;第二个是接口列表,即这个代理类需要实现那些接口,因为JDK的Proxy是完全基于接口的,它封装的是接口的方法而不是实体类;第三个参数就是InvocationHandler的实例,它会被放置在最终的代理类中,作为方法拦截和代理的桥梁。注意到这里的handler包含了一个Person实例。


总结一下JDK Proxy的原理,首先它是完全面向接口的,其实这才是符合代理模式的标准定义的。我们有两个类,被代理类Person和需要动态生成的代理类ProxyClass,都实现了接口Anmail。类ProxyClass需要拦截接口Anmail上所有方法的调用,并且最终转发到实体类Person上,这两者之间的桥梁就是方法拦截器InvocatioHandler的invoke方法。


2、自定义注解实现日志管理

AOP概念在这里不在介绍详情请看这两篇文章:aop概念和7个专业术语

AOP基本概念和使用


切入点语法有两种:


@Pointcut(“包名…”)

//任何类的任何返回值的任何方法

execution(public * *(..))   


//任何类的set开头的方法

execution(* set*(..))       


//任何返回值的规定类里面的方法

execution(* com.example.demo.AccountService.*(..))


//任何返回值的,规定包或者规定包子包的任何类任何方法

execution(* com.example.demo.service..*.*(..)) 

1

2

3

4

5

6

7

8

9

10

11

@Pointcut("@annotation(自定义注解)")

下面将以自定义注解举例说明


有两个类需要重点关注


Joinpoint

 Signature getSignature() /*获取连接点的方法签名对象*/

1

ProceedingJoinPoint

ProceedingJoinPoint继承JoinPoint子接口,它新增了两个用于执行连接点方法的方法

//通过反射执行目标对象的连接点处的方法

- java.lang.Object proceed() throws Throwable

//通过反射执行目标对象连接点处的方法,不过使用新的入参替换原来的入参。

- java.lang.Object proceed(java.lang.Object[] args) throws Throwable

1

2

3

4

自定义注解

@Target({ElementType.METHOD})

@Retention(RetentionPolicy.RUNTIME)

@Documented

public @interface Log {

    String value() default ""; //用于描述方法作用


    /**

     * 是否忽略返回值,仅方法上有效

     * @return

     */

    boolean ignoreReturn() default false;


}


@Aspect

@Component

public class LogAspect {

    private static final Logger log = LoggerFactory.getLogger(LogAspect.class);

    private static final String dateFormat = "yyyy-MM-dd HH:mm:ss";

    private static final String STRING_START = "\n--------------------------->\n";

    private static final String STRING_END = "\n<----------------------------\n";


    @Pointcut("@annotation(Log)")

    public void logPointCut() {

    }

    

    public Object controllerAround(ProceedingJoinPoint joinPoint) {

        try {

            return printLog(joinPoint);

        } catch (Throwable throwable) {

            log.error(throwable.getMessage(), throwable);

            return true;

        }

    }

    //通知:拦截到连接点之后要执行的代码

    @Around("logPointCut()")

    private Object printLog(ProceedingJoinPoint joinPoint) throws Throwable {

        //获取连接点的方法签名

        MethodSignature signature = (MethodSignature) joinPoint.getSignature();

        //获取方法签名里的方法:方法签名里有两个方法:getReturnType   getMethod

        Method method = signature.getMethod();

        //获取类

        Class<?> targetClass = method.getDeclaringClass();

        StringBuffer classAndMethod = new StringBuffer();


        // 获取目标方法上的Log注解

        Log methodAnnotation = method.getAnnotation(Log.class);


        // 判断是否有LOG注解以及是否带有ignore参数

        if (methodAnnotation != null) {

            classAndMethod.append(methodAnnotation.value());

        }

        //拼接目标切入的类名称和方法名称

        String target = targetClass.getName() + "#" + method.getName();

        // 请求参数转JSON,对日期进行格式转换并打印出所有为null的参数

        String params = JSONObject.toJSONStringWithDateFormat(joinPoint.getArgs(), dateFormat, SerializerFeature.WriteMapNullValue);

        //日志打印拼接的调用信息

        log.info(STRING_START + "{} 开始调用--> {} 参数:{}", classAndMethod.toString(), target, params);


        long start = System.currentTimeMillis();

        //proceed()通过反射执行目标对象的连接点处的方法;

        Object result = joinPoint.proceed();

        long timeConsuming = System.currentTimeMillis() - start;

        if (methodAnnotation != null && methodAnnotation.ignoreReturn()) {

            log.info("\n{} 调用结束<-- {} 耗时:{}ms" + STRING_END, classAndMethod.toString(), target, timeConsuming);

            return result;

        }

        // 响应参数转JSON,对日期进行格式转换并打印出所有为null的参数

        log.info("\n{} 调用结束<-- {} 返回值:{} 耗时:{}ms" + STRING_END, classAndMethod.toString(), target, JSONObject.toJSONStringWithDateFormat(result, dateFormat, SerializerFeature.WriteMapNullValue), timeConsuming);


        return result;

    }


}

运行结果:


--------------------------->

 开始调用--> com.sl.dao.UserDao#save 参数:[{"age":"20","id":null,"name":"张三"}]

2020-04-06 14:45:05.695  INFO 16864 --- [nio-8080-exec-4] com.sl.aspect.LogAspect                  : 

 调用结束<-- com.sl.dao.UserDao#save 返回值:null 耗时:8ms

<----------------------------

上面代码所有源码地址为:反射基础部分和自定义AOP源码

点赞
收藏
回复
分享
举报
浏览290 发布于2020-04-12 07:17未知归属地
全部评论
最多点赞
最新发布
最早发布
写回答
新增插入模板功能
一键使用模板,快速填写内容,轻松发帖~
知道了
  • 为了保障您的信息安全,请勿上传您的敏感个人信息(如您的密码等信息)和您的敏感资产信息(如关键源代码、签名私钥、调试安装包、业务日志等信息),且您需自行承担由此产生的信息泄露等安全风险。
  • 如您发布的内容为转载内容,请注明内容来源。

我要发帖子

了解社区公约,与您携手共创和谐专业的开发者社区。