All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.nutz.boot.starter.feign.FeignStarter Maven / Gradle / Ivy

The newest version!
package org.nutz.boot.starter.feign;

import java.lang.reflect.Field;
import java.net.URI;
import java.util.List;

import org.nutz.boot.AppContext;
import org.nutz.boot.annotation.PropDoc;
import org.nutz.boot.starter.feign.annotation.FeignInject;
import org.nutz.ioc.Ioc;
import org.nutz.ioc.IocEventListener;
import org.nutz.ioc.impl.PropertiesProxy;
import org.nutz.ioc.loader.annotation.Inject;
import org.nutz.ioc.loader.annotation.IocBean;
import org.nutz.json.Json;
import org.nutz.json.JsonFormat;
import org.nutz.lang.Mirror;
import org.nutz.lang.Strings;
import org.nutz.log.Log;
import org.nutz.log.Logs;

import com.netflix.client.config.DefaultClientConfigImpl;
import com.netflix.discovery.EurekaClient;
import com.netflix.loadbalancer.AvailabilityFilteringRule;
import com.netflix.loadbalancer.IRule;
import com.netflix.loadbalancer.LoadBalancerBuilder;
import com.netflix.loadbalancer.RandomRule;
import com.netflix.loadbalancer.ServerList;
import com.netflix.loadbalancer.ServerListFilter;
import com.netflix.loadbalancer.ServerListUpdater;
import com.netflix.loadbalancer.ZoneAffinityServerListFilter;
import com.netflix.loadbalancer.ZoneAwareLoadBalancer;
import com.netflix.niws.loadbalancer.DiscoveryEnabledNIWSServerList;
import com.netflix.niws.loadbalancer.DiscoveryEnabledServer;
import com.netflix.niws.loadbalancer.EurekaNotificationServerListUpdater;

import feign.Client;
import feign.Feign;
import feign.Logger;
import feign.Logger.Level;
import feign.Request.Options;
import feign.codec.Decoder;
import feign.codec.Encoder;
import feign.gson.GsonDecoder;
import feign.gson.GsonEncoder;
import feign.httpclient.ApacheHttpClient;
import feign.hystrix.HystrixFeign;
import feign.jackson.JacksonDecoder;
import feign.jackson.JacksonEncoder;
import feign.jaxb.JAXBContextFactory;
import feign.jaxb.JAXBDecoder;
import feign.jaxb.JAXBEncoder;
import feign.okhttp.OkHttpClient;
import feign.ribbon.LBClient;
import feign.ribbon.LBClientFactory;
import feign.ribbon.RibbonClient;
import feign.slf4j.Slf4jLogger;

@IocBean
public class FeignStarter implements IocEventListener {
    
    private static final Log log = Logs.get();

    protected static String PRE = "feign.";

    @PropDoc(value = "日志级别", defaultValue = "basic", possible = {"none", "basic", "headers", "full"})
    public static final String PROP_LOGLEVEL = PRE + "logLevel";

    @PropDoc(value = "Api Base URL", defaultValue = "http://127.0.0.1:8080")
    public static final String PROP_URL = PRE + "url";

    @PropDoc(value = "客户端实现类", defaultValue = "jdk", possible = {"jdk", "httpclient", "okhttp", "ribbon", "ioc:XXX"})
    public static final String PROP_CLIENT = PRE + "client";

    @PropDoc(value = "WebService的schema地址")
    public static final String PROP_SCHEMA = PRE + "schema";

    @PropDoc(value = "默认编码器", possible = {"raw", "nutzjson", "jackson", "gson", "jaxb", "jaxrs", "ioc:XXX"})
    public static final String PROP_ENCODER = PRE + "encoder";

    @PropDoc(value = "默认解码器", possible = {"raw", "nutzjson", "jackson", "gson", "jaxb", "jaxrs", "ioc:XXX"})
    public static final String PROP_DECODER = PRE + "decoder";

    @PropDoc(value = "是否使用Hystrix", defaultValue = "false", possible = {"true", "false"})
    public static final String PROP_HYSTRIX_ENABLE = PRE + "hystrix.enable";

    @PropDoc(value = "默认负载均衡的规则", defaultValue = "availability", possible = {"availability", "random"})
    public static final String PROP_LB_RULE = PRE + "loadbalancer.rule";

    @PropDoc(value = "JsonFormat", possible = {"full", "forLook", "compact", "nice", "tidy", "ioc:XXX",  "{...}"})
    public static final String PROP_JSON_FORMAT = PRE + "jsonFormat";

    @PropDoc(value = "连接超时", defaultValue="10000", type="int")
    public static final String PROP_CONNECT_TIMEOUT = PRE + "connectTimeout";

    @PropDoc(value = "读取超时", defaultValue="60000", type="int")
    public static final String PROP_READ_TIMEOUT = PRE + "readTimeout";

    @Inject("refer:$ioc")
    protected Ioc ioc;
    
    @Inject
    protected AppContext appContext;

    @Inject
    protected PropertiesProxy conf;

    @SuppressWarnings({"unchecked", "rawtypes"})
    public Object afterBorn(Object obj, String beanName) {
        try {
            Mirror mirror = Mirror.me(obj);
            for (Field field : obj.getClass().getDeclaredFields()) {
                FeignInject fc = field.getAnnotation(FeignInject.class);
                if (fc == null)
                    continue;
                String url = fc.apiBaseUrl();
                if (Strings.isBlank(url))
                    url = Strings.sBlank(conf.get(PROP_URL), "http://127.0.0.1:8080");
                Encoder encoder = getEncoder(fc, field);
                Decoder decoder = getDecoder(fc, field);
                Client client = getClient(fc, field, url);
                Logger.Level level = Level.valueOf(conf.get(PROP_LOGLEVEL, "BASIC").toUpperCase());
                Class apiType = field.getType();
                Logger logger = new Slf4jLogger(apiType);

                boolean useHystrix = "true".equals(Strings.sBlank(fc.useHystrix(), conf.get(PROP_HYSTRIX_ENABLE)));
                Feign.Builder builder = useHystrix ? HystrixFeign.builder() : Feign.builder();
                if (encoder != null)
                    builder.encoder(encoder);
                if (decoder != null)
                    builder.decoder(decoder);
                if (client != null)
                    builder.client(client);
                builder.logger(logger);
                builder.logLevel(level);
                int connectTimeout = fc.connectTimeout();
                if (connectTimeout == 0)
                    connectTimeout = conf.getInt(PROP_CONNECT_TIMEOUT, 10*1000);
                int readTimeout = fc.readTimeout();
                if (readTimeout == 0)
                    readTimeout = conf.getInt(PROP_READ_TIMEOUT, 10*1000);
                builder.options(new Options(connectTimeout, readTimeout));
                Object t = null;
                if (useHystrix) {
                    t = ((HystrixFeign.Builder) builder).target(apiType, url, getFallbackIocBean(apiType, fc.fallback()));
                } else {
                    t = builder.target(apiType, url);
                }
                mirror.setValue(obj, field.getName(), t);
            }
        }
        catch (Throwable e) {
            throw new RuntimeException(e);
        }
        return obj;
    }

    public Object afterCreate(Object obj, String beanName) {
        return obj;
    }

    public int getOrder() {
        return 0;
    }

    protected Decoder getDecoder(FeignInject fc, Field field) {
        String decoderStr = Strings.sBlank(fc.decoder(), conf.get(PROP_DECODER, ""));
        switch (decoderStr) {
        case "":
        case "raw":
            break;
        case "nutzjson":
            return new NutzJsonDecoder();
        case "jackson":
            return new JacksonDecoder();
        case "gson":
            return new GsonDecoder();
        case "jaxb":
            JAXBContextFactory jaxbFactory = new JAXBContextFactory.Builder().withMarshallerJAXBEncoding("UTF-8")
                                                                             .withMarshallerSchemaLocation(getSchemaString(fc.schema()))
                                                                             .build();
            return new JAXBDecoder(jaxbFactory);
        default:
            if (decoderStr.startsWith("ioc:"))
                return ioc.get(Decoder.class, decoderStr.substring(4));
            break;
        }
        return null;
    }

    protected Encoder getEncoder(FeignInject fc, Field field) {
        String encoderStr = Strings.sBlank(fc.encoder(), conf.get(PROP_ENCODER, ""));
        switch (encoderStr) {
        case "":
        case "raw":
            break;
        case "nutzjson":
            JsonFormat jf = JsonFormat.full();
            String jfStr = Strings.sBlank(fc.jsonFormat(), conf.get(PROP_JSON_FORMAT, ""));
            if (!Strings.isBlank(jfStr)) {
                if (jfStr.startsWith("{")) {
                    jf = Json.fromJson(JsonFormat.class, jfStr);
                }
                else if (jfStr.startsWith("ioc:")) {
                    jf = ioc.get(JsonFormat.class, jfStr.substring(4));
                }
                else {
                   try {
                        jf = (JsonFormat) JsonFormat.class.getMethod(jfStr).invoke(null);
                    }
                     catch (Exception e) {
                         log.infof("invaild JsonFormat=[%s] at %s", jfStr, field);
                    }
                }
            }
            return new NutzJsonEncoder(jf);
        case "jackson":
            return new JacksonEncoder();
        case "gson":
            return new GsonEncoder();
        case "jaxb":
            JAXBContextFactory jaxbFactory = new JAXBContextFactory.Builder().withMarshallerJAXBEncoding("UTF-8")
                                                                             .withMarshallerSchemaLocation(getSchemaString(fc.schema()))
                                                                             .build();
            return new JAXBEncoder(jaxbFactory);
        default:
            if (encoderStr.startsWith("ioc"))
                return ioc.get(Encoder.class, encoderStr.substring(4));
            break;
        }
        return null;
    }

    protected Client getClient(FeignInject fc, Field field, String url) {
        String clientStr = getClientString(fc);
        switch (clientStr) {
        case "jdk":
            // nop
            break;
        case "okhttp":
            return new OkHttpClient();
        case "httpclient":
            return new ApacheHttpClient();
        case "ribbon":
            LBClient lb = (LBClient)getLoadBalancer(URI.create(url).getHost(), fc);
            return RibbonClient.builder().lbClientFactory(new LBClientFactory() {
                public LBClient create(String clientName) {
                    return lb;
                }
            }).build();
        default:
            if (clientStr.startsWith("ioc:"))
                return ioc.get(Client.class, clientStr.substring(4));
            break;
        }
        return null;
    }

    public String getClientString(FeignInject fc) {
        return Strings.sBlank(fc.client(), conf.get(PROP_CLIENT, "jdk"));
    }

    public  T getFallbackIocBean(Class apiType, String fallbackName) {
        if (!Strings.isBlank(fallbackName))
            return ioc.get(apiType, fallbackName);
        List list = appContext.getBeans(apiType);
        if (list.size() > 0)
            return list.get(0);
        return null;
    }

    public String getSchemaString(String schema) {
        return Strings.sBlank(schema, conf.get(PROP_SCHEMA));
    }
    
    public String getLbRuleString(String lbRule) {
        return Strings.sBlank(lbRule, conf.get(PROP_LB_RULE, "availability"));
    }
    
    public Object getLoadBalancer(String name, FeignInject fc) {
        EurekaClient eurekaClient = ioc.get(EurekaClient.class, "eurekaClient");
        DefaultClientConfigImpl clientConfig = DefaultClientConfigImpl.getClientConfigWithDefaultValues(name);
        ServerList list = new DiscoveryEnabledNIWSServerList(name, ()->eurekaClient);
        ServerListFilter filter = new ZoneAffinityServerListFilter(clientConfig);
        ServerListUpdater updater = new EurekaNotificationServerListUpdater(()->eurekaClient);

        IRule rule = null;
        switch (getLbRuleString(fc.lbRule())) {
        case "random":
            rule = new RandomRule();
            break;
        case "availability":
        default:
            AvailabilityFilteringRule _rule = new AvailabilityFilteringRule();
            _rule.initWithNiwsConfig(clientConfig);
            rule = _rule;
            break;
        }
        ZoneAwareLoadBalancer lb = LoadBalancerBuilder.newBuilder()
                .withDynamicServerList(list)
                .withRule(rule)
                .withServerListFilter(filter)
                .withServerListUpdater(updater)
                .withClientConfig(clientConfig)
                .buildDynamicServerListLoadBalancerWithUpdater();
        return LBClient.create(lb, clientConfig);
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy