关于泛型T的类型动态获取

动态获取SpringAOP代理对象的泛型类型:ErpMqListener示例
本文介绍如何在SpringAOP环境中,通过AopUtils.getTargetClass()方法获取ErpMqListener的动态泛型类型,解决因代理对象导致的泛型擦除问题,适用于处理不同类型的接口实现。

定义一个接口ErpMqListener<T>,现有实现类ErpMqListenerImpl implements ErpMqListener<String>,现在希望根据ErpMqListenerImpl在运行时动态的获取泛型T的实际类型String.class。

先直接放代码

public static Class<?> getMessageType(ErpMqListener erpMessageListener) {
        Class<?> targetClass = AopUtils.getTargetClass(erpMessageListener);
        Type[] interfaces = targetClass.getGenericInterfaces();
        Class<?> superclass = targetClass.getSuperclass();
        while ((Objects.isNull(interfaces) || 0 == interfaces.length) && Objects.nonNull(superclass)) {
            interfaces = superclass.getGenericInterfaces();
            superclass = targetClass.getSuperclass();
        }
        if (Objects.nonNull(interfaces)) {
            for (Type type : interfaces) {
                if (type instanceof ParameterizedType) {
                    ParameterizedType parameterizedType = (ParameterizedType) type;
                    if (Objects.equals(parameterizedType.getRawType(), ErpMqListener.class)) {
                        Type[] actualTypeArguments = parameterizedType.getActualTypeArguments();
                        if (Objects.nonNull(actualTypeArguments) && actualTypeArguments.length > 0) {
                            return (Class) actualTypeArguments[0];
                        } else {
                            return Object.class;
                        }
                    }
                }
            }

        }
        return Object.class;
    }

一行行的看

AopUtils.getTargetClass

        首先,我们需要了解Spring AOP(面向切面编程)的基本概念。在Spring框架中,AOP允许开发者定义横切关注点(cross-cutting concerns),这些关注点是应用程序中多个位置都需要处理的通用功能,比如日志记录、事务管理、安全性等。通过将这些关注点与主要业务逻辑分离,AOP有助于提高代码的可维护性和可重用性。

        为了实现AOP,Spring创建了代理对象,这些代理对象包含了目标对象(即主要业务逻辑)和切面逻辑(即横切关注点)。代理对象在运行时拦截对目标对象的调用,并在必要时执行切面逻辑。

        现在,让我们来看看AopUtils.getTargetClass()方法的具体作用。这个方法接受一个对象作为参数,并尝试确定这个对象是否是Spring AOP代理对象。如果是代理对象,该方法将返回被代理的原始目标类的Class对象;如果不是代理对象,它将直接返回传入对象的类的Class对象。

这个方法在以下场景中特别有用:

  1. 判断对象类型:当你需要判断一个对象是否是某个特定类型的实例时,但由于该对象可能是一个代理对象,直接使用instanceof关键字可能会得到错误的结果。通过使用AopUtils.getTargetClass(),你可以获取到对象的原始类,然后再进行类型判断。
  2. 访问目标对象的方法或属性:有时,你可能需要直接访问目标对象的特定方法或属性,而不是通过代理对象。通过获取目标类的Class对象,你可以使用反射来调用这些方法或访问这些属性。
  3. 在切面中处理特定类型的目标对象:在编写切面时,你可能需要根据目标对象的类型来执行不同的逻辑。通过AopUtils.getTargetClass(),你可以在切面中获取到目标对象的原始类,并根据这个类来编写条件逻辑。

当然,如果你没有使用截面,可以直接

Class<?> targetClass = erpMessageListener.getClass();
Type[] interfaces = targetClass.getGenericInterfaces();

编译后的字节码会把所有泛型信息擦除(泛型擦除),也就是说,无法通过反射的办法拿到参数。但是Java提供了其他方法。getGenericInterfaces()方法就是拿到当前Class实现的所有接口信息。

带有泛型的Type是ParameterizedType类型,不带有泛型的Type是Class类型,可以参考下面代码

        for (Type type : interfaces) {
            if (type instanceof ParameterizedType) {
                System.out.println("带有泛型:" + type.getTypeName());
            } else if (type instanceof Class){
                System.out.println("非泛型:" + type.getTypeName());
            } else {
                System.out.println("N:" + type.getTypeName());
            }
        }
while循环

Interface A带有泛型T,其被Class B实现并定义Interface A泛型T为String.class,Class C继承Class B。此时Class C调用getGenericInterfaces()是没有任何返回的,因为它没有实现任何接口。所以就需要用一个while循环来不断向上寻找,找到实现的接口。

后面的逻辑就很好理解,不多说了。

<think> 我们正在讨论Java中如何动态指定类型。根据之前的引用和问题,用户希望了解动态指定T类型的方法。在Java中,由于类型擦除,运行时无法直接获取的具体类型,但我们可以通过一些技巧在运行时传递类型信息。 ### 动态指定类型的方法 Java中通常使用`Class<T>`对象来传递类型信息,以便在运行时动态指定类型。以下是几种常见的方法: #### 方法1:使用Class对象作为参数(最常用) 在方法签名中添加`Class<T>`参数,调用时传入具体的Class对象来指定类型。 ```java public <T> T createInstance(Class<T> clazz) throws InstantiationException, IllegalAccessException { return clazz.newInstance(); } // 使用示例 String str = createInstance(String.class); ``` #### 方法2:通过子类获取类型(适用于继承场景) 定义一个参数化基类,子类继承时指定具体类型,然后通过反射获取父类的类型。 ```java public abstract class GenericType<T> { private final Class<T> type; @SuppressWarnings("unchecked") public GenericType() { // 获取T的实际类型 this.type = (Class<T>) ((ParameterizedType) getClass().getGenericSuperclass()) .getActualTypeArguments()[0]; } public Class<T> getType() { return type; } } // 子类继承时指定类型 public class StringGenericType extends GenericType<String> {} // 使用 StringGenericType stringType = new StringGenericType(); Class<?> type = stringType.getType(); // 得到String.class ``` #### 方法3:使用TypeReference(适用于复杂) Jackson库中的`TypeReference`可以用来捕获复杂的类型信息(如`List<String>`)。 ```java ObjectMapper mapper = new ObjectMapper(); List<String> list = mapper.readValue(json, new TypeReference<List<String>>() {}); ``` ### 注意事项 1. **类型安全**:使用`Class<T>`参数的方法在编译时和运行时都是类型安全的。 2. **类型擦除限制**:由于Java在运行时被擦除,无法直接通过`T.class`获取类型,必须传递`Class<T>`对象。 3. **复杂类型**:对于嵌套(如`Map<String, List<Integer>>`),需要使用`TypeReference`或`ParameterizedType`。 ### 示例:动态创建指定类型的列表 ```java public <T> List<T> createList(Class<T> elementType) { return new ArrayList<T>(); // 虽然运行时类型被擦除,但创建集合时类型安全 } ``` ### 相关问题 1. 如何在Java中获取类的实际类型参数? 2. 使用`TypeToken`(Guava)和`TypeReference`(Jackson)有什么区别? 3. 如何动态创建数组? 4. Java类型擦除会带来哪些问题? 5. 如何通过反射调用方法? </think> ### 动态指定类型的方法 在 Java 中动态指定类型主要依赖于 **类型令牌(Type Token)** 机制,通过传递 `Class<T>` 对象来实现类型绑定。以下是完整解决方案: #### 1. 基本方法:使用 `Class<T>` 参数 ```java public <T> T createInstance(Class<T> clazz) { try { return clazz.getDeclaredConstructor().newInstance(); } catch (Exception e) { throw new RuntimeException("创建实例失败", e); } } // 使用示例 RequestPaying paying = createInstance(RequestPaying.class); RequestAccable accable = createInstance(RequestAccable.class); ``` **原理**: 通过 `Class<T>` 参数在编译期绑定类型 `T`,然后通过反射创建实例[^1][^4]。 --- #### 2. 处理复杂:使用 `TypeReference`(Jackson) 适用于嵌套类型(如 `List<String>`): ```java ObjectMapper mapper = new ObjectMapper(); // 读取JSON到复杂类型 List<RequestPaying> result = mapper.readValue(json, new TypeReference<List<RequestPaying>>() {}); // 转换对象到指定 Map<String, RequestAccable> map = mapper.convertValue(rawObj, new TypeReference<Map<String, RequestAccable>>() {}); ``` **原理**: `TypeReference` 通过匿名子类捕获类型信息[^5]。 --- #### 3. 继承方案:从父类获取类型 ```java public abstract class AbstractProcessor<T> { private final Class<T> type; @SuppressWarnings("unchecked") public AbstractProcessor() { // 获取T的实际类型 this.type = (Class<T>) ((ParameterizedType) getClass() .getGenericSuperclass()).getActualTypeArguments()[0]; } public void process(T obj) { System.out.println("处理类型: " + type.getName()); // 业务逻辑 } } // 使用:继承时指定具体类型 public class PayingProcessor extends AbstractProcessor<RequestPaying> {} new PayingProcessor().process(new RequestPaying()); // 自动识别RequestPaying ``` --- ### 关键技巧汇总 | 场景 | 方案 | 示例 | |------|------|------| | 简单类型 | `Class<T>` 参数 | `createInstance(MyClass.class)` | | 集合类型 | `TypeReference` | `new TypeReference<List<T>>()` | | 类型推断 | 构造器传参 | `new Wrapper<>(MyClass.class)` | | 继承结构 | 获取父类 | `getGenericSuperclass()` | --- ### 注意事项 1. **类型擦除限制** Java 在运行时被擦除,无法直接获取 `T.class`,必须显式传递 `Class<T>`[^1][^3] 2. **空安全初始化** 使用 `default(T)` 替代 `null`(值类型默认返回 0/false,引用类型返回 null)[^3]: ```java public <T> T getDefault() { return null; // 不安全 } // 正确方式 public <T> T getDefault(Class<T> clazz) { return clazz.isPrimitive() ? (T) DEFAULT_VALUES.get(clazz) : null; } ``` 3. **类型验证** 动态创建前进行类型检查: ```java if (!RequestPaying.class.isAssignableFrom(clazz)) { throw new IllegalArgumentException("无效类型"); } ``` --- ### 相关问题 1. 如何避免 Java 类型擦除导致的问题? 2. `TypeToken`(Guava)和 `TypeReference`(Jackson)有何区别? 3. 如何动态创建数组? 4. Java 中的通配符 `<?>` 和 `<T>` 适用场景有何不同? 5. 如何在运行时检测类型的继承关系?
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值