2024年11月

  • -t
    (或
    --tag
    )参数:用于给构建的镜像指定标签(tag)。标签的格式通常是
    [仓库名/][用户名/]镜像名:版本号
  • -f
    (或
    --file
    )参数: 指定构建镜像所使用的
    Dockerfile
    的路径。默认情况下,
    docker build
    会在当前目录下查找名为
    Dockerfile
    的文件,但通过这个参数可以指定其他位置的
    Dockerfile
  • --build - arg
    参数: 在构建过程中传递参数给
    Dockerfile
    。在
    Dockerfile
    中可以使用
    ARG
    指令来接收这些参数,这样可以使镜像构建更加灵活,例如可以根据不同的参数构建具有不同配置的镜像。例如
    Dockerfile
    如下:
       ARGAPP_ENVRUN if [ "$APP_ENV" = "production"]; then \
echo
"这是生产环境配置"; \
elif [
"$APP_ENV" = "development"]; then \
echo
"这是开发环境配置"; \
else \
echo
"未定义环境"; \
fi

Avalonia跨平台上位机控件开发之水泵

随着国产化的推进,越来越多的开发者选择使用跨平台的框架来创建上位机应用,而Avalonia正是一个优秀的选择。本文将探讨如何利用Avalonia框架进行水泵控件的开发,并重点记录在开发的过程中所碰到的一些问题。

控件的构成

水泵控件主要在控件的内部需要创建一个旋转的动画来表示水泵当前的运行状态,在没有工作时为静止状态没有任何动画,当水泵启动时会对中间的部分进行连续的旋转。运行效果如下:

动画的创建

在控件中为了方便控制旋转动画的编写,我这边使用了一个Canvas将需要旋转的部分包裹在其中,这样在制作动画的时候直接对这个Canvas进行旋转就可以了,否则要旋转部分的Path的坐标数据为相对于最外层Canvas的坐标进行绘制,此时要找到旋转中心是一个很困难的事情。同时为这个Canvas指定了一个名称,方便后续在制作动画时的Selector处理。

<Canvas
        Canvas.Left="19.5"
        Canvas.Top="39.5"
        Height="34.5"
        Name="BumpCircle"
        UseLayoutRounding="False"
        Width="34.2">
        <Path
                Fill="#ffffffff"
                Stroke="#ff6c6c6c"
                StrokeJoin="Miter"
                StrokeThickness="2.05166">
                      <Path.Data>
                      		<PathGeometry Figures="M 11.3573 15.4557 C 6.78011 13.3704 4.04416 9.6096 3.14943 4.1733 M 11.3573 19.7314 C 6.82241 21.3797 2.37649 20.6383 -1.98048 17.5071 M 15.4612 22.6354 C 13.3753 27.2112 9.61334 29.9464 4.17541 30.8408 M 19.7381 22.6354 c 1.64889 4.53351 0.907239 8.97809 -2.22494 13.3338 m 5.12991 -17.4365 c 4.57717 2.08531 7.31313 5.84611 8.20786 11.2824 M 22.6431 14.2571 c 4.53487 -1.64839 8.98079 -0.906966 13.3378 2.22428 M 18.5392 11.353 c 2.08594 -4.5758 5.84787 -7.31093 11.2858 -8.20539 m -15.5627 8.20539 c -1.64889 -4.53351 -0.907239 -8.97809 2.22494 -13.3338 m 4.50314 14.9864 c 2.20369 2.20302 2.20369 5.77484 0 7.97786 c -2.20369 2.20302 -5.77658 2.20302 -7.98027 0 c -2.20369 -2.20302 -2.20369 -5.77484 0 -7.97786 c 2.20369 -2.20302 5.77658 -2.20302 7.98027 0 m 9.43124 -9.4284 c 7.41241 7.41018 7.41241 19.4245 0 26.8347 c -7.41241 7.41019 -19.4304 7.41019 -26.8428 0 c -7.41241 -7.41018 -7.41241 -19.4245 0 -26.8347 c 7.41241 -7.41019 19.4304 -7.41019 26.8428 0" />
                          </Path.Data>
            </Path>
</Canvas>

动画的部分定义了一个伪类running,用来表示当前水泵已经处在了运行状态。因此在进行动画编写的时候先使用一下Selector选择伪类为running

<Style Selector="controls|Pump.running">

然后需要选择到我们需要需要进行动画的部分,使用如下的Selector进行选择

<Style Selector="^ /template/ Canvas#BumpCircle">

^ : 继承上一层Selector
/template/ :进入到控件内部的Template
Canvas#BumpCircle:选择名字为BumpCircle的Canvas

使用Selector选择到需要动画的控件部分后就可以进行动画的编写,动画的完整代码如下:

<Style Selector="controls|Pump.running">
  <Style Selector="^ /template/ Canvas#BumpCircle">
    <Style.Animations>
      <Animation Duration="0:0:5" IterationCount="INFINITE">
        <KeyFrame Cue="0%">
          <Setter Property="RotateTransform.Angle" Value="0" />
        </KeyFrame>
        <KeyFrame Cue="100%">
          <Setter Property="RotateTransform.Angle" Value="360" />
        </KeyFrame>
      </Animation>
    </Style.Animations>
  </Style>
</Style>

以上代码创建了一个0到360度的旋转动画。

踩到的坑

创建伪类时的一个小知识点

当在avalonia中使用伪类的时候经常会碰到:pointerover等内置的一些伪类,而要使用Selector来选择这些伪类的时候通常我们会使用

<Style Selector="Button:pointerover"/>

但是当我们定义了一个自定义的伪类时,比如上面控件用到的running,此时我们在使用Selector的时候需要使用如下格式

<Style Selector="Pump.running"/>

在平时的开发中要注意以上的一些不同

在属性改变时为控件添加伪类

为了表示控件的运行状态,我在控件的C#代码中添加了一个名为Running的StyledProperty,用这个属性来区分水泵的运行状态,因此我需要在Running属性进行修改的时候为控件添加或移除running伪类。而为了方便测试我将这个Running绑定至了Checkbox的IsChecked属性,代码如下:

<CheckBox
          Canvas.Left="600"
          Canvas.Top="250"
          Name="CheckBox">
  运行状态
</CheckBox>

<controls:Pump
               Canvas.Left="600"
               Canvas.Top="100"
               Height="100"
               Running="{Binding #CheckBox.IsChecked}"
               Width="100" />

开始时我将伪类的创建与移除代码编写在Runing属性的set中进行处理,代码如下:

    public bool? Running
    {
        get => GetValue(RunningProperty);
        set
        {
            SetValue(RunningProperty, value);
            if (value == true)
            {
                PseudoClasses.Add("running");
            }
            else
            {
                PseudoClasses.Remove("running");
            }
        }
    }

但是以上的代码在CheckBox的IsChecked属性变更时并没有办法触发到set方法,导致动画无法被触发。

经过一段时间的摸索正确的做法为在OnPropertyChanged方法中进行相关的处理,代码如下:

    protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
    {
        base.OnPropertyChanged(change);

        if (change.Property == RunningProperty)
        {
            if (change.NewValue != null && (bool)change.NewValue)
            {
                PseudoClasses.Add("running");
            }
            else
            {
                PseudoClasses.Remove("running");
            }
        } 
    }

总结

在本文中,我们探讨了如何利用Avalonia框架开发水泵控件,并详细介绍了控件构成、动画创建及开发过程中的一些问题和解决方案。通过使用Canvas包裹可旋转部分,我们简化了动画实现,同时利用伪类管理水泵的运行状态,使得控件的交互性和视觉效果得到了有效提升。

我们还分享了在创建自定义伪类时的一些注意事项,以及如何在属性变更时动态更新伪类的实现。这个过程强调了Avalonia框架在构建灵活、跨平台的用户界面方面的强大能力。

希望这篇文章能够为开发者在使用Avalonia进行控件开发时提供一些实用的参考和启发,助力他们更好地实现工业自动化中的各类应用。无论是初学者还是经验丰富的开发者,都可以在这个过程中获得新的见解和技巧。让我们共同期待在Avalonia上实现更多的创意与创新!

控件的完整代码由于内容较多,请至文章底部的控件代码链接自行获取。

欢迎关注我的公众号“
nodered-co
”,原创技术文章第一时间推送。

Style Selector Syntax

How To Bind to a Contro

控件代码

序言:过拟合是人工智能训练中的一个常见问题,类似于一位“读死书”的学生,他只能机械地背诵书本内容,缺乏灵活性,一旦题目稍有变化便无法理解。为了解决这一问题,科学家们从人脑的学习方式中获得启发,设计出“随机失活”方法。在学习过程中,随机关闭部分神经元,避免神经元之间过度依赖,从而提升模型的灵活性与应变能力。

随机失活

在本章前面,我们讨论了过拟合现象,即网络可能会对某一类输入数据变得过于专注,而在其他数据上表现较差。为了解决这个问题,可以使用一种叫做随机失活的正则化技术。

当神经网络在训练时,每个神经元都会对后续层的神经元产生影响。随着时间推移,尤其是在较大的网络中,某些神经元可能会变得过于专注,这种影响会继续向下传播,最终导致整个网络变得过度专注,出现过拟合问题。

此外,相邻的神经元可能会拥有非常相似的权重和偏差值,如果不加以监控,整个模型会变得过于依赖那些神经元激活的特征,导致模型整体变得过于专注。

举个例子,看看图3-21中的神经网络,它包含2个、6个、6个和2个神经元的层结构。中间层的神经元可能会最终拥有非常相似的权重和偏差值。

                                图3-21. 一个简单的神经网络

在训练时,如果随机去除一些神经元并忽略它们,这些神经元对下一层神经元的影响会被暂时阻断(见图3-22)。

                                图3-22:一个带有随机失活的神经网络

这样做可以减少神经元过于专注的风险。网络虽然依然学习相同数量的参数,但在泛化能力上会更好——也就是说,它对不同输入的适应能力会更强。

随机失活的概念由Nitish Srivastava等人在2014年的论文《Dropout: A Simple Way to Prevent Neural Networks from Overfitting》中提出。

要在TensorFlow中实现随机失活,可以直接使用一个简单的Keras层,如下所示:

tf.keras.layers.Dropout(0.2),

这样可以在指定层中随机失活指定比例的神经元(这里是20%)。需要注意的是,找到适合你网络的失活比例可能需要一些实验。

举个简单的例子来说明这一点,比如第二章中的Fashion MNIST分类器。我将改变网络定义,增加很多层,如下所示:

model = tf.keras.models.Sequential([

tf.keras.layers.Flatten(input_shape=(28,28)),

tf.keras.layers.Dense(256, activation=tf.nn.relu),

tf.keras.layers.Dense(128, activation=tf.nn.relu),

tf.keras.layers.Dense(64, activation=tf.nn.relu),

tf.keras.layers.Dense(10, activation=tf.nn.softmax)

])

将其训练20个周期后,训练集的准确率约为94%,验证集的准确率约为88.5%。这是潜在过拟合的一个迹象。

在每个全连接层之后引入随机失活,如下所示:

model = tf.keras.models.Sequential([

tf.keras.layers.Flatten(input_shape=(28,28)),

tf.keras.layers.Dense(256, activation=tf.nn.relu),

tf.keras.layers.Dropout(0.2),

tf.keras.layers.Dense(128, activation=tf.nn.relu),

tf.keras.layers.Dropout(0.2),

tf.keras.layers.Dense(64, activation=tf.nn.relu),

tf.keras.layers.Dropout(0.2),

tf.keras.layers.Dense(10, activation=tf.nn.softmax)

])

当在相同的数据上以相同的时间训练该网络时,训练集的准确率下降到约89.5%。验证集的准确率保持基本不变,为88.3%。这些值彼此更接近;因此,引入随机失活不仅证明了过拟合正在发生,而且使用随机失活可以通过确保网络不会对训练数据过度专注来帮助消除这种模糊性。

在设计神经网络时要记住,训练集上的高准确率并不总是好事。这可能是过拟合的一个信号。引入随机失活可以帮助你消除这个问题,从而在没有这种虚假安全感的情况下优化网络的其他方面。

总结:随机失活在模型训练中至关重要,否则我们训练出的模型就像‘死记硬背’的学生,只会对付特定的数据,无法应对真实应用场景中的变化。通过随机失活,我们让模型在学习过程中更加灵活,从而提高它在不同数据上的适应能力,避免成为‘傻子模型’。

下一篇我们将讲解如何在TensorFlow Datasets中使用公共数据集进行训练”。它指的是利用TensorFlow Datasets库(一个提供多种预处理数据集的库)来访问和加载公共训练数据集,以便在模型训练中使用。

Python3新特性

python3.11增加了许多特性,让python更快更加安全,本文从应用层面来讲一下python3.11的这些新特性

特化自适应解析器是什么,如何利用特化写出更高性能的代码

如何在项目中落地type hint写出健壮的代码,有那些注意事项

asyncio的概念及应用场景

Faster Python 3.11

Faster

  1. Zero cost exception(if not thrown)
  2. 10% faster re & atomic grouping, possessive qualifiers
  3. 10-15% faster startup
  4. Faster function calls
  5. C-style formatting sometimes as fast as f-string
  6. Less memory for string keys in dicts
  7. Specialized adaptive interpreter
  8. And more!

Future

  1. Major focus for the next several releases
  2. Simple JIT planned eventually
  3. The main driver behind C API changes

Specializing(特化)

当一个函数被执行的次数足够多(>53)就会被特化,被特化的指令叫hot code。(次数需要看对应不同版本cpython源码)

源码:Python/specialize.c -> _PyCode_Warmup

特化流程:

​ 原始指令 —— 中间状态(名称中含ADAPTIVE) —— 特化后的指令(非常快)

img

图片来源:
Python3-源码剖析(二)-指令特化 | Yuerer's Blog

碰到问题

同样的代码在命令行中可以被特化,而放一个.py文件中,再通过dis.dis(module.func,adaptive=True)就无法被特化

示例函数代码如下:

>>> def f(x):
...     return x*x
... for i in range(100):
...     f(i)

image-20241028155628526

解释:乘法的opcode为BINARY_OP,在这个例子中我们传的是int当被特化后会变成BINARY_OP_MULTIPLY_INT,因为python弱类型,确定的类型可以极大提高速度,建议去看cpython的实现源码加深理解。

把上面代码放在.py文件中,发现无法进行特化

>>> dis.dis(adaptiveTest.f,adaptive=True)
 10           RESUME                   0

 11           LOAD_FAST                0 (x)
              LOAD_CONST               1 (2)
              BINARY_OP                5 (*)
              RETURN_VALUE

最终找到原因:我在vscode自带的终端import之后,在运行时修改了py代码,没有重新reload,导致没有加载最新的代码(py3的reload和py2有区别)。

另一个方法就是:重新打开windows的cmd中并执行一遍

image-20241028161816185

还有一种方法就是:稍稍调整一下代码,把dis加到.py中,然后运行python文件也可以看到函数被特化

import dis
def foo(x):
	return x*x

for i in range(100):
	foo(i)

dis.dis(foo,adaptive=True)
#在python中调用dis打印出字节码

LOAD_ATTR(getattr)特化

self.xx 本质就是getattr,对应的opcode为 LOAD_ATTR,在python3中默认可以被特化,例如:

  1. 继承object的原生Python类可以特化

  2. 继承后object重写
    __getattr__
    的Python 类无法特化

  3. C 扩展 Python 类无法特化

为什么后面2种不能完成特化?

class B(object):
	def __getattr__(self, name):#重写__getattr__
		return super(B, self).__getattr__(name)

b = B()
b.x = 1
def mytest(n):
	for i in range(n):
		b.x #无法被特化

因为:cpython中特化前判断是否为原始的getattr函数,见:Python\specialize.c

image-20241103104311246

image-20241103104308223

如何让C扩展python类可以特化?

重点讲解:2种实现方法

  1. 在c扩展类中增加cache保存下标
  2. 修改虚拟机的实现,传入下标

如何检查代码是否被特化?

可视化特化工具,github:
https://github.com/brandtbucher/specialist

运行代码并生成(网页)报表,那么如何纳入到项目中进行可视化呢?因为游戏项目依赖于引擎API,需要跑在游戏引擎之上,不同于纯python环境

Typing check(type hint)

base vscode Pylance

Type Ignore

pyrightconfig.json 兼容py2的文件,忽略整个文件

overload

配合vscode的pylance特性来做代码检查

当函数传参个数不符合要求时,在IDE中进行报错提示

Stub Files

和py同名的文件格式为.pyi,语法也一样,在这里写type hint,提供给IDE使用,运行时无关

AsyncIO

What is it?

Keywords pair(async / await)

So what?

What is it?

Asyncio is used as a foundation for multiple Python asynchronous frameworks that
provide high-performance network and web-servers, database connection libraries,
distributed task queues, etc.
Asyncio is often a perfect fit for IO-bound and high-level structured network code.

简单的例子发挥不出作用

import asyncio
async def foo():
	await asyncio.sleep(1)
	print ('foo')
event_loop = asyncio.get_event_loop()
event_loop.run_until_complete(foo())

上面这个简单的asyncio的例子和下面这段代码作用一样,无法体现出asyncio的作用

def foo():
	time.sleep(1)
	print('foo')
foo()

适合用在那些地方?

Patch

  • Simultaneously download multiple block of patch

Distributed Task Framework

  • Multi-Process Management through ProcessPoolExecutor
  • Export-table-tools
  • Texture Compressor
  • build packer

感兴趣的可以搜索ProcessPoolExecutor去了解

UVLoop

uvloop用来替换asyncio的event loop更高效,底层使用libuv通过cython实现,比原生的asyncio快2~4倍,有线上项目已验证过其稳定性

开源地址:
https://github.com/MagicStack/uvloop

简单几行就可以替换asyncio的event loop

import asyncio
import sys

import uvloop

async def main():
    # Main entry-point.
    ...

if sys.version_info >= (3, 11):
    with asyncio.Runner(loop_factory=uvloop.new_event_loop) as runner:
        runner.run(main())
else:
    uvloop.install()
    asyncio.run(main())

Java学习十七—反射机制:解锁代码的无限可能

一、关于反射

1.1 简介

Java 反射(Reflection)是Java 的特征之一,它允许程序在运行时动态地访问和操作类的信息,包括类的属性、方法和构造函数。

反射机制能够使程序具备更大的灵活性和扩展性

5f4435ae-1d9b-4267-a355-cf75d4ee49861

Oracle 官方对反射的解释:

Reflection enables Java code to discover information about the fields, methods and constructors of loaded classes, and to use reflected fields, methods, and constructors to operate on their underlying counterparts, within security restrictions.
The API accommodates applications that need access to either the public members of a target object (based on its runtime class) or the members declared by a given class. It also allows programs to suppress default reflective access control.

1.2 发展

Java 反射机制的发展历程可以分为几个重要的阶段,随着 Java 语言和平台的演进,反射的功能和使用场景也不断丰富。

1. Java 1.0 版本(1996 年)

  • 初步引入
    :Java 在最初的版本中就引入了反射机制的基础概念,但功能较为有限。主要是为了支持动态加载类和基础的类信息查询。

2. Java 1.1 版本(1997 年)

  • 增强功能
    :Java 1.1 对反射机制进行了增强,增加了对接口的支持,使得开发者可以更灵活地操作对象。
  • 引入

    java.lang.reflect



    :这个包提供了获取类的信息、调用方法、访问属性的功能。

3. Java 2(JDK 1.2,1998 年)

  • 引入集合框架
    :反射与集合框架的结合使用得到了广泛关注,开发者能够通过反射创建和操作集合中的对象。
  • 安全性增强
    :反射机制也开始关注安全性,引入了安全管理器,限制某些反射操作。

4. Java 5(JDK 1.5,2004 年)

  • 泛型支持
    :Java 5 引入了泛型,反射也随之支持泛型类型的查询和操作,提升了类型安全性。
  • 注解机制
    :引入了注解(Annotations),反射机制开始被广泛应用于框架中,通过反射读取和处理注解信息。

5. Java 6(2006 年)及后续版本

  • 性能优化
    :随着反射在大型框架(如 Spring、Hibernate)中的广泛应用,Java 的开发团队逐步对反射的性能进行了优化,尽量减少反射操作的开销。
  • 动态代理
    :Java 6 中的
    java.lang.reflect.Proxy
    ​ 类使得动态代理的实现成为可能,进一步增强了反射的应用场景。

6. Java 8(2014 年)

  • Lambda 表达式
    :与反射结合使用,提升了函数式编程的灵活性。
  • 增强的类型推断
    :使得反射在处理复杂数据类型时变得更加高效和安全。

7. Java 9 及后续版本

  • 模块化系统
    :Java 9 引入了模块化(Project Jigsaw),反射的使用在模块间的访问控制上得到了新的关注。
  • API 和性能持续改进
    :持续优化反射的 API 和性能,以适应现代应用程序的需求。

1.3 特点

优点

  • 灵活性
    :能够在运行时决定使用哪个类或方法。
  • 动态性
    :支持在程序运行过程中动态生成类和对象。

缺点

  • 性能开销
    :反射操作相对较慢,因为涉及动态解析。
  • 安全性问题
    :使用反射可能会破坏封装性,访问私有成员。
  • 代码可读性
    :反射使代码难以理解和维护。

1.4 应用场景

很多人都认为反射在实际的 Java 开发应用中并不广泛,其实不然。当我们在使用 IDE(如 Eclipse,IDEA)时,当我们输入一个对象或类并想调用它的属性或方法时,一按点号,编译器就会自动列出它的属性或方法,这里就会用到反射。

反射最重要的用途就是开发各种通用框架。
很多框架(比如 Spring)都是配置化的(比如通过 XML 文件配置 Bean),为了保证框架的通用性,它们可能需要根据配置文件加载不同的对象或类,调用不同的方法,这个时候就必须用到反射,运行时动态加载需要加载的对象。

举一个例子,在运用 Struts 2 框架的开发中我们一般会在
struts.xml
​ 里去配置
Action
​,比如:

	   <action name="login"
               class="org.ScZyhSoft.test.action.SimpleLoginAction"
               method="execute">
           <result>/shop/shop-index.jsp</result>
           <result name="error">login.jsp</result>
       </action>

配置文件与
Action
​ 建立了一种映射关系,当 View 层发出请求时,请求会被
StrutsPrepareAndExecuteFilter
​ 拦截,然后
StrutsPrepareAndExecuteFilter
​ 会去动态地创建 Action 实例。比如我们请求
login.action
​,那么
StrutsPrepareAndExecuteFilter
​就会去解析struts.xml文件,检索action中name为login的Action,并根据class属性创建SimpleLoginAction实例,并用invoke方法来调用execute方法,这个过程离不开反射。

对与框架开发人员来说,反射虽小但作用非常大,它是各种容器实现的核心。而对于一般的开发者来说,不深入框架开发则用反射用的就会少一点,不过了解一下框架的底层机制有助于丰富自己的编程思想,也是很有益的。

以下是一些常见的应用场景:

2736216443-61df8d5be03c61

1. 框架开发

  • 依赖注入(Dependency Injection, DI)
    :框架如Spring使用反射来创建和管理Bean对象,实现依赖注入。通过反射,框架可以在运行时动态地将依赖对象注入到目标对象中。
  • 面向切面编程(Aspect-Oriented Programming, AOP)
    :框架如Spring AOP使用反射来织入切面,实现横切关注点的分离。
  • 对象关系映射(Object-Relational Mapping, ORM)
    :框架如Hibernate使用反射来映射数据库表和Java对象,实现持久化操作。

2. 单元测试

  • 单元测试框架
    :框架如JUnit使用反射来发现和执行测试方法。通过反射,测试框架可以自动扫描类中的测试方法并执行它们。
  • Mock对象
    :在单元测试中,经常需要模拟(mock)一些对象的行为。框架如Mockito使用反射来创建和管理这些Mock对象。

3. 动态代理

  • Java动态代理
    :通过反射生成实现了特定接口的代理对象,用于实现AOP、远程调用(RPC)等功能。
  • CGLIB动态代理
    :CGLIB是一个强大的高性能代码生成库,它可以在运行时生成一个类的子类对象,并使用反射来实现方法拦截。

4. 配置文件解析

  • XML/JSON/YAML配置文件
    :许多框架和库使用反射来解析配置文件,并根据配置文件中的信息动态创建和配置对象。例如,Spring可以通过XML或注解配置文件来管理Bean。

5. 序列化和反序列化

  • JSON库
    :库如Gson和Jackson使用反射来将Java对象转换为JSON字符串,或将JSON字符串转换为Java对象。
  • XML库
    :库如JAXB使用反射来将Java对象转换为XML文档,或将XML文档转换为Java对象。

6. 插件系统

  • 动态加载插件
    :通过反射,可以在运行时动态加载和卸载插件。这种机制常用于开发可扩展的应用程序,如IDE、游戏引擎等。

7. 数据绑定

  • 数据绑定框架
    :框架如JavaFX和Vaadin使用反射来将用户界面组件的数据绑定到模型对象上,实现双向数据绑定。

8. 脚本语言集成

  • 嵌入脚本语言
    :Java可以通过反射来调用嵌入的脚本语言(如JavaScript、Python)中的函数和方法,实现混合编程。

9. 安全性和权限管理

  • 安全管理
    :通过反射可以动态地检查和设置对象的访问权限,实现细粒度的安全控制。

10. 日志记录

  • 日志框架
    :框架如Log4j和SLF4J使用反射来获取类的信息,以便在日志记录中包含详细的上下文信息。

1.5 主要API

在Java中,反射主要涉及以下几个重要的类和接口:

  1. Class类
    :


    • 是进行反射的核心类。每个类在Java中都有一个与之对应的Class对象。

    • 常用方法:


      • forName(String className)
        ​:通过类的完全限定名获取Class对象。
      • getDeclaredMethods()
        ​:获取所有声明的方法,包括私有、保护和公共方法。
      • getDeclaredFields()
        ​:获取所有声明的字段。
      • getDeclaredConstructors()
        ​:获取所有声明的构造函数。
      • newInstance()
        ​:创建一个类的新实例。
  2. Method类
    :


    • 表示一个类的方法。

    • 常用方法:


      • invoke(Object obj, Object... args)
        ​:调用此Method对象所表示的方法。
      • getName()
        ​:获取方法的名称。
      • getParameterTypes()
        ​:获取方法的参数类型。
      • getReturnType()
        ​:获取方法的返回类型。
  3. Field类
    :


    • 表示一个类的字段(属性)。

    • 常用方法:


      • get(Object obj)
        ​:获取指定对象的字段值。
      • set(Object obj, Object value)
        ​:设置指定对象的字段值。
      • getType()
        ​:获取字段的类型。
  4. Constructor类
    :


    • 表示类的构造函数。

    • 常用方法:


      • newInstance(Object... initargs)
        ​:创建一个新对象的实例。
      • getParameterTypes()
        ​:获取构造函数的参数类型。
  5. AccessibleObject类
    :


    • Method、Field和Constructor类都继承自AccessibleObject,提供了控制访问权限的能力。

    • 常用方法:


      • setAccessible(boolean flag)
        ​:设置对象是否可以进行访问(即使是私有)。

二、反射工作原理

调用反射的总体流程如下:

1、当编写完一个Java项目之后,每个java文件都会被编译成一个.class文件。

2307111361-61df8d3a17ba4_fix7321

2、这些class文件在程序运行时会被ClassLoader加载到JVM中,当一个类被加载以后,JVM就会在内存中自动产生一个Class对象。

3287271321-61df8d4274427_fix7321

3、通过Class对象获取Field/Method/Construcor

4270769209-61df8d48e0216_fix7321

我们一般平时是通过new的形式创建对象实际上就是通过这些Class来创建的,只不过这个class文件是编译的时候就生成的,程序相当于写死了给jvm去跑。

3494671770-61df8d54cc33f_fix7321

反射是什么呢?当我们的程序在运行时,需要动态的加载一些类这些类可能之前用不到所以不用加载到jvm,而是在运行时根据需要才加载。

原来使用new的时候,需要明确的指定类名,这个时候属于硬编码实现,而在使用反射的时候,可以只传入类名参数,就可以生成对象,降低了耦合性,使得程序更具灵活性。

二、反射的基本运用

反射可以用于判断任意对象
所属的类
,获得 Class 对象,构造任意一个对象以及调用一个对象。

这里介绍基本反射功能的使用和实现(反射相关的类一般都在 java.lang.relfect 包里)。

2.1 获取Class 对象

在Java中,获取一个类的
Class
​对象是使用反射的第一步,
Class
​对象用于操作类的信息。

方式

有以下集中方式可以获取Class类的实例:

  1. 若已知具体的类,可以通过类的class属性获取,该方式最为安全可靠,且程序性能最高。

    //类的class属性
    Class classOne = User.class;
    
  2. 已知某个类的实例,通过调用该实例的getClass方法获取Class对象。

    //已有类对象的getClass方法
    Class collatz = user.getClass();
    
  3. 已知一个类的全类名,且该类在类路径下,可以通过静态方法forName()获取。

    Class c = Class.forName("com.dcone.zhuzqc.demo.User");
    
  4. 内置基本数据类型可以直接使用类名.Type获取。

    //内置对象才有的TYPE属性,较大的局限性
    Class<Integer> type = Integer.TYPE;
    
  5. 利用ClassLoader(类加载器)获取。

    使用类加载器可以加载类并获取
    Class
    ​ 对象,特别是在自定义类加载的场景中:

ClassLoader classLoader = MyClass.class.getClassLoader();
Class<?> clazz = classLoader.loadClass("com.example.MyClass");

示例

image

public class ReflectionDemo {

    public static void main(String[] args) throws ClassNotFoundException {

        //获取 Class 对象
        //方法1:使用 Class 类的 forName 静态方法(通过类的全名(包括包名)来获取)
        Class<?> baseAreaInfoDTOClass = Class.forName("com.gree.gpurchase.finance.infrastructure.dto.BaseAreaInfoDTO");
        System.out.println(baseAreaInfoDTOClass);

        //方法2:使用类名.class
        Class<BaseAreaInfoDTO> dtoClass = BaseAreaInfoDTO.class;
        System.out.println(dtoClass);

        //方法3:调用某个对象的 getClass() 方法
        BaseAreaInfoDTO baseAreaInfoDTO = new BaseAreaInfoDTO();
        Class<? extends BaseAreaInfoDTO> infoDTOClass = baseAreaInfoDTO.getClass();
        System.out.println(infoDTOClass);

        //方法4:使用 ClassLoader
        ClassLoader classLoader = BaseAreaInfoDTO.class.getClassLoader();
        Class<?> bClass = classLoader.loadClass("com.gree.gpurchase.finance.infrastructure.dto.BaseAreaInfoDTO");
        System.out.println(bClass);

        //示例:获取基本类型的 Class 对象
        Class<?> intClass = int.class;
        Class<?> voidClass = void.class;
        System.out.println("int class对象:" + intClass);
        System.out.println("void class对象:" + voidClass);

        //示例:获取数组的 Class 对象
        Class<BaseAreaInfoDTO[]> dClass = BaseAreaInfoDTO[].class;
        System.out.println("数组的class对象:" + dClass);

        int[] intArray = {5, 7};
        Class<?> intArrayClass = intArray.getClass();
        System.out.println(intArrayClass);

        String[] strArray = {"a", "b"};
        Class<?> strArrayClass = strArray.getClass();
        System.out.println(strArrayClass);
    }

}

2.2
获取类的字段

使用反射获取类的成员变量(字段)的方法主要依赖于
Class
​类和
Field
​类。下面是详细的步骤,展示如何利用反射获取类的成员变量。

步骤

  1. 获取Class对象


    • 使用
      Class.forName()
      ​或者
      MyClass.class
      ​方式获取目标类的
      Class
      ​对象。
  2. 获取字段


    • 可以使用
      getDeclaredFields()
      ​方法获取所有声明的字段,包括私有、保护和公共字段。
    • 如果只想获取公共字段,可以使用
      getFields()
      ​方法。
  3. 访问字段


    • 通过
      Field
      ​对象,可以获取字段的类型、名称和访问字段的值或者设置字段的值。

示例

以下示例中定义了一个简单的类
Person
​,然后通过反射获取其成员变量。

import java.lang.reflect.Field;

class Person {
    private String name;
    public int age;

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }
}

public class ReflectionExample {
    public static void main(String[] args) {
        try {
            // 获取Person类的Class对象
            Class<?> personClass = Person.class;

            // 获取所有声明的字段
            Field[] fields = personClass.getDeclaredFields();

            // 遍历字段并打印信息
            for (Field field : fields) {
                // 获取字段名称
                String fieldName = field.getName();
                // 获取字段类型
                Class<?> fieldType = field.getType();

                System.out.println("Field Name: " + fieldName + ", Field Type: " + fieldType.getName());

                // 如果字段是私有的,需要设置可访问性
                if (!field.isAccessible()) {
                    field.setAccessible(true);
                }

                // 创建实例以获取字段的值
                Person person = new Person("John Doe", 30);

                // 获取字段的值
                Object value = field.get(person);
                System.out.println("Value of " + fieldName + ": " + value);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

输出结果

运行该代码后,输出如下所示:

Field Name: name, Field Type: java.lang.String
Value of name: John Doe
Field Name: age, Field Type: int
Value of age: 30

注意事项

  • 访问控制
    :对于私有字段,需要调用
    setAccessible(true)
    ​来允许访问。
  • 异常处理
    :在使用反射时,可能会遇到多种异常,例如
    ClassNotFoundException
    ​、
    NoSuchFieldException
    ​、
    IllegalAccessException
    ​等,所以需要进行适当的异常处理。
  • 性能
    :过度使用反射可能会导致性能下降,通常应在必要时使用。

通过上述步骤和示例,您可以轻松获取类的成员变量。使用反射时要谨慎,确保理解其影响和使用场景

2.3 获取类的方法

使用反射机制可以动态地获取类的信息,包括类的方法。下面是如何使用反射获取类的方法的步骤和示例代码。

步骤

  1. 获取

    Class


    对象
    :首先,需要获取目标类的
    Class
    ​ 对象。

  2. 使用反射获取方法


    • 使用
      getMethods()
      ​ 方法获取所有公有的方法(包括继承的方法)。
    • 使用
      getDeclaredMethods()
      ​ 方法获取所有声明的方法(包括私有方法)。
  3. 遍历方法
    :遍历获取的方法,并打印出相关的信息,例如方法名、返回类型和参数类型。

示例

以下是一个示例代码,展示了如何获取某个类的方法信息:

image

import java.lang.reflect.Method;

public class ReflectionExample {
    public static void main(String[] args) {
        // 示例类
        Class<?> clazz = SampleClass.class; // 获取SampleClass的Class对象

        // 获取所有公有的方法
        Method[] methods = clazz.getMethods();
        System.out.println("公有方法:");
        for (Method method : methods) {
            System.out.println("方法名: " + method.getName());
            System.out.println("返回类型: " + method.getReturnType().getName());
            System.out.print("参数类型: ");
            Class<?>[] parameterTypes = method.getParameterTypes();
            for (Class<?> paramType : parameterTypes) {
                System.out.print(paramType.getName() + " ");
            }
            System.out.println("\n");
        }

        // 获取所有声明的方法,包括私有方法
        Method[] declaredMethods = clazz.getDeclaredMethods();
        System.out.println("所有声明的方法:");
        for (Method declaredMethod : declaredMethods) {
            System.out.println("方法名: " + declaredMethod.getName());
            System.out.println("返回类型: " + declaredMethod.getReturnType().getName());
            System.out.print("参数类型: ");
            Class<?>[] parameterTypes = declaredMethod.getParameterTypes();
            for (Class<?> paramType : parameterTypes) {
                System.out.print(paramType.getName() + " ");
            }
            System.out.println("\n");
        }
    }
}

// 示例类
class SampleClass {
    public void publicMethod(String param) {
        // 方法实现
    }

    private void privateMethod() {
        // 方法实现
    }

    protected int protectedMethod(int x) {
        return x * 2;
    }

    void defaultMethod() {
        // 方法实现
    }
}

2.4
获得类的构造函数

使用反射机制可以获取类的构造函数。下面是获取构造函数的步骤和示例代码。

步骤

  1. 获取

    Class


    对象
    :获取目标类的
    Class
    ​ 对象。

  2. 使用反射获取构造函数


    • 使用
      getConstructors()
      ​ 方法获取所有公有构造函数。
    • 使用
      getDeclaredConstructors()
      ​ 方法获取所有声明的构造函数(包括私有构造函数)。
  3. 遍历构造函数
    :遍历获取的构造函数,打印出构造函数的信息,例如名称和参数类型。

示例

image

import java.lang.reflect.Constructor;

public class ReflectionExample {
    public static void main(String[] args) {
        // 示例类
        Class<?> clazz = SampleClass.class; // 获取SampleClass的Class对象

        // 获取所有公有构造函数
        Constructor<?>[] constructors = clazz.getConstructors();
        System.out.println("公有构造函数:");
        for (Constructor<?> constructor : constructors) {
            System.out.println("构造函数名: " + constructor.getName());
            System.out.print("参数类型: ");
            Class<?>[] parameterTypes = constructor.getParameterTypes();
            for (Class<?> paramType : parameterTypes) {
                System.out.print(paramType.getName() + " ");
            }
            System.out.println("\n");
        }

        // 获取所有声明的构造函数,包括私有构造函数
        Constructor<?>[] declaredConstructors = clazz.getDeclaredConstructors();
        System.out.println("所有声明的构造函数:");
        for (Constructor<?> declaredConstructor : declaredConstructors) {
            System.out.println("构造函数名: " + declaredConstructor.getName());
            System.out.print("参数类型: ");
            Class<?>[] parameterTypes = declaredConstructor.getParameterTypes();
            for (Class<?> paramType : parameterTypes) {
                System.out.print(paramType.getName() + " ");
            }
            System.out.println("\n");
        }
    }
}

// 示例类
class SampleClass {
    public SampleClass() {
        // 默认构造函数
    }

    public SampleClass(String name) {
        // 带参数的构造函数
    }

    private SampleClass(int age) {
        // 私有构造函数
    }
}

2.5 调用类的方法

使用反射机制可以动态地调用一个类的方法。下面是实现这一功能的步骤和示例代码。

步骤

  1. 获取

    Class


    对象
    :获取目标类的
    Class
    ​ 对象。

  2. 获取方法


    • 使用
      getMethod()
      ​ 方法获取公有方法。
    • 使用
      getDeclaredMethod()
      ​ 方法获取所有声明的方法(包括私有方法)。
  3. 调用方法
    :通过
    Method
    ​ 对象调用目标方法。

示例

以下是一个示例代码,展示如何使用反射调用类的方法:

image

import java.lang.reflect.Method;

public class ReflectionExample {
    public static void main(String[] args) {
        try {
            // 获取SampleClass的Class对象
            Class<?> clazz = SampleClass.class;

            // 创建SampleClass的实例
            Object sampleInstance = clazz.getDeclaredConstructor().newInstance();

            // 获取公有方法
            Method publicMethod = clazz.getMethod("publicMethod", String.class);
            publicMethod.invoke(sampleInstance, "Hello, World!");

            // 获取私有方法
            Method privateMethod = clazz.getDeclaredMethod("privateMethod");
            privateMethod.setAccessible(true); // 允许访问私有方法
            privateMethod.invoke(sampleInstance);

        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

// 示例类
class SampleClass {
    public void publicMethod(String message) {
        System.out.println("公有方法被调用,消息: " + message);
    }

    private void privateMethod() {
        System.out.println("私有方法被调用");
    }
}

2.6 利用反射创建数组

可以使用反射来动态创建数组。通过
java.lang.reflect.Array
​ 类,可以创建和操作数组。以下是创建数组的步骤和示例代码。

步骤

  1. 获取数组的

    Class


    对象
    :使用
    Class.forName()
    ​ 或
    类型名.class
    ​ 获取数组元素的
    Class
    ​ 对象。
  2. 使用

    Array.newInstance()


    创建数组
    :调用该方法并传入数组的
    Class
    ​ 对象和数组的维度(长度)。

示例

以下示例展示如何使用反射创建一维和二维数组:

image

import java.lang.reflect.Array;

public class ReflectionArrayExample {
    public static void main(String[] args) {
        try {
            // 创建一个一维数组,类型为Integer,长度为5
            Class<?> arrayClass = Integer.class; // 数组元素类型
            int length = 5;
            Object intArray = Array.newInstance(arrayClass, length);

            // 设置数组元素
            for (int i = 0; i < length; i++) {
                Array.set(intArray, i, i * 10); // 设置数组元素
            }

            // 打印数组内容
            for (int i = 0; i < length; i++) {
                System.out.println(Array.get(intArray, i)); // 获取数组元素
            }

            // 创建一个二维数组,类型为String,维度为3x2
            Class<?> strArrayClass = String.class;
            int rows = 3;
            int cols = 2;
            Object strArray = Array.newInstance(strArrayClass, rows, cols);

            // 设置二维数组元素
            Array.set(strArray, 0, Array.newInstance(strArrayClass, cols)); // 第一行
            Array.set(Array.get(strArray, 0), 0, "Hello");
            Array.set(Array.get(strArray, 0), 1, "World");

            Array.set(strArray, 1, Array.newInstance(strArrayClass, cols)); // 第二行
            Array.set(Array.get(strArray, 1), 0, "Java");
            Array.set(Array.get(strArray, 1), 1, "Reflection");

            // 打印二维数组内容
            for (int i = 0; i < rows; i++) {
                for (int j = 0; j < cols; j++) {
                    System.out.print(Array.get(Array.get(strArray, i), j) + " ");
                }
                System.out.println();
            }

        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}