JAVA SPI设计模式之策略模式文字版主页有视频

本文涉及的产品
服务治理 MSE Sentinel/OpenSergo,Agent数量 不受限
可观测可视化 Grafana 版,10个用户账号 1个月
简介: JAVA SPI设计模式之策略模式文字版主页有视频

配套视频:https://cloud.tencent.com/developer/video/76631

在业务开发中,登录接口是非常常见的场景,随着业务的发展,需要支持多种登录形式

那么登陆接口该如何书写?

如何设计接口?

面对大量if else如何进行优化?

我们今天讲解的是在项目中如何应用策略模式。

登陆场景

登录方法

实现

策略(Strategy)模式的定义:该模式定义了一系列算法,并将每个算法封装起来,使它们可以相互替换,且算法的变化不会影响使用算法的客户。策略模式属于对象行为模式,它通过对算法进行封装,把使用算法的责任和算法的实现分割开来,并委派给不同的对象对这些算法进行管理。

建立Springboot demo

https://start.spring.io/

1.SQL

CREATE TABLE `user` (
 
  `id` bigint(20) unsigned  NOT NULL AUTO_INCREMENT COMMENT '主键ID',
 
  `user_name` varchar(20) NOT NULL DEFAULT '' COMMENT '用户名',
 
  `password` decimal(15,4) NOT NULL COMMENT '密码',
 
  `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
 
  `create_by` varchar(30) NOT NULL DEFAULT 'sys' COMMENT '创建者',
 
  `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
 
  `update_by` varchar(30) NOT NULL DEFAULT 'sys' COMMENT '更新者',
 
  PRIMARY KEY (`id`)
 
) COMMENT='用户信息表';

2.pom

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
   xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
   <modelVersion>4.0.0</modelVersion>
   <parent>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-parent</artifactId>
      <version>2.7.9</version>
      <relativePath/> <!-- lookup parent from repository -->
   </parent>
   <groupId>testdemo</groupId>
   <artifactId>demo</artifactId>
   <version>0.0.1-SNAPSHOT</version>
   <name>demo</name>
   <description>Demo project for Spring Boot</description>
   <properties>
      <java.version>11</java.version>
   </properties>
   <dependencies>
      <dependency>
         <groupId>org.springframework.boot</groupId>
         <artifactId>spring-boot-starter-web</artifactId>
      </dependency>
      <dependency>
         <groupId>org.springframework.boot</groupId>
         <artifactId>spring-boot-starter-test</artifactId>
         <scope>test</scope>
      </dependency>

      <!--akka-->
      <dependency>
         <groupId>com.typesafe.akka</groupId>
         <artifactId>akka-slf4j_2.11</artifactId>
         <version>2.5.16</version>
      </dependency>
      <dependency>
         <groupId>junit</groupId>
         <artifactId>junit</artifactId>
         <version>RELEASE</version>
         <scope>test</scope>
      </dependency>
      <dependency>
         <groupId>org.projectlombok</groupId>
         <artifactId>lombok</artifactId>
         <version>RELEASE</version>
         <scope>compile</scope>
      </dependency>
      <!-- https://mvnrepository.com/artifact/com.alibaba/fastjson -->
      <dependency>
         <groupId>com.alibaba</groupId>
         <artifactId>fastjson</artifactId>
         <version>1.2.41</version>
      </dependency>
      <!--Mysql依赖包-->
      <dependency>
         <groupId>mysql</groupId>
         <artifactId>mysql-connector-java</artifactId>
      </dependency>

      <!-- druid数据源驱动 -->
      <dependency>
         <groupId>com.alibaba</groupId>
         <artifactId>druid</artifactId>
         <version>1.1.6</version>
      </dependency>
      <dependency>
         <groupId>org.lionsoul</groupId>
         <artifactId>ip2region</artifactId>
         <version>1.7.2</version>
      </dependency>
      <dependency>
         <groupId>com.baomidou</groupId>
         <artifactId>mybatis-plus-boot-starter</artifactId>
         <version>3.2.0</version>
      </dependency>
      <dependency>
         <groupId>com.baomidou</groupId>
         <artifactId>mybatis-plus-generator</artifactId>
         <version>3.2.0</version>
      </dependency>
      <dependency>
         <groupId>org.freemarker</groupId>
         <artifactId>freemarker</artifactId>
         <version>2.3.28</version>
      </dependency>
   </dependencies>

   <build>
      <plugins>
         <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
         </plugin>
      </plugins>
   </build>

</project>

新增一个controller

@Slf4j
@RestController
@RequestMapping(produces = MediaType.APPLICATION_JSON_VALUE)
public class LoginController {


    @Autowired
    private LoginService loginService;

    @PostMapping(value = "login/create")
    public String loginVerify(@RequestBody LoginRequest request){
        if(request == null) throw new RuntimeException("网络丢了,请稍后再试......");
        return JSONObject.toJSONString(loginService.login(request));
    }

    @PostMapping(value = "add/user")
    public String addUser(@RequestBody User user){
        if(user == null) throw new RuntimeException("网络丢了,请稍后再试......");
        return JSONObject.toJSONString(loginService.addUser(user));
    }
}

新增一个service

public interface LoginService {

    Boolean login(LoginRequest request);

    Boolean addUser(User user);
}

新增一个impl

@Service
@Slf4j
public class LoginServiceImpl implements LoginService {


    @Autowired
    private UserMapper userMapper;

    @Override
    public Boolean login(LoginRequest request) {
        var msg = "当前登录方式是";
        if(request.getType() == 1){
            log.info(msg + "账号密码登录,用户名是{}密码是{}",request.getLoginName(),request.getPassword());
        }
        if(request.getType() == 2){
            log.info(msg + "验证码登录,用户名是{}密码是{}",request.getLoginName(),request.getPassword());
        }
        return true;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public Boolean addUser(User user) {
        user.setCreateBy(user.getUserName());
        user.setCreateTime(new Date());
        user.setUpdateTime(new Date());
        user.setUpdateBy(user.getUserName());
        userMapper.insert(user);
        return true;
    }
}

@Mapper

public interface UserMapper extends BaseMapper<User> {

}

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="springboot.strategy.dao.UserMapper">

    <resultMap id="BaseResultMap" type="springboot.strategy.entity.User">
        <id column="id" property="id" jdbcType="BIGINT"/>
        <result column="user_name" property="userName" jdbcType="VARCHAR"/>
        <result column="password" property="password" jdbcType="VARCHAR"/>
        <result column="create_time" property="createTime" jdbcType="TIMESTAMP"/>
        <result column="create_by" property="createBy" jdbcType="VARCHAR"/>
        <result column="update_time" property="updateTime" jdbcType="TIMESTAMP"/>
        <result column="update_by" property="updateBy" jdbcType="VARCHAR"/>
    </resultMap>

    <select id="selectUserInfo" resultType="springboot.strategy.entity.User">
        SELECT * FROM user WHERE user_name is  not null order by create_time desc
    </select>
</mapper>


package springboot.strategy.entity;

import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;

import java.util.Date;

/**
 * @author zhaokk
 * @create 2023-03-25 19:09
 */
@Data
@TableName("user")
public class User {
    @TableId(type = IdType.AUTO)
    private Long id;

    /**
     * 用户名
     */
    private String userName;

    /**
     * 密码
     */
    private String password;


    private Date createTime;
    /**
     * 创建人
     */
    private String createBy;
    /**
     * 修改时间
     */
    private Date updateTime;
    /**
     * 修改人
     */
    private String updateBy;


}

3.登录场景

1.策略模式

定义一个策略类

public class LoginStrategy {
    
private DynamicLoginService  strategy;

    public void setStrategy(Strategy strategy) {
        this.strategy = strategy;
    }
    
    public Boolean login(LoginRequest request) {
        return strategy.login(request);
    }
}

实际调用

LoginStrategy strategy  = new LoginStrategy();
if(request.getType == 1){
    strategy.setStrategy(new AccountNamePasswordLoginServiceImpl());
}
strategy.login(request)

API = Application Programming Interface 程序之间的接口

SPI(service provider interface)机制是JDK内置的一种服务发现机制,可以动态的发现服务,即服务提供商,它通过在ClassPath路径下的META-INF/services文件夹查找文件,自动加载文件里所定义的类。目前这种大部分都利用SPI的机制进行服务提供.

在 JDK SPI 中,通过 ServiceLoader 加载实现类时,底层会使用反射机制加载并实例化对应的类。具体地,ServiceLoader 会通过线程上下文类加载器加载指定的服务接口的实现类,并调用其无参构造方法进行实例化。这意味着,实现类必须提供一个公共的无参构造方法,以便在加载时被正确实例化。

在 Spring SPI 中,通过 ApplicationContext 加载 Bean 时,底层也使用了反射机制来实例化对应的类。具体地,Spring 会扫描项目中所有的 class 文件,查找带有特定注解(例如 @Component、@Service 等)的类,并将其实例化成 Bean。当需要获取某个 Bean 时,Spring 会根据 Bean 的名称或类型,在容器的内部缓存中查找并返回相应的实例。

mysql-connector-java-5.1.35.jar

java.util.ServiceLoader

JDK8 VS JDK11

https://download.java.net/openjdk/jdk8u40/ri/openjdk-8u40-src-b25-10\_feb\_2015.zip

https://www.apiref.com/java11-zh/java.base/java/util/ServiceLoader.html

2.JDK SPI

META-INF/services新建一个service的全限定类名的文件

     ServiceLoader<DynamicService> serviceLoader = ServiceLoader.load(DynamicService.class);   
     System.out.println("Java SPI");       
     serviceLoader.forEach(Robot::sayHello);

为什么替换JDK自带的SPI?

2.SpringFactoriesLoader类的主要作用是通过类路径下的META-INF/spring.factories文件获取工厂类接口的实现类,初始化并保存在缓存中,以供Springboot启动过程中各个阶段的调用。Spring的自动化配置功能从而加载配置中的bean,也与此息息相关。

反射,属于主动调用,所以会触发类初始化

org.springframework.core.io.support.SpringFactoriesLoader#loadSpringFactories方法
既然能够获取所有的实现类,那我们能指定码?自带的方法是不支持的,我们需要自己改造
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.core.annotation.AnnotationAwareOrderComparator;
import org.springframework.core.io.UrlResource;
import org.springframework.core.io.support.PropertiesLoaderUtils;
import org.springframework.core.io.support.SpringFactoriesLoader;
import org.springframework.lang.Nullable;
import org.springframework.stereotype.Component;
import org.springframework.util.*;

import java.io.IOException;
import java.net.URL;
import java.util.*;
import java.util.stream.Collectors;

/**
 * @author zhaokk
 * @create 2023-03-27 11:45
 */
@Component
public class SpringFactoriesLoaderExtend{


    /**
     * The location to look for factories.
     * <p>Can be present in multiple JAR files.
     */
    public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";


    private static final Log logger = LogFactory.getLog(SpringFactoriesLoader.class);

    //private static final Map<ClassLoader, MultiValueMap<String, String>> cache = new ConcurrentReferenceHashMap<>();


    private SpringFactoriesLoaderExtend() {
    }


    /**
     * Load and instantiate the factory implementations of the given type from
     * {@value #FACTORIES_RESOURCE_LOCATION}, using the given class loader.
     * <p>The returned factories are sorted through {@link AnnotationAwareOrderComparator}.
     * <p>If a custom instantiation strategy is required, use {@link #loadFactoryNames}
     * to obtain all registered factory names.
     * @param factoryClass the interface or abstract class representing the factory
     * @param classLoader the ClassLoader to use for loading (can be {@code null} to use the default)
     * @throws IllegalArgumentException if any factory implementation class cannot
     * be loaded or if an error occurs while instantiating any factory
     * @see #loadFactoryNames
     */
    public static <T> List<T> loadFactories(Class<T> factoryClass, @Nullable ClassLoader classLoader) {
        Assert.notNull(factoryClass, "'factoryClass' must not be null");
        ClassLoader classLoaderToUse = classLoader;
        if (classLoaderToUse == null) {
            classLoaderToUse = SpringFactoriesLoader.class.getClassLoader();
        }
        List<String> factoryNames = loadFactoryNames(factoryClass, classLoaderToUse);
        if (logger.isTraceEnabled()) {
            logger.trace("Loaded [" + factoryClass.getName() + "] names: " + factoryNames);
        }
        List<T> result = new ArrayList<>(factoryNames.size());
        for (String factoryName : factoryNames) {
            result.add(instantiateFactory(factoryName, factoryClass, classLoaderToUse));
        }
        AnnotationAwareOrderComparator.sort(result);
        return result;
    }

    /**
     * Load the fully qualified class names of factory implementations of the
     * given type from {@value #FACTORIES_RESOURCE_LOCATION}, using the given
     * class loader.
     * @param factoryClass the interface or abstract class representing the factory
     * @param classLoader the ClassLoader to use for loading resources; can be
     * {@code null} to use the default
     * @throws IllegalArgumentException if an error occurs while loading factory names
     * @see #loadFactories
     */
    public static List<String> loadFactoryNames(Class<?> factoryClass, @Nullable ClassLoader classLoader) {
        String factoryClassName = factoryClass.getName();
        Collection<List<String>> values = loadSpringFactories(classLoader, factoryClassName).values();
        List<String> names = new ArrayList<>();
        for (List<String> value : values) {
            for (String s : value) {
                names.add(s);
            }
        }
        Collection<List<String>> values1 = loadSpringFactories(classLoader, factoryClassName).values();
        List<String> collect = values1.stream().filter(item -> item.size() > 1).flatMap(set -> set.stream()).collect(Collectors.toList());
        return  names;
    }

    private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader,String className) {
        MultiValueMap<String, String> result = new LinkedMultiValueMap<>();
        try {
            Enumeration<URL> urls =  classLoader.getResources(FACTORIES_RESOURCE_LOCATION);
            result = new LinkedMultiValueMap<>();
            while (urls.hasMoreElements()) {
                if (!result.isEmpty()) {
                    break;
                }
                URL url = urls.nextElement();
                UrlResource resource = new UrlResource(url);
                Properties properties = PropertiesLoaderUtils.loadProperties(resource);
                for (Map.Entry<?, ?> entry : properties.entrySet()) {

                    String factoryClassName = ((String) entry.getKey()).trim();
                    for (String factoryName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) {
                        if(factoryName.equals(className)){
                            result.add(factoryClassName, factoryName.trim());
                            break;
                        }
                    }
                }
            }
            //cache.put(classLoader, result);
            return result;
        }catch (IOException ex) {
            throw new IllegalArgumentException("Unable to load factories from location [" +
                    FACTORIES_RESOURCE_LOCATION + "]", ex);
        }
    }

    @SuppressWarnings("unchecked")
    private static <T> T instantiateFactory(String instanceClassName, Class<T> factoryClass, ClassLoader classLoader) {
        try {
            Class<?> instanceClass = ClassUtils.forName(instanceClassName, classLoader);
            if (!factoryClass.isAssignableFrom(instanceClass)) {
                throw new IllegalArgumentException(
                        "Class [" + instanceClassName + "] is not assignable to [" + factoryClass.getName() + "]");
            }
            return (T) ReflectionUtils.accessibleConstructor(instanceClass).newInstance();
        }
        catch (Throwable ex) {
            throw new IllegalArgumentException("Unable to instantiate factory class: " + factoryClass.getName(), ex);
        }
    }

}

4.有没有一种方法,等我们实际使用的时候在进行加载,并且还支持ByName获取呢?

ApplicationContext  
实例化所有剩余的(非惰性init)singleton
→org.springframework.boot.SpringApplication#refreshContext
→→org.springframework.context.support.AbstractApplicationContext#refresh
→→→org.springframework.context.support.AbstractApplicationContext#finishBeanFactoryInitialization
initializing all remaining singleton beans
protected void finishBeanFactoryInitialization(ConfigurableListableBeanFactory beanFactory) {
   // Initialize conversion service for this context.
   if (beanFactory.containsBean(CONVERSION_SERVICE_BEAN_NAME) &&
         beanFactory.isTypeMatch(CONVERSION_SERVICE_BEAN_NAME, ConversionService.class)) {
      beanFactory.setConversionService(
            beanFactory.getBean(CONVERSION_SERVICE_BEAN_NAME, ConversionService.class));
   }

   // Register a default embedded value resolver if no bean post-processor
   // (such as a PropertyPlaceholderConfigurer bean) registered any before:
   // at this point, primarily for resolution in annotation attribute values.
   if (!beanFactory.hasEmbeddedValueResolver()) {
      beanFactory.addEmbeddedValueResolver(strVal -> getEnvironment().resolvePlaceholders(strVal));
   }

   // Initialize LoadTimeWeaverAware beans early to allow for registering their transformers early.
   String[] weaverAwareNames = beanFactory.getBeanNamesForType(LoadTimeWeaverAware.class, false, false);
   for (String weaverAwareName : weaverAwareNames) {
      getBean(weaverAwareName);
   }

   // Stop using the temporary ClassLoader for type matching.
   beanFactory.setTempClassLoader(null);
    //冻结所有的beanDefinition
   // Allow for caching all bean definition metadata, not expecting further changes.
   beanFactory.freezeConfiguration();

   // Instantiate all remaining (non-lazy-init) singletons.
   beanFactory.preInstantiateSingletons();
}

SPI实际上是“接口+策略模式+配置文件”实现的动态加载机制。在系统设计中,模块之间通常基于接口编程,不直接显示指定实现类。一旦代码里指定了实现类,就无法在不修改代码的情况下替换为另一种实现。为了达到动态可插拔的效果,java提供了SPI以实现服务发现。

扩展点开发指南 | Apache Dubbo

SPI 扩展使用手册 | Apache Dubbo

源码下载:

https://github.com/apache/dubbo-samples.git

Dubbo 扩展的特性

Dubbo 中的扩展能力是从 JDK 标准的 SPI 扩展点发现机制加强而来,它改进了 JDK 标准的 SPI 以下问题:

JDK 标准的 SPI 会一次性实例化扩展点所有实现,如果有扩展实现初始化很耗时,但如果没用上也加载,会很浪费资源。

如果扩展点加载失败,连扩展点的名称都拿不到了。比如:JDK 标准的 ScriptEngine,通过 getName() 获取脚本类型的名称,但如果 RubyScriptEngine 因为所依赖的 jruby.jar 不存在,导致 RubyScriptEngine 类加载失败,这个失败原因被吃掉了,和 ruby 对应不起来,当用户执行 ruby 脚本时,会报不支持 ruby,而不是真正失败的原因。

用户能够基于 Dubbo 提供的扩展能力,很方便基于自身需求扩展其他协议、过滤器、路由等。下面介绍下 Dubbo 扩展能力的特性。

按需加载。Dubbo 的扩展能力不会一次性实例化所有实现,而是用扩展类实例化,减少资源浪费。

增加扩展类的 IOC 能力。Dubbo 的扩展能力并不仅仅只是发现扩展服务实现类,而是在此基础上更进一步,如果该扩展类的属性依赖其他对象,则 Dubbo 会自动的完成该依赖对象的注入功能。

增加扩展类的 AOP 能力。Dubbo 扩展能力会自动的发现扩展类的包装类,完成包装类的构造,增强扩展类的功能。

具备动态选择扩展实现的能力。Dubbo 扩展会基于参数,在运行时动态选择对应的扩展类,提高了 Dubbo 的扩展能力。

可以对扩展实现进行排序。能够基于用户需求,指定扩展实现的执行顺序。

提供扩展点的 Adaptive 能力。该能力可以使的一些扩展类在 consumer 端生效,一些扩展类在 provider 端生效。

从 Dubbo 扩展的设计目标可以看出,Dubbo 实现的一些例如动态选择扩展实现、IOC、AOP 等特性,能够为用户提供非常灵活的扩展能力。

4.Applicationcontext

加载bean

finishBeanFactoryInitialization(beanFactory);

注入Applicationcontext

维护枚举类

@Getter
@AllArgsConstructor
public enum DynamicStrategyEnum {

    账号密码(DynamicEnum.账号密码.getCode(), UnRouteTaskServiceImpl.class),

    验证码(DynamicEnum.验证码.getCode(), CancelRouteTaskServiceImpl.class),

    人脸(DynamicEnum.人脸.getCode(), ChangeOrderTaskServiceImpl.class),

    指纹(DynamicEnum.指纹.getCode(), RoutedAndUnDistributeServiceImpl.class),

    微信登录(DynamicEnum.微信登录.getCode(), DistributeApplyOrderServiceImpl.class),

    支付宝(DynamicEnum.支付宝.getCode(),UnApprovedSettlementServiceIimpl.class);

    private Integer code;

    private Class<? extends WmsDailySettlementService> clazz;

    public static Class<? extends WmsDailySettlementService> getClazz(Integer param) {
        if (StringUtils.isEmpty(param)) {
            throw new NullPointerException("param is null");
        } else {
            for (DynamicStrategyEnum strategyEnum : DynamicStrategyEnum.values()) {
                if (strategyEnum.getCode().equals(param)) {
                    return strategyEnum.getClazz();
                }
            }
            throw new NullPointerException(String.format("param is null is %s", param));
        }
    }
}

5.注解驱动

注解是如何获取的?

https://blog.csdn.net/A\_art\_xiang/article/details/121954905

findCandidateComponents
scanCandidateComponents -> 
getMetadataReader -> 
MetadataReader metadataReader = getMetadataReaderFactory().getMetadataReader(resource);

CachingMetadataReaderFactory::getMetadataReader -> new SimpleMetadataReader
@Override
public String generateBeanName(BeanDefinition definition, BeanDefinitionRegistry registry) {
   if (definition instanceof AnnotatedBeanDefinition) {
      String beanName = determineBeanNameFromAnnotation((AnnotatedBeanDefinition) definition);
      if (StringUtils.hasText(beanName)) {
         // Explicit bean name found.
         return beanName;
      }
   }
   // Fallback: generate a unique default bean name.
   return buildDefaultBeanName(definition, registry);
}

新建一个注解驱动

@Target(ElementType.TYPE)

@Retention(RetentionPolicy.RUNTIME)

@Documented

public @interface DynamicHandlerType {

DynamicEnum handler();

}

在实现类添加注解

@DynamicHandlerType(handler = DynamicEnum.账号密码登录)

在应用的地方

private Map<DynamicEnum, DynamicLoginService> dynamicLoginServiceMap;

@Autowired
public void setDynamicLoginServiceMap(List<DynamicLoginService> list) {
    dynamicLoginServiceMap = list.stream()
            .collect(Collectors.toMap(
                    handler-> AnnotationUtils.findAnnotation(handler.getClass(), DynamicHandlerType.class).handler(),
                    handler-> handler));
}
}

 DynamicLoginService dynamicLoginService = dynamicLoginServiceMap.get(DynamicEnum.tEnum(request.getType().toString()));
 dynamicLoginService.dynamicLogin(request);


6.CompletableFuture
private static ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor();
static {
    taskExecutor.setCorePoolSize(2);
    taskExecutor.setMaxPoolSize(2);
    taskExecutor.setKeepAliveSeconds(0);
    taskExecutor.setQueueCapacity(0);
    taskExecutor.setThreadNamePrefix("threadpool");
    taskExecutor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
    taskExecutor.initialize();
}
CompletableFuture<Void> t1 = CompletableFuture.runAsync(() -> {
    Service.login
    
    
}, taskExecutor);
相关实践学习
基于CentOS快速搭建LAMP环境
本教程介绍如何搭建LAMP环境,其中LAMP分别代表Linux、Apache、MySQL和PHP。
全面了解阿里云能为你做什么
阿里云在全球各地部署高效节能的绿色数据中心,利用清洁计算为万物互联的新世界提供源源不断的能源动力,目前开服的区域包括中国(华北、华东、华南、香港)、新加坡、美国(美东、美西)、欧洲、中东、澳大利亚、日本。目前阿里云的产品涵盖弹性计算、数据库、存储与CDN、分析与搜索、云通信、网络、管理与监控、应用服务、互联网中间件、移动服务、视频服务等。通过本课程,来了解阿里云能够为你的业务带来哪些帮助 &nbsp; &nbsp; 相关的阿里云产品:云服务器ECS 云服务器 ECS(Elastic Compute Service)是一种弹性可伸缩的计算服务,助您降低 IT 成本,提升运维效率,使您更专注于核心业务创新。产品详情: https://www.aliyun.com/product/ecs
目录
相关文章
|
19天前
|
设计模式 算法 PHP
php设计模式--策略模式(六)
php设计模式--策略模式(六)
11 0
|
21天前
|
设计模式 监控 Java
设计模式 - 观察者模式(Observer):Java中的战术与策略
【4月更文挑战第7天】观察者模式是构建可维护、可扩展系统的关键,它在Java中通过`Observable`和`Observer`实现对象间一对多的依赖关系,常用于事件处理、数据绑定和同步。该模式支持事件驱动架构、数据同步和实时系统,但需注意避免循环依赖、控制通知粒度,并关注性能和内存泄漏问题。通过明确角色、使用抽象和管理观察者注册,可最大化其效果。
|
1天前
|
设计模式 消息中间件 Java
Java 设计模式:探索发布-订阅模式的原理与应用
【4月更文挑战第27天】发布-订阅模式是一种消息传递范式,被广泛用于构建松散耦合的系统。在 Java 中,这种模式允许多个对象监听和响应感兴趣的事件。
8 2
|
1天前
|
设计模式 算法 Java
Java 设计模式:探索策略模式的概念和实战应用
【4月更文挑战第27天】策略模式是一种行为设计模式,它允许在运行时选择算法的行为。在 Java 中,策略模式通过定义一系列的算法,并将每一个算法封装起来,并使它们可以互换,这样算法的变化不会影响到使用算法的客户。
6 1
|
1天前
|
设计模式 安全 Java
【JAVA】Java 中什么叫单例设计模式?请用 Java 写出线程安全的单例模式
【JAVA】Java 中什么叫单例设计模式?请用 Java 写出线程安全的单例模式
|
4天前
|
设计模式 算法 Java
[设计模式Java实现附plantuml源码~行为型]定义算法的框架——模板方法模式
[设计模式Java实现附plantuml源码~行为型]定义算法的框架——模板方法模式
|
4天前
|
设计模式 JavaScript Java
[设计模式Java实现附plantuml源码~行为型] 对象状态及其转换——状态模式
[设计模式Java实现附plantuml源码~行为型] 对象状态及其转换——状态模式
|
4天前
|
设计模式 存储 JavaScript
[设计模式Java实现附plantuml源码~创建型] 多态工厂的实现——工厂方法模式
[设计模式Java实现附plantuml源码~创建型] 多态工厂的实现——工厂方法模式
|
4天前
|
设计模式 Java Go
[设计模式Java实现附plantuml源码~创建型] 集中式工厂的实现~简单工厂模式
[设计模式Java实现附plantuml源码~创建型] 集中式工厂的实现~简单工厂模式
|
4天前
|
设计模式 存储 前端开发
Java从入门到精通:2.2.1学习Java Web开发,了解Servlet和JSP技术,掌握MVC设计模式
Java从入门到精通:2.2.1学习Java Web开发,了解Servlet和JSP技术,掌握MVC设计模式

热门文章

最新文章