注解的使用踩过坑

参考:https://mp.weixin.qq.com/s/_41ebOwXhP3QGwiwYk1QLQ

问题背景

很久很久前,在我还是青铜的时候(现在依旧是青铜段位)去面试,面试官问我怎么获取类,方法上的注解。

当时的我也算用过注解,顺口就回答了,用 isAnnotationPresent判断是否加了注解, getAnnotation获取注解对象,然后获取注解中的值。

大致的代码是这样子的:

Class<?> clz = bean.getClass();Method[] methods = clz.getMethods();for (Method method : methods) {    if (method.isAnnotationPresent(Encrypt.class)) {         String uri = method.getAnnotation(Encrypt.class).value();    }}复制代码

正在我沾沾自喜的时候,面试官又乘胜追击了,那么在读取注解的时候,有没有什么情况会导致刚刚你说的方式是不能成功判断和读取的呢?

这我一下蒙圈了,还会有读取不到的情况么?之前没遇到过啊,于是我斩钉截铁的回答面试官,不可能读取不到的,面试官笑了笑............

在我的加密框架monkey-api-encrypt(https://github.com/yinjihuan/monkey-api-encrypt)中,支持了注解标识加解密的功能,其实是通过读取注解,转换成uri的操作。

一开始也是用的上面的方式进行注解的读取操作,当我们程序中的Controller被AOP切入后,注解读取不到了,这就是今天要分享的问题。

正常情况下,我们的class是 com.cxytiandi.eureka_client.controller.ArticleController这种形式,如果用了AOP后,那么就会变成 com.cxytiandi.eureka_client.controller.ArticleController$$EnhancerBySpringCGLIB$$3323dd1e这样了。

解决方案一

这种情况下拿到的Method也是被代理了的,所以Method上的注解自然获取不到,既然知道原因了,最简单快速的解决方法就是将多余的内容截取掉,然后重新得到一个没有被代理的Class对象,通过这个Class对象来获取Method,这样就可以获取到Method上的注解。

Class<?> clz = bean.getClass();
String fullName = clz.getName();
if (fullName.contains("EnhancerBySpringCGLIB") || fullName.contains("$$")) { 
   fullName = fullName.substring(0, fullName.indexOf("$$"));    
   try {        
       clz = Class.forName(fullName);   
   } catch (ClassNotFoundException e) { 
       throw new RuntimeException(e);  
  }
}
Method[] methods = clz.getMethods();
for (Method method : methods) {   
 if (method.isAnnotationPresent(Encrypt.class)) {  
       String uri = method.getAnnotation(Encrypt.class).value();   
 }
}复制代码

解决方案二

虽然问题解决了,但是还是觉得不够优雅,有没有更好的方式呢?我们可以用Spring里面提供的AnnotationUtils来读取注解。

Encrypt encrypt = AnnotationUtils.findAnnotation(method, Encrypt.class);
if (encrypt != null) { 
   String uri = encrypt.value();
}复制代码

AnnotationUtils.findAnnotation()原理是什么呢?为什么它可以获取到被代理后方法上的注解呢?

要想知道原理,那就只能看源码啦,源码多,不贴出来了,贴一点点关键的就行了

首先会构建一个AnnotationCacheKey,从本地缓存中获取,如果有的话直接返回,也就意味着只要读取过就会被缓存起来:

AnnotationCacheKey cacheKey = new AnnotationCacheKey(method, annotationType);
A result = (A) findAnnotationCache.get(cacheKey);复制代码

然后就是判断是否桥接方法,如果不是就直接返回,是的话则获取桥接方法的注解,如果还获取不到就通过接口来获取。

Method resolvedMethod = BridgeMethodResolver.findBridgedMethod(method);
   result = findAnnotation((AnnotatedElement) resolvedMethod, annotationType);
if (result == null) {    
   result = searchOnInterfaces(method, annotationType, method.getDeclaringClass().getInterfaces());
}复制代码

后面就不继续下去了,最关键的代码其实是这句:

clazz = clazz.getSuperclass();复制代码

因为CGLIB代理会为目标类动态生成一个子类,所以我们要获取最原始的类,直接使用getSuperclass就可以了,跟第一种方案是一致的,只是第一种看起来有点那啥哈.....

推荐大家用AnnotationUtils去获取,这里面封装了很多的逻辑,考虑了很多场景下的问题,切莫重复造轮子。


转载于:https://juejin.im/post/5ce5f7c8f265da1bc07e10fa

<think>嗯,用户这次问的是Hibernate如何通过实体类注解自动生成数据库表。这个问题其实在ORM框架里很典型,用户应该是个Java开发者,可能正在搭建Spring或Hibernate项目。从引用内容看,用户已经收集了相关资料,但需要更落地的配置指导。特别注意到引用[1]提到IDEA添加Hibernate支持,引用[2]详细解释了hbm2ddl的几种模式——这说明用户真正关心的是具体配置步骤和参数选择,不是理论概念。用户可能过的是:配置文件位置不对(比如把配置写在SpringBoot的application.properties里但Hibernate没识别),或者对update模式的理解有偏差(例如以为增删字段都支持)。深层需求应该是“如何避免手动执行SQL脚本”,这在敏捷开发中很常见。需要重点提醒三个易错点:1)表名/字段名大小写敏感问题2)实体类扫描范围配置3)生产环境必须关闭自动更新。另外引用[3]提到用反射+注解实现ORM的思路很有趣,虽然和当前问题无关,但可以作为延伸知识点备用。回复时要突出实用性:先给出最短路径的配置示例,再解释关键参数,最后强调生产环境的风险控制。考虑到用户参考了MyBatis的做法(引用[4]),可以适当对比两种框架的实现差异。</think> 根据您的需求,以下是使用Hibernate通过实体类注解自动生成数据库表的完整配置方法(基于Spring Boot环境): ### 🛠 核心配置步骤 1. **添加依赖** (pom.xml) ```xml <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency> <dependency> <groupId>mysql</groupId> <!-- 按实际数据库调整 --> <artifactId>mysql-connector-java</artifactId> </dependency> ``` 2. **配置数据库连接与DDL策略** (application.properties) ```properties # 数据源配置 spring.datasource.url=jdbc:mysql://localhost:3306/your_db?createDatabaseIfNotExist=true spring.datasource.username=root spring.datasource.password=your_password # Hibernate DDL自动生成策略 ✅ spring.jpa.hibernate.ddl-auto=update # 关键配置 spring.jpa.show-sql=true # 显示生成SQL(调试用) spring.jpa.properties.hibernate.format_sql=true ``` 3. **实体类注解示例** ```java @Entity @Table(name = "users") // 指定表名 public class User { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @Column(name = "username", length = 50, unique = true, nullable = false) private String name; @Column(columnDefinition = "TEXT") private String bio; // Lombok注解(可选) @Data @NoArgsConstructor // 其他字段和方法... } ``` ### ⚙ 关键参数解释 `spring.jpa.hibernate.ddl-auto` 可选值: - **`update`**:启动时更新表结构(**推荐开发环境**) - `create`:每次删除旧表后重建(**数据会丢失!**) - `create-drop`:程序关闭时删除表(测试用) - `validate`:仅验证表结构不修改 - `none`:禁用DDL自动操作 > 📌 生产环境务必设置为 **`validate`** 或 **`none`** ,避免数据丢失风险 [^2][^4] ### 🚨 常见问题解决 1. **表未生成** - 检查实体类是否在Spring扫描路径(如 `@SpringBootApplication` 所在包或其子包) - 确认是否添加 `@EntityScan` 注解扫描外部包 2. **字段与预期不符** - 检查 `@Column` 注解是否正确应用 - 默认规则:未加注解的字段仍会生成列,列名=字段名(全小写) 3. **多数据源配置** 需单独指定Hibernate配置: ```java @Bean public LocalContainerEntityManagerFactoryBean entityManagerFactory(DataSource dataSource) { HibernateJpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter(); vendorAdapter.setGenerateDdl(true); // ✅ 启用DDL生成 LocalContainerEntityManagerFactoryBean factory = new LocalContainerEntityManagerFactoryBean(); factory.setJpaVendorAdapter(vendorAdapter); factory.setPackagesToScan("com.example.entities"); factory.setDataSource(dataSource); return factory; } ``` ### 🔄 工作原理 1. 应用启动时Hibernate扫描所有 `@Entity` 类 2. 根据注解解析表/字段/约束信息 3. 与当前数据库元数据比对 4. 执行差异SQL(如 `ALTER TABLE ADD COLUMN...`)[^3] > 💡 开发时建议结合 [Liquibase](https://www.liquibase.org/) 或 [Flyway](https://flywaydb.org/) 管理版本化数据库变更
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值