接口多实现

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

二、接口模式的特性
(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 {

    @Override
    public void print(String msg) {
        System.out.println("console print: " + msg);
    }

}

实现类2:

@Component
public class LogPrint implements IPrint {

    @Override
    public void print(String msg) {
        System.out.println("logPrint print: " + msg);
    }
}

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

@Autowired
    private IPrint logPrint / abc;

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

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

@RestController
@RequestMapping("/api/{edition}/page")
public class MyPageHelperController {

    @Autowired
    private IPrint logPrint / consolePrint; //这里的logPrint / consolePrint的名字就是实现类的bean的名字,所以可以正确引入

    @GetMapping("/myPage")
    @ResponseBody
    public Map<Object, Object> expenseStatement(HttpServletRequest request, HttpServletResponse response)  {

        logPrint.print("zjg");

        return new ConcurrentHashMap<>();
    }
}

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

@RestController
@RequestMapping("/api/{edition}/page")
public class MyPageHelperController {

    @Autowired
    private ConsolePrint abc; //这里使用实现类进行注入
    //@Autowired
    //private LogPrint abc;
    

    @GetMapping("/myPage")
    @ResponseBody
    public Map<Object, Object> 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 方法传入接口类型,一次性获取所有实现类
<T> Map<String, T> getBeansOfType(Class<T> 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<TypeEnum, TestService> testServiceMap;
 
    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        Map<String,TestService> 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
     */
    @Override
    public 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 {
    @Override
    public TypeEnum getCode() {
        return TypeEnum.impl1;
    }
 
    @Override
    public 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 {
    @Override
    public TypeEnum getCode() {
        return TypeEnum.impl2;
    }
 
    @Override
    public 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 {
 
    @Autowired
    TestServiceFactory 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里
     */
    @Autowired
    private Map<String, OrderInfoDao> 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 {
    @Override
    public 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 {
    @Override
    public void test() {
        System.out.println("接口5实现类 ...");
    }
}

配置文件:
application.properties

demo.test.service.impl=one

测试:

    @Autowired
    ITestServiceConditional testServiceConditional;

    @Test
   public 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:注解所在位置的注释信息
     * */
    @Override
    public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata annotatedTypeMetadata) {
        //获取ioc使用的beanFactory
        ConfigurableListableBeanFactory beanFactory = conditionContext.getBeanFactory();
        //获取类加载器
        ClassLoader classLoader = conditionContext.getClassLoader();
        //获取当前环境信息
        Environment environment = conditionContext.getEnvironment();
        //获取bean定义的注册类
        BeanDefinitionRegistry registry = conditionContext.getRegistry();
 
        //获得当前系统名
        String property = environment.getProperty("os.name");
        //包含Windows则说明是windows系统,返回true
        if (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。然后在配置文件中写入具体实现类的全限定类名,如有多个则换行写入。
在这里插入图片描述
执行:
在这里插入图片描述
结果:
在这里插入图片描述

  • 9
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值