工厂+策略模式之最佳实践(疾病报卡维护模块API设计)

目录

💻业务场景

🔧应用技术

⚙概要流程

❗开发注意

[服务类上标注了 自定义注解 却无法直接利用getDeclaredAnnotation 获取](#服务类上标注了 自定义注解 却无法直接利用getDeclaredAnnotation 获取)

*Spring代理机制

代理机制的工作原理

代理的工作机制

代理的使用场景

[已获取EmrXXXServiceImpl 的Class,如何获取public class EmrXXXServiceImpl extends ServiceImpl implements EmrXXXService 泛型类型:EmrXXX,>](#已获取EmrXXXServiceImpl 的Class,如何获取public class EmrXXXServiceImpl extends ServiceImpl implements EmrXXXService 泛型类型:EmrXXX,>)

工厂类


💻业务场景

某医院急诊系统需要于诊断列表关联相关疾病报卡,并且当诊断提交后,支持医生进行报卡的维护,可与HIS系统端的公卫下疾病报卡管理模块进行联动。

经调研,考虑各个报卡的新增操作的数据流(各个报卡对应的数据库表不一)有出入,相关业务流一致,则可利用设计模式中的策略+工厂模式,以及泛型、反射、注解等技术实现相关上层API的设计。到达代码简洁,避免硬编码,且更易于扩展和支持更多类型的报卡的目的。

利用自定义报卡标识注解将每个服务的实例化逻辑封装成不同的策略类;利用仿写Spring Bean工厂的方式,通过依赖注入和组件扫描来自动管理 Bean 的创建和查找,利用符合单一性原则自定义注解,服务类自行声明它们所支持的疾病类型,工厂可动态根据已报卡的服务去初始化报卡服务实例。

🔧应用技术

  • **枚举:**报卡相关固化数据的常量(提高代码可读性和可维护性、增强类型安全、简化代码)
  • **泛型:**API设计,以此提供更加灵活和通用的接口,实现接口通用。注意泛型擦除机制
  • **反射:**灵活获取指定类对象,进行类之父类上的泛型Type的获取等操作
  • **注解:**是标识各个报卡服务,支持后续Bean工厂初始化集合存储报卡Service Bean
  • **框架:**Spring、Mybatis Plus

⚙概要流程

❗开发注意

服务类上标注了 自定义注解 却无法直接利用getDeclaredAnnotation 获取

已知注解于类的声明处,并且继承链无误(Java 的注解默认不会被继承,除非使用 @Inherited 元注解),注解作用范围为 ElementType.TYPE,并且保留策略是 RetentionPolicy.RUNTIME

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface SupportedDiseaseType {
    String value();
}

最终定位错误为Spring 使用代理机制来管理 Bean,从而导致注解无法直接通过 getDeclaredAnnotation 获取。特别是当使用 CGLIB 代理时,代理类不会继承原始类的注解。

则利用Spring 提供了 AnnotatedElementUtils 工具类,它可以更可靠地获取注解,包括处理代理类的情况。

java 复制代码
SupportedDiseaseType annotation = AnnotatedElementUtils.findMergedAnnotation(service.getClass(), SupportedDiseaseType.class);

*Spring代理机制

Spring 使用代理机制来管理 Bean,主要是为了实现 AOP(面向切面编程)和事务管理等功能。Spring 的代理机制允许在不修改原有代码的基础上,动态地为目标对象添加额外的功能,比如日志记录、性能监控、事务管理等。

代理机制的工作原理

Spring 中的代理有两种常见的方式:

1. JDK 动态代理(基于接口):

  • 通过反射机制创建一个实现目标对象接口的代理类,并将代理类与目标对象绑定。

  • 适用于目标类实现了接口的情况。

2. CGLIB 代理(基于子类):

  • 通过继承目标类,动态创建一个目标类的子类,并在该子类中织入增强代码。

  • 适用于目标类没有实现接口的情况,或者目标类没有实现接口但需要增强的情况。

Spring 通过这两种代理方式在运行时动态地生成代理对象,来增强目标对象的行为。代理对象和目标对象的使用是透明的,开发者只需要关注接口或原有类,Spring 会自动为其注入代理功能。

代理的工作机制
  • **生成代理对象**:Spring 使用 JDK 动态代理或 CGLIB 代理创建一个代理对象,这个对象通常是目标对象的一个包装。

  • **拦截方法调用**:每次调用代理对象的方法时,Spring 会通过代理对象执行相关的增强逻辑,例如日志、事务、权限控制等。

  • **目标方法执行**:增强逻辑执行完毕后,代理对象会调用目标对象的实际方法。

代理的使用场景

1. AOP(面向切面编程):

  • 使用 Spring 的 AOP 可以在运行时动态地为对象添加横切关注点(如日志、事务等),而不修改目标对象的源代码。

2. 事务管理:

  • Spring 可以通过代理机制来自动管理数据库事务,确保在方法执行过程中根据需要进行事务的开启、提交或回滚。

3. 懒加载:

  • 代理对象可以通过懒加载的方式,在实际调用方法时才去创建目标对象,从而提高系统性能。

已获取EmrXXXServiceImpl 的Class,如何获取public class EmrXXXServiceImpl extends ServiceImpl<EmrXXXMapper, EmrXXX> implements EmrXXXService 泛型类型:EmrXXX

直接上代码!

java 复制代码
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;

public class Main {
    public static void main(String[] args) {
        // 假设你已经通过 AOP 得到了 `EmrXXXServiceImpl` 的 Class 对象
        Class<?> targetClass = EmrXXXServiceImpl.class;

        // 获取父类的类型参数
        Type genericSuperclass = targetClass.getGenericSuperclass();

        // 检查是否是带有泛型的类
        if (genericSuperclass instanceof ParameterizedType) {
            ParameterizedType parameterizedType = (ParameterizedType) genericSuperclass;

            // 获取泛型参数类型
            Type[] actualTypeArguments = parameterizedType.getActualTypeArguments();

            // 这里假设第一个泛型参数是 `EmrXXXMapper`
            // 第二个泛型参数是 `EmrXXX`
            Type secondGenericType = actualTypeArguments[1];

            // 输出泛型类型的类
            if (secondGenericType instanceof Class<?>) {
                Class<?> EmrXXXClass = (Class<?>) secondGenericType;
                System.out.println("泛型中的 EmrXXX 的类是: " + EmrXXXClass.getName());
            }
        }
    }
}

工厂类

工厂模式+策略模式最佳实践

java 复制代码
@Slf4j
@Component
public class DiseaseReportCardServiceFactory {

    private final List<DiseaseReportCardService<?>> services;
    private final Map<String, DiseaseReportCardService<?>> serviceCache = new ConcurrentHashMap<>();

    @Autowired
    public DiseaseReportCardServiceFactory(List<DiseaseReportCardService<?>> services) {
        this.services = services;
    }

    @PostConstruct
    public void initialize() {
        for (DiseaseReportCardService<?> service : services) {
            SupportedDiseaseType annotation = AnnotatedElementUtils.findMergedAnnotation(service.getClass(), SupportedDiseaseType.class);
            if (annotation == null) {
                log.error("Service {} 不含 @SupportedDiseaseType 注解", service.getClass().getName());
                continue;
            }
            String diseaseType = annotation.value();
            if (diseaseType == null || diseaseType.isEmpty()) {
                log.error("Service {} 的 @SupportedDiseaseType value为空", service.getClass().getName());
                continue;
            }
            serviceCache.put(diseaseType, service);
        }
    }

    public DiseaseReportCardService<?> getService(String diseaseType) {
        return serviceCache.get(diseaseType);
    }
}
相关推荐
Tomorrow'sThinker1 分钟前
25年1月更新。Windows 上搭建 Python 开发环境:Python + PyCharm 安装全攻略(文中有安装包不用官网下载)
开发语言·python·pycharm
禁默7 分钟前
深入浅出:Java 抽象类与接口
java·开发语言
小万编程1 小时前
【2025最新计算机毕业设计】基于SSM的医院挂号住院系统(高质量源码,提供文档,免费部署到本地)【提供源码+答辩PPT+文档+项目部署】
java·spring boot·毕业设计·计算机毕业设计·项目源码·毕设源码·java毕业设计
白宇横流学长1 小时前
基于Java的银行排号系统的设计与实现【源码+文档+部署讲解】
java·开发语言·数据库
123yhy传奇1 小时前
【学习总结|DAY027】JAVA操作数据库
java·数据库·spring boot·学习·mybatis
想要打 Acm 的小周同学呀1 小时前
亚信科技Java后端外包一面
java·求职·java后端
勉灬之1 小时前
封装上传组件,提供各种校验、显示预览、排序等功能
开发语言·前端·javascript
西猫雷婶4 小时前
python学opencv|读取图像(二十三)使用cv2.putText()绘制文字
开发语言·python·opencv
我要学编程(ಥ_ಥ)4 小时前
速通前端篇——JavaScript
开发语言·前端·javascript
lishiming03085 小时前
TestEngine with ID ‘junit-jupiter‘ failed to discover tests 解决方法
java·junit·intellij-idea