作者归档:徐承恩

Java生产者消费者模型实践一

考查Java的并发编程时,手写“生产者-消费者模型”是一个经典问题。有如下几个考点:

  • 对Java并发模型的理解
  • 对Java并发编程接口的熟练程度
  • bug free
  • coding style

JDK版本:oracle java 1.8.0_102

本文主要归纳了4种写法,阅读后,最好在白板上练习几遍,检查自己是否掌握。这4种写法或者编程接口不同,或者并发粒度不同,但本质是相同的——都是在使用或实现BlockingQueue。

生产者-消费者模型

网上有很多生产者-消费者模型的定义和实现。本文研究最常用的有界生产者-消费者模型,简单概括如下:

  • 生产者持续生产,直到缓冲区满,阻塞;缓冲区不满后,继续生产
  • 消费者持续消费,直到缓冲区空,阻塞;缓冲区不空后,继续消费
  • 生产者可以有多个,消费者也可以有多个

可通过如下条件验证模型实现的正确性:

  • 同一产品的消费行为一定发生在生产行为之后
  • 任意时刻,缓冲区大小不小于0,不大于限制容量

该模型的应用和变种非常多,不赘述。

准备

面试时可语言说明以下准备代码。关键部分需要实现,如AbsConsumer。

下面会涉及多种生产者-消费者模型的实现,可以先抽象出关键的接口,并实现一些抽象类:

package com.github.xuchengen.concurrent;

/**
 * 生产者接口
 * 作者:徐承恩
 * 邮箱:xuchengen@gmail.com
 * 日期:2019/12/11
 */
public interface Producer {

    /**
     * 生产者负责生产
     *
     * @throws InterruptedException 线程意外终止异常
     */
    void produce() throws InterruptedException;

}
package com.github.xuchengen.concurrent;

/**
 * 消费者接口
 * 作者:徐承恩
 * 邮箱:xuchengen@gmail.com
 * 日期:2019/12/11
 */
public interface Consumer {

    /**
     * 消费者负责消费
     *
     * @throws InterruptedException 线程意外终止异常
     */
    void consume() throws InterruptedException;

}
package com.github.xuchengen.concurrent;

/**
 * 抽象生产者
 * 作者:徐承恩
 * 邮箱:xuchengen@gmail.com
 * 日期:2019/12/11
 */
public abstract class AbsProducer implements Producer, Runnable {

    @Override
    public void run() {
        while (true) {
            try {
                produce();
            } catch (InterruptedException e) {
                e.printStackTrace();
                break;
            }
        }
    }
}
package com.github.xuchengen.concurrent;

/**
 * 抽象生产者
 * 作者:徐承恩
 * 邮箱:xuchengen@gmail.com
 * 日期:2019/12/11
 */
public abstract class AbsConsumer implements Consumer, Runnable {

    @Override
    public void run() {
        while (true) {
            try {
                consume();
            } catch (InterruptedException e) {
                e.printStackTrace();
                break;
            }
        }
    }

}

不同的模型实现中,生产者、消费者的具体实现也不同,所以需要为模型定义抽象工厂方法:

package com.github.xuchengen.concurrent;

/**
 * 模型
 * 作者:徐承恩
 * 邮箱:xuchengen@gmail.com
 * 日期:2019/12/11
 */
public interface Model {

    /**
     * 实例化一个消费者
     *
     * @return Runnable
     */
    Runnable newRunnableConsumer();

    /**
     * 实例化一个生产者
     *
     * @return Runnable
     */
    Runnable newRunnableProducer();

}

我们将Task作为生产和消费的单位:

package com.github.xuchengen.concurrent;

/**
 * 任务
 * 作者:徐承恩
 * 邮箱:xuchengen@gmail.com
 * 日期:2019/12/11
 */
public class Task {

    /**
     * 任务号
     */
    public int no;

    public Task(int no) {
        this.no = no;
    }
}

如果需求还不明确(这符合大部分工程工作的实际情况),建议边实现边抽象,不要“面向未来编程”

设计模式分类(创建型模式、结构型模式、行为模式)

创建型模式

创建型模式,就是创建对象的模式,抽象了实例化的过程。它帮助一个系统独立于如何创建、组合和表示它的那些对象。关注的是对象的创建,创建型模式将创建对象的过程进行了抽象,也可以理解为将创建对象的过程进行了封装,作为客户程序仅仅需要去使用对象,而不再关系创建对象过程中的逻辑。

社会化的分工越来越细,自然在软件设计方面也是如此,因此对象的创建和对象的使用分开也就成为了必然趋势。因为对象的创建会消耗掉系统的很多资源,所以单独对对象的创建进行研究,从而能够高效地创建对象就是创建型模式要探讨的问题。这里有6个具体的创建型模式可供研究,它们分别是:

  • 简单工厂模式(Simple Factory)
  • 工厂方法模式(Factory Method)
  • 抽象工厂模式(Abstract Factory)
  • 创建者模式(Builder)
  • 原型模式(Prototype)
  • 单例模式(Singleton)

简单工厂模式不是GoF总结出来的23种设计模式之一

结构型模式

结构型模式是为解决怎样组装现有的类,设计它们的交互方式,从而达到实现一定的功能目的。结构型模式包容了对很多问题的解决。例如:扩展性(外观、组成、代理、装饰)、封装(适配器、桥接)。

在解决了对象的创建问题之后,对象的组成以及对象之间的依赖关系就成了开发人员关注的焦点,因为如何设计对象的结构、继承和依赖关系会影响到后续程序的维护性、代码的健壮性、耦合性等。对象结构的设计很容易体现出设计人员水平的高低,这里有7个具体的结构型模式可供研究,它们分别是:

  • 外观模式/门面模式(Facade门面模式)
  • 适配器模式(Adapter)
  • 代理模式(Proxy)
  • 装饰模式(Decorator)
  • 桥梁模式/桥接模式(Bridge)
  • 组合模式(Composite)
  • 享元模式(Flyweight)

行为型模式

行为型模式涉及到算法和对象间职责的分配,行为模式描述了对象和类的模式,以及它们之间的通信模式,行为模式刻划了在程序运行时难以跟踪的复杂的控制流可分为行为类模式和行为对象模式。1. 行为类模式使用继承机制在类间分派行为。2. 行为对象模式使用对象聚合来分配行为。一些行为对象模式描述了一组对等的对象怎样相互协作以完成其中任何一个对象都无法单独完成的任务。

在对象的结构和对象的创建问题都解决了之后,就剩下对象的行为问题了,如果对象的行为设计的好,那么对象的行为就会更清晰,它们之间的协作效率就会提高,这里有11个具体的行为型模式可供研究,它们分别是:

  • 模板方法模式(Template Method)
  • 观察者模式(Observer)
  • 状态模式(State)
  • 策略模式(Strategy)
  • 职责链模式(Chain of Responsibility)
  • 命令模式(Command)
  • 访问者模式(Visitor)
  • 调停者模式(Mediator)
  • 备忘录模式(Memento)
  • 迭代器模式(Iterator)
  • 解释器模式(Interpreter)

三者之间的区别和联系

创建型模式提供生存环境,结构型模式提供生存理由,行为型模式提供如何生存。

  1. 创建型模式为其他两种模式使用提供了环境。
  2. 结构型模式侧重于接口的使用,它做的一切工作都是对象或是类之间的交互,提供一个门。
  3. 行为型模式顾名思义,侧重于具体行为,所以概念中才会出现职责分配和算法通信等内容。

设计原则

  1. 开闭原则: 对扩展开放,对修改关闭
  2. 里氏转换原则: 子类继承父类,单独完全可以运行
  3. 依赖倒转原则: 引用一个对象,如果这个对象有底层类型,直接引用底层类型
  4. 接口隔离原则: 每一个接口应该是一种角色
  5. 合成/聚合复用原则: 新的对象应使用一些已有的对象,使之成为新对象的一部分
  6. 迪米特原则: 一个对象应对其他对象有尽可能少的了解

CentOS7安装zookeeper后建立systemctl自启动脚本

Apache ZooKeeper是Apache软件基金会的一个软件项目,它为大型分布式计算提供开源的分布式配置服务、同步服务和命名注册。ZooKeeper曾经是Hadoop的一个子项目,但现在是一个独立的顶级项目。

ZooKeeper的架构通过冗余服务实现高可用性。因此,如果第一次无应答,客户端就可以询问另一台ZooKeeper主机。ZooKeeper节点将它们的数据存储于一个分层的命名空间,非常类似于一个文件系统或一个前缀树结构。客户端可以在节点读写,从而以这种方式拥有一个共享的配置服务。更新是全序的。

使用ZooKeeper的公司包括Rackspace、雅虎和eBay,以及类似于像Solr这样的开源企业级搜索系统。

systemctl自启动脚本

[Unit]
Description=Zookeeper
Requires=network.target
After=network.target

[Service]
User=java
Group=java
Type=forking
Environment=JAVA_HOME=/opt/java/java8
WorkingDirectory=/opt/zookeeper
ExecStart=/opt/zookeeper/bin/zkServer.sh start /opt/zookeeper/conf/zoo.cfg
ExecStop=/opt/zookeeper/bin/zkServer.sh stop /opt/zookeeper/conf/zoo.cfg
ExecReload=/opt/zookeeper/bin/zkServer.sh restart /opt/zookeeper/conf/zoo.cfg

[Install]
WantedBy=multi-user.target

 

Java中finally和return优先级

作为一名Java开发者,拥有扎实的Java基础才能立于不败之地,比如面试或者被面试等等情况。在某些情况下Java的语法极具迷惑性也就是所谓的“坑”比如finally和return语句最终返回谁的结果?,那么本篇将总结一下Java中finally和return的优先级。

代码

package com.github.xuchengen.other;

/**
 * 最终返回实例
 * 作者:徐承恩
 * 邮箱:xuchengen@gmail.com
 * 日期:2019/11/4
 */
public class FinallyReturnExample {

    public static void main(String[] args) {

        System.out.println(finallyReturn1());

        System.out.println(finallyReturn2());
    }

    private static String finallyReturn1() {
        try {
            throw new RuntimeException();
        } catch (Exception e) {
            return "catch";
        } finally {
            return "finally";
        }
    }

    private static int finallyReturn2() {
        int a = 1;
        try {
            return a;
        } catch (Exception e) {
            a = -1;
        } finally {
            a = 30;
            System.out.println(a);
        }
        return a;
    }
}

finallyReturn1函数最终执行结果为:finally

finallyReturn2函数最终执行结果为:打印30,函数返回1

上述问题的本质就是在try、catch、finally中都有return语句时,执行代码的顺序是怎么样的,是根据哪个值来进行返回呢?

我们知道在处理异常时,finally中的代码是必定要执行的。这是由Java编译器决定的,在编译的时候将try模块的代码与finally模块的代码合并在一起,将catch模块的代码与finally模块的代码合并在一起,这是毫无疑问的。
这样,当finally模块有return那么将会执行finally中的return返回函数的结果,无论try、catch,还是函数体有没有return语句。所以该位置的return的优先级是最高的。

那么当finally没有return时是如何返回的呢?
这时在执行完try中的模块后,有return语句,实际不会真正的return,即只是会计算return中的表达式,之后将计算的结果保存在一个临时栈中,接着执行finally中的语句,最后才会从临时栈中取出之前的结果返回。
所以,函数的返回值是1而非30。

总体来说,return语句的位置有如下几种。

public static int getNumer() {
    try {            
        return a;
    } catch (Exception e) {
        return b;
    } finally {
       return c;
    }
    return d;
}

当无异常抛出时,返回的优先级如下:c>a>d

当然,如果c存在,d是不可达代码,编译会错误的,如下:

Exception in thread "main" java.lang.Error: Unresolved compilation problem: Unreachable code

当有异常抛出时,返回的优先级如下:c>b>d

总之,大家记住,finally块中的return优先级最高,而函数体中的return的优先级最低就好了。

Java窗口编程仿网页分页原理实现

仿照未必是一件坏事,先要学会仿照,接着理解他,吃透他,等你懂了一定原理后,发现以前的东西不怎么够好,需要改进,那么通过你的改进,使你的软件用户体验更好,我想这应该就是微创新吧。

这个例子的源码是CSDN的一位开发者所分享且一直珍藏在我的浏览器收藏夹多年,由于历史原因现在CSDN已经找不到原文。本着开源分享互助的精神我将源码完善再次公布如下:

package com.github.xuchengen.windows;

import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

/**
 * 仿网页分页
 * 作者:徐承恩
 * 邮箱:xuchengen@gmail.com
 * 日期:2019/10/30
 */
public class Pagination extends JFrame {

    /**
     * 总的页码数
     */
    private int pageTotal = 35;

    /**
     * 当前页
     */
    private int currentPage = 1;

    /**
     * 存放一系列页码按钮,默认流布局
     */
    private JPanel pageBtns = new JPanel();

    /**
     * 无参数构造方法
     */
    private Pagination() {
        //设置窗口标题
        this.setTitle("仿网页分页By徐承恩");
        //设置JFrame的布局
        this.setLayout(new FlowLayout(FlowLayout.LEFT, 20, 20));
        //设置窗口可拉伸
        this.setResizable(true);
        //档点击叉叉时,窗口可关闭
        this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        //默认显示第一页 ,设置页码布局
        setPageBottom(currentPage);
        //自适应子控件宽和高度度来调整窗口大小
        this.pack();
        //窗口居中
        this.setLocationRelativeTo(null);
        //窗口可见
        this.setVisible(true);
    }

    /**
     * 设置页码按钮
     *
     * @param currentPage 当前页码
     */
    private void setPageBottom(int currentPage) {

        pageBtns.removeAll();

        if (currentPage <= 0 || currentPage > pageTotal) {
            return;
        }

        //这个是显示数字按钮的总个数,不包括首页 尾页等其他按钮
        int countNumBtn = 9; // 首页 1 2 3 4 5 6 7 8 9 尾页

        int half = countNumBtn / 2;

        int startNum = 0;

        int endNum = 0;

        JButton btnFistPage = new JButton("首页");
        btnFistPage.setActionCommand("首页");
        btnFistPage.setToolTipText("首页");
        btnFistPage.setToolTipText("首页");
        btnFistPage.addActionListener(new BottomPageButtonAction());

        JButton btnLastPage = new JButton("末页");
        btnLastPage.setActionCommand("末页");
        btnLastPage.addActionListener(new BottomPageButtonAction());
        btnLastPage.setToolTipText("共" + pageTotal + "页");

        Container con = this.getContentPane();
        con.invalidate();

        pageBtns.add(btnFistPage);

        if (currentPage != 1) {
            JButton btnPrePage = new JButton("上一页");
            btnPrePage.setActionCommand("上一页");
            btnPrePage.setToolTipText("上一页是第" + (Math.max(currentPage - 1, 1)) + "页");
            btnPrePage.addActionListener(new BottomPageButtonAction());
            pageBtns.add(btnPrePage);
        }

        // 下面开始计算从左至右数字键(JButton)上的text
        int minBtnNum = currentPage - half;
        int maxBtnNum = currentPage + half;
        if (minBtnNum > 0 && maxBtnNum <= pageTotal) {
            startNum = minBtnNum;
            endNum = maxBtnNum;
        } else if (minBtnNum <= 0) {
            startNum = 1;
            endNum = Math.min(countNumBtn, pageTotal);
        } else {
            startNum = pageTotal > countNumBtn ? pageTotal - (countNumBtn - 1) : 1;
            endNum = pageTotal;
        }

        for (int i = startNum; i <= endNum; i++) {
            JButton btn = new JButton();
            btn.addActionListener(new BottomPageButtonAction(i));
            btn.setActionCommand("数字");
            btn.setToolTipText("第" + i + "页");
            btn.setText(i + "");
            if (i == currentPage) {
                btn.setBackground(Color.red);
            } else {
                btn.setBackground(Color.white);
            }
            pageBtns.add(btn);
        }

        if (currentPage != pageTotal) {
            JButton btnNextPage = new JButton("下一页");
            btnNextPage.setActionCommand("下一页");
            btnNextPage.setToolTipText("下一页是第" + (Math.min(currentPage + 1, pageTotal)) + "页");
            btnNextPage.addActionListener(new BottomPageButtonAction());
            pageBtns.add(btnNextPage);
        }
        pageBtns.add(btnLastPage);
        con.validate();
        this.add(pageBtns);
    }

    //页码事件处理
    class BottomPageButtonAction implements ActionListener {

        int btnNumText = 0;

        BottomPageButtonAction() {

        }

        BottomPageButtonAction(int num) {
            btnNumText = num;
        }

        @Override
        public void actionPerformed(ActionEvent e) {
            String command = e.getActionCommand();
            System.out.println(command);
            switch (command) {
                case "首页":  //
                    setPageBottom(1); // 首页就是第一页,所以直接传个1

                    currentPage = 1;
                    break;
                case "上一页":
                    setPageBottom(currentPage - 1 >= 1 ? --currentPage : 1);
                    break;
                case "下一页":
                    setPageBottom(currentPage + 1 <= pageTotal ? ++currentPage : pageTotal);

                    break;
                case "末页":
                    setPageBottom(pageTotal); // 末页是最后的页数

                    currentPage = pageTotal;
                    break;
                case "数字":
                    setPageBottom(btnNumText);
                    currentPage = btnNumText;
                    break;
            }
            //自适应子控件宽和高度度来调整窗口大小
            pack();
            System.out.println("当前是第 " + currentPage + "页");
        }

    }

    public static void main(String[] args) {
        new Pagination();
    }

}

JAVA仿网页分页原理

Java中八种数据类型所占字节计算

Java中有八种基本数据类型,分别为:byte、short、int、long、float、double、char、boolean。
这八种基本类型都有对应的包装类,分别为:Byte、Short、Integer、Long、Float、Double、Character、Boolean。

面试时时常会闻到这八种基本类型及其包装类,而且各种基本类型所占的字节数即使记不住、能用代码实现也是可以的。所以给出基本数据类型所占字节的代码。以供大家参考。

    @Test
    public void t1() {
        //一个字节占8个二进制位

        System.out.println("byte的二进制位数:" + Byte.SIZE);
        System.out.println("byte所占用的字节数:" + Byte.SIZE / 8);

        System.out.println("short的二进制位数:" + Short.SIZE);
        System.out.println("short所占用的字节数:" + Short.SIZE / 8);

        System.out.println("int的二进制位数:" + Integer.SIZE);
        System.out.println("int所占用的字节数:" + Integer.SIZE / 8);

        System.out.println("long的二进制位数:" + Long.SIZE);
        System.out.println("long所占用的字节数:" + Long.SIZE / 8);

        System.out.println("float的二进制位数:" + Float.SIZE);
        System.out.println("float所占用的字节数:" + Float.SIZE / 8);

        System.out.println("double的二进制位数:" + Double.SIZE);
        System.out.println("double所占用的字节数:" + Double.SIZE / 8);

        System.out.println("char的二进制位数:" + Character.SIZE);
        System.out.println("char所占用的字节数:" + Character.SIZE / 8);
    }
byte的二进制位数:8
byte所占用的字节数:1
short的二进制位数:16
short所占用的字节数:2
int的二进制位数:32
int所占用的字节数:4
long的二进制位数:64
long所占用的字节数:8
float的二进制位数:32
float所占用的字节数:4
double的二进制位数:64
double所占用的字节数:8
char的二进制位数:16
char所占用的字节数:2

所以就可以理解基本数据类型的大小关系:

 【byte(1Byte) < char(2Byte) < short(2Byte) < int(4Byte) < float(4Byte) <double(8Byte) < long(8Byte)】 这个排序是错误的!

正确的是:在Java中整型、实型、字符型被视为简单数据类型,这些类型由低级到高级分别为:

(byte,short,char)–int–long–float–double

注意,整数比浮点数低级。低级到高级可以自动转换。而高级到低级需要用代码强制转换,不强转会编译错误。

总结

  1. 位:”位(bit)”是电子计算机中最小的数据单位。每一位的状态只能是0或1。
  2. 字节:8个二进制位构成1个”字节(Byte)”,它是存储空间的基本计量单位。
  3. 1个字节可以储存1个英文字母或者半个汉字,换句话说,1个汉字占据2个字节的存储空间。
  4. 字:”字”由若干个字节构成,字的位数叫做字长,不同档次的机器有不同的字长。
  5. KB:K在二进制中表示1024,也就是2的10次 方。1KB表示1K个Byte,也就是1024个字节。

例如一台8位机,它的1个字就等于1个字节,字长为8位。如果是一台16位机,那么,它的1个字就由2个字节构成,字长为16位。

字是计算机进行数据处理和运算的单位。

【习题】

一个函数定义的返回值是float,它不能在return语句中返回的值的类型是(  )?

A.char              B.float        C.long         D.double

答案:D

double比float高级,long比float低级

升级MacOS Catalina10.15导致SVN或Git报错解决方案

最近把Mac升级到最新10.15版本导致SVN或GIT导项目时报错具体报错如下:

Git doesn't work on MacOS Catalina: “xcrun: error: invalid active developer 
path (/Library/Developer/CommandLineTools), missing” [duplicate]

在stackoverflow找到解决方案:

sudo rm -rf /Library/Developer/CommandLineTools
xcode-select --install

没权限记得加上sudo

发布您的Jar包到Central中央仓库

最近开源了自己写的银联在线网关支付SDK,并尝试将其发布到中央仓库。仅以此篇博客记录整个过程。

注册Sonatype帐号

[gt href=’https://issues.sonatype.org/secure/Signup!default.jspa’]注册Sonatype帐号[/gt]

注册逻辑非常简单跟注册QQ号差不多,这里过程不表。

创建问题

问题表单请如实填写

项目选择:Community Support – Open Source Project Repository Hosting (OSSRH)

问题类型:New Project

概要:X-UnionPay

描述:X-UnionPay–中国银联在线网关支付接口第三方SDK,旨在屏蔽底层逻辑提供一套简单的API方便调用。

Group Id:com.github.xuchengen

Project URL:https://github.com/Xuchengen/X-UnionPay

SCM url:https://github.com/Xuchengen/X-UnionPay.git

其它可填可不填。问题的审核大概需要一天的时间,审核期间还需验证Github仓库地址。

 

如上图所示,第一条评论需要我们在github上创建一个仓库进行身份验证。看到第二条评论基本可以确认我们可以上传自己的Jar包到Sonatype仓库。

配置Maven Setting.xml

<?xml version="1.0"encoding="UTF-8"?>
<settings xmlns="http://maven.apache.org/SETTINGS/1.0.0"
          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
          xsi:schemaLocation="http://maven.apache.org/SETTINGS/1.0.0 http://maven.apache.org/xsd/settings-1.0.0.xsd">

    <!-- 自己指定本地仓库路径 -->
    <localRepository>/Users/xuchengen/z_dev/apache/maven/repo</localRepository>

    <servers>
        <server>
            <id>to-central-maven</id>
            <username>Sonatype帐号</username>
            <password>Sonatype密码</password>
        </server>
    </servers>
	
    <mirrors>
    </mirrors>

    <profiles>
        <!-- 上传Jar包到中央库 -->
        <profile>
            <id>to-central-maven</id>
            <activation>
                <activeByDefault>true</activeByDefault>
            </activation>
            <properties>
                <gpg.executable>gpg</gpg.executable>
                <gpg.passphrase>GPG密码</gpg.passphrase>
            </properties>
        </profile>
    </profiles>
</settings>

配置项目pom.xml

[gt href=’https://github.com/Xuchengen/X-UnionPay/blob/master/pom.xml’]X-UnionPay项目POM配置参考[/gt]

<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.github.xuchengen</groupId>
    <artifactId>X-UnionPay</artifactId>
    <version>1.0-SNAPSHOT</version>
    <name>X-UnionPay</name>
    <description>X-UnionPay——中国银联在线网关支付接口第三方SDK,旨在屏蔽底层逻辑提供一套简单的API方便调用。</description>
    <url>https://github.com/Xuchengen/X-UnionPay</url>

    <scm>
        <connection>scm:git:https://github.com/Xuchengen/X-UnionPay.git</connection>
        <developerConnection>scm:git:https://github.com/Xuchengen/X-UnionPay.git</developerConnection>
        <url>https://github.com/Xuchengen/X-UnionPay.git</url>
    </scm>

    <licenses>
        <license>
            <name>The Apache License, Version 2.0</name>
            <url>http://www.apache.org/licenses/LICENSE-2.0.txt</url>
        </license>
    </licenses>

    <developers>
        <developer>
            <name>徐承恩</name>
            <email>xuchengen@gmail.com</email>
            <organization>github</organization>
            <organizationUrl>https://xuchengen.github.io/</organizationUrl>
        </developer>
    </developers>

    <properties>
        <jdk>1.7</jdk>
        <encoding>UTF-8</encoding>
    </properties>

    <dependencies>
        <dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-all</artifactId>
            <version>4.6.1</version>
        </dependency>
    </dependencies>

    <profiles>
        <profile>
            <id>to-central-maven</id>
            <build>
                <plugins>
                    <!-- GPG -->
                    <plugin>
                        <groupId>org.apache.maven.plugins</groupId>
                        <artifactId>maven-gpg-plugin</artifactId>
                        <version>1.5</version>
                        <executions>
                            <execution>
                                <phase>verify</phase>
                                <goals>
                                    <goal>sign</goal>
                                </goals>
                            </execution>
                        </executions>
                    </plugin>
                </plugins>
            </build>
            <distributionManagement>
                <snapshotRepository>
                    <id>to-central-maven</id>
                    <url>https://oss.sonatype.org/content/repositories/snapshots</url>
                </snapshotRepository>
                <repository>
                    <id>to-central-maven</id>
                    <url>https://oss.sonatype.org/service/local/staging/deploy/maven2</url>
                </repository>
            </distributionManagement>
        </profile>
    </profiles>

    <build>
        <plugins>
            <!-- 编译插件 -->
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.8.1</version>
                <configuration>
                    <source>${jdk}</source>
                    <target>${jdk}</target>
                    <encoding>${encoding}</encoding>
                    <skip>true</skip>
                </configuration>
            </plugin>
            <!-- 源码插件 -->
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-source-plugin</artifactId>
                <version>3.1.0</version>
                <executions>
                    <execution>
                        <phase>compile</phase>
                        <goals>
                            <goal>jar-no-fork</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
            <!-- Javadoc -->
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-javadoc-plugin</artifactId>
                <version>3.1.0</version>
                <executions>
                    <execution>
                        <phase>package</phase>
                        <goals>
                            <goal>jar</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
            <!--自动发布Jar到Maven仓库-->
            <plugin>
                <groupId>org.sonatype.plugins</groupId>
                <artifactId>nexus-staging-maven-plugin</artifactId>
                <version>1.6.7</version>
                <extensions>true</extensions>
                <configuration>
                    <serverId>to-central-maven</serverId>
                    <nexusUrl>https://oss.sonatype.org/</nexusUrl>
                    <autoReleaseAfterClose>true</autoReleaseAfterClose>
                </configuration>
            </plugin>
            <!-- 资源文件处理插件 -->
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-resources-plugin</artifactId>
                <version>3.1.0</version>
                <configuration>
                    <encoding>${encoding}</encoding>
                </configuration>
            </plugin>
        </plugins>
    </build>

</project>

使用GPG

Mac系统安装gpg

brew install gpg

其它系统平台各自去探索。

生成key只需要填写姓名和邮箱即可其它保持默认。

gpg --gen-key

上传到服务器

gpg --keyserver hkp://keyserver.ubuntu.com:11371 --send-keys CF21873A--上传到服务器
gpg --keyserver hkp://keyserver.ubuntu.com:11371 --recv-keys  CF21873A --查看是否上传整个

部署Jar包到中央仓库

执行maven命令

mvn clean deploy -P to-central-maven

不出意外的话大概两个小时就能同步你的jar包到中央仓库。

[gt href=’https://search.maven.org’]搜索你自己的Jar包是否已上传到仓库[/gt]

 

vpsdime大内存月付7刀服务器支持部署Docker

VPSDime是VPS托管的领先提供商,为预算有限的人提供廉价的Linux VPS。如果您需要专门提供高RAM和存储产品的自我管理VPS,那么VPSDime就是您的供应商。在我们慷慨的资源分配之上,您会对服务器性能,正常运行时间和我们的支持质量感到惊讶。

专业的高存储和高内存虚拟专用服务器解决方案,VPSDime为人们提供预算而不影响质量。我们提供目前市场上最便宜的自我管理VPS。您可以指望我们作为您的预算VPS托管服务提供商。

VPSDime已被评为上一季度LowEndBox Top Providers民意调查中最佳vps托管服务提供商之一。

没错正如你所见月付7刀即可享受4核6G大内存VPS,基于最新的OpenVZ虚拟化架构可以部署Docker.在徐叔看来唯一的缺点非CN2线路。

vpsdime客服7*24小时服务响应非常及时,工程师技术非常棒服务贴心开工单能够很快进入状态处理问题。

vpsdime注册返佣

[gt href=”https://vpsdime.com/aff.php?aff=675″]注册[/gt]

测试节点地址

[dl href=”http://23.227.163.10/100MB.test”]西雅图节点[/dl]

[dl href=”http://23.92.53.100/100MB.test”]洛杉矶节点[/dl]

[dl href=”http://104.251.214.195/100MB.test”]达拉斯节点[/dl]

[dl href=”http://104.251.214.195/100MB.test”]新泽西节点[/dl]