接口多实现
创始人
2024-03-27 07:25:39

一、什么是接口
接口是抽象类的延伸,可以将它看作是纯粹的对象类

二、接口模式的特性
(1)接口不可以被实例化。

(2)实现类必须实现接口的所有方法(类似于抽象类和抽象方法)。

(3)实现类可以实现多个接口,不同接口之间使用“,”隔开。

(4)接口中的变量都是静态常量,即使用static和final修饰的最终变量(一般不在接口中声明变量)。

三、接口模式的优势
(1)更加抽象,更加面向对象

(2)提高编程的灵活性(提高了代码的可扩展性)

(3)实现高内聚、低耦合

(4)提高可维护性,降低系统维护成本

三、接口的默认方法

public interface DefaultFuncInter {int getInt();default String getString(){return "Default String";}
}

默认方法的优势

默认方法主要优势是提供了一种扩展接口的方法,而不破坏现有代码。如果一个已经投入使用的接口需要扩展一个新的方法,在JDK8以前,我们必须再该接口的所有实现类中都添加该方法的实现,否则编译会出错。如果实现类数量很少且我们有修改的权限,可能工作量会少,但是如果实现类很多或者我们没有修改代码的权限,这样的话就很难解决了。而默认方法提供了一个实现,当没有显式提供时就默认采用这个实现,这样新添加的接口就不会破坏现有的代码。默认方法另一个优势是该方法是可选的,子类可以根据不同的需求而且经override或者采用默认实现。例如我们定义一个集合几口,其中有增、删、改等操作,如果我们的实现类90%都是以数组保存数据,那么我们可以定义针对这些方法给出默认实现,而对于其他非数组集合或者有其他类似业务,可以选择性复写接口中默认方法。(由于接口不允许有成员变量,所以本示例旨在说明默认方法的优势,并不具有生产可能性)

四、接口的静态方法

java8中为接口新增了一项功能:定义一个或者更多个静态方法。类似于类中的静态方法,接口定义的静态方法可以独立于任何对象调用。所以,在调用静态方法时,不需要实现接口,也不需要接口的实例,也就是说和调用类的静态方法的方式类似。语法如:接口名字.静态方法名
interface A  
{  static String getName()  {  return "接口A。。。";  }  
}public class Test implements A  
{  public static void main(String[] args)  {  System.out.println(A.getName());  }   
}

注意:实现接口的类或者子接口不会继承接口中的静态方法。static不能和default同时使用。在java8中很多接口中都增加了静态方法

五、接口的实现
定义接口:

public interface IPrint {void print(String msg);
}

实现类1:

@Component
public class ConsolePrint implements IPrint {@Overridepublic void print(String msg) {System.out.println("console print: " + msg);}}

实现类2:

@Component
public class LogPrint implements IPrint {@Overridepublic void print(String msg) {System.out.println("logPrint print: " + msg);}
}

(1)一个接口只有一个实现类
如果接口只有一个实现,这里的logPrint可以用任何字符串替代

@Autowiredprivate IPrint logPrint / abc;

(2)一个接口多个实现类,如何指定特定实现类进行调用

方法一: 接口有多个实现类,使用的时候,要表明具体使用的是哪一个实现,也就是使用具体实现类的名字

@RestController
@RequestMapping("/api/{edition}/page")
public class MyPageHelperController {@Autowiredprivate IPrint logPrint / consolePrint; //这里的logPrint / consolePrint的名字就是实现类的bean的名字,所以可以正确引入@GetMapping("/myPage")@ResponseBodypublic Map expenseStatement(HttpServletRequest request, HttpServletResponse response)  {logPrint.print("zjg");return new ConcurrentHashMap<>();}
}

方法二: 直接使用实现类进行注入

@RestController
@RequestMapping("/api/{edition}/page")
public class MyPageHelperController {@Autowiredprivate ConsolePrint abc; //这里使用实现类进行注入//@Autowired//private LogPrint abc;@GetMapping("/myPage")@ResponseBodypublic Map expenseStatement(HttpServletRequest request, HttpServletResponse response)  {abc.print("zjg");return new ConcurrentHashMap<>();}
}

方法三: 指明实现类的优先级

在写实现类的时候事先指明实现类的优先级,注入的时候就会使用优先级高的实现类。在调用的类中注入接口,默认使用的是Primary 标注的实现类的方法
@Service("dog")
@Primary
public class Dog implements Animal {.......
}

方法四: 通过@Autowride和@Qualifier两个注解配合使用

@Autowired
@Qualifier("dog")
private Animal animal;    //正常启动

方法五: 使用@Resource注解,默认类名区分

@Resource(name = "dog")
private Animal animal;     //正常启动

方法六: 使用@Resource注解,根据@Service区分

@Resource(name = "dogImpl")
private Animal animal;     //正常启动

方法七: 直接new 一个实例


private Animal animal = new Dog();     //正常启动

(3)接口实现的动态调用
方法一:ApplicationContextAware + getBeansOfType

通过spring 的ApplicationContext(应用上下文)的getBeansOfType 方法传入接口类型,一次性获取所有实现类
 Map getBeansOfType(Class var1) throws BeansException;

定义了一个接口,来对外提供服务,这个接口下的方法不能随便改变,而接口有一系列实现,且实现还在不断添加,如何在传入外部不同的条件下,实现实时更换接口的实现类

package com.util;import com.service.TestService;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.Configurable;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;import java.util.HashMap;
import java.util.Map;@Component
public class TestServiceFactory implements ApplicationContextAware {private static Map testServiceMap;@Overridepublic void setApplicationContext(ApplicationContext applicationContext) throws BeansException {Map map = applicationContext.getBeansOfType(TestService.class);testServiceMap = new HashMap<>();map.forEach((key,value) -> testServiceMap.put(value.getCode(),value));}public TestService getTestService(TypeEnum typeEnum) {return testServiceMap.get(typeEnum);}
}或者如下方式也可以@Component
public class OrderInfoServiceImpl implements ApplicationContextAware {private ApplicationContext applicationContext;public void query(String username){OrderInfoDao dao = null;if("A".equals(username)){dao = (OrderInfoDao) applicationContext.getBean("orderInfoDaoAImpl");}else if("B".equals(username)){dao = (OrderInfoDao) applicationContext.getBean("orderInfoDaoBImpl");}dao.queryOrderList();}/*** ApplicationContextAware接口的实现类,可以拿到上下文* @param applicationContext* @throws BeansException*/@Overridepublic void setApplicationContext(ApplicationContext applicationContext) throws BeansException {this.applicationContext = applicationContext;}
}

怎么根据外部条件实现获得对应的实现类?

可以在接口中加一个getCode方法,实现类实现这个方法,然后返回一个定义的枚举类型,然后将getBeansOfType获得map进行转换

package com.util;public enum  TypeEnum {impl1,impl2
}

接口

package com.service;import com.util.TypeEnum;
import org.springframework.stereotype.Service;@Service
public interface TestService {public TypeEnum getCode();public String test();}

实现类1

package com.service.impl;import com.service.TestService;
import com.util.TypeEnum;
import org.springframework.stereotype.Service;@Service
public class TestServiceImpl1 implements TestService {@Overridepublic TypeEnum getCode() {return TypeEnum.impl1;}@Overridepublic String test() {return this.toString();}
}

实现类2

package com.service.impl;import com.service.TestService;
import com.util.TypeEnum;
import org.springframework.context.annotation.Primary;
import org.springframework.stereotype.Service;@Service
public class TestServiceImpl2  implements TestService {@Overridepublic TypeEnum getCode() {return TypeEnum.impl2;}@Overridepublic String test() {return this.toString();}
}

使用

package com.controller;import com.util.TestServiceFactory;
import com.util.TypeEnum;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import com.service.TestService;import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;@Controller
@RequestMapping("test")
public class TestController {@AutowiredTestServiceFactory testServiceFactory;private TestService testService;@ResponseBody@RequestMapping("test")public String test(HttpServletRequest request, HttpServletResponse response){String type = request.getParameter("type");testService = getTestService(type);return testService.test();}public TestService getTestService(String type) {TypeEnum typeEnum = null;if(type.equals("1")) typeEnum = TypeEnum.impl1;if(type.equals("2")) typeEnum = TypeEnum.impl2;return testServiceFactory.getTestService(typeEnum);}}

方法二:使用@Autowired注入Map实现

@Component
public class OrderInfoServiceImpl {/*** 用这个方式Spring会在初始化时,自动把OrderInfoDao接口的实现类,全放到Map里*/@Autowiredprivate Map map;public void query(String username){// 拼接Dao实现类的名称String daoName = "orderInfoDao" + username + "Impl";OrderInfoDao dao = map.get(daoName);if(dao != null){dao.queryOrderList();}}}

方法三:使用@Conditional注解实现

@Conditional是Spring4新提供的注解,它的作用是按照一定的条件进行判断,满足条件给容器注册bean。* 可以作为类级别的注解直接或者间接的与@Component相关联,包括@Configuration类;
* 可以作为元注解,用于自动编写构造性注解;
* 作为方法级别的注解,作用在任何@Bean方法上。* 一个方法只能注入一个bean实例,所以@Conditional标注在方法上只能控制一个bean实例是否注入。* 一个类中可以注入很多实例,@Conditional标注在类上就决定了一批bean是否注入。

如下示例:
接口:

package com.cj.interfaces;public interface ITestServiceConditional {void test();
}

实现1:

package com.cj.interfaces;import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Configuration;
import org.springframework.stereotype.Service;/*** 使用配置文件控制注入:* 我们需要在实现类上添加配置文件注解,使用配置文件控制该实现类是否生效*/
@Service
@Configuration
@ConditionalOnProperty(prefix = "demo.test.service",name = "impl",havingValue = "one")
public class TestServiceImpl4 implements ITestServiceConditional {@Overridepublic void test() {System.out.println("接口4实现类 ...");}
}

实现2:

package com.cj.interfaces;import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Configuration;
import org.springframework.stereotype.Service;@Service
@Configuration
@ConditionalOnProperty(prefix = "demo.test.service",name = "impl",havingValue = "two")
public class TestServiceImpl5 implements ITestServiceConditional {@Overridepublic void test() {System.out.println("接口5实现类 ...");}
}

配置文件:
application.properties

demo.test.service.impl=one

测试:

    @AutowiredITestServiceConditional testServiceConditional;@Testpublic void test1() {testServiceConditional.test();}

@Conditional扩展注解:
@ConditionalOnBean 当容器中至少存在一个指定name或class的Bean时,进行实例化 OnBeanCondition @ConditionalOnBean(CacheManager.class)
@ConditionalOnMissingBean 当容器中指定name或class的Bean都不存在时,进行实例化 OnBeanCondition @ConditionalOnMissingBean(CacheManager.class)
@ConditionalOnClass 当类路径下至少存在一个指定的class时,进行实例化 OnClassCondition @ConditionalOnClass({Aspect.class, Advice.class })
@ConditionalOnMissingClass 当容器中指定class都不存在时,进行实例化 OnClassCondition @ConditionalOnMissingClass(“org.thymeleaf.templatemode.TemplateMode”)
@ConditionalOnSingleCandidate 当指定的Bean在容器中只有一个,或者有多个但是指定了首选的Bean时触发实例化 OnBeanCondition @ConditionalOnSingleCandidate(DataSource.class)
@ConditionalOnProperty 当指定的属性有指定的值时进行实例化 OnPropertyCondition @ConditionalOnProperty(prefix = “spring.aop”, name = “auto”)
@ConditionalOnResource 当类路径下有指定的资源时触发实例化 OnResourceCondition @ConditionalOnResource(resources = “classpath:META-INF/build.properties”)
@ConditionalOnExpression 基于SpEL表达式的条件判断,当为true的时候进行实例化 OnExpressionCondition @ConditionalOnExpression(“true”)
@ConditionalOnWebApplication 当项目是一个Web项目时进行实例化 OnWebApplicationCondition @ConditionalOnWebApplication
@ConditionalOnNotWebApplication 当项目不是一个Web项目时进行实例化 OnWebApplicationCondition @ConditionalOnNotWebApplication
@ConditionalOnJava 当JVM版本为指定的版本范围时触发实例化 OnJavaCondition @ConditionalOnJava(ConditionalOnJava.JavaVersion.EIGHT)
@ConditionalOnJndi 在JNDI存在的条件下触发实例化 OnJndiCondition @ConditionalOnJndi({ “java:comp/TransactionManager”})

方法三:使用@Conditional注解 + Condition接口实现

自定义Condition条件

/*** 自定义Condition条件*/
public class WindowsCondition implements Condition {//这里的逻辑根据实际情况进行处理/*** @param conditionContext:判断条件能使用的上下文环境* @param annotatedTypeMetadata:注解所在位置的注释信息* */@Overridepublic boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata annotatedTypeMetadata) {//获取ioc使用的beanFactoryConfigurableListableBeanFactory beanFactory = conditionContext.getBeanFactory();//获取类加载器ClassLoader classLoader = conditionContext.getClassLoader();//获取当前环境信息Environment environment = conditionContext.getEnvironment();//获取bean定义的注册类BeanDefinitionRegistry registry = conditionContext.getRegistry();//获得当前系统名String property = environment.getProperty("os.name");//包含Windows则说明是windows系统,返回trueif (property.contains("Windows")){return true;}return false;}
}

示例1:注解标注在方法上

@Configuration
public class BeanConfig {//只有一个类时,大括号可以省略//如果WindowsCondition的实现方法返回true,则注入这个bean    @Conditional({WindowsCondition.class})@Bean(name = "bill")public Person person1(){return new Person("Bill Gates",62);}//如果LinuxCondition的实现方法返回true,则注入这个bean@Conditional({LinuxCondition.class})@Bean("linus")public Person person2(){return new Person("Linus",48);}
}

示例2:注解标注在类上
将BeanConfig改写,这时,如果WindowsCondition返回true,则两个Person实例将被注入(注意:上一个测试将os.name改为linux,这是我将把这个参数去掉)

@Conditional({WindowsCondition.class})
@Configuration
public class BeanConfig {@Bean(name = "bill")public Person person1(){return new Person("Bill Gates",62);}@Bean("linus")public Person person2(){return new Person("Linus",48);}
}
多个条件类@Conditional注解传入的是一个Class数组,存在多种条件类的情况。
@Conditional({WindowsCondition.class,ObstinateCondition.class})
@Configuration
public class BeanConfig {@Bean(name = "bill")public Person person1(){return new Person("Bill Gates",62);}@Bean("linus")public Person person2(){return new Person("Linus",48);}
}

结论:
第一个条件类实现的方法返回true,第二个返回false,则结果false,不注入进容器。

第一个条件类实现的方法返回true,第二个返回true,则结果true,注入进容器中。

(4)SPI机制

服务提供者接口(Service Provider Interface,简写为SPI)是JDK内置的一种服务提供发现机制。可以用来加载框架扩展和替换组件,主要是被框架的开发人员使用

Java中SPI机制主要思想是将装配的控制权移到程序之外,是“基于接口的编程+策略模式+配置文件”组合实现的动态加载机制,有点类似Spring的IOC机制。在模块化设计中这个机制尤其重要,其核心思想就是解耦。
在这里插入图片描述
SPI的接口是Java核心库的一部分,是由引导类加载器(Bootstrap Classloader)来加载的。SPI的实现类是由系统类加载器(System ClassLoader)来加载的。

应用场景:
Java提供了很多SPI,允许第三方为这些接口提供实现。

常见的SPI使用场景:

  • JDBC加载不同类型的数据库驱动。
  • 日志门面接口实现类加载,SLF4J加载不同提供商的日志实现类。
  • Spring中大量使用了SPI。可以在spring.factories中加上我们自定义的自动配置类,事件监听器或初始化器等。
    3.1 对servlet3.0规范。3.2 对ServletContainerInitializer的实现。
  • Dubbo里面有很多个组件,每个组件在框架中都是以接口的形成抽象出来。具体的实现又分很多种,在程序执行时根据用户的配置来按需取接口的实现。如果Dubbo的某个内置实现不符合业务需求,那么只需要利用其SPI机制将新的业务实现替换掉Dubbo的实现即可。

SPI具体约定:
Java SPI的具体约定:当服务的提供者,提供了服务接口的某种实现之后,在jar包的META-INF/services/目录里同时创建一个以服务接口命名的文件。该文件里就是实现该服务接口的具体实现类。而当外部程序装配这个模块的时候,就能通过该jar包META-INF/services/里的配置文件找到具体的实现类名,并装载实例化,完成模块的注入。基于这样一个约定就能实现服务接口与实现的解耦。

Java SPI机制的缺点:
不能按需加载,需要遍历所有的实现,并实例化,然后在循环中才能找到我们需要的实现。如果不想用某些实现类,或者某些类实例化很耗时,它也被载入并实例化了,这就造成了浪费。
多个并发多线程使用ServiceLoader类的实例是不安全的。
扩展如果依赖其他的扩展,做不到自动注入和装配。
不提供类似于Spring的IOC和AOP功能。
扩展很难和其他的框架集成,比如扩展里面依赖了一个Spring bean,原生的Java SPI不支持。

示例:
(1)定义需要的接口,然后编码接口的实现类。
在这里插入图片描述
实现类1:
在这里插入图片描述
实现类2:
在这里插入图片描述
配置:
增加配置文件
在项目的\src\main\resources\下创建\META-INF\services目录,并增加一个配置文件,这个文件必须以接口的全限定类名保持一致,例如:
com.xiaohui.spi.HelloService。然后在配置文件中写入具体实现类的全限定类名,如有多个则换行写入。
在这里插入图片描述
执行:
在这里插入图片描述
结果:
在这里插入图片描述

相关内容

热门资讯

埃菲尔铁塔在哪 中国仿建埃菲尔... 2019年4月26日,广西南宁市,街头惊现一座巨型山寨版埃菲尔铁塔,高约20米,白色塔身,造型逼真,...
苗族的传统节日 贵州苗族节日有... 【岜沙苗族芦笙节】岜沙,苗语叫“分送”,距从江县城7.5公里,是世界上最崇拜树木并以树为神的枪手部落...
北京的名胜古迹 北京最著名的景... 北京从元代开始,逐渐走上帝国首都的道路,先是成为大辽朝五大首都之一的南京城,随着金灭辽,金代从海陵王...
长白山自助游攻略 吉林长白山游... 昨天介绍了西坡的景点详细请看链接:一个人的旅行,据说能看到长白山天池全凭运气,您的运气如何?今日介绍...
世界上最漂亮的人 世界上最漂亮... 此前在某网上,选出了全球265万颜值姣好的女性。从这些数量庞大的女性群体中,人们投票选出了心目中最美...
应用未安装解决办法 平板应用未... ---IT小技术,每天Get一个小技能!一、前言描述苹果IPad2居然不能安装怎么办?与此IPad不...
脚上的穴位图 脚面经络图对应的... 人体穴位作用图解大全更清晰直观的标注了各个人体穴位的作用,包括头部穴位图、胸部穴位图、背部穴位图、胳...
demo什么意思 demo版本... 618快到了,各位的小金库大概也在准备开闸放水了吧。没有小金库的,也该向老婆撒娇卖萌服个软了,一切只...
猫咪吃了塑料袋怎么办 猫咪误食... 你知道吗?塑料袋放久了会长猫哦!要说猫咪对塑料袋的喜爱程度完完全全可以媲美纸箱家里只要一有塑料袋的响...
埃菲尔铁塔在哪 中国仿建埃菲尔... 2019年4月26日,广西南宁市,街头惊现一座巨型山寨版埃菲尔铁塔,高约20米,白色塔身,造型逼真,...
苗族的传统节日 贵州苗族节日有... 【岜沙苗族芦笙节】岜沙,苗语叫“分送”,距从江县城7.5公里,是世界上最崇拜树木并以树为神的枪手部落...
北京的名胜古迹 北京最著名的景... 北京从元代开始,逐渐走上帝国首都的道路,先是成为大辽朝五大首都之一的南京城,随着金灭辽,金代从海陵王...
长白山自助游攻略 吉林长白山游... 昨天介绍了西坡的景点详细请看链接:一个人的旅行,据说能看到长白山天池全凭运气,您的运气如何?今日介绍...
应用未安装解决办法 平板应用未... ---IT小技术,每天Get一个小技能!一、前言描述苹果IPad2居然不能安装怎么办?与此IPad不...
脚上的穴位图 脚面经络图对应的... 人体穴位作用图解大全更清晰直观的标注了各个人体穴位的作用,包括头部穴位图、胸部穴位图、背部穴位图、胳...
世界上最漂亮的人 世界上最漂亮... 此前在某网上,选出了全球265万颜值姣好的女性。从这些数量庞大的女性群体中,人们投票选出了心目中最美...
demo什么意思 demo版本... 618快到了,各位的小金库大概也在准备开闸放水了吧。没有小金库的,也该向老婆撒娇卖萌服个软了,一切只...
猫咪吃了塑料袋怎么办 猫咪误食... 你知道吗?塑料袋放久了会长猫哦!要说猫咪对塑料袋的喜爱程度完完全全可以媲美纸箱家里只要一有塑料袋的响...