一、什么是接口
接口是抽象类的延伸,可以将它看作是纯粹的对象类
二、接口模式的特性
(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。然后在配置文件中写入具体实现类的全限定类名,如有多个则换行写入。
执行:
结果: