Spring服务定制

问题总述

? 我们都知道如果使用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等来定制类的加载过程。

原文地址:https://www.cnblogs.com/Kidezyq/p/8611438.html

时间: 2024-11-09 17:32:25

Spring服务定制的相关文章

推送服务定制

推送服务定制流程 Q_Q!!! 1:配置Server URL www.benben.net.isis  ---------www.benben.net(域名)+isis(app的名称),全部小写,不要空格, IPD版本为www.benben.net.isishd,  尾部加上hd 2:证书的配置 一:iphone和ipd证书需要转化,将证书将 xxxx.cer 用 openssl x509 -in aps_production.cer -inform DER -out aps_productio

Spring Boot 定制URL匹配规则的方法

事情的起源:有人问我,说编写了一个/hello访问路径,但是吧,不管是输入/hello还是/hello.html,还是/hello.xxx都能进行访问.当时我还以为他对代码进行处理了,后来发现不是,后来发现这是Spring Boot路由规则.好了,有废话了下,那么看看我们解决上面这个导致的问题. 构建web应用程序时,并不是所有的URL请求都遵循默认的规则.有时,我们希望RESTful URL匹配的时候包含定界符“.”,这种情况在Spring中可以称之为“定界符定义的格式”:有时,我们希望识别斜

spring服务定位器,可在任何地方获取bean

通过持有的Spring应用场景ApplicationContext,可在任何地方获取bean. 1 import org.apache.commons.logging.Log; 2 import org.apache.commons.logging.LogFactory; 3 import org.springframework.beans.factory.DisposableBean; 4 import org.springframework.context.ApplicationContex

Android网络定位服务定制简述

Android 添加高德或百度网络定位服务 Android的网络定位服务以第三方的APK方式提供服务,由于在国内Android原生自带的com.google.android.gms服务几乎处于不可用状态,因此对于第三方OEM厂商经常需要与高德或百度合作,使用这两个服务提供商提供的网络位置定位服务.现将在Android平台集成第三方网络定位服务的步骤简述如下: 1.向高德或百度获取网络定位服务apk,并集成至system/app目录下,有时需要同步获取其运行时需要的库,并集成与指定目录,一般是sy

spring boot定制Jackson ObjectMapper,为什么不生效

先说结论: 项目中定制了spring 的redisTemplate,而这个template没有使用我自定义的Jackson ObjectMapper.所以不生效. 下面是详细过程: 起因是spring boot项目加入了shiro,我打算使用redis去存储shiro的会话,方便以后横向扩展. 参考了网上的实现后,决定通过扩展org.apache.shiro.session.mgt.eis.AbstractSessionDAO来实现. 以下是实现代码: 1 package com.ceiec.b

Spring boot 定制自己的错误

1).如何定制错误的页面: ? 1).有模板引擎的情况下:error/状态码; [将错误页面命名为 错误状态码.html 放在模板引擎文件夹里面的 error文件夹下],发生此状态码的错误就会来到 对应的页面: ? 我们可以使用4xx和5xx作为错误页面的文件名来匹配这种类型的所有错误,精确优先(优先寻找精确的状态码.html): ? 页面能获取的信息: ? timestamp:时间戳 ? status:状态码 ? error:错误提示 ? exception:异常对象 ? message:异常

Spring Boot项目中如何定制PropertyEditors

本文首发于个人网站:Spring Boot项目中如何定制PropertyEditors 在Spring Boot: 定制HTTP消息转换器一文中我们学习了如何配置消息转换器用于HTTP请求和响应数据,实际上,在一次请求的完成过程中还发生了其他的转换,我们这次关注将参数转换成多种类型的对象,如:字符串转换成Date对象或字符串转换成Integer对象. 在编写控制器中的action方法时,Spring允许我们使用具体的数据类型定义函数签名,这是通过PropertyEditor实现的.Propert

Spring Boot实战与原理分析

1:Spring Boot概述与课程概要介绍 2:Spring4 快速入门 3:Spring4 扩展分析(一) 4:Spring4 扩展分析(二) 5:Spring Boot 快速入门 6:Spring Boot 配置分析(一) 7:Spring Boot 配置分析(二) 8:Spring Boot 自动配置 9:Spring Boot @Enable*注解的工作原理 10:Spring Boot @EnableAutoConfiguration深入分析 11:Spring Boot 事件监听

微服务架构设计

微服务 软件架构是一个包含各种组织的系统组织,这些组件包括 Web服务器, 应用服务器, 数据库,存储, 通讯层), 它们彼此或和环境存在关系.系统架构的目标是解决利益相关者的关注点. Conway’s law: Organizations which design systems[...] are constrained to produce designs which are copies of the communication structures of these organizati