Chinaxiang

Chinaxiang 查看完整档案

苏州编辑河南理工大学  |  计算机科学与技术 编辑同程网络科技股份有限公司  |  攻城狮 编辑 huangyanxiang.com 编辑
编辑

这个人很懒,什么也没有留下。

个人动态

Chinaxiang 评论了文章 · 2018-08-04

springboot应用启动原理(二) 扩展URLClassLoader实现嵌套jar加载

在上篇文章《springboot应用启动原理(一) 将启动脚本嵌入jar》中介绍了springboot如何将启动脚本与Runnable Jar整合为Executable Jar的原理,使得生成的jar/war文件可以直接启动
本篇将介绍springboot如何扩展URLClassLoader实现嵌套jar的类(资源)加载,以启动我们的应用。

本篇示例使用 java8 + grdle4.2 + springboot2.0.0.release 环境

首先,从一个简单的示例开始

build.gradle

group 'com.manerfan.spring'
version '1.0.0'

apply plugin: 'java'
apply plugin: 'java-library'

sourceCompatibility = 1.8

buildscript {
    ext {
        springBootVersion = '2.0.0.RELEASE'
    }

    repositories {
        mavenLocal()
        maven {
            name 'aliyun maven central'
            url 'http://maven.aliyun.com/nexus/content/groups/public'
        }
    }

    dependencies {
        classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}")
    }
}

apply plugin: 'org.springframework.boot'
apply plugin: 'io.spring.dependency-management'

bootJar {
    launchScript()
}

repositories {
    mavenLocal()
    maven {
        name 'aliyun maven central'
        url 'http://maven.aliyun.com/nexus/content/groups/public'
    }
}

dependencies {
    api 'org.springframework.boot:spring-boot-starter-web'
}

WebApp.java

@SpringBootApplication
@RestController
public class WebApp {
    public static void main(String[] args) {
        SpringApplication.run(WebApp.class, args);
    }

    @RequestMapping("/")
    @GetMapping
    public String hello() {
        return "Hello You!";
    }
}

执行gradle build构建jar包,里面包含应用程序第三方依赖以及springboot启动程序,其目录结构如下

spring-boot-theory-1.0.0.jar
├── META-INF
│   └── MANIFEST.MF
├── BOOT-INF
│   ├── classes
│   │   └── 应用程序
│   └── lib
│       └── 第三方依赖jar
└── org
    └── springframework
        └── boot
            └── loader
                └── springboot启动程序

查看MANIFEST.MF的内容(MANIFEST.MF文件的作用请自行GOOGLE)

Manifest-Version: 1.0
Start-Class: com.manerfan.springboot.theory.WebApp
Main-Class: org.springframework.boot.loader.JarLauncher

可以看到,jar的启动类为org.springframework.boot.loader.JarLauncher,而并不是我们的com.manerfan.springboot.theory.WebApp,应用程序入口类被标记为了Start-Class

jar启动并不是通过应用程序入口类,而是通过JarLauncher代理启动。其实SpringBoot拥有3中不同的Launcher:JarLauncherWarLauncherPropertiesLauncher

launcher

springboot使用Launcher代理启动,其最重要的一点便是可以自定义ClassLoader,以实现对jar文件内(jar in jar)或其他路径下jar、class或资源文件的加载
关于ClassLoader的更多介绍可参考《深入理解JVM之ClassLoader》

Archive

  • 归档文件
  • 通常为tar/zip等格式压缩包
  • jar为zip格式归档文件

SpringBoot抽象了Archive的概念,一个Archive可以是jar(JarFileArchive),可以是一个文件目录(ExplodedArchive),可以抽象为统一访问资源的逻辑层。

上例中,spring-boot-theory-1.0.0.jar既为一个JarFileArchive,spring-boot-theory-1.0.0.jar!/BOOT-INF/lib下的每一个jar包也是一个JarFileArchive
将spring-boot-theory-1.0.0.jar解压到目录spring-boot-theory-1.0.0,则目录spring-boot-theory-1.0.0为一个ExplodedArchive

public interface Archive extends Iterable<Archive.Entry> {
    // 获取该归档的url
    URL getUrl() throws MalformedURLException;
    // 获取jar!/META-INF/MANIFEST.MF或[ArchiveDir]/META-INF/MANIFEST.MF
    Manifest getManifest() throws IOException;
    // 获取jar!/BOOT-INF/lib/*.jar或[ArchiveDir]/BOOT-INF/lib/*.jar
    List<Archive> getNestedArchives(EntryFilter filter) throws IOException;
}

JarLancher

Launcher for JAR based archives. This launcher assumes that dependency jars are included inside a /BOOT-INF/lib directory and that application classes are included inside a /BOOT-INF/classes directory.

按照定义,JarLauncher可以加载内部/BOOT-INF/lib下的jar及/BOOT-INF/classes下的应用class

其实JarLauncher实现很简单

public class JarLauncher extends ExecutableArchiveLauncher {
    public JarLauncher() {}
    public static void main(String[] args) throws Exception {
        new JarLauncher().launch(args);
    }
}

其主入口新建了JarLauncher并调用父类Launcher中的launch方法启动程序
再创建JarLauncher时,父类ExecutableArchiveLauncher找到自己所在的jar,并创建archive

public abstract class ExecutableArchiveLauncher extends Launcher {
    private final Archive archive;
    public ExecutableArchiveLauncher() {
        try {
            // 找到自己所在的jar,并创建Archive
            this.archive = createArchive();
        }
        catch (Exception ex) {
            throw new IllegalStateException(ex);
        }
    }
}

public abstract class Launcher {
    protected final Archive createArchive() throws Exception {
        ProtectionDomain protectionDomain = getClass().getProtectionDomain();
        CodeSource codeSource = protectionDomain.getCodeSource();
        URI location = (codeSource == null ? null : codeSource.getLocation().toURI());
        String path = (location == null ? null : location.getSchemeSpecificPart());
        if (path == null) {
            throw new IllegalStateException("Unable to determine code source archive");
        }
        File root = new File(path);
        if (!root.exists()) {
            throw new IllegalStateException(
                    "Unable to determine code source archive from " + root);
        }
        return (root.isDirectory() ? new ExplodedArchive(root)
                : new JarFileArchive(root));
    }
}

在Launcher的launch方法中,通过以上archive的getNestedArchives方法找到/BOOT-INF/lib下所有jar及/BOOT-INF/classes目录所对应的archive,通过这些archives的url生成LaunchedURLClassLoader,并将其设置为线程上下文类加载器,启动应用

public abstract class Launcher {
    protected void launch(String[] args) throws Exception {
        JarFile.registerUrlProtocolHandler();
        // 生成自定义ClassLoader
        ClassLoader classLoader = createClassLoader(getClassPathArchives());
        // 启动应用
        launch(args, getMainClass(), classLoader);
    }

    protected void launch(String[] args, String mainClass, ClassLoader classLoader)
            throws Exception {
        // 将自定义ClassLoader设置为当前线程上下文类加载器
        Thread.currentThread().setContextClassLoader(classLoader);
        // 启动应用
        createMainMethodRunner(mainClass, args, classLoader).run();
    }
}

public abstract class ExecutableArchiveLauncher extends Launcher {
    protected List<Archive> getClassPathArchives() throws Exception {
        // 获取/BOOT-INF/lib下所有jar及/BOOT-INF/classes目录对应的archive
        List<Archive> archives = new ArrayList<>(
                this.archive.getNestedArchives(this::isNestedArchive));
        postProcessClassPathArchives(archives);
        return archives;
    }
}

public class MainMethodRunner {
    // Start-Class in MANIFEST.MF
    private final String mainClassName;

    private final String[] args;

    public MainMethodRunner(String mainClass, String[] args) {
        this.mainClassName = mainClass;
        this.args = (args == null ? null : args.clone());
    }

    public void run() throws Exception {
        // 加载应用程序主入口类
        Class<?> mainClass = Thread.currentThread().getContextClassLoader()
                .loadClass(this.mainClassName);
        // 找到main方法
        Method mainMethod = mainClass.getDeclaredMethod("main", String[].class);
        // 调用main方法,并启动
        mainMethod.invoke(null, new Object[] { this.args });
    }
}

至此,才执行我们应用程序主入口类的main方法,所有应用程序类文件均可通过/BOOT-INF/classes加载,所有依赖的第三方jar均可通过/BOOT-INF/lib加载

LaunchedURLClassLoader

在分析LaunchedURLClassLoader前,首先了解一下URLStreamHandler

URLStreamHandler

java中定义了URL的概念,并实现多种URL协议(见URLhttpfileftpjar 等,结合对应的URLConnection可以灵活地获取各种协议下的资源

public URL(String protocol,
           String host,
           int port,
           String file,
           URLStreamHandler handler)
    throws MalformedURLException

对于jar,每个jar都会对应一个url,如
jar:file:/data/spring-boot-theory/BOOT-INF/lib/spring-aop-5.0.4.RELEASE.jar!/

jar中的资源,也会对应一个url,并以'!/'分割,如
jar:file:/data/spring-boot-theory/BOOT-INF/lib/spring-aop-5.0.4.RELEASE.jar!/org/springframework/aop/SpringProxy.class

对于原始的JarFile URL,只支持一个'!/',SpringBoot扩展了此协议,使其支持多个'!/',以实现jar in jar的资源,如
jar:file:/data/spring-boot-theory.jar!/BOOT-INF/lib/spring-aop-5.0.4.RELEASE.jar!/org/springframework/aop/SpringProxy.class

自定义URL的类格式为[pkgs].[protocol].Handler,在运行Launcher的launch方法时调用了JarFile.registerUrlProtocolHandler()以注册自定义的 Handler

private static final String PROTOCOL_HANDLER = "java.protocol.handler.pkgs";
private static final String HANDLERS_PACKAGE = "org.springframework.boot.loader";
public static void registerUrlProtocolHandler() {
    String handlers = System.getProperty(PROTOCOL_HANDLER, "");
    System.setProperty(PROTOCOL_HANDLER, ("".equals(handlers) ? HANDLERS_PACKAGE
            : handlers + "|" + HANDLERS_PACKAGE));
    resetCachedUrlHandlers();
}

在处理如下URL时,会循环处理'!/'分隔符,从最上层出发,先构造spring-boot-theory.jar的JarFile,再构造spring-aop-5.0.4.RELEASE.jar的JarFile,最后构造指向SpringProxy.class的
JarURLConnection ,通过JarURLConnection的getInputStream方法获取SpringProxy.class内容

jar:file:/data/spring-boot-theory.jar!/BOOT-INF/lib/spring-aop-5.0.4.RELEASE.jar!/org/springframework/aop/SpringProxy.class

从一个URL,到读取其中的内容,整个过程为

  • 注册一个Handler处理‘jar:’这种协议
  • 扩展JarFile、JarURLConnection,处理jar in jar的情况
  • 循环处理,找到内层资源
  • 通过getInputStream获取资源内容

URLClassLoader可以通过原始的jar协议,加载jar中从class文件
LaunchedURLClassLoader 通过扩展的jar协议,以实现jar in jar这种情况下的class文件加载

WarLauncher

构建war包很简单

  1. build.gradle中引入插件 apply plugin: 'war'
  2. build.gradle中将内嵌容器相关依赖设为providedprovidedRuntime 'org.springframework.boot:spring-boot-starter-tomcat'
  3. 修改WebApp内容,重写SpringBootServletInitializer的configure方法
@SpringBootApplication
@RestController
public class WebApp extends SpringBootServletInitializer {
    public static void main(String[] args) {
        SpringApplication.run(WebApp.class, args);
    }

    @Override
    protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) {
        return builder.sources(WebApp.class);
    }

    @RequestMapping("/")
    @GetMapping
    public String hello() {
        return "Hello You!";
    }
}

构建出的war包,其目录机构为

spring-boot-theory-1.0.0.war
├── META-INF
│   └── MANIFEST.MF
├── WEB-INF
│   ├── classes
│   │   └── 应用程序
│   └── lib
│       └── 第三方依赖jar
│   └── lib-provided
│       └── 与内嵌容器相关的第三方依赖jar
└── org
    └── springframework
        └── boot
            └── loader
                └── springboot启动程序

MANIFEST.MF内容为

Manifest-Version: 1.0
Start-Class: com.manerfan.springboot.theory.WebApp
Main-Class: org.springframework.boot.loader.WarLauncher

此时,启动类变为了org.springframework.boot.loader.WarLauncher,查看WarLauncher实现,其实与JarLauncher并无太大差别

public class WarLauncher extends ExecutableArchiveLauncher {
    private static final String WEB_INF = "WEB-INF/";
    private static final String WEB_INF_CLASSES = WEB_INF + "classes/";
    private static final String WEB_INF_LIB = WEB_INF + "lib/";
    private static final String WEB_INF_LIB_PROVIDED = WEB_INF + "lib-provided/";

    public WarLauncher() {
    }

    @Override
    public boolean isNestedArchive(Archive.Entry entry) {
        if (entry.isDirectory()) {
            return entry.getName().equals(WEB_INF_CLASSES);
        }
        else {
            return entry.getName().startsWith(WEB_INF_LIB)
                    || entry.getName().startsWith(WEB_INF_LIB_PROVIDED);
        }
    }

    public static void main(String[] args) throws Exception {
        new WarLauncher().launch(args);
    }
}

差别仅在于,JarLauncher在构建LauncherURLClassLoader时,会搜索BOOT-INF/classes目录及BOOT-INF/lib目录下jar,WarLauncher在构建LauncherURLClassLoader时,则会搜索WEB-INFO/classes目录及WEB-INFO/lib和WEB-INFO/lib-provided两个目录下的jar

如此依赖,构建出的war便支持两种启动方式

  • 直接运行./spring-boot-theory-1.0.0.war start
  • 部署到Tomcat容器下

PropertiesLauncher

PropretiesLauncher 的实现与 JarLauncher WarLauncher 的实现极为相似,通过PropretiesLauncher可以实现更为轻量的thin jar,其实现方式可自行查阅源码

总结

  • SpringBoot通过扩展JarFile、JarURLConnection及URLStreamHandler,实现了jar in jar中资源的加载
  • SpringBoot通过扩展URLClassLoader--LauncherURLClassLoader,实现了jar in jar中class文件的加载
  • JarLauncher通过加载BOOT-INF/classes目录及BOOT-INF/lib目录下jar文件,实现了fat jar的启动
  • WarLauncher通过加载WEB-INF/classes目录及WEB-INF/lib和WEB-INF/lib-provided目录下的jar文件,实现了war文件的直接启动及web容器中的启动

订阅号

查看原文

Chinaxiang 发布了文章 · 2017-10-11

使用GitHub Pages构建个人博客

写博客是一个程序猿必备技能,一是可以备忘,二是可以有更多的机会跟大家交流,三是增加个人成就感,四是说不定可以帮你找一份好工作等等,好处多多。

最近闲暇时间又把我的个人域名翻了出来,买的服务器到期了,所以使用GitHub Pages免费搭建一个,项目开源出来给大家提供参考。

项目地址:https://github.com/Chinaxiang/Chinaxiang.github.io

效果预览地址:http://huangyanxiang.com

图片描述

使用Jekyll的三篇总结文章:

使用GitHub Pages构建的博客系统,使用了默认的主题 minima 并重构。

GitHub Pages是免费的静态资源托管服务,依托GitHub提供高效,稳定,安全的用户体验,因此借助巨人的肩膀让自己看得更远。

GitHub Pages有如下限制:

  • GitHub Pages空间大小不能超过1G(足够大了,写个文档、博客无需担心不够用).
  • GitHub Pages提供每月100G的带宽(能超过这个,我的年薪也能上100W+了).
  • GitHub Pages提供每小时10次构建(谁没事一会提交一个版本呢?).

之前就想过通过GitHub管理自己的博客,直到最近才有空闲时间来做这个事情。
博客内容没有高深的内容,不期为别人提供什么帮助,纯粹是为了备忘,有可能是转载的,有可能是累赘重复的,欢迎吐槽。
如有侵犯您的相关权益,请及时联系我。

对于我博客中的任何内容,如有需要请随意拿走,不需要得到我的许可,只要你觉得有用就好。

如果你也想通过GitHub Pages构建自己的站点,可以直接fork本项目,我也会提供我使用GitHub Pages的相关经验供参考。

分支介绍:

  • master: 博客主项目。
  • init: 博客未自定义样式,仅仅覆盖了主题默认的一些配置项,增加了分页,SEO插件,准备自定义样式的阶段。
  • custom: 自定义了样式后的阶段。

Plugins

Jekyll

GitHub Pages支持Jekyll编译静态文件。

Jekyll需要Ruby环境。
Jekyll官网

~ $ gem install jekyll bundler
~ $ jekyll new my-awesome-site
~ $ cd my-awesome-site
~/my-awesome-site $ bundle exec jekyll serve
# => Now browse to http://localhost:4000
查看原文

赞 0 收藏 8 评论 0

Chinaxiang 发布了文章 · 2017-10-11

Jekyll With Usefule Plugins

博客原文:http://huangyanxiang.com/2017/09/20/jekyll-with-useful-plugins.html

Jekyll 有很多有用的插件,比如分页,SEO优化等,另外Jekyll中可以使用一些Liquid没有定义的filter,可以让我们的站点更加好用。

Filters

Jekyll添加了自己的一些方法,如:

{{ "/assets/style.css" | relative_url }}
/my-baseurl/assets/style.css

{{ "/assets/style.css" | absolute_url }}
http://example.com/my-baseurl/assets/style.css

{{ site.members | where:"graduation_year","2014" }}
{{ site.members | where_exp:"item", "item.graduation_year == 2014" }}

{{ "http://foo.com/?q=foo, \bar?" | uri_escape }}
http://foo.com/?q=foo,%20%5Cbar?

{{ page.content | number_of_words }}
1337

{{ site.posts | sort: 'author' }}

Tags

Jekyll支持额外的标签。

include

导入其他文件。待导入的文件都需要放置到_includes目录下。

{% include footer.html %}

highlight

代码高亮支持。

{% highlight ruby %}
def foo
  puts 'foo'
end
{% endhighlight %}

Line numbers

{% highlight ruby linenos %}
def foo
  puts 'foo'
end
{% endhighlight %}

Pagination

如果文章较多,我们可以引入分页插件。

本地需要安装Pagination插件:

gem install jekyll-paginate

需要在_config.yml中配置一下:

plugins:
  - jekyll-paginate

paginate: 15
paginate_path: "/blog/p:num"

Gemfile中最好也配置一下:

# If you have any plugins, put them here!
group :jekyll_plugins do
   gem "jekyll-paginate"
end

分页只能在index.html文件中使用,所以我这创建了/blog/index.html

里面的核心内容如下:

文章列表
{% assign posts = paginator.posts | sort: 'date' %}
{% for post in posts %}
  {{ post.title }}
  {{ post.excerpt | remove: '<p>' | remove: '</p>'  }}
  {{ post.date | date: site.minima.date_format }}
{% endfor %}

页码跳转
{% if paginator.total_pages > 1 %}
<nav aria-label="Page navigation">
  <ul class="pagination">
    {% if paginator.previous_page %}
      <li>
        <a href="{{ paginator.previous_page_path | prepend: site.baseurl | replace: '//', '/' }}" aria-label="Previous">
          <span aria-hidden="true">&laquo;</span>
        </a>
      </li>
    {% else %}
      <li class="disabled"><a href="javascript:;" aria-label="Previous"><span aria-hidden="true">&laquo;</span></a></li>
    {% endif %}

    {% assign curPage = paginator.page %}
    {% assign totalPage = paginator.total_pages %}
    {% assign leftPage = totalPage | minus: curPage %}
    {% if totalPage > 7 %}
      {% if curPage < 4 %}
        {% assign start = 1 %}
        {% assign end = 7 %}
      {% else %}
        {% if leftPage < 3 %}
          {% assign start = totalPage | minus: 6 %}
          {% assign end = totalPage %}
        {% else %}
          {% assign start = curPage | minus: 3 %}
          {% assign end = curPage | plus: 3 %}
        {% endif %}
      {% endif %}
      {% assign pageList = (start..end) %}
    {% else %}
      {% assign pageList = (1..totalPage) %}
    {% endif %}

    {% for page in pageList %}
      {% if page == paginator.page %}
        <li class="active"><a href="javascript:;">{{ page }}</a></li>
      {% elsif page == 1 %}
        <li><a href="{{ "/blog" }}">1</a></li>
      {% else %}
        <li><a href="{{ site.paginate_path | prepend: site.baseurl | replace: '//', '/' | replace: ':num', page }}">{{ page }}</a></li>
      {% endif %}
    {% endfor %}

    {% if paginator.next_page %}
      <li>
        <a href="{{ paginator.next_page_path | prepend: site.baseurl | replace: '//', '/' }}" aria-label="Next">
          <span aria-hidden="true">&raquo;</span>
        </a>
      </li>
    {% else %}
      <li class="disabled"><a href="javascript:;" aria-label="Next"><span aria-hidden="true">&raquo;</span></a></li>
    {% endif %}
    
  </ul>
</nav>
{% endif %}

SEO

博客的话,我们当然想被搜索引擎很好的收录,可以引入SEO插件。

本地需要安装:

gem install jekyll-seo-tag

同样配置_config.yml

plugins:
  - jekyll-seo-tag

配置Gemfile

# If you have any plugins, put them here!
group :jekyll_plugins do
   gem "jekyll-seo-tag", "~> 2.1"
end

使用比较简单,在你的html模板的head标签下:

<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
{% seo %}

Tag Posts

一般的博客文章我们都会给他们打上标签,便于管理和系统查看,我们可以通过Jekyll的标签功能实现博客分类的目的,不过这需要做不少的内容。

标签文章列表需要有一个单独的导航页面,我的都建到/tags/目录下,如:/tags/jekyll.html

里面的核心内容:

{% assign posts = site.tags.jekyll | sort: 'date' %}
{% for post in posts %}
  {{ post.title }}
  {{ post.excerpt | remove: '<p>' | remove: '</p>'  }}
  {{ post.date | date: site.minima.date_format }}
{% endfor %}

标签和分类功能类似,可以使用相同的导航索引,我之所以没有使用category, categories是因为我不想让文章的url太长。

标签和分类的文章列表都不支持分页,尽量合理控制文章数目,避免列表过长影响体验。

博客原文:http://huangyanxiang.com/2017/09/20/jekyll-with-useful-plugins.html

查看原文

赞 0 收藏 0 评论 0

Chinaxiang 发布了文章 · 2017-10-11

Jekyll With Liquid

博客原文:http://huangyanxiang.com/2017/09/20/jekyll-with-liquid.html

Jekyll的模板中可以使用Liquid语法进行取值,计算和数据处理,如果仅仅只为了使用Jekyll撰写博客文章,你可以不需要了解liquid, 但是如果你需要定制Jekyll的主题,灵活的控制你的站点,你确实有必要了解一些liquid相关的语法。

Liquid和大多数模板语言类似,也是比较简单的。

官方网址:

概览

Liquid可以分为objects, tags, and filters.

使用英文双大括号对objects进行取值。

获取页面的标题:

{{ page.title }}

标签,例如:条件控制,循环,都是包裹在{%%}中的。

如果user不为空,则输出user.name:

{% if user %}
  Hello {{ user.name }}!
{% endif %}

对于filter, 你可以理解为提供了一系列常用的方法,并且支持管道操作。

对url地址添加.html后缀:

{{ "/my/fancy/url" | append: ".html" }}

输出:

/my/fancy/url.html

首字母大写,然后在前面添加Hello :

{{ "adam!" | capitalize | prepend: "Hello " }}

输出:

Hello Adam!

是不是很easy?

操作符

Liquid包含如下操作符:

==equals
!=does not equal
>greater than
<less than
>=greater than or equal to
<=less than or equal to
orlogical or
andlogical and

举个栗子,你一看就懂得。

{% if product.title == "Awesome Shoes" %}
  These shoes are awesome!
{% endif %}

{% if product.type == "Shirt" or product.type == "Shoes" %}
  This is a shirt or a pair of shoes.
{% endif %}

另外还有一个操作符:contains, 它可以判断字符串的包含,还可以判断数组中的字符串包含关系(如果数组中是非字符串的,是不能判断出来的)。

{% if product.title contains 'Pack' %}
  This product's title contains the word Pack.
{% endif %}

{% if product.tags contains 'Hello' %}
  This product has been tagged with 'Hello'.
{% endif %}

数据类型

和大多数语言一样,Liquid也有常用的数据类型。

Boolean

true or false

{% assign foo = true %}
{% assign bar = false %}

Number

统一都叫数字,不分整型,浮点型了

{% assign my_int = 25 %}
{% assign my_float = 39.756 %}

String

使用单引号或双引号包裹的字符串。

{% assign my_string = "Hello World!" %}

Nil

类似js的undefined.

Array

Liquid中无法单独定义出数组,不过你可以使用filter处理得到数组。

数组的取值采用普遍接受的索引取值方式:

{{ site.users[0] }}
{{ site.users[1] }}
{{ site.users[3] }}

对于数组比较常用的还有遍历操作:

{% for user in site.users %}
  {{ user }}
{% endfor %}

Truthy and Falsy

这两个不叫数据类型,但是是条件判断的依据,他们叫:真 和 假。什么时候是真?什么时候是假?很简单,你只需要记着:Nil 和 false 是假,其他的都是真就对了,包括0,空字符串都是真。

 truthyfalsy
true 
false 
nil 
string 
empty string 
0 
integer 
float 
array 
empty array 
page 
EmptyDrop 

Tags

Tags你可以理解为Liquid的关键词,如条件判断if, 循环语句for, Tag是 {%%} 包裹起来的。

注释

注释是不会被Liquid引擎处理的。Liquid的注释既可以作用于单行,又可以作用于多行。

{% comment %} line comment {% endcomment %}
{% comment %}
block comment
{% endcomment %}

条件流程控制

直接看例子吧。

{% if product.title == 'Awesome Shoes' %}
  These shoes are awesome!
{% endif %}

{% unless product.title == 'Awesome Shoes' %}
  These shoes are not awesome.
{% endunless %}

equals

{% if product.title != 'Awesome Shoes' %}
  These shoes are not awesome.
{% endif %}

{% if customer.name == 'kevin' %}
  Hey Kevin!
{% elsif customer.name == 'anonymous' %}
  Hey Anonymous!
{% else %}
  Hi Stranger!
{% endif %}

{% assign handle = 'cake' %}
{% case handle %}
  {% when 'cake' %}
     This is a cake
  {% when 'cookie' %}
     This is a cookie
  {% else %}
     This is not a cake nor a cookie
{% endcase %}

循环遍历

{% for i in (1..5) %}
  {% if i == 4 %}
    {% break %}
  {% else %}
    {{ i }}
  {% endif %}
{% endfor %}

{% for i in (1..5) %}
  {% if i == 4 %}
    {% continue %}
  {% else %}
    {{ i }}
  {% endif %}
{% endfor %}

limit

<!-- if array = [1,2,3,4,5,6] -->
{% for item in array limit:2 %}
  {{ item }}
{% endfor %}

1 2

offset

<!-- if array = [1,2,3,4,5,6] -->
{% for item in array offset:2 %}
  {{ item }}
{% endfor %}

3 4 5 6

range

{% for i in (3..5) %}
  {{ i }}
{% endfor %}

{% assign num = 4 %}
{% for i in (1..num) %}
  {{ i }}
{% endfor %}

3 4 5
1 2 3 4

reversed

<!-- if array = [1,2,3,4,5,6] -->
{% for item in array reversed %}
  {{ item }}
{% endfor %}

6 5 4 3 2 1

cycle, 周期循环的意思。

{% cycle 'one', 'two', 'three' %}
{% cycle 'one', 'two', 'three' %}
{% cycle 'one', 'two', 'three' %}
{% cycle 'one', 'two', 'three' %}

one
two
three
one

tablerow, 快速构建表格的行。

<table>
{% tablerow product in collection.products %}
  {{ product.title }}
{% endtablerow %}
</table>

<table>
  <tr class="row1">
    <td class="col1">
      Cool Shirt
    </td>
    <td class="col2">
      Alien Poster
    </td>
    <td class="col3">
      Batman Poster
    </td>
    <td class="col4">
      Bullseye Shirt
    </td>
    <td class="col5">
      Another Classic Vinyl
    </td>
    <td class="col6">
      Awesome Jeans
    </td>
  </tr>
</table>

{% tablerow product in collection.products cols:2 %}
  {{ product.title }}
{% endtablerow %}

<table>
  <tr class="row1">
    <td class="col1">
      Cool Shirt
    </td>
    <td class="col2">
      Alien Poster
    </td>
  </tr>
  <tr class="row2">
    <td class="col1">
      Batman Poster
    </td>
    <td class="col2">
      Bullseye Shirt
    </td>
  </tr>
  <tr class="row3">
    <td class="col1">
      Another Classic Vinyl
    </td>
    <td class="col2">
      Awesome Jeans
    </td>
  </tr>
</table>

{% tablerow product in collection.products cols:2 limit:3 %}
  {{ product.title }}
{% endtablerow %}

{% tablerow product in collection.products cols:2 offset:3 %}
  {{ product.title }}
{% endtablerow %}

<!--variable number example-->

{% assign num = 4 %}
<table>
{% tablerow i in (1..num) %}
  {{ i }}
{% endtablerow %}
</table>

<!--literal number example-->

<table>
{% tablerow i in (3..5) %}
  {{ i }}
{% endtablerow %}
</table>

变量

Liquid, 支持变量的定义和计算。

assign

{% assign my_variable = false %}
{% if my_variable != true %}
  This statement is valid.
{% endif %}

{% assign foo = "bar" %}
{{ foo }}

capture

{% capture my_variable %}I am being captured.{% endcapture %}
{{ my_variable }}

I am being captured.

{% assign favorite_food = 'pizza' %}
{% assign age = 35 %}

{% capture about_me %}
I am {{ age }} and my favorite food is {{ favorite_food }}.
{% endcapture %}

{{ about_me }}

I am 35 and my favourite food is pizza.

Creates a new number variable, and increases its value by one every time it is called. 
The initial value is 0.

{% increment my_counter %}
{% increment my_counter %}
{% increment my_counter %}

0
1
2

Variables created through the increment tag are independent from variables created through assign or capture.

{% assign var = 10 %}
{% increment var %}
{% increment var %}
{% increment var %}
{{ var }}

0
1
2
10

{% decrement variable %}
{% decrement variable %}
{% decrement variable %}

-1
-2
-3

Filters

Liquid提供了一些常用的过滤器(方法)。

abs

{{ -17 | abs }}
17

{{ "-19.86" | abs }}
19.86

append, prepend

{% assign filename = "/index.html" %}
{{ "website.com" | append: filename }}
website.com/index.html

capitalize

首字母大写。

{{ "title" | capitalize }}
Title
{{ "my great title" | capitalize }}
My great title

date

{{ article.published_at | date: "%a, %b %d, %y" }}
Fri, Jul 17, 15
{{ article.published_at | date: "%Y" }}
2015
{{ "March 14, 2016" | date: "%b %d, %y" }}
Mar 14, 16

To get the current time, pass the special word "now" (or "today") to date:
This page was last updated at {{ "now" | date: "%Y-%m-%d %H:%M" }}.
This page was last updated at 2017-06-29 14:45.

default

{{ product_price | default: 2.99 }}
2.99
{% assign product_price = 4.99 %}
{{ product_price | default: 2.99 }}
4.99
{% assign product_price = "" %}
{{ product_price | default: 2.99 }}
2.99

lstrip, rstrip, strip

去除句子首部的空白字符。

{{ "          So much room for activities!          " | lstrip }}
So much room for activities!          

去除句子尾部的空白字符。

{{ "          So much room for activities!          " | rstrip }}
          So much room for activities!

去除首尾的空白字符。

{{ "          So much room for activities!          " | strip }}
So much room for activities!

plus, minus, times, divided_by

加,减,乘,除运算。

{{ 16 | divided_by: 4 }}
4
{{ 5 | divided_by: 3 }}
1
{{ 20 | divided_by: 7.0 }}
2.857142857142857
{% assign my_integer = 7 %}
{% assign my_float = my_integer | times: 1.0 %}
{{ 20 | divided_by: my_float }}
2.857142857142857

remove, remove_first

{{ "I strained to see the train through the rain" | remove: "rain" }}
I sted to see the t through the 
{{ "I strained to see the train through the rain" | remove_first: "rain" }}
I sted to see the train through the rain

replace, replace_first

{{ "Take my protein pills and put my helmet on" | replace: "my", "your" }}
Take your protein pills and put your helmet on
{% assign my_string = "Take my protein pills and put my helmet on" %}
{{ my_string | replace_first: "my", "your" }}
Take your protein pills and put my helmet on

slice

Returns a substring of 1 character beginning at the index specified by the argument passed in. An optional second argument specifies the length of the substring to be returned.

截取指定长度的字符串,第二个表示长度,第一个表示索引,从0开始,如果为负数,从后面往前数,从-1开始。

{{ "Liquid" | slice: 0 }}
L
{{ "Liquid" | slice: 2 }}
q
{{ "Liquid" | slice: 2, 5 }}
quid
{{ "Liquid" | slice: -3, 2 }}
ui

split

Divides an input string into an array using the argument as a separator. split is commonly used to convert comma-separated items from a string to an array.

分割字符串组成一个数组。

{% assign beatles = "John, Paul, George, Ringo" | split: ", " %}

{% for member in beatles %}
  {{ member }}
{% endfor %}


  John

  Paul

  George

  Ringo

当然这里没有列举完所有的filter, 这些是相对比较常用的,记着这些,如果满足不了需求,再去官方文档上翻阅翻阅。

博客原文:http://huangyanxiang.com/2017/09/20/jekyll-with-liquid.html

查看原文

赞 0 收藏 1 评论 0

Chinaxiang 发布了文章 · 2017-10-10

Welcome To Jekyll

博客原文:http://huangyanxiang.com/2017/09/20/welcome-to-jekyll.html

欢迎来到Jekyll, 本文将带你初步领略Jekyll的风采。

Jekyll 究竟是什么?

Transform your plain text into static websites and blogs.

Jekyll 是一个简单的静态站点生产器。根据它的规范,我们可以将我们书写的 Markdown (或者 Textile) 以及 Liquid 转化成一个完整的可发布的静态网站,你可以发布在任何你喜爱的服务器上。Jekyll 也可以运行在 GitHub Page 上,也就是说,你可以使用 GitHub 的服务来搭建你的项目页面、博客或者网站,而且是完全免费的。

官方网站:

快速指南

安装 Jekyll 相当简单,但是你得先做好一些准备工作,开始前你需要确保你在系统里已经有如下配置。

  • Ruby: Jekyll需要Ruby环境
  • RubyGems: 这货是Ruby的包管理工具,类似brew, yum之类的,我们可以通过它去安装Jekyll

安装好Ruby 和 RubyGems后,你只需要打开终端输入以下命令就可以了:

# Install Jekyll and Bundler through RubyGems
~ $ gem install jekyll bundler

# Create a new Jekyll site at ./myblog
~ $ jekyll new myblog

# Change into your new directory
~ $ cd myblog

# Build the site on the preview server
~/myblog $ bundle exec jekyll serve

# Now browse to http://localhost:4000

Bundler是Ruby的其他gems的管理者。

jekyll new 命令会使用Jekyll minima主题构建一个Jekyll项目,如果你想要构建一个空白的项目,可以使用:

# Create a new blank Jekyll site at ./myblog
~ $ jekyll new myblog --blank 

一个Jekyll站点一般会添加一些有用的插件或依赖,这些插件或依赖配置在站点下的Gemfile文件中。

bundle exec 处理Gemfile并管理相关依赖的,如果你的站点是空白的项目,没有什么依赖,可以只执行 jekyll serve 即可。

jekyll serve 会自动执行 jekyll build 命令,如果你不需要启动本地预览,你可以只执行 jekyll build 构建站点静态资源。

帮助文档

Jekyll的官网文档: 官方文档 , 你也可以将此帮助文档构建到本地。

~ $ gem install jekyll-docs
~ $ jekyll docs
Configuration file: none
    Server address: http://127.0.0.1:4000
  Server running... press ctrl-c to stop.

如果你想查看jekyll相关命令的用法,你可以执行:

~ $ jekyll help new
~ $ jekyll help build

目录结构

.
├── _config.yml
├── _data
|   └── members.yml
├── _drafts
|   ├── begin-with-the-crazy-ideas.md
|   └── on-simplicity-in-technology.md
├── _includes
|   ├── footer.html
|   └── header.html
├── _layouts
|   ├── default.html
|   └── post.html
├── _posts
|   ├── 2007-10-29-why-every-programmer-should-play-nethack.md
|   └── 2009-04-26-barcamp-boston-4-roundup.md
├── _sass
|   ├── _base.scss
|   └── _layout.scss
├── _site
├── .jekyll-metadata
└── index.html # can also be an 'index.md' with valid YAML Frontmatter

上面的目录结构是jekyll项目定义的目录结构,当然不是必须都存在,比如我们创建的blank项目就只有如下目录结构:

.
├── _drafts
├── _layouts
├── _posts
└── index.html

当我们使用 jekyll new myblog 创建项目时,Jekyll 会使用默认的主题帮助我们构建一个项目,目录结构如下:

.
├── _config.yml
├── _posts
|   └── 20017-09-24-welcome-to-jekyll.markdown
├── .gitignore
├── 404.html
├── about.md
├── Gemfile
├── Gemfile.lock
└── index.md

构建的项目继承了默认主题的一些目录及文件,所以我们不需要创建任何文件就可以创建具有一定简陋样式的博客系统了。当然,我们是可以覆盖(重写)主题默认的配置,完全随你自己控制的。
关于默认主题,你可以到github上查看: Minima
那么Jekyll定义的目录都是用来干嘛的呢?

_config.yml

存储配置信息,可以通过site.XX很方便的读取到配置信息。

_drafts

草稿箱,未发布或未写完的文章可以先放置到这里。

_includes

一些共用的模板,如HTML的head部分,可以很方便的被其他文件引用。 {% include head.html %}

_layouts

可以定义一些模板框架,如post.html, 发布的文章内容会放置到 post.html 中的 {{ content }}.

_posts

文章都放置到这里,这里不要再创建目录,尽管放文件即可,文章的命名遵循如下规则: YEAR-MONTH-DAY-title.MARKUP.

_data

这里你可以放置一些格式化的数据(using either the .yml, .yaml, .json or .csv formats and extensions). If there's a file members.yml under the directory, then you can access contents of the file through site.data.members.

_sass

These are sass partials that can be imported into your main.scss which will then be processed into a single stylesheet main.css that defines the styles to be used by your site.

_site

生成的静态资源文件都放置在这里. It’s probably a good idea to add this to your .gitignore file.

.jekyll-metadata

This helps Jekyll keep track of which files have not been modified since the site was last built, and which files will need to be regenerated on the next build. This file will not be included in the generated site. It’s probably a good idea to add this to your .gitignore file.

index.html or index.md and other HTML, Markdown, Textile files

给这些文件提供YAML Front Matter配置, 它将会自动被Jekyll处理,并加载到_site目录下。

Other Files/Folders

除了上述列举的目录,文件外,如果你有其他文件或目录,Jekyll会自动将他们加载到_site目录下,如css and images 目录, favicon.ico 文件。

配置

Jekyll的配置有很多,但我们用到的不多,默认的基本就够我们使用的了,如果想了解,可以查阅文档。

Configuration

也可以参照我的博客项目了解_config.yml配置。

# Welcome to Chinaxiang' personal page!
lang: zh_CN
title: 黄彦祥的个人网站
author: Chinaxiang
email: forkmail@qq.com
description: > 
  黄彦祥,90后程序猿一枚,热爱分享,喜欢折腾,乐于探索,勇于挑战自我,对新事物充满热情,广交天下有志之士,共谋IT发展大计。
baseurl: ""
url: "http://huangyanxiang.com"
github_username:  Chinaxiang
twitter_username: ""

minima:
  date_format: "%b %-d, %Y"

timezone: Asia/Shanghai

markdown: kramdown
theme: minima
plugins:
  - jekyll-feed
  - jekyll-seo-tag
  - jekyll-paginate

paginate: 15
paginate_path: "/blog/p:num"

文件头配置

文件头配置是Jekyll很炫酷和方便的一个功能,可以给页面或文章指定文件头配置。你可以给首页指定 _layout/home.html 模板, 你可以给个人简介页面指定固定的地址 permalink: /about.html.

头文件配置就是在页面,文章的头部添加类似这样的内容:

---
layout: post
title: Blogging Like a Hacker
---

Global Variables

下面的配置可以添加到普通页面或者文章页面。

  • layout: 指定_layout中的一个模板,当然也可以不使用模板layout: null.
  • permalink: 给页面或文章设置固定的地址。
  • published: 如果不想发布某一篇文章,你可以设置此项为false.

Variables for Posts

  • date: 设置文章的发布时间,格式:YYYY-MM-DD HH:MM:SS +/-TTTT, hours, minutes, seconds, and timezone offset are optional.
  • category: 设置文章的分类,单数
  • categories: 设置文章的分类,可以用空格分隔表示多个
  • tags: 设置文章的标签,可以用空格分隔表示多个

变量

在上面我们已经接触了一些配置变量,配置完了之后我们怎么使用配置过的变量呢,在这里需要简单的介绍一下。

更多详细的内容请移步官方文档:Jekyll Variables

Global Variables

  • site: 站点级别的配置,如:site.title
  • page: 页面级别的配置,如:page.title
  • layout: _layout/目录下的文件的头文件配置,可以在父模板中获取定义的配置,如:layout.desc
  • content: 获取子元素的内容,不包含头配置
  • paginator: 如果使用了分页插件,可以读取到分页信息

Site Variables

可以获取站点级别定义的变量。

  • site.time: The current time (when you run the jekyll command).
  • site.pages: 所有的页面集合,包含文章页面,首页等
  • site.posts: 所有的文章集合
  • site.data: _data目录下的数据集合,如:site.data.githubs 获取_data/githubs.yml中定义的数据
  • site.categories.CATEGORY: 获取指定分类的文章列表
  • site.tags.TAG: 获取指定标签的文章列表
  • site.[CONFIGURATION_DATA]: 可以获取在_config.yml中配置的自定义变量

Page Variables

可以获取页面级别定义的变量。

  • page.content: 获取页面的内容
  • page.title: 获取页面定义的title, 对于文章,如果没有定义,则取文件名中的title
  • page.excerpt: 页面的摘要
  • page.url: 页面的地址,不带域名的,如:/2008/12/14/my-post.html
  • page.date: 页面的时间,可以自定义
  • page.id: 页面的ID,也可以理解为页面的路径,如:/2008/12/14/my-post
  • page.categories: 页面的分类,是个数组,如:['java', 'tool']
  • page.tags: 页面的标签,是个数组,同上。
  • page.next: 下一篇文章,如果没有了,返回nil
  • page.previous: 上一篇文章。

Paginator

如果使用了分页插件,可以获取到分页信息。

  • paginator.per_page: 每页显示几条记录
  • paginator.posts: 当前页的文章集合
  • paginator.total_posts: 总共多少文章
  • paginator.total_pages: 总共多少页
  • paginator.page: 当前是第几页
  • paginator.previous_page: 上一页页码
  • paginator.previous_page_path: 上一页页面路径
  • paginator.next_page: 下一页页码
  • paginator.next_page_path: 下一页页面路径

分页信息只能在index.html中读取,比如你可以将分页信息放置到/blog/index.html.

本文先介绍这么多,避免单篇文章过长,休息片刻,稍后补充

博客原文:http://huangyanxiang.com/2017/09/20/welcome-to-jekyll.html

查看原文

赞 0 收藏 0 评论 0

Chinaxiang 赞了文章 · 2017-07-28

慕课网_《Java定时任务调度工具详解之Quartz篇》学习总结

时间:2017年06月26日星期一
说明:本文部分内容均来自慕课网。@慕课网:http://www.imooc.com
教学示例源码:https://github.com/zccodere/s...
个人学习源码:https://github.com/zccodere/s...

第一章:课程介绍

1-1 初识Quartz

Quartz概要

OpenSymphony提供的强大的开源任务调度框架
官网:http://www.quartz-scheduler.org
纯Java实现,精细控制排程

Quartz特点

强大的调度功能
灵活的应用方式
分布式和集群能力

Quartz的设计模式

Builder模式
Factory模式
组件模式
链式写法

Quartz三个核心概念

调度器
任务
触发器

Quartz体系结构

JobDetail
trigger
    SimpleTrigger
    CronTrigger
scheduler
    start
    stop
    pause
    resume

示意图

clipboard.png

Quartz重要组成

Job
JobDetail
JobBuilder
JobStore
Trigger
TriggerBuilder
ThreadPool
Scheduler
Calendar:一个Trigger可以和多个Calendar关联,以排除或包含某些时间点
监听器
    JobListener
    TriggerListener
    SchedulerListener

第二章:Quartz详解

2-1 第一个Quartz程序

准备工作

建立Maven项目工程
引入Quartz包

编写第一个Quartz任务

让任务每隔两秒打印一次hellworld

代码演示

1.编写任务类

package com.myimooc.helloquartz.one;

import java.text.SimpleDateFormat;
import java.util.Date;

import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;

/**
 * 编写 自定义任务
 * @author ZhangCheng on 2017-06-26
 *
 */
public class HelloJob implements Job{
    
    @Override
    public void execute(JobExecutionContext context) throws JobExecutionException {
        // 打印当前的执行时间,格式为2017-01-01 00:00:00
        Date date = new Date();
        SimpleDateFormat sf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        System.out.println("Current Exec Time Is : " + sf.format(date));
        
        // 编写具体的业务逻辑
        System.out.println("Hello World!");
    }
    
}

2.编写任务调度类

package com.myimooc.helloquartz.one;

import java.text.SimpleDateFormat;
import java.util.Date;

import org.quartz.JobBuilder;
import org.quartz.JobDetail;
import org.quartz.Scheduler;
import org.quartz.SchedulerException;
import org.quartz.SchedulerFactory;
import org.quartz.SimpleScheduleBuilder;
import org.quartz.Trigger;
import org.quartz.TriggerBuilder;
import org.quartz.impl.StdSchedulerFactory;

/**
 * 编写 任务调度类
 * @author ZhangCheng on 2017-06-26
 *
 */
public class HelloScheduler {
    
    public static void main(String[] args) throws SchedulerException {
        
        // 创建一个 JobDetail 实例,将该实例与 HelloJob 实例绑定
        JobDetail jobDeatil = JobBuilder.newJob(HelloJob.class)
                .withIdentity("myjob", "jobgroup1")// 定义标识符
                .build();
        
        System.out.println("jobDetail's name : " + jobDeatil.getKey().getName());
        System.out.println("jobDetail's group : " + jobDeatil.getKey().getGroup());
        System.out.println("jobDetail's jobClass : " + jobDeatil.getJobClass().getName());
        
        // 创建一个 Trigger 实例,定义该 job 立即执行,并且每隔两秒重复执行一次,直到永远
        Trigger trigger = TriggerBuilder.newTrigger()
                .withIdentity("myTrigger","trigroup1")// 定义标识符
                .startNow()// 定义立即执行
                .withSchedule(SimpleScheduleBuilder
                        .simpleSchedule()
                        .withIntervalInSeconds(2)
                        .repeatForever())// 定义执行频度
                .build();
        
        // 创建 Scheduler 实例
        SchedulerFactory sfact = new StdSchedulerFactory();
        Scheduler scheduler = sfact.getScheduler();
        
        // 绑定 JobDetail 和 trigger
        scheduler.scheduleJob(jobDeatil, trigger);
        
        // 执行任务
        scheduler.start();
        
        // 打印当前的执行时间,格式为2017-01-01 00:00:00
        Date date = new Date();
        SimpleDateFormat sf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        System.out.println("Current Time Is : " + sf.format(date));
    }
    
}

2-2 浅谈Job&JobDetail

Job定义

实现业务逻辑的任务接口

浅谈Job

Job接口非常容易实现,只有一个execute方法,类似TimerTask的run方法,在里面编写业务逻辑
Job接口源码
public interface Job {
    void execute(JobExecutionContext context) throws JobExecutionException;
}

Job实例在Quartz中的生命周期

每次调度器执行job时,它在调用execute方法前会创建一个新的job实例
当调用完成后,关联的job对象实例会被释放,释放的实例会被垃圾回收机制回收

浅谈JobDetail

JobDetail为Job实例提供了许多设置属性,以及JobDetailMap成员变量属性,它用来存储特定Job实例的状态信息,调度器需要借助JobDetail对象来添加Job实例。

JobDetail属性

name:任务名称
group:任务所属组
jobClass:任务实现类
jobDataMap:传参的作用

2-3 浅谈JobExecutionContext&JobDataMap(上)

JobExecutionContext是什么

当Scheduler调用一个Job,就会将JobExecutionContext传递给Job的execute()方法;
Job能通过JobExecutionContext对象访问到Quartz运行时候的环境以及Job本身的明细数据。

JobDataMap是什么

在进行任务调度时JobDataMap存储在JobExecutionContext中,非常方便获取
JobDataMap可以用来装载任务可序列化的数据对象,当job实例对象被执行时这些参数对象会传递给它
JobDataMap实现了JDK的Map接口,并且添加了一些非常方便的方法用来存取基本数据类型

2-4 浅谈JobExecutionContext&JobDataMap(下)

获取JobDataMap的两种方式

从Map中直接获取
Job实现类中添加setter方法对应JobDataMap的键值
(Quartz框架默认的JobFactory实现类在初始化job实例对象时会自动地调用这些setter方式)

代码演示

1.HelloScheduler类改造

package com.myimooc.helloquartz.two;

import java.text.SimpleDateFormat;
import java.util.Date;

import org.quartz.JobBuilder;
import org.quartz.JobDetail;
import org.quartz.Scheduler;
import org.quartz.SchedulerException;
import org.quartz.SchedulerFactory;
import org.quartz.SimpleScheduleBuilder;
import org.quartz.Trigger;
import org.quartz.TriggerBuilder;
import org.quartz.impl.StdSchedulerFactory;

/**
 * 编写 任务调度类
 * @author ZhangCheng on 2017-06-26
 *
 */
public class HelloScheduler {
    
    public static void main(String[] args) throws SchedulerException {
        
        // 创建一个 JobDetail 实例,将该实例与 HelloJob 实例绑定
        JobDetail jobDeatil = JobBuilder.newJob(HelloJob.class)
                .withIdentity("myjob", "jobgroup1")// 定义标识符
                .usingJobData("message", "hello myjob1")// 传入自定义参数
                .usingJobData("floatJobValue", 3.14F)// 传入自定义参数
                .build();
        
        System.out.println("jobDetail's name : " + jobDeatil.getKey().getName());
        System.out.println("jobDetail's group : " + jobDeatil.getKey().getGroup());
        System.out.println("jobDetail's jobClass : " + jobDeatil.getJobClass().getName());
        
        // 创建一个 Trigger 实例,定义该 job 立即执行,并且每隔两秒重复执行一次,直到永远
        Trigger trigger = TriggerBuilder.newTrigger()
                .withIdentity("myTrigger","trigroup1")// 定义标识符
                .usingJobData("message", "hello mytrigger1")// 传入自定义参数
                .usingJobData("doubleTriggerValue", 2.0D)// 传入自定义参数
                .startNow()// 定义立即执行
                .withSchedule(SimpleScheduleBuilder
                        .simpleSchedule()
                        .withIntervalInSeconds(2)
                        .repeatForever())// 定义执行频度
                .build();
        
        // 创建 Scheduler 实例
        SchedulerFactory sfact = new StdSchedulerFactory();
        Scheduler scheduler = sfact.getScheduler();
        
        // 绑定 JobDetail 和 trigger
        scheduler.scheduleJob(jobDeatil, trigger);
        
        // 执行任务
        scheduler.start();
        
        // 打印当前的执行时间,格式为2017-01-01 00:00:00
        Date date = new Date();
        SimpleDateFormat sf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        System.out.println("Current Time Is : " + sf.format(date));
    }
    
}

2.HelloJob类改造

package com.myimooc.helloquartz.two;

import java.text.SimpleDateFormat;
import java.util.Date;

import org.quartz.Job;
import org.quartz.JobDataMap;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.quartz.JobKey;
import org.quartz.TriggerKey;

/**
 * 编写 自定义任务
 * @author ZhangCheng on 2017-06-26
 *
 */
public class HelloJob implements Job{
    
    // 方式二:getter和setter获取
    // 成员变量 与 传入参数的key一致
    private String message;
    private Float floatJobValue;
    private Double doubleTriggerValue;
    
    public String getMessage() {
        return message;
    }
    public void setMessage(String message) {
        this.message = message;
    }
    public Float getFloatJobValue() {
        return floatJobValue;
    }
    public void setFloatJobValue(Float floatJobValue) {
        this.floatJobValue = floatJobValue;
    }
    public Double getDoubleTriggerValue() {
        return doubleTriggerValue;
    }
    public void setDoubleTriggerValue(Double doubleTriggerValue) {
        this.doubleTriggerValue = doubleTriggerValue;
    }
    @Override
    public void execute(JobExecutionContext context) throws JobExecutionException {
        // 打印当前的执行时间,格式为2017-01-01 00:00:00
        Date date = new Date();
        SimpleDateFormat sf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        System.out.println("Current Exec Time Is : " + sf.format(date));
        
        // 编写具体的业务逻辑
        //System.out.println("Hello World!");
        
        JobKey key = context.getJobDetail().getKey();
        System.out.println("My name and group are : " + key.getName() + " : " + key.getGroup());
        
        TriggerKey trkey = context.getTrigger().getKey();
        System.out.println("My Trigger name and group are : " + trkey.getName() + " : " + trkey.getGroup());
        
        // 方式一:Map中直接  获取自定义参数
        JobDataMap jdataMap = context.getJobDetail().getJobDataMap();
        JobDataMap tdataMap = context.getTrigger().getJobDataMap();
        String jobMsg = jdataMap.getString("message");
        Float jobFloatValue = jdataMap.getFloat("floatJobValue");
        
        String triMsg = tdataMap.getString("message");
        Double triDoubleValue = tdataMap.getDouble("doubleTriggerValue");
        
        System.out.println("jobMsg is : " + jobMsg);
        System.out.println("jobFloatValue is : " + jobFloatValue);
        System.out.println("triMsg is : " + triMsg);
        System.out.println("triDoubleValue is : " + triDoubleValue);
        
        // 方式一:Map中直接获取 获取自定义参数
        JobDataMap jobDataMap = context.getMergedJobDataMap();
        jobMsg = jobDataMap.getString("message");
        jobFloatValue = jobDataMap.getFloat("floatJobValue");
        
        triMsg = jobDataMap.getString("message");
        triDoubleValue = jobDataMap.getDouble("doubleTriggerValue");
        
        System.out.println("jobMsg is : " + jobMsg);
        System.out.println("jobFloatValue is : " + jobFloatValue);
        System.out.println("triMsg is : " + triMsg);
        System.out.println("triDoubleValue is : " + triDoubleValue);
        
        // 方式二:getter和setter获取
        System.out.println("message is : " + this.message);
        System.out.println("jobFloatValue is : " + this.floatJobValue);
        System.out.println("triDoubleValue is : " + this.doubleTriggerValue);
    }
    
}

2-5 浅谈Trigger

Trigger是什么

Quartz中的触发器用来告诉调度程序作业什么时候触发
即Trigger对象时用来触发执行Job的

Quartz框架中的Trigger示意图

clipboard.png

触发器通用属性

JobKey:表示job实例的标识,触发器被触发时,该指定的job实例会执行
StartTime:表示触发器的时间表首次被触发的时间,它的值的类型是Java.util.Date
EndTime:指定触发器的不再被触发的时间,它的值的类型是Java.util.Date

代码演示

1.HelloJob类改造

package com.myimooc.helloquartz.three;

import java.text.SimpleDateFormat;
import java.util.Date;

import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.quartz.JobKey;
import org.quartz.Trigger;

/**
 * 自定义任务  触发器通用属性
 * @author ZhangCheng on 2017-06-26
 *
 */
public class HelloJob implements Job{
    
    @Override
    public void execute(JobExecutionContext context) throws JobExecutionException {
        // 打印当前的执行时间,格式为2017-01-01 00:00:00
        Date date = new Date();
        SimpleDateFormat sf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        System.out.println("Current Exec Time Is : " + sf.format(date));
        
        Trigger currentTrigger = context.getTrigger();
        System.out.println("Start Time Is : " + sf.format(currentTrigger.getStartTime()));
        System.out.println("End Time Is : " + sf.format(currentTrigger.getEndTime()));
        
        JobKey jobKey = currentTrigger.getJobKey();
        System.out.println("JobKey info : " + " jobName : " + jobKey.getName()
                + " jobGroup : " + jobKey.getGroup());
    }
    
}

2.HelloScheduler类改造

package com.myimooc.helloquartz.three;

import java.text.SimpleDateFormat;
import java.util.Date;

import org.quartz.JobBuilder;
import org.quartz.JobDetail;
import org.quartz.Scheduler;
import org.quartz.SchedulerException;
import org.quartz.SchedulerFactory;
import org.quartz.SimpleScheduleBuilder;
import org.quartz.Trigger;
import org.quartz.TriggerBuilder;
import org.quartz.impl.StdSchedulerFactory;

/**
 * 任务调度类  触发器通用属性
 * @author ZhangCheng on 2017-06-26
 *
 */
public class HelloScheduler {
    
    public static void main(String[] args) throws SchedulerException {
        // 打印当前的执行时间,格式为2017-01-01 00:00:00
        Date date = new Date();
        SimpleDateFormat sf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        System.out.println("Current Time Is : " + sf.format(date));
        
        // 创建一个 JobDetail 实例,将该实例与 HelloJob 实例绑定
        JobDetail jobDeatil = JobBuilder.newJob(HelloJob.class)
                .withIdentity("myjob", "jobgroup1")// 定义标识符
                .build();
        
        // 获取距离当前时间3秒后的时间
        date.setTime(date.getTime() + 3000);
        // 获取距离当前时间6秒后的时间
        Date endDate = new Date();
        endDate.setTime(endDate.getTime() + 6000);
        
        // 创建一个 Trigger 实例,定义该 job 立即执行,并且每隔两秒重复执行一次,直到永远
        Trigger trigger = TriggerBuilder.newTrigger()
                .withIdentity("myTrigger","trigroup1")// 定义标识符
                .startAt(date)// 定义3秒后执行
                .endAt(endDate)// 定义6秒后结束
                .withSchedule(SimpleScheduleBuilder
                        .simpleSchedule()
                        .withIntervalInSeconds(2)
                        .repeatForever())// 定义执行频度
                .build();
        
        // 创建 Scheduler 实例
        SchedulerFactory sfact = new StdSchedulerFactory();
        Scheduler scheduler = sfact.getScheduler();
        
        // 绑定 JobDetail 和 trigger
        scheduler.scheduleJob(jobDeatil, trigger);
        
        // 执行任务
        scheduler.start();
    }
    
}

2-6 SimpleTrigger

SimpleTrigger的作用

在一个指定时间段内执行一次作业任务
或是在指定的时间间隔内多次执行作业任务

需要注意的点

重复次数可以为0,正整数或是SimpleTrigger.REPEAT_INDEFINITELY常量值
重复执行间隔必须为0或长整数
一旦被指定了endTime参数,那么它会覆盖重复次数参数的效果

代码演示

1.HelloJob类改造

package com.myimooc.helloquartz.four;

import java.text.SimpleDateFormat;
import java.util.Date;

import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;

/**
 * SimpleTrigger 演示
 * @author ZhangCheng on 2017-06-26
 *
 */
public class HelloJob implements Job{
    
    @Override
    public void execute(JobExecutionContext context) throws JobExecutionException {
        // 打印当前的执行时间,格式为2017-01-01 00:00:00
        Date date = new Date();
        SimpleDateFormat sf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        System.out.println("Current Exec Time Is : " + sf.format(date));
        
        System.out.println("Hello World!");
    }
    
}

2.HelloScheduler类改造

package com.myimooc.helloquartz.four;

import java.text.SimpleDateFormat;
import java.util.Date;

import org.quartz.JobBuilder;
import org.quartz.JobDetail;
import org.quartz.Scheduler;
import org.quartz.SchedulerException;
import org.quartz.SchedulerFactory;
import org.quartz.SimpleScheduleBuilder;
import org.quartz.SimpleTrigger;
import org.quartz.TriggerBuilder;
import org.quartz.impl.StdSchedulerFactory;

/**
 * SimpleTrigger 演示
 * @author ZhangCheng on 2017-06-26
 *
 */
public class HelloScheduler {
    
    public static void main(String[] args) throws SchedulerException {
        // 打印当前的执行时间,格式为2017-01-01 00:00:00
        Date date = new Date();
        SimpleDateFormat sf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        System.out.println("Current Time Is : " + sf.format(date));
        
        // 创建一个 JobDetail 实例,将该实例与 HelloJob 实例绑定
        JobDetail jobDeatil = JobBuilder.newJob(HelloJob.class)
                .withIdentity("myjob", "jobgroup1")// 定义标识符
                .build();
        
        // 获取距离当前时间4秒钟之后的具体时间
        date.setTime(date.getTime() + 4000);
        // 获取距离当前时间6秒钟之后的具体时间
        Date endDate = new Date();
        endDate.setTime(endDate.getTime() + 6000);
        
        // 距离当前时间4秒钟后首次执行任务,之后每隔2秒钟重复执行一次任务
        // 知道距离当前时间6秒钟之后为止
        SimpleTrigger trigger = (SimpleTrigger) TriggerBuilder
                .newTrigger()
                .withIdentity("myTrigger","trigroup1")// 定义标识符
                .startAt(date)
                .endAt(endDate)
                .withSchedule(SimpleScheduleBuilder
                        .simpleSchedule()
                        .withIntervalInSeconds(2)
                        .withRepeatCount(SimpleTrigger.REPEAT_INDEFINITELY))
                .build();
         
        // 创建 Scheduler 实例
        SchedulerFactory sfact = new StdSchedulerFactory();
        Scheduler scheduler = sfact.getScheduler();
        scheduler.scheduleJob(jobDeatil, trigger);
        scheduler.start();
    }
    
}

2-7 CronTrigger

CronTrigger的作用

基于日历的作业调度器,而不是像SimpleTrigger那样精确指定间隔时间,比SimpleTrigger更常用

Cron表达式

用于配置CronTrigger实例
是由7个子表达式组成的字符串,描述了时间表的详细信息
格式:[秒][分][小时][日][月][周][年]

Cron表达式特殊字符意义对应表

clipboard.png

特殊符号解释

clipboard.png

Cron表达式举例

clipboard.png

Cron表达式小提示

L和W可以一组合使用
周字段英文字母不区分大小写即MON与mon相同
利用工具,在线生成

2-8 浅谈Scheduler

Scheduler工厂模式

所有的Scheduler实例应该由SchedulerFactory来创建

SchedulerFactory类图

clipboard.png

回顾Quartz三个核心概念

调度器
任务
触发器

示意图

clipboard.png

Scheduler的创建方式

clipboard.png

StdSchedulerFactory

使用一组参数(Java.util.Properties)来创建和初始化Quartz调度器
配置参数一般存储在quartz.properties中
调用getScheduler方法就能创建和初始化调度器对象

Scheduler的主要函数

// 绑定 jobDetail 和 trigger,将它注册进 Scheduler 当中
Date scheduleJob(JobDetail jobDetail, Trigger trigger)
// 启动 Scheduler
void start()
// 暂停 Scheduler
void standby()
// 关闭 Scheduler
void shutdown()

2-9 QuartzProperties文件

quartz.properties组成部分

调度器属性
线程池属性
作业存储设置
插件配置

调度器属性

clipboard.png

线程池属性

threadCount:工作线程数量
threadPriority:工作线程优先级
org.quartz.threadPool.class:配置线程池实现类

作业存储设置

描述了在调度器实例的生命周期中,Job和Trigger信息是如何被存储的

插件配置

满足特定需求用到的Quartz插件的配置

第三章:Spring、Quartz大合体

3-1 搭建工程

基于Maven创建一个简单的SpringMVC工程,学习时,使用springboot框架。

项目POM文件

<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.myimooc</groupId>
  <artifactId>springquartz</artifactId>
  <version>0.0.1-SNAPSHOT</version>
  <packaging>jar</packaging>

  <name>springquartz</name>
  <url>http://maven.apache.org</url>

  <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>1.5.1.RELEASE</version>
        <relativePath /> <!-- lookup parent from repository -->
    </parent>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>
    
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context-support</artifactId>
        </dependency>
        
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-tx</artifactId>
        </dependency>
        
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-freemarker</artifactId>
        </dependency>
        
        <!-- https://mvnrepository.com/artifact/org.quartz-scheduler/quartz -->
        <dependency>
            <groupId>org.quartz-scheduler</groupId>
            <artifactId>quartz</artifactId>
            <version>2.3.0</version>
        </dependency>
        
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>
  
  <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <configuration>
                    <source>1.8</source>
                    <target>1.8</target>
                </configuration>
            </plugin>
        </plugins>
    </build>
    
</project>

项目目录结构

clipboard.png

3-2 Quartz和Spring大合体

使用Quartz配置作业两种方式

MethodInvokingJobDetailFactoryBean
JobDetailFactoryBean

方式一:使用MethodInvokingJobDetailFactoryBean

1.调用myBean的printMessage方法

clipboard.png

2.MyBean类

clipboard.png

方式二:使用JobDetailFactoryBean

1.需要给作业传递数据,想要更加灵活的话就是用这种方式

clipboard.png

2.FirstScheduleJob类

clipboard.png

3.AnotherBean类

clipboard.png

代码演示:

演示说明:教学基于xml进行配置,学习时基于javaconfig进行配置。

1.编写MyBean类

package com.myimooc.springquartz.quartz;

import java.text.SimpleDateFormat;
import java.util.Date;

import org.springframework.stereotype.Component;

/**
 * 方式一:使用MethodInvokingJobDetailFactoryBean 演示
 * @author ZhangCheng on 2017-06-28
 *
 */
@Component
public class MyBean {
    
    public void printMessage(){
        
        // 打印当前的执行时间,格式为2017-01-01 00:00:00
        Date date = new Date();
        SimpleDateFormat sf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        
        System.out.println("MyBean Executes!" + sf.format(date));
    }
    
}

2.编写AnotherBean类

package com.myimooc.springquartz.quartz;

import java.text.SimpleDateFormat;
import java.util.Date;

import org.springframework.stereotype.Component;

/**
 * 方式二:使用JobDetailFactoryBean 演示
 * @author ZhangCheng on 2017-06-28
 *
 */
@Component
public class AnotherBean {
    
    public void printAnotherMessage(){
        Date date = new Date();
        SimpleDateFormat sf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        
        System.out.println("I am AnotherBean." + sf.format(date));
    }
    
}

3.编写FirstScheduledJob类

package com.myimooc.springquartz.quartz;

import java.text.SimpleDateFormat;
import java.util.Date;

import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.springframework.scheduling.quartz.QuartzJobBean;

/**
 * 方式二:使用JobDetailFactoryBean 演示
 * @author ZhangCheng on 2017-06-28
 *
 */
public class FirstScheduledJob extends QuartzJobBean {
    
    private AnotherBean anotherBean;
    
    public void setAnotherBean(AnotherBean anotherBean) {
        this.anotherBean = anotherBean;
    }

    @Override
    protected void executeInternal(JobExecutionContext arg0) throws JobExecutionException {
        Date date = new Date();
        SimpleDateFormat sf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        
        System.out.println("FirstScheduledJob Excutes!" + sf.format(date));
        this.anotherBean.printAnotherMessage();
    }

}

4.编写QuartzConfig类

package com.myimooc.springquartz.config;

import org.quartz.JobDataMap;
import org.quartz.JobDetail;
import org.quartz.Trigger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.quartz.CronTriggerFactoryBean;
import org.springframework.scheduling.quartz.JobDetailFactoryBean;
import org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean;
import org.springframework.scheduling.quartz.SchedulerFactoryBean;
import org.springframework.scheduling.quartz.SimpleTriggerFactoryBean;

import com.myimooc.springquartz.quartz.AnotherBean;
import com.myimooc.springquartz.quartz.FirstScheduledJob;
import com.myimooc.springquartz.quartz.MyBean;

/**
 * Quartz 配置类
 * @author ZhangCheng on 2017-06-28
 *
 */
@Configuration
public class QuartzConfig {
    
    @Autowired
    private MyBean myBean;
    
    @Autowired
    private AnotherBean anotherBean;
    
    /**
     * 方式一:使用MethodInvokingJobDetailFactoryBean
     * @return
     */
    @Bean
    public MethodInvokingJobDetailFactoryBean methodInvokingJobDetailFactoryBean(){
        MethodInvokingJobDetailFactoryBean mb = new MethodInvokingJobDetailFactoryBean();
        mb.setTargetObject(myBean);// 指定要运行的类
        mb.setTargetMethod("printMessage");// 指定要允许类中的那个方法
        return mb;
    }
    
    /**
     * 方式二:使用JobDetailFactoryBean
     * @return
     */
    @Bean
    public JobDetailFactoryBean jobDetailFactoryBean(){
        JobDetailFactoryBean  jb= new JobDetailFactoryBean();
        jb.setJobClass(FirstScheduledJob.class);// 指定要运行的类
        JobDataMap jobDataMap = new JobDataMap();
        jobDataMap.put("anotherBean", anotherBean);
        
        jb.setJobDataMap(jobDataMap);// 设置传入自定义参数
        jb.setDurability(true);
        return jb;
    }
    
    /**
     * 配置 SimpleTriggerFactoryBean
     * @return
     */
    @Bean
    public SimpleTriggerFactoryBean simpleTriggerFactoryBean(){
        SimpleTriggerFactoryBean sb = new SimpleTriggerFactoryBean();
        sb.setJobDetail(methodInvokingJobDetailFactoryBean().getObject());// 设置需要绑定的 jobDetail
        sb.setStartDelay(1000L);// 距离当前时间1秒后执行
        sb.setRepeatInterval(2000L);// 之后每隔2秒执行一次
        return sb;
    }
    
    /**
     * 配置 SimpleTriggerFactoryBean
     * @return
     */
    @Bean
    public CronTriggerFactoryBean cronTriggerFactoryBean(){
        CronTriggerFactoryBean cb = new CronTriggerFactoryBean();
        cb.setJobDetail(jobDetailFactoryBean().getObject());// 设置需要绑定的 jobDetail
        cb.setStartDelay(1000L);// 距离当前时间1秒后执行
        cb.setCronExpression("0/5 * * ? * *");// 设置 Cron 表达式,之后每隔5秒执行一次
        return cb;
    }
    
    /**
     * 配置 SchedulerFactoryBean
     * @return
     */
    @Bean
    public SchedulerFactoryBean schedulerFactoryBean(){
        SchedulerFactoryBean sb= new SchedulerFactoryBean();
        // 配置 JobDetails
        JobDetail[] jobDetails = new JobDetail[2];
        jobDetails[0] = methodInvokingJobDetailFactoryBean().getObject();
        jobDetails[1] = jobDetailFactoryBean().getObject();
        sb.setJobDetails(jobDetails);
        
        // 配置 Trigger
        Trigger[] triggers = new Trigger[2];
        triggers[0] = simpleTriggerFactoryBean().getObject();
        triggers[1] = cronTriggerFactoryBean().getObject();
        sb.setTriggers(triggers);
        return sb;
        
    }
}

第四章:课程总结

4-1 课程总结

课程回顾

Timer
Quartz
Quartz&Spring融合
查看原文

赞 17 收藏 17 评论 2

Chinaxiang 发布了文章 · 2017-07-28

分布式云调度处理系统

分布式云调度处理系统。

项目地址:
http://git.oschina.net/chinax...

项目参考xxl-job进行若干改动。

项目基于quartz并进行若干扩展而成,适用于公司内部做定时调度处理,方便,快捷,简单。

支持bean, groovy, shell, python四种任务处理方式。

项目架构图

  • core: 云调度核心,封装了各种工具,基础组件。
  • job: quartz可视化控制台及quartz调度器,去调度具体的executor, 内置了一个最简单的executor.
  • executor: 可选处理器,可扩展,可自定义,可分布式部署。

技术选型

业务架构图

job可视化控制台架构图

控制台首页

任务管理

脚本编辑器

日志管理

日志控制台

集群管理

项目配置

项目采用分模块开发,其中的微服务模块:facade, facade-impl, integration为测试模块,没有使用,可以自行去掉。

项目下载下来,只需要更改日志路径及数据库相关配置即可正常使用。

日志配置在:conf/config/logback.xml

数据库配置在:conf/config/application.properties

执行器模块比较简单,主要用来开发一些自定义的bean处理类。默认job模块中已经内置了一个基础处理器。

数据库表定义在:dal 模块中的 qs_scheduler.sql 中,创建所需要的表即可。其中11张表是quartz的表,另外5张表是业务扩展表。

项目站点

项目集成了一些maven的插件,如findbugs, taglist, 你可以有选择的生成项目站点,查看项目报告。

mvn clean site

即可在target目录下得到 site 信息。

各个子模块的target目录下都有 site 信息,可以查看项目报告,如findbugs。

项目启动

数据库和配置修改完毕后,直接在项目目录执行:

mvn clean package -DskipTests

将项目 job/target/job.war 拷贝到 tomcat 的webapps下,启动tomcat即可。

查看原文

赞 2 收藏 4 评论 0

Chinaxiang 发布了文章 · 2017-07-27

微服务应用架构脚手架

微服务应用架构脚手架

开源项目地址:

http://git.oschina.net/chinax...

作为一名码农,经常要为了工作或业余爱好自己搭建项目(大公司除外),配环境,找Jar包,很是烦人。

Maven支持自定义脚手架,所以自己整了一套多模块的,面向微服务应用的Maven Archetype供大家参考使用。

可快速创建用于开发环境的基础应用架构。

模板生成应用架构组成

生成代码结构

模块是可以自己配置的。

  • dal: 数据访问层,脚手架中采用的是mybatis, druid.
  • biz: 逻辑处理层,依赖dal,处理业务逻辑,事物等。
  • facade: 微服务接口层,对外提供的接口定义,参数定义,数据传输格式定义等。
  • facade-impl: 微服务接口实现层,具体的微服务逻辑,包括服务的注册发布等,依赖biz层。
  • integration: 第三方服务层,如果需要调用别的系统发布的服务,可以在这一层订阅具体的服务供内部使用。
  • web: 视图接口层,处理http请求,响应数据或页面,可以依赖biz和integration.
  • assembly: 打包处理,将各模块组装成war包。
  • conf: 配置文件,配合assembly实现不同环境使用不同配置信息的功能。
  • webdocs: 静态资源文件,模板引擎使用的是freemarker, 当然,你可以自行替换。

安装脚手架

关于 maven 脚手架相关的知识请自行搜索或查阅相关书籍文档。

将本项目克隆或下载到本地目录,执行

mvn clean install

将脚手架发布到本地maven仓库中。

如果需要将其发布到私服中,修改脚手架的pom文件中的 distributionManagement 元素内容为自己的私服地址即可。(前提是你得有私服的发布账号及密码)

执行

mvn clean deploy

将脚手架发布到私服。

查看仓库中会多一个:archetype-catalog.xml 文件。

内容大致如下:

...
<archetype>
  <groupId>com.quanshi</groupId>
  <artifactId>sof-archetype</artifactId>
  <version>1.0.0</version>
  <description>sof-archetype</description>
</archetype>
...

使用脚手架

当脚手架发布成功后,就可以使用了。

使用交互的方式:

mvn archetype:generate

出现脚手架列表:

Choose archetype:
...
10: internal -> org.apache.maven.archetypes:maven-archetype-webapp (An archetype which contains a sample Maven Webapp project.)
11: local -> com.quanshi:sof-archetype (sof-archetype)
Choose a number or apply filter (format: [groupId:]artifactId, case sensitive contains): 7: 

选择 11: local -> com.quanshi:sof-archetype (sof-archetype)

按照提示,输入下面几项内容:

  • groupId: 你要创建的项目的groupId
  • artifactId: 你要创建的项目的artifactId
  • version: 项目版本号
  • package: 项目基础包路径
Choose a number or apply filter (format: [groupId:]artifactId, case sensitive contains): 7: 11
Define value for property 'groupId': com.coder4j
Define value for property 'artifactId': demo
Define value for property 'version' 1.0-SNAPSHOT: : 1.0.0
Define value for property 'package' com.coder4j: : com.coder4j.demo
Confirm properties configuration:
groupId: com.coder4j
artifactId: demo
version: 1.0.0
package: com.coder4j.demo
 Y: : y
[INFO] ----------------------------------------------------------------------------
[INFO] Using following parameters for creating project from Archetype: sof-archetype:1.0.0
[INFO] ----------------------------------------------------------------------------
[INFO] Parameter: groupId, Value: com.coder4j
[INFO] Parameter: artifactId, Value: demo
[INFO] Parameter: version, Value: 1.0.0
[INFO] Parameter: package, Value: com.coder4j.demo
[INFO] Parameter: packageInPathFormat, Value: com/coder4j/demo
[INFO] Parameter: package, Value: com.coder4j.demo
[INFO] Parameter: version, Value: 1.0.0
[INFO] Parameter: groupId, Value: com.coder4j
[INFO] Parameter: artifactId, Value: demo
[INFO] Parent element not overwritten in C:\Users\yanxiang.huang\workspace\test\demo\app\dal\pom.xml
[INFO] Parent element not overwritten in C:\Users\yanxiang.huang\workspace\test\demo\app\biz\pom.xml
[INFO] Parent element not overwritten in C:\Users\yanxiang.huang\workspace\test\demo\app\facade\pom.xml
[INFO] Parent element not overwritten in C:\Users\yanxiang.huang\workspace\test\demo\app\facade-impl\pom.xml
[INFO] Parent element not overwritten in C:\Users\yanxiang.huang\workspace\test\demo\app\integration\pom.xml
[INFO] Parent element not overwritten in C:\Users\yanxiang.huang\workspace\test\demo\app\web\pom.xml
[INFO] Parent element not overwritten in C:\Users\yanxiang.huang\workspace\test\demo\assembly\pom.xml
[INFO] Parent element not overwritten in C:\Users\yanxiang.huang\workspace\test\demo\webdocs\pom.xml
[INFO] Project created from Archetype in dir: C:\Users\yanxiang.huang\workspace\test\demo
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 06:50 min
[INFO] Finished at: 2017-07-27T16:16:00+08:00
[INFO] Final Memory: 13M/243M
[INFO] ------------------------------------------------------------------------

最终得到项目。

或者可以使用一条命令快速生成:

mvn archetype:generate \
-DarchetypeGroupId=com.quanshi \
-DarchetypeArtifactId=sof-archetype \
-DarchetypeVersion=1.0.0 \
-DgroupId=com.coder4j \
-DartifactId=bee \
-Dversion=1.0.0 \
-Dpackage=com.coder4j.bee

如果是别人发布到私服,你通过私服来创建的话。

将私服地址配置到setting.xml中。

配置mirror或者repository均可。

项目配置

通过脚手架得到的项目需要简单的配置部分内容。

日志路径

项目日志路径的配置文件在:conf/config/logback.xml 中,自行更改一个可以使用的日志路径。

数据库

初始项目的dal层是有初始代码的,需要用到一张表:t_demo

表结构在 dal/src/main/resources/test.sql 中。

数据库的配置文件在 conf/config/application.properties

我这里没有配置到 conf/filter 下,如果你们需要不同环境,请将配置放置到filter目录的不同文件内,assembly打包模块会将filter中的文件填充到config中。

项目启动

配置更改完毕后,下面开始编译启动。

mvn clean package

启动完成,将 target/${artifactId}.war 文件拷贝到 tomcat 的webapps下,启动tomcat即可。

项目默认使用的jdk 1.8编译。

访问:

http://localhost:8080/${artifactId}/
查看原文

赞 2 收藏 4 评论 0

Chinaxiang 赞了回答 · 2017-07-27

解决dubbo应用自产自消报错?

从错误来看应该是没有找到这个服务的提供者, 有可能是消费者先启动了, 服务者还没注册上, 题主可以关闭启动检查。

关闭启动时检查特性请参考:http://dubbo.io/Check+On+Star...

如果问题未解决, 请评论回复。

关注 4 回答 2

Chinaxiang 赞了回答 · 2017-07-27

解决dubbo应用自产自消报错?

从错误来看应该是没有找到这个服务的提供者, 有可能是消费者先启动了, 服务者还没注册上, 题主可以关闭启动检查。

关闭启动时检查特性请参考:http://dubbo.io/Check+On+Star...

如果问题未解决, 请评论回复。

关注 4 回答 2

认证与成就

  • 获得 81 次点赞
  • 获得 14 枚徽章 获得 1 枚金徽章, 获得 2 枚银徽章, 获得 11 枚铜徽章

擅长技能
编辑

(゚∀゚ )
暂时没有

开源项目 & 著作
编辑

(゚∀゚ )
暂时没有

注册于 2013-10-15
个人主页被 1.5k 人浏览