公众号_芋道源码

公众号_芋道源码 查看完整档案

填写现居城市  |  填写毕业院校  |  填写所在公司/组织填写个人主网站
编辑

个人博客 http://www.iocoder.cn?sf
公众号:芋道源码。如果你喜欢阅读源码,欢迎关注我的公众号。

个人动态

公众号_芋道源码 赞了文章 · 2019-01-23

SpringBoot多环境配置self4j + logback 以及遇到的问题

SpringBoot Starter 包下面默认已经集成了Self4J + LogBack
图片描述

问题

大家都知道SpringBoot可以使用yml和properties两种配置方式。但是这边有一个问题,在使用yml方式配置logback的时候,不能用LogHome指定它的路径。如:

logging:
  config:
    classpath: config/logback-spring:xml
  path: log
  file: loginfo

<property name="LOG_HOME" value="${LOG_PATH}/${LOG_FILE}" />

这样做会导致在你的目录下生成一个loginfo的文件,而没有路径。但是如果你用properties配置文件,就不会有这个问题。我被困扰了好久···。

yml多环境配置logback

首先是主要的配置文件:application.yml

spring:
  profiles:
    active: dev  //这边代表你需要激活哪个环境

创建你需要的环境配置文件,

在同一目录下:如application-dev.yml

在resource根目录下创建logback配置文件

<?xml version="1.0" encoding="UTF-8"?>
<configuration debug="true">
    <!-- 项目名称 -->
    <property name="PROJECT_NAME" value="projectmanage"/>

    <!--定义日志文件的存储地址 勿在 LogBack 的配置中使用相对路径-->
    <property name="LOG_HOME" value="${catalina.base:-.}/logs"/>

    <!-- 控制台输出 -->
    <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
        <withJansi>true</withJansi>
        <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
            <!--格式化输出:%d表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度%msg:日志消息,%n是换行符-->
            <pattern>[%d{yyyy-MM-dd HH:mm:ss.SSS}] [%thread] %highlight([%-5level] %logger{50} - %msg%n)</pattern>
            <charset>UTF-8</charset>
        </encoder>
    </appender>

    <!-- 系统错误日志文件 -->
    <appender name="SYSTEM_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <!-- 过滤器,只打印ERROR级别的日志 -->
        <filter class="ch.qos.logback.classic.filter.LevelFilter">
            <level>ERROR</level>
            <onMatch>ACCEPT</onMatch>
            <onMismatch>DENY</onMismatch>
        </filter>
        <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
            <!--日志文件输出的文件名-->
            <FileNamePattern>${LOG_HOME}/${PROJECT_NAME}.system_error.%d{yyyy-MM-dd}.%i.log</FileNamePattern>
            <!--日志文件保留天数-->
            <MaxHistory>15</MaxHistory>
            <!--日志文件最大的大小-->
            <MaxFileSize>10MB</MaxFileSize>
        </rollingPolicy>

        <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
            <!--格式化输出:%d表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度%msg:日志消息,%n是换行符-->
            <pattern>[%d{yyyy-MM-dd HH:mm:ss.SSS}] [%thread] [%-5level] %logger{50} - %msg%n</pattern>
            <charset>UTF-8</charset>
        </encoder>
    </appender>
    <logger name="system_error" additivity="true">
        <appender-ref ref="SYSTEM_FILE"/>
    </logger>

    <!-- 自己打印的日志文件,用于记录重要日志信息 -->
    <appender name="MY_INFO_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <!-- 过滤器,只打印ERROR级别的日志 -->
        <filter class="ch.qos.logback.classic.filter.LevelFilter">
            <level>INFO</level>
            <onMatch>ACCEPT</onMatch>
            <onMismatch>DENY</onMismatch>
        </filter>
        <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
            <!--日志文件输出的文件名-->
            <FileNamePattern>${LOG_HOME}/${PROJECT_NAME}.my_info.%d{yyyy-MM-dd}.%i.log</FileNamePattern>
            <!--日志文件保留天数-->
            <MaxHistory>15</MaxHistory>
            <!--日志文件最大的大小-->
            <MaxFileSize>10MB</MaxFileSize>
        </rollingPolicy>

        <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
            <!--格式化输出:%d表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度%msg:日志消息,%n是换行符-->
            <pattern>[%d{yyyy-MM-dd HH:mm:ss.SSS}] [%thread] [%-5level] %logger{50} - %msg%n</pattern>
            <charset>UTF-8</charset>
        </encoder>
    </appender>
    <logger name="my_info" additivity="true" level="info">
        <appender-ref ref="MY_INFO_FILE"/>
    </logger>

    

    <!-- 开发环境下的日志配置  这边就是你开发环境的配置-->
    <springProfile name="dev">
        <root level="INFO">
            <appender-ref ref="CONSOLE"/>
            <appender-ref ref="SYSTEM_FILE"/>
        </root>
    </springProfile>

    <!-- 生产环境下的日志配置 这边就是你生产环境的配置-->
    <springProfile name="prod">
        <root level="INFO">
            <appender-ref ref="SYSTEM_FILE"/>
        </root>
    </springProfile>
</configuration>
查看原文

赞 3 收藏 2 评论 0

公众号_芋道源码 赞了文章 · 2019-01-23

Dubbo源码解析(二十五)远程调用——hessian协议

远程调用——hessian协议

目标:介绍远程调用中跟hessian协议相关的设计和实现,介绍dubbo-rpc-hessian的源码。

前言

本文讲解多是dubbo集成的第二种协议,hessian协议,Hessian 是 Caucho 开源的一个 RPC 框架,其通讯效率高于 WebService 和 Java 自带的序列化。dubbo集成hessian所提供的hessian协议相关介绍可以参考官方文档,我就不再赘述。

文档地址:http://dubbo.apache.org/zh-cn...

源码分析

(一)DubboHessianURLConnectionFactory

该类继承了HessianURLConnectionFactory类,是dubbo,用于创建与服务器的连接的内部工厂,重写了父类中open方法。

public class DubboHessianURLConnectionFactory extends HessianURLConnectionFactory {

    /**
     * 打开与HTTP服务器的新连接或循环连接
     * @param url
     * @return
     * @throws IOException
     */
    @Override
    public HessianConnection open(URL url) throws IOException {
        // 获得一个连接
        HessianConnection connection = super.open(url);
        // 获得上下文
        RpcContext context = RpcContext.getContext();
        for (String key : context.getAttachments().keySet()) {
            // 在http协议头里面加入dubbo中附加值,key为 header+key  value为附加值的value
            connection.addHeader(Constants.DEFAULT_EXCHANGER + key, context.getAttachment(key));
        }

        return connection;
    }
}

在hessian上加入dubbo自己所需要的附加值,放到协议头里面进行发送。

(二)HttpClientConnection

该类是基于HttpClient封装来实现HessianConnection接口,其中逻辑比较简单。

public class HttpClientConnection implements HessianConnection {

    /**
     * http客户端对象
     */
    private final HttpClient httpClient;

    /**
     * 字节输出流
     */
    private final ByteArrayOutputStream output;

    /**
     * http post请求对象
     */
    private final HttpPost request;

    /**
     * http 响应对象
     */
    private volatile HttpResponse response;

    public HttpClientConnection(HttpClient httpClient, URL url) {
        this.httpClient = httpClient;
        this.output = new ByteArrayOutputStream();
        this.request = new HttpPost(url.toString());
    }

    /**
     * 增加协议头
     * @param key
     * @param value
     */
    @Override
    public void addHeader(String key, String value) {
        request.addHeader(new BasicHeader(key, value));
    }

    @Override
    public OutputStream getOutputStream() throws IOException {
        return output;
    }

    /**
     * 发送请求
     * @throws IOException
     */
    @Override
    public void sendRequest() throws IOException {
        request.setEntity(new ByteArrayEntity(output.toByteArray()));
        this.response = httpClient.execute(request);
    }

    /**
     * 获得请求后的状态码
     * @return
     */
    @Override
    public int getStatusCode() {
        return response == null || response.getStatusLine() == null ? 0 : response.getStatusLine().getStatusCode();
    }

    @Override
    public String getStatusMessage() {
        return response == null || response.getStatusLine() == null ? null : response.getStatusLine().getReasonPhrase();
    }

    @Override
    public String getContentEncoding() {
        return (response == null || response.getEntity() == null || response.getEntity().getContentEncoding() == null) ? null : response.getEntity().getContentEncoding().getValue();
    }

    @Override
    public InputStream getInputStream() throws IOException {
        return response == null || response.getEntity() == null ? null : response.getEntity().getContent();
    }

    @Override
    public void close() throws IOException {
        HttpPost request = this.request;
        if (request != null) {
            request.abort();
        }
    }

    @Override
    public void destroy() throws IOException {
    }

(三)HttpClientConnectionFactory

该类实现了HessianConnectionFactory接口,是创建HttpClientConnection的工厂类。该类的实现跟DubboHessianURLConnectionFactory类类似,但是DubboHessianURLConnectionFactory是标准的Hessian接口调用会采用的工厂类,而HttpClientConnectionFactory是Dubbo 的 Hessian 协议调用。当然Dubbo 的 Hessian 协议也是基于http的。

public class HttpClientConnectionFactory implements HessianConnectionFactory {

    /**
     * httpClient对象
     */
    private final HttpClient httpClient = new DefaultHttpClient();

    @Override
    public void setHessianProxyFactory(HessianProxyFactory factory) {
        // 设置连接超时时间
        HttpConnectionParams.setConnectionTimeout(httpClient.getParams(), (int) factory.getConnectTimeout());
        // 设置读取数据时阻塞链路的超时时间
        HttpConnectionParams.setSoTimeout(httpClient.getParams(), (int) factory.getReadTimeout());
    }

    
    @Override
    public HessianConnection open(URL url) throws IOException {
        // 创建一个HttpClientConnection实例
        HttpClientConnection httpClientConnection = new HttpClientConnection(httpClient, url);
        // 获得上下文,用来获得附加值
        RpcContext context = RpcContext.getContext();
        // 遍历附加值,放入到协议头里面
        for (String key : context.getAttachments().keySet()) {
            httpClientConnection.addHeader(Constants.DEFAULT_EXCHANGER + key, context.getAttachment(key));
        }
        return httpClientConnection;
    }

}

实现了两个方法,第一个方法是给http连接设置两个参数配置,第二个方法是创建一个连接。

(四)HessianProtocol

该类继承了AbstractProxyProtocol类,是hessian协议的实现类。其中实现类基于hessian协议的服务引用、服务暴露等方法。

1.属性

/**
 * http服务器集合
 * key为ip:port
 */
private final Map<String, HttpServer> serverMap = new ConcurrentHashMap<String, HttpServer>();

/**
 * HessianSkeleto 集合
 * key为服务名
 */
private final Map<String, HessianSkeleton> skeletonMap = new ConcurrentHashMap<String, HessianSkeleton>();

/**
 * HttpBinder对象,默认是jetty实现
 */
private HttpBinder httpBinder;

2.doExport

@Override
protected <T> Runnable doExport(T impl, Class<T> type, URL url) throws RpcException {
    // 获得ip地址
    String addr = getAddr(url);
    // 获得http服务器对象
    HttpServer server = serverMap.get(addr);
    // 如果为空,则重新创建一个server,然后放入集合
    if (server == null) {
        server = httpBinder.bind(url, new HessianHandler());
        serverMap.put(addr, server);
    }
    // 获得服务path
    final String path = url.getAbsolutePath();
    // 创建Hessian服务端对象
    final HessianSkeleton skeleton = new HessianSkeleton(impl, type);
    // 加入集合
    skeletonMap.put(path, skeleton);

    // 获得通用的path
    final String genericPath = path + "/" + Constants.GENERIC_KEY;
    // 加入集合
    skeletonMap.put(genericPath, new HessianSkeleton(impl, GenericService.class));

    // 返回一个线程
    return new Runnable() {
        @Override
        public void run() {
            skeletonMap.remove(path);
            skeletonMap.remove(genericPath);
        }
    };
}

该方法是服务暴露的主要逻辑实现。

3.doRefer

@Override
@SuppressWarnings("unchecked")
protected <T> T doRefer(Class<T> serviceType, URL url) throws RpcException {
    // 获得泛化的参数
    String generic = url.getParameter(Constants.GENERIC_KEY);
    // 是否是泛化调用
    boolean isGeneric = ProtocolUtils.isGeneric(generic) || serviceType.equals(GenericService.class);
    // 如果是泛化调用。则设置泛化的path和附加值
    if (isGeneric) {
        RpcContext.getContext().setAttachment(Constants.GENERIC_KEY, generic);
        url = url.setPath(url.getPath() + "/" + Constants.GENERIC_KEY);
    }

    // 创建代理工厂
    HessianProxyFactory hessianProxyFactory = new HessianProxyFactory();
    // 是否是Hessian2的请求 默认为否
    boolean isHessian2Request = url.getParameter(Constants.HESSIAN2_REQUEST_KEY, Constants.DEFAULT_HESSIAN2_REQUEST);
    // 设置是否应使用Hessian协议的版本2来解析请求
    hessianProxyFactory.setHessian2Request(isHessian2Request);
    // 是否应为远程调用启用重载方法,默认为否
    boolean isOverloadEnabled = url.getParameter(Constants.HESSIAN_OVERLOAD_METHOD_KEY, Constants.DEFAULT_HESSIAN_OVERLOAD_METHOD);
    // 设置是否应为远程调用启用重载方法。
    hessianProxyFactory.setOverloadEnabled(isOverloadEnabled);
    // 获得client实现方式,默认为jdk
    String client = url.getParameter(Constants.CLIENT_KEY, Constants.DEFAULT_HTTP_CLIENT);
    if ("httpclient".equals(client)) {
        // 用http来创建
        hessianProxyFactory.setConnectionFactory(new HttpClientConnectionFactory());
    } else if (client != null && client.length() > 0 && !Constants.DEFAULT_HTTP_CLIENT.equals(client)) {
        // 抛出不支持的协议异常
        throw new IllegalStateException("Unsupported http protocol client=\"" + client + "\"!");
    } else {
        // 创建一个HessianConnectionFactory对象
        HessianConnectionFactory factory = new DubboHessianURLConnectionFactory();
        // 设置代理工厂
        factory.setHessianProxyFactory(hessianProxyFactory);
        // 设置工厂
        hessianProxyFactory.setConnectionFactory(factory);
    }
    // 获得超时时间
    int timeout = url.getParameter(Constants.TIMEOUT_KEY, Constants.DEFAULT_TIMEOUT);
    // 设置超时时间
    hessianProxyFactory.setConnectTimeout(timeout);
    hessianProxyFactory.setReadTimeout(timeout);
    // 创建代理
    return (T) hessianProxyFactory.create(serviceType, url.setProtocol("http").toJavaURL(), Thread.currentThread().getContextClassLoader());
}

该方法是服务引用的主要逻辑实现,根据客户端配置,来选择标准 Hessian 接口调用还是Dubbo 的 Hessian 协议调用。

4.getErrorCode

@Override
protected int getErrorCode(Throwable e) {
    // 如果属于HessianConnectionException异常
    if (e instanceof HessianConnectionException) {
        if (e.getCause() != null) {
            Class<?> cls = e.getCause().getClass();
            // 如果属于超时异常,则返回超时异常
            if (SocketTimeoutException.class.equals(cls)) {
                return RpcException.TIMEOUT_EXCEPTION;
            }
        }
        // 否则返回网络异常
        return RpcException.NETWORK_EXCEPTION;
    } else if (e instanceof HessianMethodSerializationException) {
        // 序列化异常
        return RpcException.SERIALIZATION_EXCEPTION;
    }
    return super.getErrorCode(e);
}

该方法是针对异常的处理。

5.HessianHandler

private class HessianHandler implements HttpHandler {

    @Override
    public void handle(HttpServletRequest request, HttpServletResponse response)
            throws IOException, ServletException {
        // 获得请求的uri
        String uri = request.getRequestURI();
        // 获得对应的HessianSkeleton对象
        HessianSkeleton skeleton = skeletonMap.get(uri);
        // 如果如果不是post方法
        if (!request.getMethod().equalsIgnoreCase("POST")) {
            // 返回状态设置为500
            response.setStatus(500);
        } else {
            // 设置远程地址
            RpcContext.getContext().setRemoteAddress(request.getRemoteAddr(), request.getRemotePort());

            // 获得请求头内容
            Enumeration<String> enumeration = request.getHeaderNames();
            // 遍历请求头内容
            while (enumeration.hasMoreElements()) {
                String key = enumeration.nextElement();
                // 如果key开头是deader,则把附加值取出来放入上下文
                if (key.startsWith(Constants.DEFAULT_EXCHANGER)) {
                    RpcContext.getContext().setAttachment(key.substring(Constants.DEFAULT_EXCHANGER.length()),
                            request.getHeader(key));
                }
            }

            try {
                // 执行下一个
                skeleton.invoke(request.getInputStream(), response.getOutputStream());
            } catch (Throwable e) {
                throw new ServletException(e);
            }
        }
    }

}

该内部类是Hessian的处理器,用来处理请求中的协议头内容。

后记

该部分相关的源码解析地址:https://github.com/CrazyHZM/i...

该文章讲解了远程调用中关于hessian协议的部分,内容比较简单,可以参考着官方文档了解一下。接下来我将开始对rpc模块关于hessian协议部分进行讲解。

查看原文

赞 3 收藏 1 评论 0

公众号_芋道源码 关注了用户 · 2019-01-23

小小聪 @xiaoxiaocong_58ab02b3e5d1e

关注 8

公众号_芋道源码 关注了用户 · 2019-01-23

大桔子 @djz

下一步该干什么呢...

得花点时间想想辙了...

2020年会发生点什么呢?🤔🤔🤔

关注 22

公众号_芋道源码 关注了用户 · 2019-01-23

达观数据 @datagrand

达观数据是一家为企业提供各类场景办公机器人的人工智能企业。达观智能办公机器人深度集成自然语言处理(NLP)、光学字符识别(OCR)、机器人流程自动化(RPA)和知识图谱等各类技术,结合企业业务场景提供高效便捷可控的办公机器人产品和解决方案。

关注 16

公众号_芋道源码 关注了用户 · 2019-01-23

lemonc @cheng_5a90d02685a12

关注 2

公众号_芋道源码 关注了用户 · 2019-01-23

我不想学习了 @zhang_5ba9e26983d95

csdn博客:我不想学习了
简书:幸人的杏仁

关注 3

公众号_芋道源码 关注了用户 · 2019-01-23

余志威 @yuzhiwei

知不知对你牵上万缕爱意,
每晚也痛心空费心思,
这小子预断难断这故事,
全为我爱上你偏偏你不知。

关注 1

公众号_芋道源码 关注了用户 · 2019-01-23

_Junjun @_junjun

关注 6

认证与成就

  • 获得 0 次点赞
  • 获得 1 枚徽章 获得 0 枚金徽章, 获得 1 枚银徽章, 获得 0 枚铜徽章

擅长技能
编辑

(゚∀゚ )
暂时没有

开源项目 & 著作
编辑

(゚∀゚ )
暂时没有

注册于 2018-12-22
个人主页被 2.1k 人浏览