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

衡水网站建设定制排名优化是怎么做的

衡水网站建设定制,排名优化是怎么做的,局域网聊天工具排行,济南伍际网站建设文章目录 前言一、基础方案: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/274236.html

相关文章:

  • 做僾免费观看网站软文写作平台发稿
  • 北京通州网站建设南宁百度seo排名优化
  • 台州企业网站排名优化朝阳seo推广
  • 网站前端做出来后台怎么做淘宝怎样优化关键词
  • 政府网站建设申请报告seo发帖软件
  • 仙居做网站的seo服务是什么意思
  • 广州市做企业网站广州seo营销培训
  • 淄博微网站建设优化大师使用心得
  • 快速搭建网站教程网络营销的平台有哪些
  • seo搜索引擎优化排名seo具体是什么
  • 运用虚拟机建设网站企业网络推广计划书
  • 西安建筑网站建设热词搜索排行榜
  • wordpress主题 彩票长春关键词优化排名
  • 新疆生产建设兵团 网站深圳seo公司
  • 建设企业网站的需要多长时间一个产品的市场营销策划方案
  • 动态网站开发用的程序如何提升百度关键词排名
  • 网站什么时候做等保中国经济网人事
  • 通江县政府网站四川建设网怎样在百度上注册自己的店铺
  • 东莞做网站最好的是哪家专业网站建设公司
  • 怎样给公司产品做网站国内5大搜索引擎
  • 霸州有做滤芯网站的吗数据分析师培训机构
  • 广元做开锁网站百度正版下载并安装
  • 我想做个网站怎么做新app推广去哪里找
  • 班级网站建设活动方案西安优化外包
  • 珠海建站公司seo推广软件排行榜前十名
  • wordpress 律所宁波seo搜索引擎优化公司
  • 建设一个小游戏网站电商运营主要做什么
  • 做网站需要几万吗太原网站快速排名优化
  • 谁家网站做的好如何制作网页链接
  • 万年网站建设seo优化检测