Spring服务定制

简介: 问题总述​ 我们都知道如果使用Spring来进行bean管理的时候。如果同一个接口的实现类存在两个,直接使用@Autowired注解来实现bean注入,会在启动的时候报异常。我们通常的做法是使用@Resource注解来执行bean的名称。

问题总述

​ 我们都知道如果使用Spring来进行bean管理的时候。如果同一个接口的实现类存在两个,直接使用@Autowired注解来实现bean注入,会在启动的时候报异常。我们通常的做法是使用@Resource注解来执行bean的名称。不过通过@Resource注解类似于硬编码的方式,如果我们想修改接口的具体实现,必须修改代码。假设我们环境中针对所有接口,都有两套实现,一套在测试环境中使用,一个在生产环境中使用。那么当切换环境的时候,所有接口使用@Resource注入的地方都需要修改bean名称。

使用@Profile注解

​ 针对前面两套环境的情况,我们可以使用@Profile注解来轻松解决。具体代码示例如下:

public interface HelloService {
    
    void saySomething(String msg);
}

@Profile("kind1")
@Service
public class HelloServiceImpl1 implements HelloService {
    public void saySomething(String msg) {
        System.out.println("HelloServiceImpl1 say:" + msg);
    }
}

@Profile("kind2")
@Service
public class HelloServiceImpl2 implements HelloService {
    public void saySomething(String msg) {
        System.out.println("HelloServiceImpl2 say:" + msg);
    }
}

@EnableAspectJAutoProxy
@Configurable
@ComponentScan(basePackages="com.rampage.spring")
@EnableScheduling
public class ApplicationConfig {
}

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes=ApplicationConfig.class)
@ActiveProfiles(profiles={"kind1"})     // 启用kind1注入的bean
public class HelloServiceTest {
    
    @Autowired
    private HelloService helloService;
    
    @Test
    public void testServiceSelector() {
        helloService.saySomething("I Love U!");
    }
}

​ 最终输出的结果为:

HelloServiceImpl1 say:I Love U!

多服务共存定制

​ 考虑这样一种情况,假设HelloService是针对全国通用的服务,对于不同的省市使用不同的方言来saySomething .假设系统都是使用一套,那么在使用Spring进行bean管理的时候要么针对不同的省市只打包对应的目录下的HelloService实现,要么同前面一样使用@Profile注解来区分不同的实现类型,最后针对不同的省市修改@ActiveProfiles的具体值。这两种方法都需要针对不同的地区来进行相应的代码修改,然后再重新打包。考虑到全国几百个市,如果一次统一全部升级,估计光打包可能都要打包一天。。。

​ 更进一步的情况,东北三省大部分城市都是说普通话,那么实际上只要使用一个默认的实现类就行了。换句话将,现在想实现这样一种定制: 每个接口有一个默认实现,不同的城市有一个定制实现的类型码。如果根据定制类型码能够找到对应的接口实现,则使用该实现类。如果未找到,则使用默认的实现类。

​ 很显然,上面要实现的是在代码运行过程中动态判断最后接口的具体实现类。其中定制的类型码可以通过数据库或者配置文件的方式指定,在代码运行的过程中根据定制码去获取对应的服务实现。

​ 该方案的一种实现如下:

public interface ServiceSelector {
    /**
     * 得到定制码
     * @return
     */
    String getCustCode();
}

public interface HelloService extends ServiceSelector {
    
    void saySomething(String msg);
}

public abstract class ServiceProvider <T, S extends T> implements BeanFactoryAware  {

    private ConfigurableListableBeanFactory beanFactory;
    
    private Map<String, T> serviceMap;
    
    public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
        if (beanFactory instanceof DefaultListableBeanFactory) {  
            this.beanFactory = (ConfigurableListableBeanFactory) beanFactory;  
        }  
    }
    
    @SuppressWarnings({"unchecked", "restriction"})
    @PostConstruct
    public void init(){
        ParameterizedType pt = (ParameterizedType) this.getClass().getGenericSuperclass();
        Type[] types = pt.getActualTypeArguments();
        
        Class<T> interfaceClazz = (Class<T>)types[0];
        // Class<S> defaultImplClazz = (Class<S>)types[1];  // defaultImplClazz为默认实现
        
        Map<String, T> serviceBeanMap = beanFactory.getBeansOfType(interfaceClazz);
        serviceMap = new HashMap<String , T>(serviceBeanMap.size());
        for (T processor : serviceBeanMap.values()) {
            if (!(processor instanceof ServiceSelector)) {
                // 如果实现类没有实现OptionalServiceSelector接口,直接报错
                throw new RuntimeException("可选服务必须实现ServiceSelector接口!");
            }
            
            // 如果已经存在相同定制码的服务也直接抛异常
            ServiceSelector selector = (ServiceSelector)processor;
            if (null != serviceMap.get(selector.getCustCode())) {
                throw new RuntimeException("已经存在定制码为【" + selector.getCustCode() + "】的服务");
            }
            
            // 加入Map中
            serviceMap.put(selector.getCustCode(), processor);
        }
    }
    
    public T getService() {
        // 从配置文件或者数据库获取当前省市的定制码
        String custCode = "kind11";
        if (null != serviceMap.get(custCode)) {
            return serviceMap.get(custCode);
        }
        
        // 如果未找到则使用默认实现
        return serviceMap.get("DEFAULT");
    }
}

@Service
public class DefaultHelloService implements HelloService {

    public String getCustCode() {
        return "DEFAULT";
    }

    public void saySomething(String msg) {
        System.out.println("DefaultHelloService say:" + msg);
    }

}

@Service
public class HelloServiceImpl1 implements HelloService {
    public void saySomething(String msg) {
        System.out.println("HelloServiceImpl1 say:" + msg);
    }

    public String getCustCode() {
        return "kind1";
    }
}

@Service
public class HelloServiceImpl2 implements HelloService {
    public void saySomething(String msg) {
        System.out.println("HelloServiceImpl2 say:" + msg);
    }

    public String getCustCode() {
        return "kind2";
    }
}

@Service
public class HelloServiceProvider extends ServiceProvider<HelloService, DefaultHelloService> {
    
}

@EnableAspectJAutoProxy
@Configurable
@ComponentScan(basePackages="com.rampage.spring")
@EnableScheduling
public class ApplicationConfig {
}

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes=ApplicationConfig.class)
public class HelloServiceTest {
    
    // 注入接口服务提供器,而不是接口
    @Autowired
    private HelloServiceProvider helloServiceProvider;
    
    @Test
    public void testServiceSelector() {
        helloServiceProvider.getService().saySomething("I Love U!");
    }
}

​ 上例的最终输出为:

DefaultHelloService say:I Love U!

使用BFP来优雅定制服务实现

​ 上面的服务定制通过各种绕路实现了服务定制,但是不能看出上面的实现非常不优雅,存在很多问题:

  • 想实现一个接口的定制至少需要新增三个类。定制接口实现ServiceSelector接口,一个默认接口实现类,一个特定的定制服务实现类
  • 即使最终针对一个省市只使用一个实现类,在spring初始化的时候也会初始化定制接口的所有实现类,必须通过代码去判断针对特定的定制码是否只存在一个实现类

​ 那么针对这种情况,有没有一个优雅的实现。既能满足前面所说的业务场景需求,又能够不初始化多余的类?当然是有的,其中的一套实现方案如下:

// 定制服务的注解声明,支持多个定制码
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface Customized {
    String[] custCodes() default {"DEFAULT"};
}


public interface HelloService {
    void saySomething(String msg);
}

@Component
public class CustomizedServiceBeanFactoryPostProcessor implements BeanFactoryPostProcessor, Ordered {

    private static String custCode;

    private static final Logger LOGGER = LoggerFactory.getLogger(CustomizedServiceBeanFactoryPostProcessor.class);

    static {
        Properties properties = new Properties();
        /*try {
            // 读取配置文件定制码
            properties.load(CustomizedServiceBeanFactoryPostProcessor.class.getClassLoader()
                    .getResource("app-config.properties").openStream());
        } catch (Exception e) {
            throw new RuntimeException("读取配置文件失败!", e);
        }*/
        // 这里假设取默认定制码
        custCode = properties.getProperty("custCode", "DEFAULT");
    }

    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
        String[] beanDefNames = beanFactory.getBeanDefinitionNames();
        if (ArrayUtils.isEmpty(beanDefNames)) {
            return;
        }
        Class<?> beanClass = null;
        BeanDefinition beanDef = null;
        Customized customized = null;
        Set<Class<?>> foundCustomizedServices = new HashSet<Class<?>>();
        Map<String, Class<?>> waitDestroiedBeans = new HashMap<String, Class<?>>();
        String[] defaultCustCodes = {"DEFAULT"};
        ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
        for (String name : beanDefNames) {
            beanDef = beanFactory.getBeanDefinition(name);
            if (beanDef == null || StringUtils.isEmpty(beanDef.getBeanClassName())) {
                continue;
            }
            try {
                // 加载类得到其上的注解的定义
                beanClass = classLoader.loadClass(beanDef.getBeanClassName());
            } catch (ClassNotFoundException e) {
                // 发生了异常,这里直接跳过
            }
            if (beanClass == null) {
                continue;
            }
            customized = this.getCustomizedAnnotations(beanClass);

            // 非定制类直接跳过
            if (customized == null) {
                continue;
            }
            if (ArrayUtils.contains(customized.custCodes(), custCode)) {
                foundCustomizedServices.addAll(this.getCustomizedServices(beanClass));
                LOGGER.info("定制码【{}】下装载到定制服务实现类【{}】......", custCode, beanClass);
            } else {
                if (!ArrayUtils.isEquals(customized.custCodes(), defaultCustCodes)) {
                    ((DefaultListableBeanFactory) beanFactory).removeBeanDefinition(name);
                    LOGGER.info("定制码【{}】下卸载定制服务实现类【{}】......", custCode, beanClass);
                } else {
                    // 默认实现类暂时不知道是否需要销毁,先暂存
                    waitDestroiedBeans.put(name, beanClass);
                }
            }
        }

        // 没有需要检测的是否需要销毁的默认实现类则直接返回
        if (MapUtils.isEmpty(waitDestroiedBeans)) {
            return;
        }

        // 看定制服务的默认实现类是否已经找到特定的实现类,如果找到了则需要销毁默认实现类
        for (Entry<String, Class<?>> entry : waitDestroiedBeans.entrySet()) {
            // 直接继承定制服务类实现
            if (foundCustomizedServices.contains(entry.getValue())) {
                ((DefaultListableBeanFactory) beanFactory).removeBeanDefinition(entry.getKey());
                LOGGER.info("定制码【{}】下卸载默认定制服务实现类【{}】......", custCode, entry.getValue());
            } else {
                // 通过定制服务接口实现定制
                Set<Class<?>> defaultCustServices = getCustomizedServices(entry.getValue());
                for (Class<?> clazz : defaultCustServices) {
                    if (foundCustomizedServices.contains(clazz)) {
                        ((DefaultListableBeanFactory) beanFactory).removeBeanDefinition(entry.getKey());
                        LOGGER.info("定制码【{}】下卸载默认定制服务实现类【{}】......", custCode, entry.getValue());
                        break;
                    }
                }
            }
        }
    }

    /**
     * 得到定制类的定制注解(一旦找到立即返回)
     * 
     * @param clazz
     *            传入的定制类
     * @return 得到定制类的定制注解
     */
    private Customized getCustomizedAnnotations(Class<? extends Object> clazz) {
        // 递归遍历寻找定制注解
        Annotation[] annotations = null;
        Class<?>[] interfaces = null;
        while (clazz != Object.class) {
            // 先直接找该类上的注解
            annotations = clazz.getAnnotations();
            if (annotations != null && annotations.length > 0) {
                for (Annotation one : annotations) {
                    if (one.annotationType() == Customized.class) {
                        return (Customized) one;
                    }
                }
            }

            // 再找该类实现的接口上的注解
            interfaces = clazz.getInterfaces();
            if (interfaces != null && interfaces.length > 0) {
                for (Class<?> intf : interfaces) {
                    annotations = intf.getAnnotations();
                    if (annotations != null && annotations.length > 0) {
                        for (Annotation one : annotations) {
                            if (one.annotationType() == Customized.class) {
                                return (Customized) one;
                            }
                        }
                    }
                }
            }

            // 未找到继续找父类上的注解
            clazz = clazz.getSuperclass();
        }

        return null;
    }

    /**
     * 得到定制的服务类列表(即有Customized注解的类列表)
     * 
     * @param orginalClass
     *            传入的原始类
     * @return 定制服务类的列表
     */
    private Set<Class<?>> getCustomizedServices(Class<?> orginalClass) {
        Class<?> class1 = orginalClass;
        Set<Class<?>> customizedInterfaces = new HashSet<Class<?>>();
        Class<?>[] interfaces = null;
        while (class1 != Object.class) {
            // 类也进行判断,这样能实现直接不通过接口的,而通过service继承实现定制服务
            if (class1.getAnnotation(Customized.class) != null) {
                customizedInterfaces.add(class1);
            }

            // 遍历接口,看接口是是定制服务接口
            interfaces = class1.getInterfaces();
            if (interfaces == null || interfaces.length == 0) {
                class1 = class1.getSuperclass();
                continue;
            }

            // 接口的实现只能有一个,所以一旦找到带有注解的实现类的接口,都作为定制服务接口
            for (Class<?> clazz : interfaces) {
                customizedInterfaces.add(clazz);
            }

            // 寻找父类定制服务
            class1 = class1.getSuperclass();
        }

        return customizedInterfaces;
    }

    public int getOrder() {
        return Integer.MIN_VALUE;
    }
}

@Customized     // 默认服务实现类
@Service
public class DefaultHelloService implements HelloService {

    public void saySomething(String msg) {
        System.out.println("DefaultHelloService say:" + msg);
    }

}

@Customized(custCodes="kind1")
@Service
public class HelloServiceImpl1 implements HelloService {
    public void saySomething(String msg) {
        System.out.println("HelloServiceImpl1 say:" + msg);
    }
}

@Customized(custCodes="kind2")
@Service
public class HelloServiceImpl2 implements HelloService {
    public void saySomething(String msg) {
        System.out.println("HelloServiceImpl2 say:" + msg);
    }

    public String getCustCode() {
        return "kind2";
    }
}

@EnableAspectJAutoProxy
@Configurable
@ComponentScan(basePackages="com.rampage.spring")
@EnableScheduling
public class ApplicationConfig {
}

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes=ApplicationConfig.class)
public class HelloServiceTest {
    
    @Autowired
    private HelloService helloService;
    
    @Test
    public void testServiceSelector() {
        helloService.saySomething("I Love U!");
    }
    
}

​ 最终输出的结果为:

DefaultHelloService say:I Love U!

总结

​ 关于服务定制,其实前面所讲的方法都可以。只不过在特定的情况下各有各的优势,需要根据具体情况来选择合适的定制方案。而定制方案的选择,依赖于深入地理解Spring的类管理和加载过程,会用BPP、BFP等来定制类的加载过程。

黎明前最黑暗,成功前最绝望!
相关文章
|
4月前
|
监控 负载均衡 Java
深入理解Spring Cloud中的服务网关
深入理解Spring Cloud中的服务网关
|
4月前
|
Java Spring
Spring boot 运行服务jar外配置配置文件方式总结
Spring boot 运行服务jar外配置配置文件方式总结
927 0
|
3月前
|
缓存 NoSQL Java
【Azure Redis 缓存】示例使用 redisson-spring-boot-starter 连接/使用 Azure Redis 服务
【Azure Redis 缓存】示例使用 redisson-spring-boot-starter 连接/使用 Azure Redis 服务
|
2月前
|
Java API 对象存储
微服务魔法启动!Spring Cloud与Netflix OSS联手,零基础也能创造服务奇迹!
这段内容介绍了如何使用Spring Cloud和Netflix OSS构建微服务架构。首先,基于Spring Boot创建项目并添加Spring Cloud依赖项。接着配置Eureka服务器实现服务发现,然后创建REST控制器作为API入口。为提高服务稳定性,利用Hystrix实现断路器模式。最后,在启动类中启用Eureka客户端功能。此外,还可集成其他Netflix OSS组件以增强系统功能。通过这些步骤,开发者可以更高效地构建稳定且可扩展的微服务系统。
56 1
|
3月前
|
存储 Java Spring
【Azure Spring Cloud】Azure Spring Cloud服务,如何获取应用程序日志文件呢?
【Azure Spring Cloud】Azure Spring Cloud服务,如何获取应用程序日志文件呢?
|
4月前
|
Java Spring
spring cloud gateway在使用 zookeeper 注册中心时,配置https 进行服务转发
spring cloud gateway在使用 zookeeper 注册中心时,配置https 进行服务转发
111 3
|
4月前
|
监控 Java 开发者
Spring Cloud中的服务熔断与降级
Spring Cloud中的服务熔断与降级
|
4月前
|
负载均衡 Java API
Spring Cloud中的服务路由与过滤
Spring Cloud中的服务路由与过滤
|
5月前
|
安全 Java API
Java一分钟之-Spring Data REST:创建RESTful服务
【6月更文挑战第15天】Spring Data REST让基于Spring Data的项目轻松创建REST API,免去大量控制器代码。通过自动HTTP映射和链接生成,简化CRUD操作。文章涵盖启用REST、配置仓库、自定义端点、解决过度暴露、缺失逻辑和安全性问题,提供代码示例,如自定义Repository、投影和安全配置,强调在利用其便利性时注意潜在挑战。
76 5
|
5月前
|
Java Nacos 数据格式
Spring Cloud Nacos 详解:服务注册与发现及配置管理平台
Spring Cloud Nacos 详解:服务注册与发现及配置管理平台
202 3