2023年10月

本文记录了我通过VS2019编译MicroSIP-3.21.3开源项目的过程。

Microsip:
MicroSIP source code

pjproject:
Download PJSIP - Open Source SIP, Media, and NAT Traversal library

opus:
Downloads – Opus Codec (opus-codec.org)

下载并解压后如图:

用vs 2019将microsip的平台工具集改成VS2019,然后编译。

报错缺失pjsua-lib/pjsua.h

pjsua-lib 是pjproject生成了,因此去编译pjproject。

用vs2019打开pjproject-2.13.1解决方案,并将所有项目的平台工具集改成VS2019,然后编译。

报错缺失config_site.h

找错,如下图,提示config_site.h这个头文件的自己创建

参考config_site_sample.h模板,创建一个最小集:

重新编译

依然报错找不到config_site.h

找到你创建的头文件,将他移动到如下图目录

重新编译,成功

此时关于pjproject需要提供的库、头文件,就算是编译好了。

回头继续搞microsip,首先去microsip目录下创建third-party目录,用来存放需要引入的头文件及类库

打开pjproject编译后的目录,将pjsua-lib相关的头文件、lib复制到上面创建的目录下

这是头文件

复制到这里

这是pjproject中的类库

放到这里

接下来配置microsip的lib引用和依赖

包含目录,是头文件所在的位置,库目录是lib文件所在的位置,这里配置我们创建的third-party、third-party/lib

编译,之前缺少pjsua-lib/pjsua.h的错误解决了,但是又有如下错误

继续从pjproject中找到pjsip.h搬过来,如下图,将所有的头文件都弄过来。

然后去编译,提示缺

好,继续搬

继续编译,又报这个

好的,继续找,继续搬

继续编译,报这个

继续搬

继续编译,报这个

继续搬

继续编译,报这个

此时,用vs2019将opus项目打开,进行编译...

找到opus项目的解决方案

打开后编译,成功后,将lib复制过来

继续编译microsip,报这个

进入link阶段了,接下来就是缺失的库的添加了,先把这个库弄过来

重命名libpjproject-i386-Win32-vc14-Debug.lib->libpjproject-i386-Win32-vc14-Debug-Static.lib

继续编译,报这个

pjmedia_get_vid_subsys(),这个找不到

打开pjproject

生成pjproject,将这些库搬过来

连接其中加入这些

替换一遍

重新编译microsip,成功...

本文将引导你运行Util权限管理模块,并对UI按钮和API操作进行访问控制.

Util平台介绍

Util应用框架是一组类库,它们提供了有用的功能.

虽然Util配套代码生成器能够帮助你创建项目基架,但直接使用它们的成本依然高昂.

第一个挡在前面的障碍是权限功能,它是任何业务项目的基石.

为了减轻使用Util应用框架的负担,我们创建了该项目, 名为
Util Platform
, 即 Util平台.

Util平台处于起步阶段,目前提供了基于资源和角色的权限模块,可以控制前端菜单和按钮,并能同时控制API的访问.

后续将持续更新,添加更多基础功能.

开源协议: MIT

可以在商业项目随意使用.

欢迎参与贡献

你如果感兴趣,可以参与该项目的开发, 共同创建基础业务功能,为 .NET 生态添砖加瓦.

  • Util应用框架交流QQ群(24791014)

Util平台项目介绍

为了满足
单体架构

微服务架构
的需求, Util平台分为三套项目.

  • Util.Platform.Single

    Util.Platform.Single 是Util平台单体架构版本.

  • Util.Platform.Dapr

    Util.Platform.Dapr 是Util平台微服务架构版本.

  • Util.Platform.Share

    Util.Platform.Share 是Util平台的共享库,抽取单体架构版本和微服务架构版本的共享代码,并发布到Nuget供两个版本使用.

Util平台功能列表

  • 系统功能


    • 登录
    • 退出登录
  • 权限管理模块


    • 应用程序管理
    • 声明管理
    • 资源管理
      • 模块资源管理
      • 操作资源管理
      • 身份资源管理
      • Api资源管理
    • 用户管理
    • 角色管理
      • 将用户添加到角色
      • 从角色移除用户
    • 权限管理
      • 授予角色操作权限
      • 拒绝角色操作权限
      • 授予角色API权限
      • 拒绝角色API权限

准备

拉取
Util.Platform.Single
项目代码.

git clone https://gitee.com/util-core/Util.Platform.Single.git

如果已拉取,请更新到最新代码.

运行项目

打开
Util.Platform.Single.sln
解决方案.

数据迁移

设置
Util.Platform.Api
为启动项目.

打开
appsettings.Development.json
开发配置文件.

DatabaseType
配置项用于指定当前使用的数据库类型,可选值为:

  • SqlServer
  • PgSql
  • MySql

Util.Platform.Single 目前支持以上三种数据库,如果需要支持其它数据库,请告知.

检查你使用的数据库连接字符串是否正确.


F5
键,启动
Util.Platform.Api
项目.

启动时,会自动运行 EntityFrameworkCore 迁移命令,创建迁移文件目录.

会同时创建三种数据库项目迁移文件目录.

如果没有配置MySql连接字符串,或 MySql 连接失败, 则无法创建MySql迁移文件目录,并会产生一个异常,不用理会它.

查看数据库,可以看到数据库已创建.

使用 Swagger 测试 Web Api 访问控制

启动完成, 弹出
Swagger
页面.

下面选择一个最简单的操作进行测试,执行 Application 的 /api/Application/enabled 操作.

返回消息 code 为 2.

Code 是 Util 应用框架约定的返回结果代码, 2 代表
未授权
.

Util.Platform.Api
项目默认开启了API访问控制,需要先进行登录认证.

Util.Platform.Api 项目的 Swagger 已经配置好 OAuth 认证.

点击 Swagger
Authorize
按钮.

弹出
Available authorizations
对话框,点击
Authorize
按钮.

进入登录页面,如下所示.

数据迁移默认创建两个用户: admin 和 test .

admin用户是管理员,test是普通用户.

输入用户
test
, 密码
test
,使用普通用户登录.

登录成功,跳回 Swagger 页面,点击
Close
按钮关闭对话框.

重新执行 Application 的 /api/Application/enabled 操作.

可以看到,成功返回数据,说明已经授权成功.

那么是不是只要登录,就能操作所有API接口?

下面执行 Application 的 POST /api/Application 操作,它表示创建应用程序.

返回结果代码为 2,表明未授权,无法访问 API,因为没有给 test 用户设置创建应用程序的权限.

运行管理后台UI

进入 Util.Platform.Single\src\Util.Platform.Ui.Identity\ClientApp 目录.

执行命令,还原 npm 并启动 Angular 开发服务器.

.\start.ps1

设置
Util.Platform.Ui.Identity

Util.Platform.Api
为启动项目.


F5
键启动项目.

弹出 Api Swagger 和 Angular UI 页面.

Angular UI 已经开启了授权路由守卫,未授权则跳转到登录页面.

使用
admin
用户名登录,密码
admin
.

登录成功,进入管理后台UI,如下所示.

查看权限管理模块

查看应用程序

应用程序用于支持多个业务子系统的访问控制.

点击左侧
应用程序
菜单项,如下所示.

数据迁移默认创建的应用程序,名为
管理系统
.

你可以添加新的应用程序,点击
创建
按钮,打开
创建应用程序
对话框.

应用程序还用于管理 Identity Server 身份服务器客户端信息.

查看声明

声明是登录认证后可以在用户会话中访问的信息项.

声明非常简单,使用表格列表进行编辑.

查看资源

资源是任何需要访问控制的东西.

它是权限管理模块的核心.

点击左侧
资源
菜单项,如下所示.

查看模块资源

模块资源
用于按功能模块进行分层设置, 同时也显示为前端的
菜单
.

UI 左侧菜单即是由模块资源配置的.

并不是所有的模块资源都会在菜单上显示,比如资源菜单下的内部导航菜单,也可以在模块资源中配置,但不会在菜单上显示.

点击
创建
按钮,弹出
创建模块
对话框.

父模块是一个下拉树组件,如下所示.

我们顺便看看 Util UI对下拉树绑定这类常见需求提供的支持.

首先查看 html 代码.

打开 Util.Platform.Ui.Identity/Pages/Routes/Identity/Module/Edit.cshtml 文件,如下所示.

Razor TagHelper 标签设置url直接发起web api请求.

这并非 Angular 推荐用法, 而是从之前 EasyUI 保留下来的经验,对于常规项目,这样能大幅提升开发效率.

对于更复杂的场景,你可以使用 Angular 标准玩法,创建独立的服务,使用服务加载数据,并绑定到组件上.

<util-tree-select for="ParentId" query-param="queryParam" load-mode="Sync" url="module/tree" default-expand-all="true"
    sort="SortId" label-text="identity.module.parentId" control-span="8">
</util-tree-select>

下面来查看 Angular 组件 typescript 脚本代码.

打开 Util.Platform.Ui.Identity/ClientApp/src/app/routes/identity/module/module-edit.component.ts 文件,如下所示.

可以看到,代码非常简单,没有加载父模块下拉树组件的代码.

/**
 * 模块编辑页
 */
@Component({
    selector: 'module-edit',
    templateUrl: environment.production ? './html/edit.component.html' : '/view/routes/identity/module/edit'
})
export class ModuleEditComponent extends TreeEditComponentBase<ModuleViewModel> {
    /**
     * 查询参数
     */
    queryParam: ResourceQuery;
    /**
     * 应用程序标识
     */
    @Input() applicationId;
    /**
     * 应用程序名称
     */
    @Input() applicationName;

    /**
     * 初始化模块编辑页
     * @param injector 注入器
     */
    constructor(injector: Injector) {
        super(injector);
        let param = this.util.dialog.getData<any>();
        if (param) {
            this.applicationId = param.applicationId;
            this.applicationName = param.applicationName;
        }
    }

    /**
     * 初始化
     */
    ngOnInit() {
        super.ngOnInit();
        this.model.applicationId = this.applicationId;
        this.queryParam = this.createQuery();
    }

    /**
     * 创建模型
     */
    createModel() {
        let result = new ModuleViewModel();
        result.enabled = true;
        return result;
    }

    /**
     * 创建查询参数
     */
    protected createQuery() {
        let result = new ResourceQuery();
        result.applicationId = this.applicationId;
        return result;
    }

    /**
     * 获取基地址
     */
    protected getBaseUrl() {
        return "module";
    }

    /**
     * 选择图标
     */
    selectIcon(icon) {
        this.model.icon = icon;
    }
}

最后,查看 Web Api 控制器的代码.

打开 Util.Platform.Api/Controllers/Identity/ModuleController.cs 文件,如下所示.

模块控制器从 NgZorroTreeControllerBase 基类继承,已经封装将树形数据转换成 Ng Zorro 树形组件需要的数据格式.

NgZorroTreeControllerBase 基类提供的 QueryAsync 方法用于为树形表格提供数据,TreeQueryAsync 方法为树形或下拉树形提供数据.

查看操作资源

点击
模块资源
列表
资源设置
按钮,打开
操作资源
列表, 如下所示.

操作资源
列表是一个内嵌表格,为模块资源提供细粒度访问控制,比如控制页面上的按钮.

资源还有两种常见类型,

资源和
行集
资源.


资源用来控制显示的字段.

行集
资源用来控制不同角色显示不同的内容,俗称数据权限.

行集资源需要使用规约对象封装查询条件,并接入 Util 授权体系.

这两种资源尚未实现,待基础功能完善以后提供,如果你的项目对数据权限控制有需求,可以告知,我们将提前实现它.

点击 操作资源
创建
按钮,打开
创建操作权限
对话框.

操作名称输入框提示常用操作,可以从中选择,或手工输入.

资源最重要的属性是标识.

对于UI按钮的控制,可以使用有意义的标识, 比如 application.create, 表示创建应用程序.

资源的定义由开发人员根据控制粒度决定,除了在管理系统添加资源数据,还需要修改相应代码.

设置操作权限

下面以
创建应用程序
操作为例,介绍控制 UI 按钮和 API 需要的步骤.

  • 添加
    创建应用程序
    操作资源.

    数据迁移已经创建了该资源,标识为
    application.create
    .

  • UI 按钮访问控制

    打开 Util.Platform.Ui.Identity/Pages/Routes/Identity/Application/Index.cshtml 文件,如下所示.

只需要把
application.create
操作资源标识设置到 acl 属性即可.

acl 属性是 Ng Alain 提供的访问控制指令 *aclIf, 已经将 acl 属性全局添加到 Util UI所有 TagHelper 标签.

  • Api 访问控制

    打开 Util.Platform.Api/Controllers/Identity/ApplicationController.cs 文件,代码简化如下.

    /// <summary>
    /// 应用程序控制器
    /// </summary>
    [Acl( "application.view" )]
    public class ApplicationController : CrudControllerBase<ApplicationDto, ApplicationQuery> {
      /// <summary>
      /// 初始化应用程序控制器
      /// </summary>
      /// <param name="service">应用程序服务</param>
      public ApplicationController( IApplicationService service ) : base( service ) {
          ApplicationService = service;
      }
    
      /// <summary>
      /// 应用程序服务
      /// </summary>
      protected IApplicationService ApplicationService { get; }
    
      /// <summary>
      /// 创建
      /// </summary>
      /// <param name="request">创建参数</param>
      [HttpPost]
      [Acl( "application.create" )]
      public new async Task<IActionResult> CreateAsync( ApplicationDto request ) {
          return await base.CreateAsync( request );
      }
    }
    

    注意 ApplicationController 控制器上面的 [Acl( "application.view" )] 特性.

    Asp.Net Core 自带了一个授权特性 [Authorize] ,它默认的行为是只要登录认证成功,就能进行任何操作.

    Authorize 特性可以设置 Roles 属性, 设置硬编码的角色列表,这不太灵活.

    Authorize 特性还能设置 Policy 自定义授权策略, 这是一种灵活的授权方式,但每个需要授权的API都要设置 Policy 很费力.

    Util Acl 特性从 Authorize 继承,并封装了特定的授权策略.


    与 Authorize 的 Roles 不同的是, Acl 并不设置角色,因为角色是动态的,可能随时增加减少,所以设置角色是一种不太靠谱的方法,只适合角色固定的项目.

    Acl 特性设置的是
    资源标识
    , 资源标识是固定不变的,理解这一点是将权限从业务逻辑中剥离出来的关键.


    application.view
    是查看应用程序操作资源.

    任何功能页面都需要查看权限,如果没有细粒度控制需求,对整个页面和API进行控制即可.

    现在只要某个角色拥有 application.view 操作资源,他就能对控制器中所有方法进行访问.

    当需要进行更加细粒度的控制时,只需要在某个控制器方法上添加 [Acl] 特性,并设置相应的资源标识即可.

    CreateAsync
    方法添加了 [Acl( "application.create" )] 特性,这说明只有拥有
    application.create
    资源的角色才能进行创建操作.


    统一 UI 按钮与 API 操作的资源标识是权限设置的最佳实践.

    另一种方法是UI按钮与API操作使用不同的资源标识,再将API资源绑定到UI按钮,这种方式更复杂且容易出错.

    如果仅对 Web Api 进行访问控制, 可以使用API资源, 资源标识为 Api 地址,不需要在每个控制器打上 [Acl] 特性,Util 还提供了Acl过滤器,可以全局启用访问控制.

    拥有细粒度操作资源的角色必须拥有查看资源.

    对于本例,如果需要访问 CreateAsync 操作,Asp.Net Core首先验证
    application.view
    资源是否能访问,如果通过才会继续验证
    application.create
    资源.

  • 授予权限

    一旦创建操作资源,并在 UI 和 API 进行了Acl设置,就可以为角色授权.

    数据迁移默认创建了两个角色,admin 和 test,并已将用户 test 关联到 test 角色,以及将用户 admin 关联到 admin角色.

    点击左侧
    角色
    菜单,打开角色列表.

    角色列表中点击
    test
    角色
    权限设置
    链接,如下所示.

    弹出
    角色权限设置
    对话框,如下所示.

    默认 test 角色只能查看应用程序.

验证操作权限

下面来验证权限控制是否生效.

验证 UI 按钮权限

鼠标移到右上方头像,弹出菜单点击
退出登录
链接.

使用 test 用户登录,密码 test .

可以看到,只有一个应用程序菜单,并且操作按钮也消失了.

为 test 角色授予创建权限

退出登录,使用
admin
用户登录.

打开
test
角色
权限设置
窗口,勾选
授予
应用程序

创建
复选框,点击
确定
按钮,如下所示.

还支持拒绝权限,如果用户的某个角色拥有拒绝资源,则无法访问该资源.

退出登录,使用
test
用户登录.

打开应用程序页面,可以看到
创建
按钮已经显示出来.

验证 Api 权限

打开 Swagger ,之前已经测试过创建应用程序 API 接口无法访问,下面再来试试.

提交下面的数据.

{
  "code": "test",
  "name": "test",
  "enabled": true
}

提交返回操作成功消息,说明权限设置生效.

全局关闭访问控制

一旦 Web Api 控制器添加了 [Acl] 特性,权限就已经启用.

对于某些场景可能相当麻烦,比如 Web Api 集成测试.

权限是由 Identity Server 提供的,你的集成测试需要从 Identity Server 获取令牌,这造成了不必要的负担.

Util应用框架支持全局关闭访问控制,如下所示.

.AddAcl( t => t.AllowAnonymous = true )

AddAcl 配置 Util访问控制相关的服务依赖,AllowAnonymous让所有添加了 [Acl] 的方法跳过权限检测,无需登录,无需授权.

本文将介绍一个用于合并多个Excel文件中指定sheet的Python代码。这个功能可以方便地整理和分析数据。我们将逐步解释代码的每个部分,并提供示例用法。

导入库

首先,我们导入了需要使用的三个库:
os

pandas

time
。这些库分别用于操作文件和文件夹、处理Excel文件以及计算程序执行时间。

import os
import pandas aspd
import time

定义函数

我们定义了一个名为
merge_excel_sheets
的函数,用于将多个Excel文件中的指定sheet合并到一个新的Excel文件中。该函数接受三个参数:
folder_path
(文件夹路径)、
excel_list
(包含要合并的Excel文件和sheet名的列表)和
output_file
(输出文件路径)。

def merge_excel_sheets(folder_path, excel_list, output_file):
start_time =time.time()

with pd.ExcelWriter(output_file) aswriter:
for excel_name, sheet_name inexcel_list:
file_path =os.path.join(folder_path, excel_name)
df = pd.read_excel(file_path, sheet_name=sheet_name)

sheet_name_combined = f"{excel_name[:-5]}-{sheet_name}"df.to_excel(writer, sheet_name=sheet_name_combined, index=False)

end_time =time.time()
execution_time = end_time -start_time
print(f"程序执行时间:{execution_time}秒")

在函数内部,我们首先记录程序开始执行的时间。然后,我们使用
pd.ExcelWriter
创建一个空的Excel Writer对象,用于写入合并后的数据。

start_time =time.time()

with pd.ExcelWriter(output_file) as writer:

接下来,我们使用一个循环来处理每个Excel文件和sheet。对于每个文件和sheet,我们构造完整的文件路径,并使用
pd.read_excel
读取数据并存储为DataFrame对象。

for excel_name, sheet_name inexcel_list:
file_path =os.path.join(folder_path, excel_name)
df = pd.read_excel(file_path, sheet_name=sheet_name)

然后,我们构造合并后的sheet名称,格式为"原文件名-原sheet名",并使用
df.to_excel
将DataFrame对象中的数据写入到指定的sheet中。

sheet_name_combined = f"{excel_name[:-5]}-{sheet_name}"df.to_excel(writer, sheet_name=sheet_name_combined, index=False)

最后,我们计算程序执行的时间,并将其打印出来。

end_time =time.time()
execution_time = end_time -start_time
print(f"程序执行时间:{execution_time}秒")

示例用法

我们提供了一个示例用法,包括文件夹路径、要合并的Excel文件和sheet的列表,以及输出文件路径。通过调用
merge_excel_sheets
函数,我们可以执行合并操作。

folder_path = "E:\\工作内容"excel_list =[
("一店9月.xlsx", "原始数据"),
("二店9月.xlsx", "原始"),
("三店9月.xlsx", "原始数据"),
("四店9月.xlsx", "原始数据"),
("五店9月-离职.xlsx", "原始数据")
]
output_file = os.path.join(folder_path, "output.xlsx")

merge_excel_sheets(folder_path, excel_list, output_file)

完整代码

import os
import pandas aspd # 导入pandas库
import time # 导入时间库,用于计算程序执行时间

def merge_excel_sheets(folder_path, excel_list, output_file):
start_time =time.time() # 记录程序开始执行的时间

# 创建一个空的Excel Writer对象,用于写入合并后的数据
with pd.ExcelWriter(output_file) aswriter:
# 循环处理每个Excel文件和sheet
for excel_name, sheet_name inexcel_list:
# 根据文件名和文件夹路径,构造完整的文件路径
file_path =os.path.join(folder_path, excel_name)
# 读取指定Excel文件中指定sheet的数据,并存储为DataFrame类型的对象
df = pd.read_excel(file_path, sheet_name=sheet_name)

# 构造合并后的sheet名称,格式为"原文件名-原sheet名"sheet_name_combined = f"{excel_name[:-5]}-{sheet_name}"# 将DataFrame对象中的数据写入到指定sheet中
df.to_excel(writer, sheet_name=sheet_name_combined, index=False)

end_time =time.time() # 记录程序结束执行的时间
execution_time = end_time -start_time # 计算程序执行的时间
print(f"程序执行时间:{execution_time}秒") # 输出程序执行的时间

# 示例用法
folder_path = "E:\\工作内容"excel_list =[
("一店9月.xlsx", "原始数据"),
("二店9月.xlsx", "原始"),
("三店9月.xlsx", "原始数据"),
("四店9月.xlsx", "原始数据"),
("五店9月-离职.xlsx", "原始数据")
]
output_file = os.path.join(folder_path, "output.xlsx")
merge_excel_sheets(folder_path, excel_list, output_file) # 调用合并函数,将指定的Excel文件中指定sheet的数据进行合并

Java-反射

前言

Java的反射(reflection)机制是指在程序的运行状态中,可以构造任意一个类的对象,可以了解任意一个对象所属的类,可以了解任意一个类的成员变量和方法,可以调用任意一个对象的属性和方法。这种动态获取程序信息以及动态调用对象的功能称为Java语言的反射机制。反射被视为动态语言的关键

反射引出

一个需求

根据配置文件 re.properties 指定信息,创建 Cat 对象并调用方法 hi

classfullpath=com.jwt.Cat
method=hi

这样的需求在学习框架时很多,
即在通过外部文件配置,在不修改源码的情况下,来控制程序

传统方法

传统的方法是先 new 一个对象,然后再调用它的方法。

Cat cat = new Cat();
cat.hi();

通过传统方法,确实可以调用 hi() 方法,但是这和我们的需求不一样,这里我们是要根据配置文件 re.properties 指定信息来完成。可以使用 Properties 来读取配置文件。

Properties properties = new Properties();
properties.load(new FileInputStream("src//re.properties"));
String classfullpath = properties.getProperty("classfullpath");//"com.jwt.Cat"
String methodName = properties.getProperty("method");//"hi"
System.out.println("classfullpath=" + classfullpath);
System.out.println("method=" + methodName);

然后需要创建对象,怎么创建对象呢?直接
new classfullpath
,这样不就好了嘛?嗯,想法不错,下回不要想了。现在的 classfullpath 可是字符串类型,怎么能去
new
呢。所以现有技术是做不到这个事情的。那么这里就要引入我们要将的重点——反射机制。

反射方法

public class ReflectionQuestion {
    public static void main(String[] args) throws Exception {
        //1. 使用Properties 类, 可以读取配置文件
        Properties properties = new Properties();
        properties.load(new FileInputStream("src//re.properties"));
        String classfullpath = properties.getProperty("classfullpath");//"com.jwt.Cat"
        String methodName = properties.getProperty("method");//"hi"
        System.out.println("classfullpath=" + classfullpath);
        System.out.println("method=" + methodName);
      
        //2. 使用反射机制解决
        //(1) 加载类, 返回Class类型的对象cls
        Class cls = Class.forName(classfullpath);
        System.out.println("cls = " + cls);
        //(2) 通过cls得到你加载的类com.jwt.Cat的对象实例
        Object o = cls.newInstance();
        System.out.println("o 的运行类型=" + o.getClass()); //运行类型
        //(3) 通过cls得到你加载的类com.jwt.Cat的methodName "hi" 的方法对象
        Method method = cls.getMethod(methodName);
        //(4) 通过method 调用方法: 即通过方法对象来实现调用方法
        method.invoke(o); //传统方法对象.方法() , 反射机制:方法.invoke(对象)
    }
}

反射机制还有一个优点,那就是可以通过外部文件配置,在不修改源码的情况下,来控制程序。比如这里,我在Cat 类下面再写一个方法,cry()方法,如果我们使用传统方法,要调用这个方法,是不是就要修改代码了,比如
cat.cry();
通过反射,只需在配置文件 re.properties 中,将 method=hi 改为 method=cry 就可以了。

反射是什么

简介

  • 反射机制允许程序在执行时借助于 Reflection API 取得任何类的内部信息(比如成员变量,构造器,成员方法等等),并能操作对象的属性及方法。
  • 在运行状态中,对象可以通过反射获取他的类,类可以通过反射拿到所有⽅法(包括私有),拿到的⽅法可以调⽤,总之通过“反射”,我们可以将Java这种静态语⾔附加上动态特性。
  • 加载完类之后,在堆中就产生了一个 Class 类型的对象(一个类只有一个Class对象) ,这个对象包含了类的完整结构信息。通过这个对象得到类的结构。这个 Class 对象就像一面镜子,透过这个镜子看到类的结构,所以,形象的称之为反射

一句话总结:反射就是在运行时才知道要操作的类是什么,并且可以在运行时获取类的完整构造,并调用对应的方法。动态特性

反射机制原理示意

反射机制可以完成

  • 1.在运行时判断任意个对象所属的类
  • 2.在运行时构造任意一个类的对象
  • 3.在运行时得到任意一个类所具有的成员变量和方法
  • 4.在运行时调用任意一个对象的成员变量和方法
  • 5.生成动态代理

反射相关的主要类

  • java.lang.Class:代表一个类,Class 对象表示某 个类加载后在堆中的对象
  • java.lang.reflect.Method:代表类的方法,Method 对象表示某个类的方法
  • java.lang.reflect.Field:代表类的成员变量,Field 对象表示某个类的成员变量
  • java.lang.reflect.Constructor:代表类的构造方法,Constructor 对象表示构造器
package com.jwt.reflection;

import java.io.FileInputStream;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Properties;

public class Reflection01 {
    public static void main(String[] args) throws Exception {
        //1. 使用Properties 类, 可以读写配置文件
        Properties properties = new Properties();
        properties.load(new FileInputStream("src//re.properties"));
        String classfullpath = properties.get("classfullpath").toString();
        String methodName = properties.get("method").toString();
        //2. 使用反射机制解决
        //(1) 加载类, 返回Class 类型的对象cls
        Class cls = Class.forName(classfullpath);
        //(2) 通过cls 得到你加载的类com.hspedu.Cat 的对象实例
        Object o = cls.newInstance();
        System.out.println("o 的运行类型=" + o.getClass()); //运行类型
        //java.lang.reflect.Method:代表类的方法,Method 对象表示某个类的方法
        Method method = cls.getMethod(methodName);
        //通过method 调用方法: 即通过方法对象来实现调用方法
        method.invoke(o);

        //java.lang.reflect.Field: 代表类的成员变量, Field 对象表示某个类的成员变量
        //getField 不能得到私有的属性
        Field nameField = cls.getField("name");
        System.out.println(nameField.get(o)); // 传统写法对象.成员变量, 反射: 成员变量对象.get(对象)

        //java.lang.reflect.Constructor: 代表类的构造方法, Constructor 对象表示构造器
        Constructor constructor = cls.getConstructor(); //()中可以指定构造器参数类型, 返回无参构造器
        System.out.println(constructor);//Cat()
        Constructor constructor2 = cls.getConstructor(String.class); //这里传入的String.class 就是String 类的Class 对象
        System.out.println(constructor2);//Cat(String name)
    }
}

反射优点和缺点

  • 优点:可以动态的创建和使用对象(也是框架底层核心),使用灵活没有反射机制,框架技术就失去底层支撑。
  • 缺点:使用反射基本是解释执行,对执行速度有影响.

反射调用优化-关闭访问检查

  • Method、Field、 Constructor 对象都有 setAccessible() 方法
  • setAccessible 作用是启动和禁用访问安全检查的开关
    • 参数值为 true 表示反射的对象在使用时取消访问检查,提高反射的效率。
    • 参数值为 false 则表示反射的对象执行访问检查
package com.jwt.reflection;

import com.jwt.Cat;
import java.lang.reflect.Method;

public class Reflection02 {
    public static void main(String[] args) throws Exception {
        m1();//传统
        m2();//反射
        m3();//反射优化
    }

    //传统方法来调用hi
    public static void m1() {
        Cat cat = new Cat();
        long start = System.currentTimeMillis();
        for (int i = 0; i < 900000000; i++) {
            cat.hi();
        }
        long end = System.currentTimeMillis();
        System.out.println("m1() 耗时=" + (end - start)+"ms");
    }


    //反射机制调用方法hi
    public static void m2() throws Exception {
        Class cls = Class.forName("com.jwt.Cat");
        Object o = cls.newInstance();
        Method hi = cls.getMethod("hi");
        long start = System.currentTimeMillis();
        for (int i = 0; i < 900000000; i++) {
            hi.invoke(o);//反射调用方法
        }
        long end = System.currentTimeMillis();
        System.out.println("m2() 耗时=" + (end - start)+"ms");
    }

    //反射调用优化+ 关闭访问检查
    public static void m3() throws Exception {
        Class cls = Class.forName("com.jwt.Cat");
        Object o = cls.newInstance();
        Method hi = cls.getMethod("hi");
        hi.setAccessible(true);//在反射调用方法时,取消访问检查
        long start = System.currentTimeMillis();
        for (int i = 0; i < 900000000; i++) {
            hi.invoke(o);//反射调用方法
        }
        long end = System.currentTimeMillis();
        System.out.println("m3() 耗时=" + (end - start)+"ms");
    }
}
/**
m1() 耗时=2ms
m2() 耗时=934ms
m3() 耗时=769ms
**/

Class 类

基本介绍

  1. Class 也是类,继承 Object 类
  2. Class 类对象不是 new 出来的,而是系统创建的
  3. 对于某个类的 Class 类对象,在内存中只有一份,因为类只加载一 次
  4. 每个类的实例都会记得自己是由哪个 Class 实例所生成
  5. 通过 Class 对象可以完整地得到一个类的完整结构
  6. Class 对象是存放在堆的,类的字节码二进制数据, 是放在方法区的,有的地方称为类的元数据(包括方法代码,变量名,方法名,访问权限等等)

Class类的常用方法

方法名 功能说明
static Class forName(String name) 返回指定类名name的Class对象
object newlnstance() 调用缺省构造函数,返回该Class对象的一个实例
getName() 返回此Class对象所表示的实体(类、接口、数组类、基本类型等)名称
Class [] getInterfaces() 获取当前Class对象的接口
ClassLoader getClassLoader() 返回该类的类加载器
Class getSuperclass() 返回表示此Class所表示的实体的超类的Class
Constructor[] getConstructors() 返回一个包含某些Constructor对象的数组
Field[] getDeclaredFields() 返回Field对象的一个数组
Method getMethod(String name,Class…<?> paramTypes) 返回一个Method对象,此对象的形参类型为paramType
package com.jwt.reflection;

import java.lang.reflect.Field;

public class Class02 {
    public static void main(String[] args) throws Exception {
        String classAllPath = "com.jwt.reflection.Car";
        //1 . 获取到Car 类对应的Class 对象
        Class<?> cls = Class.forName(classAllPath);//<?> 表示不确定的Java 类型
        //2. 输出cls
        System.out.println(cls); //显示cls 对象, 是哪个类的Class 对象
        System.out.println(cls.getClass());//输出cls 运行类型java.lang.Class
        //3. 得到包名
        System.out.println(cls.getPackage().getName());//包名
        //4. 得到全类名
        System.out.println(cls.getName());
        //5. 通过cls 创建对象实例
        Car car = (Car) cls.newInstance();
        System.out.println(car);//car.toString()
        //6. 通过反射获取属性brand
        Field brand = cls.getField("brand");
        System.out.println(brand.get(car));//宝马
        //7. 通过反射给属性赋值
        brand.set(car, "奔驰");
        System.out.println(brand.get(car));//奔驰
        //8. 得到所有的属性(字段)
        System.out.println("=======所有的字段属性====");
        Field[] fields = cls.getFields();
        for (Field f : fields) {
            System.out.println(f.getName());//名称
        }
    }

}

class Car {
    public String brand = "宝马";
    public int price = 500000;
    public String color = "白色";

    @Override
    public String toString() {
        return "Car{" +
                "brand='" + brand + '\'' +
                ", price=" + price +
                ", color='" + color + '\'' +
                '}';
    }
}

/**
class com.jwt.reflection.Car
class java.lang.Class
com.jwt.reflection
com.jwt.reflection.Car
Car{brand='宝马', price=500000, color='白色'}
宝马
奔驰
=======所有的字段属性====
brand
price
color
**/

获取Class类对象

1、Class.forName

前提:已知一个类的全名称,且该类路径下,可通过 Class 类的静态方法 forName() 获取,可能抛出ClassNotFoundException 异常

实例:
Class cls1 = Class.forName(“java.lang.Cat”);

应用场景:多用于配置文件,读取类全路径,加载类

2、类名.class

前提:若已知具体的类,通过类的 class 获取,该方式最为安全可靠,程序性能最高

实例:
Class cls2 = Car.class;

应用场景:多用于参数传递,比如通过反射得到对应构造器对象

3、对象.getClass()

前提:已知某个类的实例,调用该实例的 getClass() 方法获取 Class 对象

实例:
Class cls3 = 对象.getClass();//运行类型

应用场景:通过创建好的对象,获取 Class 对象

4、通过类加载器

ClassLoader classLoader = 对象.getClass().getClassLoader();
Class cls4 = classLoader.loadClass(“类的全类名”);

5、基本数据(int,char,boolean,float,double,byte,long,short)按如下方式得到Class类对象

Class cls5 = 基本数据类型.class

6、基本数据类型对应的包装类,可以通过 .TYPE 得到 Class 类对象

Class cls6 = 包装类.TYPE
package com.jwt.reflection;

public class GetClass_ {
    public static void main(String[] args) throws ClassNotFoundException {
        //1. Class.forName
        String classAllPath = "com.jwt.reflection.Car"; //通过读取配置文件获取
        Class<?> cls1 = Class.forName(classAllPath);
        System.out.println(cls1);
        //2. 类名.class , 应用场景: 用于参数传递
        Class cls2 = Car.class;
        System.out.println(cls2);
        //3. 对象.getClass(), 应用场景,有对象实例
        Car car = new Car();
        Class cls3 = car.getClass();
        System.out.println(cls3);
        //4. 通过类加载器【4 种】来获取到类的Class 对象
        //(1)先得到类加载器car
        ClassLoader classLoader = car.getClass().getClassLoader();
        //(2)通过类加载器得到Class 对象
        Class cls4 = classLoader.loadClass(classAllPath);
        System.out.println(cls4);
        //5. 基本数据(int, char,boolean,float,double,byte,long,short) 按如下方式得到Class 类对象
        Class<Integer> cls5 = int.class;
        System.out.println(cls5);//int
        //6. 基本数据类型对应的包装类,可以通过.TYPE 得到Class 类对象
        Class<Integer> cls6 = Integer.TYPE;
        System.out.println(cls6);
    }
}
/**
class com.jwt.reflection.Car
class com.jwt.reflection.Car
class com.jwt.reflection.Car
class com.jwt.reflection.Car
int
int
**/

哪些类型有Class对象

  1. 外部类,成员内部类,静态内部类,局部内部类,匿名内部类
  2. interface:接口
  3. 数组
  4. enum:枚举
  5. annotation:注解
  6. 基本数据类型
  7. void
package com.jwt.reflection;

import java.io.Serializable;

public class AllTypeClass {
    public static void main(String[] args) {
        Class<String> aClass1 = String.class;//外部类
        Class<Serializable> aClass2 = Serializable.class;//接口
        Class<Integer[]> aClass3 = Integer[].class;//数组
        Class<Integer[][]> aClass4 = Integer[][].class;//二维数组
        Class<Deprecated> aClass5 = Deprecated.class;//注解
        Class<Thread.State> aClass6 = Thread.State.class;//枚举
        Class<Long> aClass7 = long.class;//基本数据类型
        Class<Void> aClass8 = void.class;//void数据类型
        Class<Class> aClass9 = Class.class;//Class类

        System.out.println(aClass1);
        System.out.println(aClass2);
        System.out.println(aClass3);
        System.out.println(aClass4);
        System.out.println(aClass5);
        System.out.println(aClass6);
        System.out.println(aClass7);
        System.out.println(aClass8);
        System.out.println(aClass9);
    }
}

类加载

静态加载和动态加载

反射机制是 Java 实现动态语言的关键,也就是通过反射实现类动态加载

  • 静态加载:编译时加载相关的类,如果没有则报错
Dog dog = new Dog();
  • 动态加载:运行时加载需要的类,编译不报错,降低了依赖性
Class cls = Class.forName("Person");
Object o = cls.newInstance();
Method method = cls.getMethod("hi");
method.invoke(o);

类加载时机

  • 当创建对象时(new) //静态加载
  • 当子类被加载时,父类也加载 //静态加载
  • 调用类中的静态成员时 //静态加载
  • 通过反射 //动态加载

类加载过程图

类加载各阶段完成任务

类加载五个阶段

1、加载阶段

JVM在改阶段的主要目的是将字节码从不同的数据源(可能是 class 文件、也可能是 jar 包,甚至网络)转化为二进制字节流加载到内存中,并生成一个代表该类的 java.lang.Class 对象

2、连接阶段——验证

  • 目的是为了确保 Class 文件的字节流中包含的信息符合当前虚拟机的要求,并且不会危害虚拟机自身的安全。
  • 包括:文件格式验证(是否以魔数 oxcafebabe开头)、元数据验证、字节码验证和符号引用验证。
  • 可以考虑使用 -Xverify:none 参数来关闭大部分的类验证措施,缩短虚拟机类加载的时间。

3、连接阶段——准备

JVM 会在该阶段对
静态变量
,分配内容并初始化(对应数据类型的默认初始值,如0、0L、null、false等)。这些变量所使用的内存都将在方法区中进行分配

class A {
//分析类加载的链接阶段-准备属性是如何处理
//1. n1 是实例属性, 不是静态变量,因此在准备阶段,是不会分配内存
//2. n2 是静态变量,分配内存n2 默认初始化0 ,而不是20
//3. n3 是static final 是常量, 他和静态变量不一样, 因为一旦赋值就不变n3 = 30
public int n1 = 10;
public static int n2 = 20;
public static final int n3 = 30;
}

4、连接阶段——解析

虚拟机将常量池内的
符号引用
替换为
直接引用
的过程。

5、初始化阶段

到初始化阶段,才真正开始执行类中定义的 Java 程序代码,此阶段是执行
()方法的过程

  • () 方法是由编译器按语句在源文件中出现的顺序,依次自动收集类中的所有静态变量的赋值动作和静态代码块中的语句,并进行合并。
  • 虚拟机会保证一个类的
    () 方法在多线程环境中被正确的加锁、同步,如果多个线程同时去初始化一个类,那么只会有一个线程去执行这个类的 () 方法,其他线程都需要阻塞等待,直到活动线程执行 () 方法完毕

反射获取类的结构信息

1、java.lang.Class类

  • getName:获取全类名
  • getSimpleName:获取简单类名
  • getFields:获取所有public修饰的属性,包含本类以及父类的
  • getDeclaredFields:获取本类中所有属性
  • getMethods:获取所有public修饰的方法,包含本类以及父类的
  • getDeclaredMethods:获取本类中所有方法
  • getConstructors:获取所有public修饰的构造器,包含本类以及父类的
  • getDeclaredConstructors:获取本类中所有构造器
  • getPackage:以Package形式返回父类信息
  • getSuperClass:以Class形式返回父类信息
  • getInterfaces:以Class[]形式返回接口信息
  • getAnnotations:以Annotation[]形式返回注解信息
@Test
public void api_01() throws ClassNotFoundException, NoSuchMethodException {
    //得到Class 对象
    Class<?> personCls = Class.forName("com.jwt.reflection.Person");

    //getName:获取全类名
    System.out.println(personCls.getName());//com.hspedu.reflection.Person
    //getSimpleName:获取简单类名
    System.out.println(personCls.getSimpleName());//Person
    //getFields:获取所有public 修饰的属性,包含本类以及父类的
    Field[] fields = personCls.getFields();
    for (Field field : fields) {//增强for
      System.out.println("本类以及父类的属性=" + field.getName());
    }
    System.out.println("==========================");
    //getDeclaredFields:获取本类中所有属性
    Field[] declaredFields = personCls.getDeclaredFields();
    for (Field declaredField : declaredFields) {
      System.out.println("本类中所有属性=" + declaredField.getName());
    }
    System.out.println("==========================");
    //getMethods:获取所有public 修饰的方法,包含本类以及父类的
    Method[] methods = personCls.getMethods();
    for (Method method : methods) {
      System.out.println("本类以及父类的方法=" + method.getName());
    }
    System.out.println("==========================");
    //getDeclaredMethods:获取本类中所有方法
    Method[] declaredMethods = personCls.getDeclaredMethods();
    for (Method declaredMethod : declaredMethods) {
      System.out.println("本类中所有方法=" + declaredMethod.getName());
    }
    System.out.println("==========================");
    //getConstructors: 获取所有public 修饰的构造器,包含本类
    Constructor<?>[] constructors = personCls.getConstructors();
    for (Constructor<?> constructor : constructors) {
      System.out.println("本类的构造器=" + constructor.getName());
    }
    System.out.println("==========================");
    //getDeclaredConstructors:获取本类中所有构造器
    Constructor<?>[] declaredConstructors = personCls.getDeclaredConstructors();
    for (Constructor<?> declaredConstructor : declaredConstructors) {
      System.out.println("本类中所有构造器=" + declaredConstructor.getName());//这里老师只是输出名
    }
    System.out.println("==========================");
    //getPackage:以Package 形式返回包信息
    System.out.println(personCls.getPackage());//com.hspedu.reflection
    //getSuperClass:以Class 形式返回父类信息
    Class<?> superclass = personCls.getSuperclass();
    System.out.println("父类的class 对象=" + superclass);
    System.out.println("==========================");
    //getInterfaces:以Class[]形式返回接口信息
    Class<?>[] interfaces = personCls.getInterfaces();
    for (Class<?> anInterface : interfaces) {
      System.out.println("接口信息=" + anInterface);
    }
    System.out.println("==========================");
    //getAnnotations:以Annotation[] 形式返回注解信息
    Annotation[] annotations = personCls.getAnnotations();
    for (Annotation annotation : annotations) {
      System.out.println("注解信息=" + annotation);//注解
    }
}
/**
com.jwt.reflection.Person
Person
本类以及父类的属性=name
本类以及父类的属性=hobby
==========================
本类中所有属性=name
本类中所有属性=age
本类中所有属性=job
本类中所有属性=sal
==========================
本类以及父类的方法=m1
本类以及父类的方法=hi
本类以及父类的方法=wait
本类以及父类的方法=wait
本类以及父类的方法=wait
本类以及父类的方法=equals
本类以及父类的方法=toString
本类以及父类的方法=hashCode
本类以及父类的方法=getClass
本类以及父类的方法=notify
本类以及父类的方法=notifyAll
==========================
本类中所有方法=m1
本类中所有方法=m2
本类中所有方法=m4
本类中所有方法=m3
==========================
本类的构造器=com.jwt.reflection.Person
本类的构造器=com.jwt.reflection.Person
==========================
本类中所有构造器=com.jwt.reflection.Person
本类中所有构造器=com.jwt.reflection.Person
本类中所有构造器=com.jwt.reflection.Person
==========================
package com.jwt.reflection
父类的class 对象=class com.jwt.reflection.A
==========================
接口信息=interface com.jwt.reflection.IA
接口信息=interface com.jwt.reflection.IB
==========================
**/

测试代码

class A {
    public String hobby;
    public void hi() {}
    public A() {}
    public A(String name) {}
}


interface IA { }

interface IB { }

@Deprecated
class Person extends A implements IA, IB {
    //属性
    public String name;
    protected static int age; // 4 + 8 = 12
    String job;
    private double sal;
    //构造器
    public Person() {}
    public Person(String name) {}
    //私有的
    private Person(String name, int age) {}
    //方法
    public void m1(String name, int age, double sal) {}
    protected String m2() {
        return null;
    }
    void m3() {}
    private void m4() {}
}

2、java.lang.reflect.Field类

  • getModifiers:以int形式返回修饰符
    • 说明:默认修饰符是0,public是1,private是2,protected是4,static是8,final是16
  • getType:以Class形式返回类型
  • getName:返回属性名

3、java.lang.reflect.Mehod类

  • getModifiers:以int形式返回修饰符
    • 说明:默认修饰符是0,public是1,private是2,protected是4,static是8,final是16
  • getReturnType:以Class形式获取返回类型
  • getName:返回方法名
  • getParameterTypes:以Class[]返回参数类型数组

4、java.lang.reflect.Constructor类

  • getModifiers:以int形式返回修饰符
  • getName:返回构造器名(全类名)
  • getParameterTypes:以Class[]返回参数类型数组
@Test
public void api_02() throws ClassNotFoundException, NoSuchMethodException {
  //得到Class 对象
  Class<?> personCls = Class.forName("com.jwt.reflection.Person");
  
  //java.lang.reflect.Field
  //规定说明: 默认修饰符是0 , public 是1 ,private 是2 ,protected 是4 , static 是8 ,final 是16
  Field[] declaredFields = personCls.getDeclaredFields();
  for (Field declaredField : declaredFields) {
    System.out.println("本类中所有属性=" + declaredField.getName()
                       + " 该属性的修饰符值=" + declaredField.getModifiers()
                       + " 该属性的类型=" + declaredField.getType()
                      );
  }
  System.out.println("==========================");

  //java.lang.reflect.Mehod
  Method[] declaredMethods = personCls.getDeclaredMethods();
  for (Method declaredMethod : declaredMethods) {
    System.out.println("本类中所有方法=" + declaredMethod.getName()
                       + " 该方法的访问修饰符值=" + declaredMethod.getModifiers()
                       + " 该方法返回类型" + declaredMethod.getReturnType()
                      );
  }
  for (Method declaredMethod : declaredMethods) {
    //输出当前这个方法的形参数组情况
    Class<?>[] parameterTypes = declaredMethod.getParameterTypes();
    for (Class<?> parameterType : parameterTypes) {
      System.out.println("该方法的形参类型=" + parameterType);
    }
  }
  System.out.println("==========================");


  //java.lang.reflect.Constructor
  Constructor<?>[] declaredConstructors = personCls.getDeclaredConstructors();
  for (Constructor<?> declaredConstructor : declaredConstructors) {
    System.out.println("本类中所有构造器=" + declaredConstructor.getName());//只是输出名

  }
  for (Constructor<?> declaredConstructor : declaredConstructors) {
    Class<?>[] parameterTypes = declaredConstructor.getParameterTypes();
    for (Class<?> parameterType : parameterTypes) {
      System.out.println("该构造器的形参类型=" + parameterType);
    }
  }
}
/**
本类中所有属性=name 该属性的修饰符值=1 该属性的类型=class java.lang.String
本类中所有属性=age 该属性的修饰符值=12 该属性的类型=int
本类中所有属性=job 该属性的修饰符值=0 该属性的类型=class java.lang.String
本类中所有属性=sal 该属性的修饰符值=2 该属性的类型=double
==========================
本类中所有方法=m1 该方法的访问修饰符值=1 该方法返回类型void
本类中所有方法=m2 该方法的访问修饰符值=4 该方法返回类型class java.lang.String
本类中所有方法=m4 该方法的访问修饰符值=2 该方法返回类型void
本类中所有方法=m3 该方法的访问修饰符值=0 该方法返回类型void
该方法的形参类型=class java.lang.String
该方法的形参类型=int
该方法的形参类型=double
==========================
本类中所有构造器=com.jwt.reflection.Person
本类中所有构造器=com.jwt.reflection.Person
本类中所有构造器=com.jwt.reflection.Person
该构造器的形参类型=class java.lang.String
该构造器的形参类型=int
该构造器的形参类型=class java.lang.String
**/

反射创建对象

  • 方式一:调用类中的public修饰的无参构造器创建对象
  • 方式二:调用类中的指定构造器创建对象

Class类相关方法

  • newInstance() 调用类中的无参构造器,获取对应类的对象
  • getConstructor(Class..clazz) 根据参数列表,获取对应的public构造器对象
  • getDecalaredConstructor(Class.cazz) 根据参数列表,获取对应的所有构造器对象

Constructor类相关方法

  • setAccessible:爆破(形参填入true,即可访问私有构造方法、变量、方法等)
  • newInstance(Object…obj):调用构造器
package com.jwt.reflection;

import java.lang.reflect.Constructor;

public class ReflecCreateInstance {
    public static void main(String[] args) throws Exception {
        //1. 先获取到User 类的Class 对象
        Class<?> userClass = Class.forName("com.jwt.reflection.User");
        //2. 通过public 的无参构造器创建实例
        Object user1 = userClass.newInstance();
        System.out.println(user1);
        //3. 通过public 的有参构造器创建实例
        //3.1 先得到对应构造器
        Constructor<?> constructor = userClass.getConstructor(String.class);
        //3.2 创建实例,并传入实参
        Object user2 = constructor.newInstance("小简");
        System.out.println(user2);
        //4. 通过非public 的有参构造器创建实例
        //4.1 得到private 的构造器对象
        Constructor<?> constructor1 = userClass.getDeclaredConstructor(int.class, String.class);
        //4.2 创建实例
        //暴破【暴力破解】, 使用反射可以访问private 构造器/方法/属性
        constructor1.setAccessible(true);
        Object user3 = constructor1.newInstance(100, "张三丰");
        System.out.println(user3);
    }
}


class User {
    private int age = 10;
    private String name = "小明";
    public User() {//无参public
    }
    public User(String name) {//public 的有参构造器
        this.name = name;
    }
    private User(int age, String name) {//private 有参构造器
        this.age = age;
        this.name = name;
    }
    @Override
    public String toString() {
        return "User [age=" + age + ", name=" + name + "]";
    }
}

反射访问类中的成员

访问属性

  • 1.根据属性名获取Field对象
    • Field f = class对象.getDeclaredField(属性名);
  • 2.暴破
    • f.setAccessible(true); //f是Field
  • 3.访问
    • f.set(o,值); //o表示对象
    • f.get(o); //o表示对象
  • 4.注意:如果是静态属性,则set和get中的参数o,可以写成null
package com.jwt.reflection;

import java.lang.reflect.Field;
public class ReflecAccessProperty {
    public static void main(String[] args) throws Exception {
        //1. 得到Student 类对应的Class 对象
        Class<?> stuClass = Class.forName("com.jwt.reflection.Student");
        //2. 创建对象
        Object o = stuClass.newInstance();//o 的运行类型就是Student
        System.out.println(o.getClass());//Student
        System.out.println(o);
        //3. 使用反射得到age 属性对象
        Field age = stuClass.getField("age");
        System.out.println(age.get(o));//返回age 属性的值
        age.set(o, 88);//通过反射来操作属性
        System.out.println(o);
        System.out.println(age.get(o));
        //4. 使用反射操作name 属性
        Field name = stuClass.getDeclaredField("name");
        //对name 进行暴破, 可以操作private 属性
        name.setAccessible(true);
        name.set(o, "小明");
        System.out.println(name.get(o)); //获取属性值
        System.out.println(o);
        
        name.set(null, "小红");//因为name 是static属性,因此o 也可以用null
        System.out.println(name.get(null));//获取属性值, 要求name 是static
        System.out.println(o);
    }
}

class Student {//类
    public int age;
    private static String name;
    public Student() {//构造器
    }
    @Override
    public String toString() {
        return "Student [age=" + age + ", name=" + name + "]";
    }
}

访问方法

  • 1.根据方法名和参数列表获取Method方法对象
    • Method m = class对象.getDeclaredMethod(方法名,XX.class);
  • 2.获取对象:
    Object o = class对象.newlnstance();
  • 3.暴破:
    m.setAccessible(true);
  • 4.访问:
    Object returnValue = m.invoke(o,实参列表);
  • 5.注意:如果是静态方法,则invoke的参数o,可以写成null!
package com.jwt.reflection;

import java.lang.reflect.Method;

public class ReflecAccessMethod {
    public static void main(String[] args) throws Exception {
        //1. 得到Boss 类对应的Class 对象
        Class<?> bossCls = Class.forName("com.jwt.reflection.Boss");
        //2. 创建对象
        Object o = bossCls.newInstance();
        //3. 调用public 的hi 方法
        //3.1 得到hi 方法对象
        Method hi = bossCls.getMethod("hi", String.class);//OK
        //Method hi = bossCls.getDeclaredMethod("hi", String.class);//OK
        //3.2 调用
        hi.invoke(o, "小明");
        //4. 调用private static 方法
        //4.1 得到say 方法对象
        Method say = bossCls.getDeclaredMethod("say", int.class, String.class, char.class);
        //4.2 因为say 方法是private, 所以需要暴破,原理和前面讲的构造器和属性一样
        say.setAccessible(true);
        System.out.println(say.invoke(o, 100, "张三", '男'));
        //4.3 因为say 方法是static 的,还可以这样调用,可以传入null
        System.out.println(say.invoke(null, 200, "李四", '女'));
        //5. 在反射中,如果方法有返回值,统一返回Object , 但是他运行类型和方法定义的返回类型一致
        Object reVal = say.invoke(null, 300, "王五", '男');
        System.out.println("reVal 的运行类型=" + reVal.getClass());//String
        //在演示一个返回的案例
        Method m1 = bossCls.getDeclaredMethod("m1");
        Object reVal2 = m1.invoke(o);
        System.out.println("reVal2 的运行类型=" + reVal2.getClass());//Monster
    }
}

class Monster {}
class Boss {//类
    public int age;
    private static String name;

    public Boss() {//构造器
    }

    public Monster m1() {
        return new Monster();
    }

    private static String say(int n, String s, char c) {//静态方法
        return n + " " + s + " " + c;
    }

    public void hi(String s) {//普通public 方法
        System.out.println("hi " + s);
    }
}

本章练习

练习1

通过反射修改私有成员变量

  • 1.定义 PrivateTest 类, 有私有 name 属性,属性值为 hellokitty
  • 2.提供 getName 的公有方法
  • 3.利用 Class 类得到私有的 name 属性,修改私有的 name 属性值,并调用 getName() 的方法打印 name 属性值
package com.jwt.reflection;

import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

public class Homework01 {
    public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException, NoSuchFieldException, NoSuchMethodException, InvocationTargetException {
        Class<?> aClass = Class.forName("com.jwt.reflection.PrivateTest");
        Object o = aClass.newInstance();
        Field name =  aClass.getDeclaredField("name");
        name.setAccessible(true);
        name.set(o,"小明");
        Method getName = aClass.getMethod("getName");
        Object returnValue = getName.invoke(o);
        System.out.println("returnValue = " + returnValue);
    }
}

class PrivateTest{
    private String name = "hellokitty";
    public String getName() {
        return name;
    }
}

练习2

利用反射和 File 完成以下功能

  • 1.利用 Class 类的 forName 方法得到 File 类的 class 对象
  • 2.在控制台打印 File 类的所有构造器
  • 3.通过 newInstance 的方法创建 File 对象, 并创建 /Users/jianjian/Downloads/mynew.txt 文件
  • 提示:创建文件的正常写法如下:
    • File file = new File(“路径”);
    • file.createNewFile();
package com.jwt.reflection;

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

public class Homework02 {
    public static void main(String[] args) throws Exception, {
        //得到 File 类的 class 对象
        Class<?> aClass = Class.forName("java.io.File");
          //打印 File 类的所有构造器
        Constructor<?>[] declaredConstructors = aClass.getDeclaredConstructors();
        for (Constructor<?> i:declaredConstructors){
            System.out.println("File构造器 = " + i);
        }
        //指定构造器创建File对象
        Constructor<?> dc = aClass.getDeclaredConstructor(String.class);
        Object fileObj = dc.newInstance("/Users/jianjian/Downloads/mynew.txt");
        //得到createNewFile的方法对象
        Method createNewFile = aClass.getMethod("createNewFile");
        createNewFile.invoke(fileObj);
        System.out.println("创建成功");
    }
}

用go封装一下临时token

本篇为
用go设计开发一个自己的轻量级登录库/框架吧
的临时token篇,会讲讲临时token的实现,给库/框架增加新的功能。

Github:
https://github.com/weloe/token-go

临时token也算是比较常见的业务,例如登录验证码信息,邀请链接等等,都属于临时token的范畴。

在token-go中同样使用Adapter组件进行了简单的封装。

封装思路

对于一个登录验证码,我们需要有创建,有效期,需要解析,需要能删除的功能。

因此,我们需要创建临时token,获取临时token有效期,解析临时token,删除临时token这几个方法。

而临时token会跟业务信息做关联,比如这个临时token是用于验证码服务或是用于邀请服务他们是互相隔离的,因此在存储的时候我们需要一个service参数做区分,我们存储为k,v结构,存储的key结构为:
"token-go:" + "temp-token" + ":temp:" + service + ":" + token
,存储的value即为设定的对应的值。

最后决定出几个api

	// Temp token api
	CreateTempToken(token string, service string, value string, timeout int64) (string, error)
	CreateTempTokenByStyle(style string, service string, value string, timeout int64) (string, error)
	GetTempTokenTimeout(service string, tempToken string) int64
	ParseTempToken(service string, tempToken string) string
	DeleteTempToken(service string, tempToken string) error

代码实现

创建临时token

这里我们提供了两个方法,一个用于根据style使用内置创建token的函数创建token,另一个用于直接由开发者生成token再传入。创建token则是Adapter对信息进行存储。

通过指定style创建

https://github.com/weloe/token-go/blob/90e576f01537c92b924670dee3e32b8d7f010585/enforcer_manager_api.go#L211

func (e *Enforcer) CreateTempTokenByStyle(style string, service string, value string, timeout int64) (string, error) {
	token, err := e.generateFunc.Exec(style)
	if err != nil {
		return "", err
	}
	err = e.setTempToken(service, token, value, timeout)
	if err != nil {
		return "", err
	}
	return token, nil
}

指定token创建。

https://github.com/weloe/token-go/blob/90e576f01537c92b924670dee3e32b8d7f010585/enforcer_manager_api.go#L223

func (e *Enforcer) CreateTempToken(token string, service string, value string, timeout int64) (string, error) {
	if token == "" {
		return "", fmt.Errorf("token must not be empty")
	}
	err := e.setTempToken(service, token, value, timeout)
	if err != nil {
		return "", err
	}
	return token, nil
}

获取剩余时间

https://github.com/weloe/token-go/blob/90e576f01537c92b924670dee3e32b8d7f010585/enforcer_manager_api.go#L234

同样是使用Adapter组件去获取剩余时间

func (e *Enforcer) GetTempTokenTimeout(service string, tempToken string) int64 {
	if tempToken == "" {
		return constant.NotValueExpire
	}
	return e.getTimeoutByTempToken(service, tempToken)
}
func (e *Enforcer) getTimeoutByTempToken(service string, token string) int64 {
	return e.adapter.GetTimeout(e.spliceTempTokenKey(service, token))
}

解析临时token

https://github.com/weloe/token-go/blob/90e576f01537c92b924670dee3e32b8d7f010585/enforcer_manager_api.go#L241

解析即是通过service,token去Adapter中获取对应的信息。

func (e *Enforcer) ParseTempToken(service string, tempToken string) string {
	if tempToken == "" {
		return ""
	}
	return e.getByTempToken(service, tempToken)
}

删除临时token

https://github.com/weloe/token-go/blob/90e576f01537c92b924670dee3e32b8d7f010585/enforcer_manager_api.go#L248

删除临时token则是从Adapter组件中删除对应的k,v数据

func (e *Enforcer) DeleteTempToken(service string, tempToken string) error {
	return e.deleteByTempToken(service, tempToken)
}

测试

func TestEnforcer_TempToken(t *testing.T) {
	enforcer, _ := NewTestEnforcer(t)
	service := "code"
	tempToken, err := enforcer.CreateTempToken("tempToken", service, "1234", -1)
	if err != nil {
		t.Fatalf("CreateTempToken() failed: %v", err)
	}
	if !reflect.DeepEqual(tempToken, "tempToken") {
		t.Fatalf("token error, want is %v", "1234")
	}
	timeout := enforcer.GetTempTokenTimeout(service, tempToken)
	if timeout != -1 {
		t.Errorf("GetTempTokenTimeout() failed, unexpected timeout: %v", timeout)
	}
	codeValue := enforcer.ParseTempToken("code", tempToken)
	if codeValue != "1234" {
		t.Errorf("ParseTempToken() failed, unexpected codeValue: %v", codeValue)
	}

	// delete
	if enforcer.DeleteTempToken(service, tempToken) != nil {
		t.Fatalf("DeleteTempToken() failed: %v", err)
	}
	tokenTimeout := enforcer.GetTempTokenTimeout(service, tempToken)
	if tokenTimeout != -2 {
		t.Errorf("GetTempTokenTimeout() failed, unexpected tokenTimeout: %v", tokenTimeout)
	}
	codeValue = enforcer.ParseTempToken(service, tempToken)
	if codeValue != "" {
		t.Errorf("ParseTempToken() failed, unexpected codeValue: %v", codeValue)
	}

	// create token
	tempToken, err = enforcer.CreateTempTokenByStyle("uuid", service, "1234", -1)
	if err != nil {
		t.Fatalf("CreateTempTokenByStyle() failed: %v", err)
	}
	// delete
	if enforcer.DeleteTempToken(service, tempToken) != nil {
		t.Fatalf("DeleteTempToken() failed: %v", err)
	}
}
--- PASS: TestEnforcer_TempToken (0.01s)
PASS