解决 failed to create bus connection no such file or directory 错误

今天在修改 hostname 使用 sudo hostnamectl set-hostname ds 命令时遇到问题:

Failed to create bus connection: No such file or directory

查了一通之后发现缺少 dbus

sudo apt-get install dbus

安装 dbus 然后再修改即可,使用 hostnamectl 方式来修改 hostname 不需要重启,直接推出登录,然后就可以实现了。

D-Bus 是一种高级的进程间通信机制,它由 freedesktop.org 项目提供,使用 GPL 许可证发行。D-Bus 最主要的用途是在 Linux 桌面环境为进程提供通信,同时能将 Linux 桌面环境和 Linux 内核事件作为消息传递到进程。D-Bus 的主要概率为总线,注册后的进程可通过总线接收或传递消息,进程也可注册后等待内核事件响应,例如等待网络状态的转变或者计算机发出关机指令。目前,D-Bus 已被大多数 Linux 发行版所采用,开发者可使用 D-Bus 实现各种复杂的进程间通信任务。


2018-07-29 linux , problem , dbus , hostnamectl

Vim 的颜色主题

Retro groove color scheme for Vim


2018-07-27 vim , color-scheme , color , scheme

Linux Mint 下禁用 Alt 拖拽窗口

问题的出现,Linux Mint 使用了很长时间了,一直也没有啥大的问题,只是最近自定义一些快捷键,Alt + Shift ,发现所有的 Alt 相关的操作,只要按住 Alt 键,然后鼠标在任何窗口中就变成了小手,拖拽会直接拖动窗口。

解决方案一

在 System Settings 中选择 Windows, 然后在 Behavior 下面有 Special key to move and resize windows 选择 Disabled 即可。

解决方案二

安装 dconf

sudo apt install dconf-tools

然后在 org -> cinnamon > desktop > wm > preferences 下面的 mouse-button-modifier 中修改 <Alt> 变为 <Super> 或者 <Ctrl>

reference


2018-07-25 linux , mint , cinnamon , shortcut

威联通折腾篇八:重启服务

家里遇到一次断电,然后 NAS 就这样异常关机了,重启之后提示磁盘有些碎片需要整理,整理的时候 Qnap 会停止 NAS 上所有的服务,包括 Container Station 中的内容,而 Qnap 说了会在检查完磁盘之后重新启动的,然而并没有,所以只能手动来重启这些服务。

幸亏 Qnap 的绝大部分服务都是用启动脚本来启动的,执行下面的命令可以把 NAS 当前运行的所有服务重启。

/etc/init.d/services.sh restart

当然如果要重启单独的比如说 Container Station 也可以使用

/etc/init.d/container-station.sh restart

重启 samba

/etc/init.d/smb.sh restart

同样的方式,在 /etc/init.d/ 目录下有很多的启动脚本,对应着名字找到要重启的内容即可。

解决特定问题

当遇到打开应用出现

This site can’t be reached”(无法访问此站点)或“ refused to connect”(《服务器》 拒绝连接)

基本上重启一下对应的服务即可。


2018-07-21 qnap , qnap-tutorial , services

Java 查漏补缺之泛型

简而言之,泛型使类型(类和接口)在定义类,接口和方法时成为参数。类型参数提供了一种简便的方法,使得不同的输入类型可以使用相同的代码。

为什么需要泛型

在强类型语言中,如果定义一个具有具体类型的类,那么这个类就只能被该类型使用。

Generics 给类,接口和方法提供了一个参数化的实现方式,使得同一个类定义,方法定义可以处理不同的类型。Oracle 官方的文档有一句话说得特别好:

Much like the more familiar formal parameters used in method declarations, type parameters provide a way for you to re-use the same code with different inputs.

在方法定义的时候使用的是 formal parameters (形式参数),在调用方法的时候会将实际参数传递给形式参数,传递的是值,而泛型(Generics)则是传递的类型(Types)。

Java 编译期会在编译阶段检查类型。

使用泛型的代码比非泛型代码有如下好处:

  • 在编译时进行类型检查,减少错误
  • 消除了强制类型装换
  • 可以让编程人员更加容易实现通用算法

定义

通常情况下,泛型类 (generic class) 可以如下定义

class name<T1, T2, ..., Tn> { /* ... */ }

尖括号中的类型通常称为类型参数( type parameters 或者称为类型变量 type variables),一旦类定义了类型参数T ,该变量就能在类中使用。

Type Parameter 命名约定

按照惯例, type parameters 的名字都是单一的大写字母,通常和普通的命令规范 区别开来。

经常被使用的泛型变量有:

  • E for Element 被 Java Collections 框架广泛使用
  • K for Key , K V 经常被 Java Collections 框架中的 Map 使用
  • N for Number
  • T for Type
  • V for Value
  • S,U,V etc - 2nd, 3rd, 4th types

调用和实例化泛型类型

泛型的调用其实非常简单,只需要将类型参数替换为具体的类型即可,比如

List<String> list;

Type Parameter 和 Type Argument 术语:大部分情况下这两个术语是可以互换的,但他们的使用场景是不一样的。因此 Foo<T> 中的 T 是 type Parameter,而 Foo<String> 中的 String 是 type argument.

泛型使用

泛型方法

对于静态泛型方法(static generic method), type parameter 定义的区域需要出现在返回值的前面

public static <K, V> boolean compare(Pair<K, V> p1, Pair<K, V> p2) {...}

调用该方法的完整的方式:

boolean same = Util.<Integer, String>compare(p1, p2);
// or
boolean same = Util.compare(p1, p2);

泛型类

泛型类也需要声明类型变量,放在类名后

class GenericClass<ID, T> {}

class SubGenericClass<T> extends GenericClass<Integer, T> {}

泛型接口

和泛型类相似,需要在接口名后面声明类型变量,作用于接口中的抽象方法返回类型和参数类型。

interface GenericInterface<T> {
    T append(T seg);
}

有界类型参数

总有一种情况,编程人员想要限制泛型的类型,比如一个操作数字的类或者方法,可能希望泛型只接受 Number 或者其子类的实例。

定义上界,比如 <E extends Comparable> 在这个例子中,表示定义的泛型需要实现 Comparable 接口,这里的 extends 只是通用表示 extends (Class) 或者 implements (interfaces)

如果要定义多个界

<T extends B1 & B2 & B3>

在定义多个界时,需要将 Class 类型放到 interface 之前,比如说上面的例子中假如有 Class B2, interface B1 & B3 ,那么 B2 必须是第一个。

通配符

在代码中也经常能看到 ? 问号,通常叫做通配符(wildcard),表示是类型未知。通配符可以用在非常多的场景,作为参数,field,或者本地变量,有时候也作为返回值(当然不推荐这么做)。

Upper Bounded Wildcards

public static void process(List<? extends Foo> list) { /* ... */ }

upper bounded wildcard,List<? extends Foo> 其中 Foo 是类型,表示 Foo 和任何子类。

Unbounded Wildcards

无界通配符类型(upper bounded wildcard),通常表示的单纯的使用 ? ,比如 List<?>,表示的是一个不知道类型的 list。有两种常用的使用场景

  • 当写方法只需要 Object 类中的方法时
  • 在泛型类中定义的方法不依赖于 type parameter 时,比如 List.size() 或者 List.clear ,并不依赖于定义的泛型类型

举一个官方文档中的例子,假设有如下的方法

public static void printList(List<Object> list) {
    for (Object elem : list)
        System.out.println(elem + " ");
    System.out.println();
}

本意上是想要实现打印一个任何类型的列表,但是并不能达到目的,他并不能打印 List<Integer>, List<String> 等,因为他们并不是 List<Object> 的子类型,如果要实现通用的 printList 方法需要使用 List<?>

public static void printList(List<?> list) {
    for (Object elem: list)
        System.out.print(elem + " ");
    System.out.println();
}

这个时候 List<Integer> 才是 List<?> 的子类型。

Lower Bounded Wildcards

和类型的上界使用 extends,相同的是,如果要表示类型的下界,则使用 super,比如 <? super A> 。需要注意的是,对于通配符 ? 可以单独指定上界,也可以指定下界,但是两者不能同时指定。

public static void addNumbers(List<? super Integer> list) {
    for (int i = 1; i <= 10; i++) {
        list.add(i);
    }
}

代码可以在 List<Integer>, List<Number>, and List<Object> — anything that can hold Integer values. 上运行。

假设

class A { /* ... */ }
class B extends A { /* ... */ }

都知道 B 是 A 的子类型,所以可以

B b = new B();
A a = b;

所以对于泛型类型呢?

List<B> listB = new ArrayList<>();
List<A> listA = listB;        // 编译错误

List<Number>List<Integer> 都是 List<?> 的子类型,而 List<Number>List<Integer> 这两者并没有任何关系。

为了让两者有关系,就需要用多泛型的上界,这样

List<? extends Integer> intList = new ArrayList<>();
List<? extends Number>  numList = intList;  // OK. List<? extends Integer> is a subtype of List<? extends Number>

下面有一张图来表示 List 类型的父子类关系

a hierarchy of several generic list class declarations

类型擦除

泛型被引入到 Java 语言中,以便在编译时提供更严格的类型检查并支持泛型编程。为了实现泛型,Java 编译器使用[[类型擦除]],声明了泛型的 .java 源代码,在编程生成 .class 文件后,泛型相关的信息就不存在了,源代码中的泛型相关的信息是提供给编译期使用的。:

  • 如果类型参数是无界的,则将泛型类型中的所有类型参数替换为其边界或对象。因此,生成的字节码仅包含普通的类,接口和方法。
  • 在合适的位置插入类型转换,以保证类型安全
  • 生成桥接方法以保留扩展泛型类型中的多态性

类型擦除确保不为参数化类型创建新类;因此,泛型不会产生运行时开销。

泛型信息对 Java 编译器可见,但是对 Java 虚拟机不可见。

在类型擦除过程中,Java 编译器会擦除所有的 type parameters,如果是有界的类型参数则替换成第一个类型,如果是无界的类型参数则替换为 Object。

Java 编译器还会擦除泛型方法参数中的类型参数。比如静态方法:

// Counts the number of occurrences of elem in anArray.
//
public static <T> int count(T[] anArray, T elem) {
    int cnt = 0;
    for (T e : anArray)
        if (e.equals(elem))
            ++cnt;
        return cnt;
}

因为 T 是无界的,则会把 T 出现的地方全部替换为 Object。和泛型类相同,有界的方法中的类型参数也会替换为第一个类型参数。

编写类:

class User implements Comparable<User> {
    String name;
	
    public int compareTo(User other){
        return this.name.compareTo(other.name);
    }
}

JDK 中接口定义:

package java.lang;
public interface Comparable<T>{
    int compareTo(T o);
}

首先反编译接口:

public interface Comparable
{

    public abstract int compareTo(Object obj);
}

擦除规则 1,参数类型被替换成 Object。如果要看有界类型可以尝试反编译 Collections.class。

编译类:

javac User.java

反编译类:

jad User.class

得到:

class User
    implements Comparable
{

    User()
    {
    }

    public int compareTo(User user)
    {
        return name.compareTo(user.name);
    }

    // 桥接方法
    public volatile int compareTo(Object obj)
    {
        return compareTo((User)obj);
    }

    String name;
}

类型参数没有了,多了无参构造方法,多了 compareTo(Object obj) 桥接方法,擦除规则2 和 3 实现。

泛型的限制

不能使用原始类型来实例化泛型

比如

Pair<int, char> p = new Pair<>(8, 'a');  // compile-time error

会有编译错误。Java 会自动装箱。

无法创建类型参数的实例

比如

public static <E> void append(List<E> list) {
    E elem = new E();  // compile-time error
    list.add(elem);
}

作为解决方案,可以使用反射来创建对象实例

public static <E> void append(List<E> list, Class<E> cls) throws Exception {
    E elem = cls.newInstance();   // OK
    list.add(elem);
}

如下调用

List<String> ls = new ArrayList<>();
append(ls, String.class);

无法申明类型参数为静态

如下是不对的

public class MobileDevice<T> {
    private static T os;

    // ...
}

不能对 parameterized types 进行类型装换和使用 instanceof

因为 Java 编译器会在编译时擦除类型,所以无法验证参数类型在运行时的类型。下面是错误的例子:

public static <E> void rtti(List<E> list) {
    if (list instanceof ArrayList<Integer>) {  // compile-time error
        // ...
    }
}

而对于有界的参数化类型,可以使用

public static void rtti(List<?> list) {
    if (list instanceof ArrayList<?>) {  // OK; instanceof requires a reifiable type
        // ...
    }
}

无法创建参数化类型的数组

不能

List<Integer>[] arrayOfLists = new List<Integer>[2];  // compile-time error

无法创建,捕获或抛出参数化类型的对象

泛型类无法直接或者间接继承 Throwable 。

// Extends Throwable indirectly
class MathException<T> extends Exception { /* ... */ }    // compile-time error

// Extends Throwable directly
class QueueFullException<T> extends Throwable { /* ... */ // compile-time error

但是可以在 throw 语句后使用

class Parser<T extends Exception> {
    public void parse(File file) throws T {     // OK
        // ...
    }
}

不能有重载的方法使用相同的泛型

比如

public class Example {
    public void print(Set<String> strSet) { }
    public void print(Set<Integer> intSet) { }
}

reference


2018-07-18 java , generics , programming , linux

威联通折腾篇九:MySQL 设置

威联通自带 MySQL 当前我使用的版本是 MariaDB 5.5.57 ,威联通也可以看成是类 Unix 系统吧,但是他和 Linux 还是有很多不同,毕竟深度定制过。

MySQL 在威联通的配置路径在

/etc/config/my.cnf

找到该文件,其他配置就和 MySQL 一样了。

如果想要 MySQL 支持远程访问,在 “控制台” - “应用服务” - “MySQL 服务器” 中选择允许远程连接即可。此时设定一个比较强的密码。


2018-07-16 mysql , qnap , qnap-tutorial

Spring 相关资源

收集了一些官方的非官方的学习资源。

文档

代码

Spring 深度探险

第一篇 Spring MVC 前传

Spring MVC 前传

主要讲了 Spring 发展的历史,和 struct 等的对比

第二篇 Spring MVC 概览

Spring MVC 概览

以 Servlet 作为入口程序是绝大多数 MVC 框架都遵循的基本设计方案,这里的 DispatcherServlet 被称之为核心分发器。Spring MVC 的核心配置文件定义在 [servlet-name]-servlet.xml 文件中。控制层 Controller 是一个单独的 Java 文件,只是使用注解将其与 HTTP 请求关联。

- 入口程序 DispatcherServlet
- 核心配置 servlet.xml
- 控制逻辑 Controller

这三者构成 Spring MVC 的基础要素。

SpringMVC 在整个 Controller 改造中所涉及到的一些要点:

  1. 使用参数 - 返回值(Param-Return)实现模式来打造 Controller
  2. 引入 Annotation 来完成请求 - 响应的映射关系

    引入 Annotation 来完成请求 - 响应的映射关系,是 SpringMVC 的一个重大改造。在早期的 SpringMVC 以及其他的 MVC 框架中,通常都是使用 XML 作为基础配置的。而 Annotation 的引入将原本分散的关注点合并到了一起,为实现配置简化打下了坚实的基础。

  3. 泛化参数和返回值的含义

    核心 Servlet 应该能够根据一定的规则对不同的 Http 请求分发到不同的 Servlet 对象上去进行处理。核心 Servlet 应该能够建立起一整套完整的对所有 Http 请求进行规范化处理的流程。

Servlet 模型中,请求 和 响应实现依赖,web.xml 中配置 <servlet><servlet-mapping> 的对应,将响应的 URL pattern 和其对应的实现类进行关联。针对这个问题,Spring MVC 提出的方案就是提炼一个核心的 Servlet 覆盖对所有 HTTP 请求的处理,这就是 org.springframework.web.servlet.DispatcherServlet 核心分发器。

因此 web.xml 中的配置就被固定

<servlet>
    <servlet-name>dispatcherServlet</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <load-on-startup>1</load-on-startup>
</servlet>

<servlet-mapping>
    <servlet-name>dispatcherServlet</servlet-name>
    <url-pattern>/**</url-pattern>
</servlet-mapping>

Spring MVC 接下来要处理的内容就是根据一定的规则处理不同的 HTTP 请求,分发到响应的 Servlet 处理。

在 SpringMVC 的设计中,这两个方面的内容总是在一个不断交叉、互为补充的过程中逐步完善的。

处理流程规范化是目的,对于处理过程的步骤划分和流程定义则是手段。因而处理流程规范化的首要内容就是考虑一个通用的 Servlet 响应程序大致应该包含的逻辑步骤:

  • 步骤 1 —— 对 Http 请求进行初步处理,查找与之对应的 Controller 处理类(方法)
  • 步骤 2 —— 调用相应的 Controller 处理类(方法)完成业务逻辑
  • 步骤 3 —— 对 Controller 处理类(方法)调用时可能发生的异常进行处理
  • 步骤 4 —— 根据 Controller 处理类(方法)的调用结果,进行 Http 响应处理

在 Java 语言中,最适合表达逻辑处理语义的语法结构是接口,因此上述的四个流程也就被定义为了四个不同接口,它们分别是:

  • 步骤 1 —— HandlerMapping
  • 步骤 2 —— HandlerAdapter
  • 步骤 3 —— HandlerExceptionResolver
  • 步骤 4 —— ViewResolver

核心配置文件在整个 SpringMVC 的构成要素中占有一席之地的重要原因就是在于:必须借助一个有效的手段对整个 SpringMVC 的组件进行定义,而这一点正是通过核心配置文件来完成的。

第三篇 DispatcherServlet 与初始化主线

DispatcherServlet 与初始化主线,SpringMVC 的设计原则 Open for extension / closed for modification

Spring MVC 设计中的要点

  • SpringMVC 将 Http 处理流程抽象为一个又一个处理单元
  • SpringMVC 定义了一系列组件(接口)与所有的处理单元对应起来
  • SpringMVC 由 DispatcherServlet 贯穿始终,并将所有的组件串联起来

根据 Servlet 规范的定义,Servlet 中的两大核心方法 init 方法和 service 方法,它们的运行时间和触发条件都截然不同:

init 方法

在整个系统启动时运行,且只运行一次。因此,在 init 方法中我们往往会对整个应用程序进行初始化操作。这些初始化操作可能包括对容器(WebApplicationContext)的初始化、组件和外部资源的初始化等等。

service 方法

在整个系统运行的过程中处于侦听模式,侦听并处理所有的 Web 请求。因此,在 service 及其相关方法中,我们看到的则是对 Http 请求的处理流程。

DispatcherServlet 的初始化主线

初始化主线的驱动要素,servlet 中的 init 方法;初始化主线的执行次序,HttpServletBean->FrameworkServlet->DispatcherServlet;初始化主线的操作对象,Spring 容器(WebApplicationContext)和组件。 一个组件的多种行为模式可以在容器中共存,容器将负责对这些实现类进行管理。DispatcherServlet 中对于组件的初始化过程实际上是应用程序在 WebApplicationContext 中选择和查找组件实现类的过程,也是指定组件在 SpringMVC 中的默认行为方式的过程。

第四篇 SpringMVC 核心配置文件详解

SpringMVC 核心配置文件详解

SpringMVC 的核心配置文件在整个应用程序中所起到的作用也是举足轻重的。在 web.xml 中指定 SpringMVC 的入口程序 DispatcherServlet 时,实际上蕴含了一个对核心配置文件的指定过程([servlet-name]-servlet.xml)

namespace     element             attributes
 |             |                         |
<mvc:annotation-driven ignore-default-model-on-redirect="true"/>
 |             |                         |
逻辑分类     过程语义             行为配置选项

Schema-based XML 中的配置节点拥有比较鲜明的功能特性,通过 namespace、element 和 attributes 这三大元素之间的配合,共同完成对一个动态过程的描述。这段配置想要表达的意思,就是在 mvc 的空间内实现 Annotation 驱动的配置方式。其中,mvc 表示配置的有效范围,annotation-driven 则表达了一个动态的过程,实际的逻辑含义是:整个 SpringMVC 的实现是基于 Annotation 模式,请为我注册相关的行为模式。


2018-07-12 spring , spring-mvc , docs , demo , tutorial

Spring @Configuration 注解

@Configuration 标注在类上,相当于把该类作为 spring 的 xml 配置文件中的 <beans>,作用为:配置 spring 容器(context)

package com.test.spring.support.configuration;

@Configuration
public class TestConfiguration {
    public TestConfiguration(){
        System.out.println("spring 容器启动初始化......");
    }
}

相当于

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:context="http://www.springframework.org/schema/context" xmlns:jdbc="http://www.springframework.org/schema/jdbc"
    xmlns:jee="http://www.springframework.org/schema/jee" xmlns:tx="http://www.springframework.org/schema/tx"
    xmlns:util="http://www.springframework.org/schema/util" xmlns:task="http://www.springframework.org/schema/task" xsi:schemaLocation="
        http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd
        http://www.springframework.org/schema/jdbc http://www.springframework.org/schema/jdbc/spring-jdbc-4.0.xsd
        http://www.springframework.org/schema/jee http://www.springframework.org/schema/jee/spring-jee-4.0.xsd
        http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.0.xsd
        http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-4.0.xsd
        http://www.springframework.org/schema/task http://www.springframework.org/schema/task/spring-task-4.0.xsd" default-lazy-init="false">

</beans>

测试

package com.test.spring.support.configuration;

public class TestMain {
    public static void main(String[] args) {

        //@Configuration 注解的 spring 容器加载方式,用 AnnotationConfigApplicationContext 替换 ClassPathXmlApplicationContext
        ApplicationContext context = new AnnotationConfigApplicationContext(TestConfiguration.class);

        // 如果加载 spring-context.xml 文件:
        //ApplicationContext context = new ClassPathXmlApplicationContext("spring-context.xml");
    }
}

2018-07-11 spring , annotation , notes , spring-boot

Java 查漏补缺之 Class.isAssignableFrom() 和 instanceof 区别

Class.isAssignableFrom() 是用来判断一个类 Class1 和另一个类 Class2 是否相同或是另一个类的超类 superclass 或接口 superinterface。

调用方式: X.class.isAssignableFrom(Y.class)

调用者和参数都是 java.lang.Class 类型。上面例子,如果返回为 true,则表示 X 是 Y 的超类或者接口,Y 可以是一个类也可以是一个接口。

instanceof 是用来判断一个对象实例是否是一个类或接口的或其子类子接口的实例。

调用方式:o instanceof TypeName

第一个参数是对象实例名,第二个参数是具体的类名或接口名

延伸

Class 中还有一个相关的方法 isInstance() ,这个方法用来检查实例

MyClass.class.isInstance(obj)

如果返回 true,则表示 obj 是类 MyClass 的实例或者子类实例。

public class NewMain
{
    public static void main(String[] args)
    {
        NewMain nm = new NewMain();
        nm.doit();
    }

    public void doit()
    {
        A myA = new A();
        B myB = new B();
        A[] aArr = new A[0];
        B[] bArr = new B[0];

        System.out.println("b instanceof a: " + (myB instanceof A));
        System.out.println("b isInstance a: " + A.class.isInstance(myB));
        System.out.println("a isInstance b: " + B.class.isInstance(myA));
        System.out.println("b isAssignableFrom a: " + A.class.isAssignableFrom(B.class));
        System.out.println("a isAssignableFrom b: " + B.class.isAssignableFrom(A.class));
        System.out.println("bArr isInstance A: " + A.class.isInstance(bArr));
        System.out.println("bArr isInstance aArr: " + aArr.getClass().isInstance(bArr));
        System.out.println("bArr isAssignableFrom aArr: " + aArr.getClass().isAssignableFrom(bArr.getClass()));
    }

    class A
    {
    }

    class B extends A
    {
    }
}

结果是:

b instanceof a: true
b isInstance a: true
a isInstance b: false
b isAssignableFrom a: true
a isAssignableFrom b: false
bArr isInstance A: false
bArr isInstance aArr: true
bArr isAssignableFrom aArr: true

reference


2018-07-10 java , linux , object , class

Maven 插件

Maven 本质上是一个插件框架,它的核心并不执行任何具体的构建任务,而是将所有任务都交给插件来完成,例如编译源代码是由 maven-compiler-plugin 完成的。进一步说,每个任务对应了一个插件目标(goal),每个插件会有一个或者多个目标,例如 maven-compiler-plugin 的 compile 目标用来编译位于 src/main/java/ 目录下的主源码,testCompile 目标用来编译位于 src/test/java/ 目录下的测试源码。

maven-source-plugin

[[maven-source-plugin]] 打包插件,会根据当前的源码文件创建 jar 包。默认情况下 jar 文件会在项目 target 目录下。

如果没有进行特殊配置,maven 会按照标准接口查找和处理各种类型文件。一个标准的 maven 项目

├── project.iml
├── pom.xml
├── src
│   ├── main
│   │   ├── java
│   │   └── resources
│   └── test
│       ├── java
│       └── resources
└── target
    ├── classes
    │   └── info
    ├── generated-sources
    │   └── annotations
    ├── generated-test-sources
    │   └── test-annotations
    └── test-classes

src/main/javasrc/test/java 中的所有 *.java 文件都会在 Maven 的 compile 和 test-compile 阶段被编译,结果会分别放到 target/classestarget/test-classes 目录中。

src/main/resourcessrc/test/resources 这两个目录的文件也会被复制到 target/classestarget/test-classes 目录中。打包插件默认会将 target/classes 中的所有内容打包到 jar 包或者 war 包中。

如果想要 deploy 阶段跳过 sources.jar ,可以在命令中使用:1

-Dmaven.source.skip

如果可以修改 POM,可以使用如下配置不生成 sources.jar 文件:

  <plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-source-plugin</artifactId>
    <version>2.2.1</version>
    <configuration>
      <skipSource>true</skipSource>
    </configuration>
  </plugin>

maven-archetype-plugin

Archetype 插件允许用户从模板中创建 Maven 项目,该插件需要 Java 6 及以上版本。2

maven-compiler-plugin

compiler 插件是最常用到的一个,定义 build 的 Java 版本,编译 Java 代码。

pom 文件定义,默认的 source 和 target 都是 1.5。

<project>
  [...]
  <build>
    [...]
    <plugins>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-compiler-plugin</artifactId>
        <version>3.7.0</version>
        <configuration>
          <source>1.8</source>
          <target>1.8</target>
          <fork>true</fork>
          <verbose>true</verbose>
          <encoding>UTF-8</encoding>
        </configuration>
      </plugin>
    </plugins>
    [...]
  </build>
  [...]
</project>

maven-dependency-plugin

maven-dependency-plugin 是处理依赖相关的插件,该插件有很多 goal,常用的比如 mvn dependency:treemvn dependency:analyze 等等。该插件方便用来排查依赖相关的问题。

<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-dependency-plugin</artifactId>
    <version>2.8</version>
</plugin>

surefire 是 maven 的单元测试的插件,默认情况下 surefire 会执行文件名以 Test 开头或者结尾的测试用例,或者以 TestCase 结尾的用例。

pom 文件中定义,需要 Maven 2.2.1 or 3.x, and JDK 1.6 or higher

<project>
  [...]
  <build>
    <pluginManagement>
      <plugins>
        <plugin>
          <groupId>org.apache.maven.plugins</groupId>
          <artifactId>maven-surefire-plugin</artifactId>
          <version>2.22.0</version>
        </plugin>
      </plugins>
    </pluginManagement>
  </build>
  [...]
</project>

在 mvn 的生命周期中 ,mvn test 时会调用该插件。如果要在 build 时跳过 test 可以定义

<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-surefire-plugin</artifactId>
    <version>2.22.0</version>
    <configuration>
        <skip>true</skip>
    </configuration>
</plugin>

排除某些类

<configuration>
  <skip>true</skip>
  <excludes>
    <exclude>**/T</exclude>
  </excludes>
</configuration>

另外如果要添加或者排除某些目录可以参考官网 的例子。

如果要在开发过程中执行 test,可以

mvn -Dtest=TestCircle test   # test 表示当前测试方法所在的测试类,不需要扩展名

maven-enforcer-plugin

在一个稍大一点的组织或团队中,你无法保证所有成员都熟悉 Maven,那他们做一些比较愚蠢的事情就会变得很正常,例如给项目引入了外部的 SNAPSHOT 依赖而导致构建不稳定,使用了一个与大家不一致的 Maven 版本而经常抱怨构建出现诡异问题。maven-enforcer-plugin 能够帮助你避免之类问题,它允许你创建一系列规则强制所有使用者遵守,包括设定 Java 版本、设定 Maven 版本、禁止某些有漏洞的依赖、禁止 SNAPSHOT 依赖等等。只需要在一个父 POM 配置规则,然后各项目继承,当规则遭到破坏的时候,Maven 就会无法编译并抛出相应的错误。除了标准的规则之外,你还可以扩展该插件,编写自己的规则。maven-enforcer-plugin 的 enforce 目标负责检查规则,它默认绑定到[[Maven 生命周期]]的 validate 阶段。

使用如下定义

<plugin>
  <groupId>org.apache.maven.plugins</groupId>
  <artifactId>maven-enforcer-plugin</artifactId>
  <version>1.3.1</version>
</plugin>

enforcer 插件有两个 goal,第一个是 display-info, 使用 mvn enforcer:display-info 会显示当前环境信息

[INFO] Maven Version: 3.3.9
[INFO] JDK Version: 1.8.0_131 normalized as: 1.8.0-131
[INFO] OS Info: Arch: amd64 Family: unix Name: linux Version: 4.4.0-116-generic
[INFO]

第二个也是最主要的 goal,就是 mvn enforcer:enforce 用来检查依赖。enforce goal 和 maven 生命周期 validate 阶段绑定,也就意味着,如果要执行 enforcer:enforce 可以通过 mvn validate 来触发。

如果使用 Maven > 3.1.1enforcer 1.4.1 可以使用 @ 语法来执行 特定 id 的 execution。

mvn enforcer:enforce@no-duplicate-declared-dependencies

configuration vs executions

插件的配置中能直接看到插件的 configuration 配置和 executions 中的 configuration 配置,这两个 configuration 配置有什么区别呢?在 executions 中的配置和 maven 的生命周期绑定,而外层插件的 configuration 是全局的配置。

<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-enforcer-plugin</artifactId>
    <version>1.0.1</version>
    <executions>
      <execution>
        <id>convergence</id>
        <goals>
          <goal>enforce</goal>
        </goals>
        <configuration>
          <rules>
            <dependencyConvergence/>
          </rules>
          <fail>false</fail>
        </configuration>
      </execution>
    </executions>
    <configuration>
        <rules>
            <AlwaysPass />
        </rules>
        <fail>true</fail>
    </configuration>
</plugin>

如果要执行 executions 中的规则,则需要运行 mvn validate 来触发,而在定义在外层 configuration 中的规则,通过 mvn enforcer:enforce 就能够执行。

内置的规则

enforcer 插件自带了很多规则 下面就列举比较常用的几个。

dependencyConvergence 规则 可以保证 maven 以来的包都使用同一个版本。

<plugin>
  <groupId>org.apache.maven.plugins</groupId>
  <artifactId>maven-enforcer-plugin</artifactId>
  <version>3.0.0-M2</version>
  <executions>
    <execution>
      <id>convergence</id>
      <configuration>
        <rules>
          <dependencyConvergence/>
        </rules>
      </configuration>
      <goals>
        <goal>enforce</goal>
      </goals>
    </execution>
  </executions>
</plugin>

单独执行该 goal 可以使用 mvn enforcer:enforce@convergence

bannedDependencies 用来禁用某依赖

<execution>
  <!-- mvn enforcer:enforce@ban-dependencies -->
  <id>ban-dependencies</id>
  <goals>
    <goal>enforce</goal>
  </goals>
  <configuration>
    <rules>
      <bannedDependencies>
        <!-- 是否检查依赖传递 -->
        <searchTransitive>false</searchTransitive>
        <excludes>
          <exclude>org.apache.maven</exclude>
          <exclude>org.apache.maven:badArtifact</exclude>
          <exclude>*:badArtifact</exclude>
          <!--<exclude>com.google.code.gson</exclude>-->
        </excludes>
        <includes>
          <!--only 1.0 of badArtifact is allowed-->
          <include>org.apache.maven:badArtifact:1.0</include>
        </includes>
        <message>检查这些依赖</message>
      </bannedDependencies>
    </rules>
    <fail>true</fail>
  </configuration>
</execution>

excludes 中包含一些列禁止的 artifacts,格式是 groupId[:artifactId][:version][:type][:scope][:classifier] 除了 groupId 其他都是可选的,可以使用通配符

  • org.apache.maven
  • org.apache.maven:badArtifact
  • org.apache.maven:artifact:badVersion
  • org.apache.maven:*:1.2 (exclude version 1.2 and above, equivalent to [1.2,) )
  • org.apache.maven:*:[1.2] (explicit exclude of version 1.2)
  • org.apache.maven:::jar:test
  • ::*:jar:compile:tests
  • org.apache.:maven-:*

bannedPlugins 禁止使用某些插件。

更多的插件内置规则可以查看:http://maven.apache.org/enforcer/enforcer-rules/index.html

maven-release-plugin

可用于构建 release 版本项目,实现自动打 tag、递增版本号、分发 release 版本 jar 包至仓库。

<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-release-plugin</artifactId>
    <version>2.5</version>
</plugin>

一般来说引入 maven release plugin 需要如下几步:

  • 在项目的 pom 文件中增加,无需添加 mvn-release-plugin 的依赖,因为它默认被包含于 maven 的 effective pom 中;
  • 检查自己的 maven 的 settings.xml 是否包含了私服的用户名密码;
  • 确保自己本地代码是在主分支,并且是最新的副本;
  • 执行 mvn release:prepare, 这时插件会扫描项目依赖查看是否有 SNAPSHOT, 是否存在未提交的文件,确定当前 release 的版本号和下一个迭代的版本号,插件会运行单元测试,并向 git 中提交两次 commit, 一次是 release 版本,一次是下一个迭代的版本。并将当前 release 版本打一个 tag 并提交到 git 上面去;
  • 执行 mvn release:perform, 插件会执行 mvn deploy 操作,并 clean 掉生成的缓存文件。

pom 设置:

<distributionManagement>
    <repository>
        <id>releases</id>
        <name>xxxx internal releases repository</name>
        <url>http://xxx.com/nexus/content/repositories/releases</url>
    </repository>
    <snapshotRepository>
        <id>snapshots</id>
        <name>xxxx internal snapshots repository</name>
        <url>http://xxx.com/nexus/content/repositories/snapshots</url>
    </snapshotRepository>
</distributionManagement>

<scm>
    <url>http://xxx.com/project</url>
    <connection>scm:git:git@xxx.com/project.git</connection>
    <developerConnection>scm:git:git@xxx.com/project.git</developerConnection>
    <tag>HEAD</tag>
</scm>

Apache Maven Checkstyle Plugin

[[maven-checkstyle-plugin]] 引入 pom

<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-checkstyle-plugin</artifactId>
    <version>3.0.0</version>
    <executions>
        <execution>
            <id>validate</id>
            <phase>validate</phase>
            <configuration>
                <configLocation>checkStyle.xml</configLocation>
                <encoding>UTF-8</encoding>
                <consoleOutput>true</consoleOutput>
                <failsOnError>true</failsOnError>
                <includeTestSourceDirectory>true</includeTestSourceDirectory>
            </configuration>
            <goals>
                <goal>check</goal>
            </goals>
        </execution>
    </executions>
</plugin>

用来检查代码规范。

jetty-maven-plugin

http://wiki.eclipse.org/Jetty/Feature/Jetty_Maven_Plugin

在进行 Web 开发的时候,打开浏览器对应用进行手动的测试几乎是无法避免的,这种测试方法通常就是将项目打包成 war 文件,然后部署到 Web 容器中,再启动容器进行验证,这显然十分耗时。为了帮助开发者节省时间,jetty-maven-plugin 应运而生,它完全兼容 Maven 项目的目录结构,能够周期性地检查源文件,一旦发现变更后自动更新到内置的 Jetty Web 容器中。做一些基本配置后(例如 Web 应用的 contextPath 和自动扫描变更的时间间隔),你只要执行 mvn jetty:run ,然后在 IDE 中修改代码,代码经 IDE 自动编译后产生变更,再由 jetty-maven-plugin 侦测到后更新至 Jetty 容器,这时你就可以直接测试 Web 页面了。需要注意的是,jetty-maven-plugin 并不是宿主于 Apache 或 Codehaus 的官方插件,因此使用的时候需要额外的配置 settings.xml 的 pluginGroups 元素,将 org.mortbay.jetty 这个 pluginGroup 加入。

更多的插件可以到这里查到:http://maven.apache.org/plugins/index.html

maven-git-commit-id-plugin

这不是一个官方的插件,实现的功能是能够在打包的二进制文件中关联 git 代码的版本。

地址:

maven 项目构建项目,打包成 jar 时,默认情况是 名字加上版本号,通过这个插件可以在命名的时候再增加一个 git 版本号,比如

com-einverne-api-1.0.0-SNAPSHOT-b31229dd.jar


<build>
    <plugins>
        <plugin>
            <groupId>pl.project13.maven</groupId>
            <artifactId>git-commit-id-plugin</artifactId>
            <version>2.2.4</version>
            <executions>
                <execution>
                    <goals>
                        <goal>revision</goal>
                    </goals>
                </execution>
            </executions>
            <configuration>
                <verbose>true</verbose>
                <generateGitPropertiesFile>true</generateGitPropertiesFile>
                <injectAllReactorProjects>true</injectAllReactorProjects>
                <!-- git 描述, JGIT 提供 -->
                <gitDescribe>
                    <!-- 是否生成描述属性 -->
                    <skip>false</skip>
                    <!-- 提交操作未发现 tag 时,仅打印提交操作 ID,-->
                    <always>false</always>
                    <!-- 提交操作 ID 显式字符长度,最大值为:40; 默认值:7;
                        0 代表特殊意义;后面有解释;
                    -->
                    <abbrev>7</abbrev>
                    <!-- 构建触发时,代码有修改时(即"dirty state"), 添加指定后缀;默认值:"";-->
                    <dirty>-dirty</dirty>
                    <!--always print using the "tag-commits_from_tag-g_commit_id-maybe_dirty" format, even if "on" a tag.
                        The distance will always be 0 if you're "on" the tag.
                    -->
                    <forceLongFormat>false</forceLongFormat>
                </gitDescribe>
            </configuration>
        </plugin>
    </plugins>
</build>

说明:

  • dotGitDirectory 默认值为 ${project.basedir}/.git,表示 .git 文件夹路径,可以自定义 ${project.basedir}/../.git
  • failOnNoGitDirectory 默认值:true,.git 文件夹未找到时,构建是否失败;若设置 true, 则构建失败;若设置 false, 则跳过执行该目标

更多详细的设置可以参考这里

appassembler-maven-plugin

Mojo Appassembler

主要作用是将 Java 程序打包成单一可执行程序,以往编写单一的可执行的 Java 程序可能非常复杂。The Application Assembler Plugin 是一个用来生成直接启动 Java 程序脚本的 Maven 插件。所有的依赖和构建都会被放到一个定义好的 assemble 目录中,所有的依赖都会在脚本中添加到 classpath 中。

Goals

  • appassembler:assemble Assembles the artifacts and generates bin scripts for the configured applications.
  • appassembler:create-repository Creates an appassembler repository.
  • appassembler:generate-daemons Generates JSW based daemon wrappers.

Usages

如果直接使用 mvn 命令

mvn archetype:generate \
  -DarchetypeGroupId=org.apache.maven.archetypes \
  -DarchetypeArtifactId=maven-archetype-quickstart \
  -DgroupId=com.mycompany.app \
  -DartifactId=my-app \
  -Dversion=1.0-SNAPSHOT

或者定义到 pom 文件中

<project>
  ...
  <build>
    <plugins>
      <plugin>
        <groupId>org.codehaus.mojo</groupId>
        <artifactId>appassembler-maven-plugin</artifactId>
        <version>1.10</version>
        <configuration>
          <programs>
            <program>
              <mainClass>com.mycompany.app.App</mainClass>
              <id>app</id>
            </program>
          </programs>
        </configuration>
      </plugin>
    </plugins>
  </build>
</project>

然后使用生成的脚本

$ mvn package appassembler:assemble
...
$ sh target/appassembler/bin/app
Hello World!

其他更多的控制选项可以参考这里

reference


2018-07-09 maven , java , build , management , build-tool

电子书

本站提供服务

最近文章

  • AI Shell 让 AI 在命令行下提供 Shell 命令 AI Shell 是一款在命令行下的 AI 自动补全工具,当你想要实现一个功能,敲一大段命令又记不住的时候,使用自然语言让 AI 给你生成一个可执行的命令,然后确认之后执行。
  • 最棒的 Navidrome 音乐客户端 Sonixd(Feishin) Sonixd 是一款跨平台的音乐播放器,可以使用 [[Subsonic API]],兼容 Jellyfin,[[Navidrome]],Airsonic,Airsonic-Advanced,Gonic,Astiga 等等服务端。 Sonixd 是一款跨平台的音乐播放器,可以使用 [[Subsonic API]],兼容 Jellyfin,[[Navidrome]],Airsonic,Airsonic-Advanced,Gonic,Astiga 等等服务端。
  • 中心化加密货币交易所 Gate 注册以及认证 Gate.io 是一个中心化的加密货币交易所。Gate 中文通常被称为「芝麻开门」,Gate 创立于 2013 年,前身是比特儿,是一家致力于安全、稳定的数字货币交易所,支持超过 1600 种数字货币的交易,提供超过 2700 个交易对。
  • 不重启的情况下重新加载 rTorrent 配置文件 因为我在 Screen 下使用 rTorrent,最近经常调试修改 rtorrent.rc 配置文件,所以想要找一个方法可以在不重启 rTorrent 的情况重新加载配置文件,网上调查了一下之后发现原来挺简单的。
  • Go 语言编写的网络穿透工具 chisel chisel 是一个在 HTTP 协议上的 TCP/UDP 隧道,使用 Go 语言编写,10.9 K 星星。