编译On Java 8注解章节的SimpleTest

使用javac编译

这段时间看On Java 8进阶卷,看到注解章节4.3.1的时候,书中引入了一个使用注解处理器的例子SimpleTest,并且提到了使用如下命令来编译

javac -processor annotation.simplest.SimpleProcessor SimpleTest.java

但是直接这样做无法得到书中提到的结果。因为SimpleTest.java使用的注解处理器SimpleProcessor和Simple注解都还未编译,在这之前需要先编译这2个文件才行。这2个文件使用普通的javac命令编译,但编译SimpleTest时还需指定注解处理器和@Simple所在路径,命令如下:

D:\books\java\onJava\OnJava8-Examples-master\annotations\simplest>javac Simple.java
D:\books\java\onJava\OnJava8-Examples-master\annotations\simplest>javac -cp D:\books\java\onJava\OnJava8-Examples-master SimpleProcessor.java
D:\books\java\onJava\OnJava8-Examples-master\annotations\simplest>javac -cp D:\books\java\onJava\OnJava8-Examples-master -processor annotations.simplest.SimpleProcessor SimpleTest.java

这样就能得到书中的结果,即在编译SimpleTest时,SimpleProcessor处理了@Simple注解。

使用gradle编译

上述步骤实在是太过麻烦了,并且On Java8的源码是使用gradle来构建的。既然如此,为啥不用gradle来编译呢?这不就是gradle这类工具的意义吗?

这里有点特殊在于On Java8的源码中,注解、注解处理器、使用注解处理器的类都在同一个工程下,注解处理器并没有作为依赖引入。我的思路是先用一个task编译Simple注解和SimpleProcessor,这样另一个task就有资源能够编译SimpleTest了。

这2个task都是JavaCompile任务类型。

在源码的annotations目录下新增一个build.gradle即可,内容如下:

plugins {
    id 'java'
}

tasks.register('compileAnnotationBase', JavaCompile) {
    source = files(
            "${projectDir}/simplest/Simple.java",
            "${projectDir}/simplest/SimpleProcessor.java"
    )

    destinationDirectory = sourceSets.main.java.destinationDirectory

    classpath = sourceSets.main.compileClasspath

    options.encoding = 'UTF-8'
    options.debug = true

    options.compilerArgs << "-Xlint:unchecked" << "-Xlint:deprecation"
}

tasks.register('compileAnnotationApp', JavaCompile) {
    dependsOn compileAnnotationBase
    
    source = files(
            "${projectDir}/simplest/SimpleTest.java"
    )
    destinationDirectory = sourceSets.main.java.destinationDirectory

    classpath = files(compileAnnotationBase.destinationDirectory)

    options.encoding = 'UTF-8'
    options.debug = true

    options.annotationProcessorPath = files(compileAnnotationBase.destinationDirectory)
    options.compilerArgs << '-processor' << 'annotations.simplest.SimpleProcessor'
}


执行compileAnnotationApp,可以得到预期的执行结果(这里我把SimpleProcessor的返回值改为了true,请忽略模拟命令行它是调试脚本时的输出):

options.annotationProcessorPath

使用gradle时需要注意要配置这个属性,否则脚本一直报找不到注解处理器。

这点其实困扰了我很久,之前使用javac直接编译时,我指定了classpath就能正确编译。所以我一直觉得配置好了JavaCompile的classpath就能正常运行,结果一直报错,把我卡了2天。。。后来查看JavaCompile文档看到了options.annotationProcessorPath配置,加上它才能正常编译。

这个配置,其实对应javac的-processorpath选项,它的作用是用于指定查找注解处理器的路径。但如果将上述的build.gradle代码中,注释options.annotationProcessorPath所在行,加上如下代码:

options.compilerArgs << '-processorpath' << compileAnnotationBase.destinationDirectory.get().asFile.absolutePath

依然是不成功的,gradle会让你使用options.annotationProcessorPath

看起来这是JavaCompile的一个强制用法。

-processorPath的作用

直接使用javac编译时,我并没有使用-processorpath选项,只设置了-classpath也能编译成功。如果通过classpath也能找到注解处理器,那么这个编译选项是不是多余存在的?

其实并不是多余的,它的作用有如下:

  1. 隔离注解处理器和应用程序依赖。注解处理器可能有自己的依赖库,这些依赖库可能与正在编译的应用程序的依赖库存在版本冲突。通过使用 -processorpath,可以将注解处理器及其依赖与应用程序的类路径隔离开来。

  2. 提高编译性能。因为-processorpath指定的路径包含的jar报或class文件比较少,在这里进行查找肯定比从classpath查找要快。

  3. 便于维护和惯例。使用-processorpath可以使注解处理器的配置更加清晰和明确,-processorpath指向的仅仅是编译时用到的目录而已。

JavaCompile把-processorpath配置单独提出来,成为options.annotationProcessorPath似乎也不无道理。

总结

之前没有系统学习过注解处理器,一些原生提供的选项也不熟悉,忽略了-processorpath这个配置选项。导致在编写gradle脚本时,一直报找不到processor,耽误了很多时间。而且JavaCompile文档也没有看仔细,遗漏了options.annotationProcessorPath配置。

下次遇到类似问题要先打好原生java的基础,并且看文档要仔细。

<think> 我们正在处理一个Jackson反序列化异常,具体是`InvalidDefinitionException`,原因是无法构造`TaskInfo`类的实例,且错误信息指向模块导出问题(module does not exports)和反射访问问题(InaccessibleObjectException)。结合引用和用户描述,问题核心在于: 1. 模块系统(JPMS)的访问控制:模块`org.example.synclickhousedata`没有将包含`TaskInfo`类的包`org.example.entity`导出给模块`com.fasterxml.jackson.databind`,导致Jackson无法通过反射访问该类的成员(如方法`getId`)。 2. 反射访问限制:在Java 9及以上版本中,由于模块系统的引入,默认情况下不允许跨模块的深度反射访问。 引用中提到的解决方案包括: - 添加无参构造器(引用[3][4]) -> 但当前错误信息并非直接关于无参构造器,而是关于反射调用`setAccess`失败。 - 注册JavaTimeModule解决Java8时间问题(引用[2]) -> 显然当前问题不是时间问题。 - 错误信息明确提到模块导出问题。 因此,针对当前问题,解决方案应围绕模块系统的配置展开。 解决步骤: 1. **检查模块描述符(module-info.java)**: 在模块`org.example.synclickhousedata`的模块描述符中,需要将包含`TaskInfo`的包(即`org.example.entity`)导出给`com.fasterxml.jackson.databind`模块。 修改`module-info.java`,添加: ```java module org.example.synclickhousedata { // 其他requires语句... // 导出org.example.entity包给jackson.databind模块 exports org.example.entity to com.fasterxml.jackson.databind; } ``` 2. **如果问题仍然存在,考虑开放反射权限**: 如果Jackson还需要深度反射(例如访问私有成员),则需要使用`opens`语句打开包。但注意,`exports`仅允许公共成员的访问,而`opens`允许反射访问私有成员。在模块描述符中,可以添加: ```java module org.example.synclickhousedata { // ... // 打开整个包以允许运行时反射 opens org.example.entity to com.fasterxml.jackson.databind; } ``` 或者,如果只需要打开给Jackson,可以指定模块名。如果希望所有模块都可以反射访问,则可以使用`opens org.example.entity;`(不指定模块),但这会降低安全性。 3. **替代方案:在启动时添加JVM参数**(如果不想修改模块描述符): 在启动JVM时,使用`--add-exports`和`--add-opens`参数来临时解决访问问题。 例如: ``` --add-exports org.example.synclickhousedata/org.example.entity=com.fasterxml.jackson.databind ``` 或者为了反射访问: ``` --add-opens org.example.synclickhousedata/org.example.entity=com.fasterxml.jackson.databind ``` 4. **确保类定义正确**: 虽然当前错误不是直接由于缺少无参构造器引起,但确保`TaskInfo`类有一个无参构造器也是一个好习惯(除非使用构造器参数注解)。引用[3][4]提到了这一点。 5. **检查Jackson版本和模块**: 确保使用的Jackson版本支持Java模块系统(Jackson 2.10+对模块系统有较好的支持)。同时,确保正确注册了必要的模块,尤其是当`TaskInfo`包含Java 8时间类型时,需要注册`JavaTimeModule`(如引用[2]所示)。 总结:优先修改模块描述符,导出或打开对应的包给`com.fasterxml.jackson.databind`模块。 注意:如果项目未使用模块系统(即没有`module-info.java`),那么就不会出现模块导出问题。但用户明确提到了模块名,因此可以确定使用了模块系统。 因此,主要解决方案是修改模块描述符文件,添加`exports`或`opens`语句。 如果用户无法修改模块描述符(例如,该模块不属于用户项目),则考虑使用JVM启动参数。 </think> 针对您遇到的 `com.fasterxml.jackson.databind.exc.InvalidDefinitionException` 异常,结合模块系统(JPMS)的访问限制问题,解决方案如下: ### 核心问题分析 1. **异常根源**: - Jackson 无法通过反射访问 `org.example.entity.TaskInfo` 类的方法(如 `getId`) - 模块 `org.example.synclickhousedata` 未向 `com.fasterxml.jackson.databind` 模块开放权限(模块系统限制了跨模块反射访问) 2. **关键错误信息**: ```log module org.example.synclickhousedata does not "exports org.example.entity" to module com.fasterxml.jackson.databind ``` --- ### 解决方案(按优先级排序) #### ✅ 最佳方案:修改模块描述符(推荐) 在模块 `org.example.synclickhousedata` 的 `module-info.java` 中添加导出声明: ```java module org.example.synclickhousedata { // 其他依赖声明... // 解决方案:向Jackson模块开放实体包权限 opens org.example.entity to com.fasterxml.jackson.databind; // 或如果只需要公共成员访问(非私有字段/方法) // exports org.example.entity to com.fasterxml.jackson.databind; } ``` **原理**: `opens` 允许 Jackson 通过反射访问私有成员(序列化/反序列化必需),`exports` 仅允许访问公共成员[^1][^3]。 #### ⚙️ 替代方案:JVM启动参数 在启动命令中添加 JVM 参数(适用于临时调试): ```bash java \ --add-opens org.example.synclickhousedata/org.example.entity=com.fasterxml.jackson.databind \ -jar your-application.jar ``` **优势**:无需修改代码,适合快速验证问题。 #### 🔧 补充措施 1. **确保类符合序列化要求**: - 添加默认无参构造器(解决对象构造问题)[^3][^4] ```java public class TaskInfo { public TaskInfo() {} // 必需的无参构造器 // 其他字段和方法... } ``` 2. **检查字段访问权限**: ```java public class TaskInfo { private Long id; // Jackson默认需要getter/setter // 必须提供getter方法 public Long getId() { return id; } } ``` 3. **显式注册Jackson模块**(如涉及Java 8时间类型)[^2]: ```java ObjectMapper mapper = new ObjectMapper(); mapper.registerModule(new JavaTimeModule()); // 支持LocalDate等类型 ``` --- ### 验证步骤 1. 修改 `module-info.java` 后重新编译 2. 检查 Jackson 版本(建议 ≥2.12,对模块系统支持更完善) 3. 单元测试验证序列化: ```java @Test void testSerialization() throws JsonProcessingException { TaskInfo task = new TaskInfo(); ObjectMapper mapper = new ObjectMapper(); String json = mapper.writeValueAsString(task); // 应无异常 } ``` ### 根本原因图示 ```mermaid graph LR A[Jackson 尝试访问 TaskInfo] --> B[反射调用 getId] B --> C{模块权限检查} C -- 拒绝访问 --> D[抛出 InaccessibleObjectException] D --> E[触发 InvalidDefinitionException] ``` > **关键点**:Java 9+ 的模块系统默认禁止跨模块反射访问私有成员,必须通过 `opens` 显式授权[^1][^3]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值