Java高阶私房菜:探索反射机制应用及高级场景

        “反射”作为Java特性之一,为我们程序在运行时动态地获取类的信息、调用对象的方法和操作对象的属性提供了途径。并且通过使用反射,我们可以在编译期间未知具体类型的情况下,对类进行操作。接下来我们将系统重新回顾一下"反射",结合新的应用场景,温故而知新,希望能给予新的启发。

什么是反射技术

        Java的反射(reflection)机制是指在程序的运行状态中,可以做以下操作:1)构造任意一个类的对象;2)了解任意一个对象所属的类;3)了解任意一个类的成员变量和方法。调用任意一个对象的属性和方法,这种动态获取程序信息以及动态调用对象的功能称为Java语言的反射机制。
说白了就是得到编译以后的class文件对象,提供了一个Class类型(编译后的class类对象)。

HelloWorld.java -> javac -> HelloWorld.class
Class clz = HelloWorld.class

可操作的反射主体

        1)类字节码 Class (本身也是一个类,是Java反射的源头)
        2)构造器 Constructor
        3)成员变量 Field
        4)方法 Method

一般反射要点

        1)类名.class
        2)对象.getClass() 
        3)Class.forName("类的全名")
        4)使用类的加载器ClassLoader

public class User {private String name;private  int age;public User() {}public User(String name, int age){this.name=name;this.age=age;}}
public static void main(String[] args) throws ClassNotFoundException {//通过类名获取Class<User> userClass1 = User.class;//通过对象获取User user = new User();Class<? extends User> userClass2 = user.getClass();//通过全限定名Class<?> userClass3 = Class.forName("priv.muller.reflect.User");//通过类加载器ClassLoader classLoader = Test.class.getClassLoader();Class<?> userClass4 = classLoader.loadClass("priv.muller.reflect.User");System.out.println(userClass1);System.out.println(userClass2);System.out.println(userClass3);System.out.println(userClass4);}

类方法Api

方法名称作用
getName( )获取 包名+类名
getSimpleName( )获取类名
getDeclaredConstructor(Class<?>... parameterTypes)根据参数,获取构造器,修饰符不影响
getDeclaredConstructors()获取全部声明的构造器,返回数组,修饰符不影响

        //获取声明的构造器,根据参数类型匹配Class<User> userClass = User.class;Constructor<User> declaredConstructor = userClass.getDeclaredConstructor(String.class, int.class);System.out.println(declaredConstructor.getName()+","+declaredConstructor.getParameterCount());//获取全部声明的构造器,返回数组,修饰符不影响Constructor<?>[] declaredConstructors = userClass.getDeclaredConstructors();for (Constructor<?> declaredConstructor1 : declaredConstructors) {System.out.println(declaredConstructor1.getName()+","+declaredConstructor1.getParameterCount());}

主要两类反射API

        1)get+要获取的东西,例如:获取属性为getField()、获取方法为getMethod(),只能获取公有的东西(public范围)。需注意的是getMethod可以获取到本类及其父类的方法;
        2)get+Declared+要获取的东西,例如:获取属性为getDeclaredField()、获取方法为geDeclaredtMethod(),可以获取全部的东西(包括private)。需要注意的是getDeclaredMethod只能获取到本类的方法;

对象创建

        反射创建对象有多种方式,常用步骤如下:1)根据全类名获取对应的`Class`对象;2)调用指定参数结构的构造器,生成`Constructor`的实例;3)通过`Constructor`的实例创建对应类的对象,并初始化类属性;

        class.getDeclaredConstructor( ).newInstance( )

方法名说明
T newInstance( )根据类的空参的构造器创建对象,类必须提供空参的构造器和public权限
T newInstance(Object...initargs)根据指定的构造方法创建对象

 代码案例

    public static void test2() throws Exception {//获取类对象Class<User> clazz = User.class;//获取public空构造函数,并创建对象User user = clazz.getDeclaredConstructor().newInstance();user.setAge(11);user.setName("张三");System.out.println(user);}public static void test3() throws Exception {//获取类对象Class<User> clazz = User.class;//获取指定构造器Constructor constructor = clazz.getConstructor(int.class,String.class);//创建对象User user = (User) constructor.newInstance(21,"李四");System.out.println(user);}

 方法和属性

       1)通过class获取方法

方法说明
getMethods()获取当前运行类和 父类中声明的方法,需要是public访问权限的方法
getDeclaredMethods()获取当前运行时类中声明的全部方法,不包含父类中声明的方法

       2)方法method的方法

getReturnType()获取全部的返回值
getParameterTypes()获取全部的参数
getModifiers()获取修饰符
getExceptionTypes()获取异常信息

代码案例


public static void test6() throws Exception {//获取类对象Class<User> clazz = User.class;//获取当前运行类和 父类中声明的方法,需要是public访问权限的方法Method[] methods = clazz.getMethods();for(Method method:methods){System.out.println("修饰符="+method.getModifiers()+",返回值="+method.getReturnType().getName()+",整体="+method);}System.out.println("——————————————————");Method[] declaredMethods = clazz.getDeclaredMethods();for(Method method:declaredMethods){System.out.println("修饰符="+method.getModifiers()+",返回值="+method.getReturnType().getName()+",整体="+method);}
}

通过class对象获取属性 

方法名说明
getFields( )获取当前运行类和 父类中声明的属性,需要是public访问权限的属性
getDeclaredFields( )获取当前运行时类中声明的全部属性,不包含父类中声明的属性

 属性Field的方法

方法说明
getModifiers()整数形式返回此Field的修饰符,整数对应在 java.lang.reflect.Modifier里面
getType()返回 Field的属性类型
getName()返回 Field的名称

 代码案例

public class Base {public String job;public String getJob() {return job;}public void setJob(String job) {this.job = job;}
}public static void test5() throws Exception {//获取类对象Class<User> clazz = User.class;//都是private,则获取不了; 属性改为public才行Field[] fields = clazz.getFields();for(Field field:fields){System.out.println("属性名="+field.getName()+",属性类型="+field.getType().getName()+",属性修饰符="+field.getModifiers());}System.out.println("——————————————————");// 获取当前运行时类中声明的全部属性,不包含父类中声明的属性Field[] declaredFields = clazz.getDeclaredFields();for(Field field:declaredFields){System.out.println("属性名="+field.getName()+",属性类型="+field.getType().getName()+",属性修饰符="+field.getModifiers());}}//输出结果
属性名=job,属性类型=java.lang.String,属性修饰符=1
——————————————————
属性名=age,属性类型=int,属性修饰符=2
属性名=name,属性类型=java.lang.String,属性修饰符=2

强制属性访问

        反射操作时如果构造器、方法、属性 没权限时,可以通过 setAccessible(true) 。修改访问权限,Method和Field、Constructor对象都有setAccessible()方法。

        1)构造器

 public static void test4() throws Exception {//获取类对象Class<User> clazz = User.class;//获取public空构造函数,并创建对象(把构造函数private私有化)Constructor<User> declaredConstructor = clazz.getDeclaredConstructor();//修改访问权限,true表示暴力反射,攻破权限declaredConstructor.setAccessible(true);User user = declaredConstructor.newInstance();user.setAge(11);user.setName("张三");System.out.println(user);}

        2)属性

方法说明
get(Object obj)获取取指定对象obj上此Field的属性内容
set(Object obj,Object value)设置指定对象obj上此Field的属性内容

 代码案例

public class Product {private int price;private String title;private Product(){}@Overridepublic String toString() {return "Product{" +"price=" + price +", title='" + title + '\'' +'}';}}

 public static void main(String[] args) throws Exception {Class clazz = Product.class;//创建运行时类的对象//获取类对象//获取public空构造函数,并创建对象Constructor<Product> declaredConstructor = clazz.getDeclaredConstructor();//修改访问权限,true表示保留反射declaredConstructor.setAccessible(true);//创建对象Product product = declaredConstructor.newInstance();System.out.println(product.toString());//获取运行时类中指定变量名的属性Field title = clazz.getDeclaredField("title");//保证当前属性是可访问的title.setAccessible(true);//设置指定对象的的属性值title.set(product,"商品1");//打印对象的title属性值System.out.println(title.get(product));System.out.println(product.toString());}//结果
Product{price=0, title='null'}
商品1
Product{price=0, title='商品1'}

 运行类方法

        运行类的指定方法步骤:1)获取class对象,创建对象;2)获取方法,invoke调用;

        Object invoke(Object obj, Object... args)

        invoke用来调用某个类中的方法的,但是它不是通过当前类直接去调用而是通过反射的机制去调用。

        参数说明:obj是调用类的实例对象, args:调用方的方法参数,是可变长度的
    1)Object 对应原方法的返回值,若原方法无返回值,此时返回null;
    2)如果原方法为静态方法,此时形参 obj可为null;
    3)如果原方法形参列表为空,则args为null;
    4)如果原方法声明为private,则需要在调用此invoke()方法前,调用对象的setAccessible(true)方法;

        具体代码

public class User extends Base {private int age;private String name;public User() {}public User(int age, String name) {this.age = age;this.name = name;}private String say(String name){System.out.println("我是"+name+",这个bug不是我写的");return name;}private int say(int age){System.out.println("我今年"+age+"岁,准备找老婆了");return age;}private static void sleep(String name){System.out.println("我是"+name+",这个是静态方法,在睡觉");}//set get方法省略
}public static void test7() throws Exception {//获取类对象Class<User> clazz = User.class;//获取public空构造函数,并创建对象Constructor<User> declaredConstructor = clazz.getDeclaredConstructor();//确保有访问权限,true表示暴力反射declaredConstructor.setAccessible(true);User user = declaredConstructor.newInstance();user.setName("张三");//获取指定的某个方法, 参数1,指明获取的方法的名称  参数2,指明获取的方法的形参列表Method say = clazz.getDeclaredMethod("say", String.class);//保证当前方法是可访问的say.setAccessible(true);//invoke调用,参数1 方法的调用者  参数2 给方法形参赋值的实参,// 返回值 是 对应类中调用的方法的返回值。Object returnValue = say.invoke(user, "李四");System.out.println(returnValue);System.out.println("—————————调用静态方法———————————");//获取指定的某个方法, 参数1,指明获取的方法的名称  参数2,指明获取的方法的形参列表Method sleepMethod = clazz.getDeclaredMethod("sleep",String.class);//保证可以访问sleepMethod.setAccessible(true);//调用静态方法,不需要获取类对象。Object returnValue2 = sleepMethod.invoke(null,"王五");//如果调用的运行时类中的方法没有返回值,则此invoke()返回nullSystem.out.println(returnValue2);}

反射应用案例

单元测试方法运行

        实现一个类似junit单元测试的注解,可批量运行某个类的全部加了注解的方法,同时支持配置自定义志兴优先级以及启用开关。

        方法:定义注解属性优先级和启用开关,作用范围为Method,测试类通过反射获取方法的注解,根据方法注解的有无和启用开关将其放入执行队列中。执行队列根据优先级排序后,再通过invoke执行。

方法说明
getAnnotations( )获取类、方法等上的所有注解内容
getAnnotation(XXX.class)获取到注解的内容
isAnnotationPresent(XXX.class)判断某个注解是否存在

        具体代码

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Test {/*** 测试的优先级,数字越大优先级越高* @return*/int priority() default 0;/*** 是否要启用* @return*/boolean disabled() default false;
}public class TestCase {@Test(priority = 1)public void testCase1() {System.out.println("TestCase1");}@Test(priority = 2)public void testCase2() {System.out.println("TestCase2");}@Test(priority = 0)public void testCase3() {System.out.println("TestCase3");}@Test(priority = 3,disabled = true)public void testCase4() {System.out.println("TestCase4");}// 执行测试用例public static void main(String[] args) throws InvocationTargetException {TestCase testCase = new TestCase();// 获取TestCase类中所有方法Method[] methods = TestCase.class.getDeclaredMethods();// 定义一个List用于存放有Test注解的方法List<Method> methodList = new ArrayList<>();// 遍历所有方法for (Method method : methods) {// 判断方法中是否有Test注解if (method.isAnnotationPresent(Test.class)) {//判断是否不执行Test annotation = method.getAnnotation(Test.class);if(!annotation.disabled()){// 将有Test注解的方法添加到List中methodList.add(method);}}}// 对List中的方法按照优先级进行排序,数字越小越先执行methodList.sort(Comparator.comparingInt(a -> a.getAnnotation(Test.class).priority()));// 遍历List中的方法,执行有Test注解的方法for (Method method : methodList) {try {method.invoke(testCase);} catch (IllegalAccessException | InvocationTargetException e) {e.printStackTrace();}}}
}

JDK动态代理

代理设计模式

        为其他对象提供一种代理以控制对这个对象的访问,属于结构型模式。 客户端并不直接调用实际的对象,而是通过调用代理,来间接的调用实际的对象。其有点在于可以在访问一个类时做一些控制,或增加功能,操作代理类无须修改原本的源代码,符合开闭原则,系统具有较好的灵活性和可扩展性。但其会增加系统复杂性和调用链路。

什么是静态代理

        由程序创建或特定工具自动生成源代码,在程序运行前,代理类的.class文件就已经存在。通过将目标类与代理类实现同一个接口,让代理类持有真实类对象,然后在代理类方法中调用真实类方法,在调用真实类方法的前后添加我们所需要的功能扩展代码来达到增强的目的。其缺点在于代理类中出现大量冗余的代码,非常不利于扩展和维护。如果接口增加一个方法,除了所有实现类需要实现这个方法外,所有代理类也需要实现此方法。增加了代码维护的复杂度。

传送门:设计模式之结构型模式-CSDN博客

public class StaticProxyPayServiceImpl implements PayService {private PayService payService;public  StaticProxyPayServiceImpl(PayService payService){this.payService = payService;}public String callback(String outTradeNo) {System.out.println("StaticProxyPayServiceImpl callback begin");String result = payService.callback(outTradeNo);System.out.println("StaticProxyPayServiceImpl callback end");return result;}public int save(int userId, int productId) {System.out.println("StaticProxyPayServiceImpl save begin");int id = payService.save(userId, productId);System.out.println("StaticProxyPayServiceImpl save end");return id;}
}public class ProxyTest {public static void main(String[] args) {PayService payService = new PayServiceImpl();payService.callback("5555");//- 代理类中出现大量冗余的代码,非常不利于扩展和维护//- 如果接口增加一个方法,除了所有实现类需要实现这个方法外,所有代理类也需要实现此方法。增加了代码维护的复杂度PayService staticPayService = new StaticProxyPayServiceImpl(payService);staticPayService.callback("66666");}
}

什么是动态代理

        在程序运行时,运用反射机制动态创建而成,无需手动编写代码。JDK动态代理与静态代理一样,目标类需要实现一个代理接口,再通过代理对象调用目标方法。常见实现方式:1)JDK动态代理:实现了被代理对象的接口,  要求目标对象实现一个接口;2)CGLIB动态代理。

        其核心方法在于定义一个java.lang.reflect.InvocationHandler接口的实现类,然后重写invoke方法。

//Object proxy:被代理的对象  
//Method method:要调用的方法  
//Object[] args:方法调用时所需要参数  
public interface InvocationHandler {  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable;  
}  

 具体代码

public class JdkProxy implements InvocationHandler {//目标类private Object targetObject;//获取代理对象public Object getProxyInstance(Object targetObject){this. targetObject = targetObject;//绑定关系,也就是和具体的哪个实现类关联return  Proxy.newProxyInstance(targetObject.getClass().getClassLoader(),// 目标类的类加载targetObject.getClass().getInterfaces(), // 需要代理的接口,可指定多个this);}public Object invoke(Object proxy, Method method, Object[] args) {Object result = null;try{System.out.println("通过JDK动态代理调用 "+method.getName() +", 打印日志 begin");result = method.invoke(targetObject,args);System.out.println("通过JDK动态代理调用 "+method.getName() +", 打印日志 end");}catch (Exception e){e.printStackTrace();}return result;}
}public class ProxyTest public static void main(String[] args) {PayService payService = new PayServiceImpl();//JDK动态代理JdkProxy jdkProxy = new JdkProxy();//获取代理类对象PayService proxyInstance = (PayService)jdkProxy.getProxyInstance(payService);//目标类新增方法,代理类不需要实现此方法,更好的维护proxyInstance.callback("proxy00000");proxyInstance.save(12,333);}
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/812968.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

自定义注解(一)——统一请求拦截

文章目录 一、为什么会用到自定义注解二、关键参数说明三、应用场景示例&#xff1a;统一token认证1. 背景2. 自定义Token注解3. AOP上定义切面方法4. 方法上应用5. 总结 一、为什么会用到自定义注解 自定义注解可以帮助我们更好地组织和管理代码&#xff0c;提高代码的可读性和…

IIS服务器更换即将过期的SSL证书

公司IIS服务器证书快要过期&#xff0c;替换证书的步骤&#xff1a; Winr输入mstsc命令&#xff0c;显示远程登录&#xff1b;输入服务器IP以及密码&#xff0c;进行远程登陆登陆IIS服务器&#xff0c;winr输入inetmgr命令显示IIS操控器&#xff1b;选择服务器证书--点击服务器…

Springboot引入swagger

讲在前面&#xff1a;在spring引入swagger时&#xff0c;由于使用的JDK、Spring、swagger 的版本不匹配&#xff0c;导致启动报错&#xff0c;一直存在版本依赖问题。所以在此声明清楚使用版本。JDK 1.8、Spring boot 2.6.13、 Swagger 2.9.2。 引入maven依赖 <dependency&…

神经射频脉冲术,破解疼痛之锁

一位十余年糖尿病病史的患者&#xff0c;右足开始出现疼痛和麻木的症状三个多月&#xff0c;给他的生活带来了极大的困扰。他曾在多家医院就诊&#xff0c;但治疗效果并不理想。直到他走进了北京精诚博爱医院&#xff0c;这里为他带来了希望和转机。 经过详细的检查&#xff0c…

数据比对步骤

##开发工作&#xff1a; 1&#xff0c;翻译代码写完&#xff08;中台生成dws结果&#xff09; 2&#xff0c;oracle结果导入CDH数据中台 3&#xff0c;编写比对SQL脚本 ##比对工作 1&#xff0c;寻找差异字段的原因&#xff1f;&#xff08;修正自己逻辑&#xff0c;修正宽表逻…

自己搭建的gitleb怎么更新购买的ssl证书

更新自己搭建的 GitLab 服务器的 SSL 证书通常涉及几个步骤&#xff0c;包括获取新的证书文件、替换旧证书文件&#xff0c;以及重新配置和重启 GitLab 服务。下面是一个详细的步骤指南&#xff1a; 步骤 1: 获取新的 SSL 证书和密钥文件 你需要有一个新的 SSL 证书&#xff…

开发环境解决跨域问题

跨域 为什么? 浏览器的同源策略(协议&#xff0c;域名&#xff0c;端口必须相等) http://localhost:9528/ &#xff08;前端页面&#xff09; ---------------------》后端接口&#xff08;https://heimahr.itheima.net/api&#xff09; 后端没有开启CORS 在后端没有开启CO…

VR紧急情况模拟|V R体验中心加盟|元宇宙文旅

通过VR技术实现紧急情况模拟&#xff0c;提升安全应急能力&#xff01; 简介&#xff1a;面对突发紧急情况&#xff0c;如火灾、地震、交通事故等&#xff0c;正确的反应和应对能够有效减少伤害和损失。为了提高人们在紧急情况下的应急能力&#xff0c;我们借助先进的虚拟现实…

Unittest单元测试框架之unittest_执行用例的详细信息

unittest_执行用例的详细信息 用unittest.main()执行测试集 这里的verbosity是一个选项,表示测试结果的信息复杂度&#xff0c;有三个值&#xff1a; 0 (静默模式): 你只能获得总的测试用例数和总的结果 比如 总共100个 失败20 成功801 (默认模式): 非常类似静默模式 只是在…

蓝桥杯 每天2题 day6

碎碎念&#xff1a;哇咔咔 要不是中间缺勤一天就圆满day7了&#xff01;最后一晚上&#xff01;写题复习哇咔咔 唉&#xff0c;睡了一觉就看不下去了&#xff0c;&#xff0c;&#xff0c;看看之前的笔记洗洗睡觉&#xff0c;&#xff0c;&#xff0c; 记得打印准考证带好东西…

华为改进点

华为公司可以在员工福利方面做出改进&#xff0c;提高员工的工作满意度和忠诚度。例如&#xff0c;可以增加员工福利&#xff0c;如提供更多灵活的工作时间、提供更好的培训和发展机会、加大健康保障和福利待遇等。 此外&#xff0c;华为公司也可以加强与客户的沟通与合作&…

快速掌握数据层内置持久化方案-jdbcTemplateSpringBoot内置数据库

环境准备 导入jdbc的起步依赖&#xff0c;mybatis的依赖中就存在jdbc的起步依赖 <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-jdbc</artifactId></dependency>在配置文件中配置datasourse的…

使用腾讯云服务器如何搭建网站?新手建站教程

使用腾讯云服务器搭建网站全流程&#xff0c;包括轻量应用服务器和云服务器CVM建站教程&#xff0c;轻量可以使用应用镜像一键建站&#xff0c;云服务器CVM可以通过安装宝塔面板的方式来搭建网站&#xff0c;腾讯云服务器网txyfwq.com整理使用腾讯云服务器建站教程&#xff0c;…

RPA实战演练UiBot6.0新食堂一楼问卷星(类似于之前的网页表单提交)

要使用RPA&#xff08;Robotic Process Automation&#xff0c;机器人流程自动化&#xff09;帮助新食堂进行调查问卷&#xff0c;我们可以结合UiBot 6.0来实施具体的计划。以下是一个大致的实战演练计划&#xff1a; 一、目标与需求分析 明确调查目标&#xff1a;了解新食堂…

ThignsBoard通过服务端订阅共享属性

MQTT基础 客户端 MQTT连接 通过服务端订阅属性 案例 1、首先需要创建整个设备的信息&#xff0c;并复制访问令牌 ​​2、通过工具MQTTX连接上对应的Topic 3、测试链接是否成功 4、在MQTT上订阅对应的Topic 5、在客户端添加共享属性信息 6、查看整个设备的遥测数据 M…

SpringBoot的启动原理

运行Main方法&#xff1a; 应用程序启动始于Main方法的执行。在Main方法中&#xff0c;创建了一个SpringApplication实例&#xff0c;用于引导应用程序的启动。同时&#xff0c;SpringApplication会根据spring.factories文件加载并注册监听器、ApplicationContextInitializer等…

flinksql

Flink SQL 是 Apache Flink 项目中的一个重要组成部分,它允许开发者使用标准的 SQL 语言来处理流数据和批处理数据。Flink SQL 提供了一种声明式的编程范式,使得用户能够以一种简洁、高效且易于理解的方式来表达复杂的数据处理逻辑。 ### 背景 Flink SQL 的设计初衷是为了简…

Flutter Getx介绍

GetX 是 Flutter 上的一个轻量且强大的解决方案&#xff0c; Getx为我们提供了高性能的状态管理、智能的依赖注入和便捷的路由管理。以下视频对Flutter Getx做了全面的介绍。 51 【Getx 试听】Flutter Getx 状态管理介绍、Getx介绍 、Getx Dialog 主题管理 GetX 有3个基本原则…

AD7982BRMZRL7 二进制 500kSPS 模数转换芯片 ADI

AD7982BRMZRL7是一款由Analog Devices&#xff08;亚德诺&#xff09;公司生产的18位逐次逼近型模数转换器&#xff08;ADC&#xff09;。它主要用于将模拟信号转换为数字信号&#xff0c;适用于数据采集系统、嵌入式系统、工业控制和医疗设备等领域。 AD7982BRMZRL7的主要功能…

redis-缓存穿透与雪崩

一&#xff0c;缓存穿透&#xff08;查不到&#xff09; 在默认情况下&#xff0c;用户请求数据时&#xff0c;会先在缓存(Redis)中查找&#xff0c;若没找到即缓存未命中&#xff0c;再在数据库中进行查找&#xff0c;数量少可能问题不大&#xff0c;可是一旦大量的请求数据&a…