当前位置: 首页 > wzjs >正文

衡水网站建设地方实时seo排名点击软件

衡水网站建设地方,实时seo排名点击软件,wordpress和ecshop区别,知名高端网站建设企业文章目录 前言一、基础方案:Qualifier、Resource 的精准匹配Qualifier 示例Resource 示例 二、默认策略:Primary 的优雅降级Primary 标记主实现直接注入默认实现 三、 动态管理:Map 注入的灵活扩展四、高阶实践:自定义注解的语义增…

文章目录

  • 前言
  • 一、基础方案:@Qualifier、@Resource 的精准匹配
    • @Qualifier 示例
    • @Resource 示例
  • 二、默认策略:@Primary 的优雅降级
    • @Primary 标记主实现
    • 直接注入默认实现
  • 三、 动态管理:Map 注入的灵活扩展
  • 四、高阶实践:自定义注解的语义增强
    • 自定义注解
    • 普通类使用自定义注解标识
      • 被@RegisterEntity 注解标识的几个普通实体类
      • 初始化扫描注入工具ExportEntityRegistryInitializer
      • 注册列表及工厂类ExportEntityRegistryFactory
      • 接口工厂类OrderUploadServiceRegistryFactory
      • 初始化工具类OrderUploadServiceRegistryInitializer
  • 五、总结


前言

在 Spring 框架中,依赖注入(DI)的优雅性往往决定了代码的可维护性。当我们为一个接口编写多个实现类(如支付接口支持微信 / 支付宝 / 银联),如何准确获取所需的 Bean 实例?直接注入接口类型时,Spring 默认会抛出NoUniqueBeanDefinitionException,提示存在多个候选实现。这种情况下,硬编码 Bean 名称或暴力使用@Lazy注解并非长久之计,我们需要一套系统化的解决方案。

本文将通过以下四个维度,带你从基础到进阶掌握 Spring 多实现类注入的核心技巧:

  • 基础方案:@Qualifier、@Resource 的精准匹配
  • 默认策略:@Primary 的优雅降级
  • 动态管理:Map 注入的灵活扩展
  • 高阶实践:自定义注解的语义增强

笔者后续介绍的着重点在高阶实践这部分,也是笔者工作中用的比较多的一种。

一、基础方案:@Qualifier、@Resource 的精准匹配

@Qualifier 示例

@Service
public class ZooService {// 通过Bean名称精准注入Dog@Autowired@Qualifier("dog")  // 对应@Component("dog") 或默认类名首字母小写private Animal animal;public void makeSound() {animal.shout(); // 输出:汪汪汪}
}

@Resource 示例

@Service
public class ZooService {// 优先按名称匹配,名称未指定时默认使用字段名@Resource(name = "cat")private Animal animal;public void makeSound() {animal.shout(); // 输出:喵喵喵}
}

适用场景:

明确知道需要注入的 Bean 名称时使用,适合固定依赖关系的场景。


二、默认策略:@Primary 的优雅降级

@Primary 标记主实现

@Component
@Primary  // 标记为主实现
public class Dog implements Animal {@Override public void shout() { System.out.println("汪汪汪"); }
}@Component
public class Cat implements Animal {@Override public void shout() { System.out.println("喵喵喵"); }
}

直接注入默认实现

@Service
public class ZooService {// 自动注入@Primary标记的Dog@Autowiredprivate Animal animal;public void makeSound() {animal.shout(); // 输出:汪汪汪}
}

这里默认注入的就直接时被@Primary标记主实现类了,即为Dog类,如果想注入非主实现的类,则需要配合@Autowired和@Qulifier(“cat”)或者@Resource(name=“cat”)

// 服务类,注入非主要实现类
@Service
class ZooService {@Autowired@Qualifier("cat")private Animal animal;public void makeSound() {animal.shout();}
}//或者如下方式@Service
class ZooService {@Resource("cat")private Animal animal;public void makeSound() {animal.shout();}
}

三、 动态管理:Map 注入的灵活扩展

假如现在我有如下接口

public interface DataProvider {
}

这个接口呢有几个实现类,分别如下

@Service("bydSupplier")
public class BydDataProvider implements DataProvider {
}@Service("qrSupplier")
public class QrDataProvider implements DataProvider {
}@Service("cxSupplier")
public class CxDataProvider implements DataProvider {
}

那我现在要使用其中一个实现类,该怎么获取呢?
我们可以定义一个工厂类工具

@Component
public class DataProviderFactory {@Resourceprivate Map<String, DataProvider> providers;public DataProvider getDataProvider(String key) {return providers.get(key);}
}

这个providers就注入了DataProvider接口的所有实现类,其中key就是各个实现类上@Service注解中标识的name名称,但这种有个问题,就是每个实现类的名称绑死固定了,如果我@Service(“bydSupplier”)、@Service(“qrSupplier”)、@Service(“cxSupplier”)中的name名称是作为唯一标识,并且有其他类需要使用怎么办?而且我需要根据外部传入的唯一标识来获取对应的实现类名称,不能都写上@Service(“bydSupplier”)、@Service(“qrSupplier”)、@Service(“cxSupplier”),这时候就需要其他方法了,接着往下看


四、高阶实践:自定义注解的语义增强

自定义注解

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

这是一个自定义注解,专门用来注解标识名称的,笔者下面分两种情况来使用这个注解

普通类使用自定义注解标识

被@RegisterEntity 注解标识的几个普通实体类

@RegisterEntity("bydSupplier")
public class BydExportEntity extends BaseExportEntity{
}@RegisterEntity("qrSupplier")
public class QrExportEntity extends BaseExportEntity{
}@RegisterEntity("cxSupplier")
public class CxExportEntity extends BaseExportEntity {
}

初始化扫描注入工具ExportEntityRegistryInitializer

/*** 启动初始化扫描所有供应商导出实体类对应注解,并注册到EntityRegistryFactory工厂中*/
@Configuration
public class ExportEntityRegistryInitializer {// 指定扫描的包路径private static final String BASE_PACKAGE = "com.usteu.exportEntity";/*** 启动时初始化注册所有供应商导出实体类*/@Beanpublic CommandLineRunner initExportEntityRegistry() {return args -> {ClassPathScanningCandidateComponentProvider scanner =new ClassPathScanningCandidateComponentProvider(false);// 添加自定义注解过滤器scanner.addIncludeFilter(new AnnotationTypeFilter(RegisterEntity.class));// 扫描包路径scanner.findCandidateComponents(BASE_PACKAGE).forEach(beanDefinition -> {try {Class<?> clazz = ClassUtils.forName(Objects.requireNonNull(beanDefinition.getBeanClassName()), getClass().getClassLoader());RegisterEntity annotation = clazz.getAnnotation(RegisterEntity.class);String identifier = annotation.value();// 确保实现 Entity 接口if (!BaseExportEntity.class.isAssignableFrom(clazz)) {throw new RuntimeException(clazz.getName() + " 必须继承基础类BaseExportEntity");}// 注册构造逻辑ExportEntityRegistryFactory.register(identifier, () -> {try {return (BaseExportEntity) clazz.getDeclaredConstructor().newInstance();} catch (Exception e) {throw new RuntimeException("创建实例失败 " + clazz.getName(), e);}});} catch (ClassNotFoundException e) {throw new RuntimeException("类未发现: " + beanDefinition.getBeanClassName(), e);}});};}
}

上面的类中有一行代码是配置注解扫描路径的

 // 指定扫描的包路径private static final String BASE_PACKAGE = "com.usteu.exportEntity";

这个就是上面三个实体类所在包路径

在这里插入图片描述

注册列表及工厂类ExportEntityRegistryFactory

public class ExportEntityRegistryFactory {private static final Map<String, Supplier<? extends BaseExportEntity>> registry = new ConcurrentHashMap<>();public static void register(String identifier, Supplier<? extends BaseExportEntity> supplier) {registry.put(identifier, supplier);System.out.println("注册了"+supplier.get().getClass().getName());}public static BaseExportEntity getExportEntity(String identifier) {Supplier<? extends BaseExportEntity> supplier = registry.get(identifier);if (supplier == null) {throw new IllegalArgumentException("未知的实体类: " + identifier);}return supplier.get();}public static void clear() {registry.clear();}
}

这样的话在启动的时候,三个有注解了@RegisterEntity的普通的实体类会被获取注册到ExportEntityRegistryFactory工厂类中的Map对象registry中,使用时我们只需要传入标识即可获取对应的实体类

ExportEntityRegistryFactory.getExportEntity("bydSupplier")

上述代码获取的就是BydExportEntity实体类,一般情况下我们的普通实体类需要统一继承一个类,这样好封装,遵循里氏替换原则,用父类类型隐藏子类实例。


上面说的是普通实体类,那如果是接口呢,其实还是有点区别的,比如我有三个接口,每个接口又分别有自己的实现类,大概如下:

//公共接口
public interface IOrderUploadService {void uploadDataHandler(List<? extends BaseOrderModel> dataList);
}@RegisterEntity("bydSupplier")
public interface IBydOrderUploadService extends IOrderUploadService{
}@RegisterEntity("qrSupplier")
public interface IQrOrderUploadService extends IOrderUploadService{
}@RegisterEntity("cxSupplier")
public interface ICxOrderUploadService extends IOrderUploadService{
}//每个子接口的具体实现类
@Slf4j
@Service
public class BydOrderUploadServiceImpl implements IBydOrderUploadService {@Resourceprivate BydOrderDataService bydOrderDataService;@Override@SuppressWarnings("unchecked")@Transactional(rollbackFor = Exception.class)public void uploadDataHandler(List<? extends BaseOrderModel> dataList) {bydOrderDataService.saveBatchData((List<BydOrderModel>) dataList);}
}@Service
@Slf4j
public class QrOrderUploadServiceImpl implements IQrOrderUploadService {@Resourceprivate QrOrderDataService qrOrderDataService;@Override@SuppressWarnings("unchecked")@Transactional(rollbackFor = Exception.class)public void uploadDataHandler(List<? extends BaseOrderModel> dataList) {qrOrderDataService.saveBatchData((List<QrOrderModel>) dataList);}
}@Slf4j
@Service
public class CxOrderUploadServiceImpl implements ICxOrderUploadService {@Resourceprivate CxOrderDataService cxOrderDataService;@Override@SuppressWarnings("unchecked")@Transactional(rollbackFor = Exception.class)public void uploadDataHandler(List<? extends BaseOrderModel> dataList) {cxOrderDataService.saveBatchData((List<CxOrderModel>) dataList);}
}

针对以上代码,我最终想要获取的是每个具体实现类,并调用其中的方法。对应的注册工厂类和初始化注入spring容器类的代码如下:

接口工厂类OrderUploadServiceRegistryFactory

public class OrderUploadServiceRegistryFactory {private static final Map<String, Supplier<? extends IOrderUploadService>> registry = new ConcurrentHashMap<>();public static void register(String identifier, Supplier<? extends IOrderUploadService> supplier) {registry.put(identifier, supplier);System.out.println("注册了"+supplier.get().getClass().getName());}public static IOrderUploadService getOrderUploadService(String identifier) {Supplier<? extends IOrderUploadService> supplier = registry.get(identifier);if (supplier == null) {throw new IllegalArgumentException("未知的实体类: " + identifier);}return supplier.get();}public static void clear() {registry.clear();}
}

初始化工具类OrderUploadServiceRegistryInitializer


@Configuration
public class OrderUploadServiceRegistryInitializer {private static final String BASE_PACKAGE = "com.usteu.service.orderUpload";@Resourceprivate ApplicationContext applicationContext;@Beanpublic CommandLineRunner initOrderUploadServiceRegistry() {return args -> {// 创建自定义扫描器以支持接口扫描ClassPathScanningCandidateComponentProvider scanner =new ClassPathScanningCandidateComponentProvider(false) {@Overrideprotected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {AnnotationMetadata metadata = beanDefinition.getMetadata();// 允许接口和具体类作为候选组件return metadata.isIndependent() &&(metadata.isConcrete() || metadata.isInterface());}};scanner.addIncludeFilter(new AnnotationTypeFilter(RegisterEntity.class));scanner.findCandidateComponents(BASE_PACKAGE).forEach(beanDefinition -> {try {Class<?> clazz = ClassUtils.forName(Objects.requireNonNull(beanDefinition.getBeanClassName()), getClass().getClassLoader());RegisterEntity annotation = clazz.getAnnotation(RegisterEntity.class);if (annotation == null) return;String identifier = annotation.value();// 增强类型检查if (!IOrderUploadService.class.isAssignableFrom(clazz)) {throw new IllegalStateException(clazz.getName() + " 必须实现 IOrderUploadService 接口");}OrderUploadServiceRegistryFactory.register(identifier, () -> {try {return (IOrderUploadService) applicationContext.getBean(clazz);} catch (Exception e) {throw new RuntimeException("实例化失败: " + clazz.getName(), e);}});} catch (ClassNotFoundException e) {throw new RuntimeException("类加载失败: " + beanDefinition.getBeanClassName(), e);}});};}
}

这个初始化接口注入工具类实际注入的是每个子接口的实现类了,是从spring容器中获取的,笔者使用了ApplicationContext 这个应用程序上下文接口获取每个子接口的实现类,这个很关键,另外一个就是创建自定义扫描器以支持接口注解扫描这部分代码,和扫描普通实体类的代码还是有区别的,注意甄别。

 // 创建自定义扫描器以支持接口扫描
ClassPathScanningCandidateComponentProvider scanner =new ClassPathScanningCandidateComponentProvider(false) {@Overrideprotected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {AnnotationMetadata metadata = beanDefinition.getMetadata();// 允许接口和具体类作为候选组件return metadata.isIndependent() &&(metadata.isConcrete() || metadata.isInterface());}
};

五、总结

以上就是笔者在面对接口多实现时的一些实际处理方法,这里介绍给大家,方便各位码友参考借鉴,笔者技术能力有限,不足之处还请多多指教!

http://www.dtcms.com/wzjs/91296.html

相关文章:

  • 游戏网站建设的策划友情链接怎么互换
  • 网站后台权限分配说明南宁seo收费
  • wordpress做单页销售网站网站seo入门基础教程
  • 郑州网站制作郑州网站制作企业推广策略
  • 菜鸟教程网站建设百度下载官网
  • 西安做网站公司哪家行网络销售公司
  • 做网站 需要工信部备案吗做营销型网站哪家好
  • 做网站全屏尺寸是多少钱网站群发软件
  • 怎么自己做个免费网站中国国家数据统计网
  • 泰安市建设职工培训中心电话网站品牌运营管理公司
  • 互联网电子商务网站开发技术百度相册登录入口
  • 上海网站seo推广营销平台
  • 建网站 赚钱快速seo整站优化排行
  • 1元涨1000粉丝网站石家庄做网站推广排名的公司
  • 教育网站开发文档广西网站建设制作
  • 网站管理建站湖南长沙今日疫情
  • 牡丹江地区做网站的公司免费推广平台排行榜
  • 焦作会计做继续教育在哪个网站seo和sem的区别是什么?
  • wordpress微信h5电脑优化软件排行榜
  • 如何让网站互动起来今天的热点新闻
  • logo设计免费设计云优化seo软件
  • 在线酒店预定网站制作如何开发一个网站
  • 网站冲突关键词优化哪家好
  • 沈阳学校网站建设武汉网站推广排名
  • 打电话沟通做网站话术四川seo推广方案
  • 有没有做培养基的网站seo渠道
  • 天津滨海新区大爆炸seo点击排名工具
  • 佛山java web网站开发软文文案范文
  • 竹中建设官方网站投广告哪个平台好
  • 网站建设项目实施计划书小程序开发公司