EclipseJdt

EclipseJdt {
    //定义一个clos的数组
    def clos = []
    //定义fp这个文件参数(.factorypath文件)
    def fp = file('.factorypath')
    //把outputs的file定义为fp所指的文件
    //这样如果.factorypath文件存在的时候,EclipseJdt就不会被重复执行
    outputs.file fp
    clos += {
        //clos这个闭包的参数为fp.withWriter
        fp.withWriter {
            //定义domaJar这个参数,寻找配置中以doma-2开头
            def domaJar = configurations.compile.find {
                it.name.startsWith('doma-2')
            }
            //增加了一行EXTJAR
            new groovy.xml.MarkupBuilder(it).factorypath() {
                factorypathentry(kind:'EXTJAR', id:domaJar, enabled:true, runInBatchMode:false)
            }
        }
    }

    //定义一个prefs的方法
    def prefs = { name, contents ->
        //定义了一个文件的参数f
        def f = file(".settings/$name")
        //把contents的内容多行分解成string,赋值给f.text
        clos += {
            f.text = contents.stripMargin()
        }
        //输出内容到f这个参数定义的文件中去
        outputs.file f
    }
    //调用prefs这个方法,org.eclipse.jdt.apt.core.prefs这个文件里面,写下面四行内容。
    //第一行的“\”是什么意思→表示这行没有换行
    prefs 'org.eclipse.jdt.apt.core.prefs', """\
        |eclipse.preferences.version=1
        |org.eclipse.jdt.apt.aptEnabled=true
        |org.eclipse.jdt.apt.genSrcDir=${aptDir}
        |org.eclipse.jdt.apt.reconcileEnabled=true
        |"""

    doLast { clos*.run() }
}
增量式构建

为一个Task定义输入(inputs)和输出(outputs),在执行该Task时,如果它的输入和输出与前一次执行时没有变化,
那么Gradle便会认为该Task是最新的(日志会输出“UP-TO-DATE“),因此不会重复执行

task combineFileContent {
   def sources = fileTree('sourceDir')
   def destination = file('destination.txt')

   inputs.dir sources        // 将sources声明为该Task的inputs
   outputs.file destination  // 将destination声明为outputs

   doLast {
      destination.withPrintWriter { writer ->
         sources.each {source ->
            writer.println source.text
         }
      }
   }
}

当首次执行combineFileContent时,Gradle会完整地执行该Task,但是紧接着再执行一次,命令行显示:
如果修改inputs(上述即sourceDir文件夹)中的任何一个文件或删除destination.txt,再次调用“gradle combineFileContent”时,该Task又会重新执行

在这个例子中inputs是目录,outputs是文件。
当然也可以inputs是文件,outputs是目录。

理解doLast

doLast意思是定义一个行为(映射Gradle中的Action类),放在当前task的最后,类似的,还有doFirst, 表示将定义的行为放在当前task最前面,例如

task hello {
  doLast{
      println "Hello world"
    }   
  doFirst{
      println "I am xxx"
    }   
}

执行gradle hello, 将输出 "I am xxx" "Hello world"

Gradle refresh时产生的classpath错误

现象

选中项目⇒Gradle⇒refresh之后, ディフォルト出力フォルダ从“APIBot/bin/main”变成了“APIBot/bin/default”

Git上有一些内容可以参考

1. #3825(Closed)

Gradle从4.4版本开始,为每一个源文件目录使用一个单独的出力目录,
缺省情况下,这些出力目录都在bin目录下。
例子:

eclipse {
    classpath.defaultOutputDir = file('build-eclipse')
}

虽然通过上述命令指定了缺省目录,但生成的classpath如下,编译的目标目录还是在bin下面

<classpathentry path="build-eclipse" kind="output"/>

<classpathentry output="bin/main" kind="src" path="src/main/java">
    <attributes>
        <attribute name="gradle_scope" value="main"/>
        <attribute name="gradle_used_by_scope" value="main,test,integrationTest"/>
    </attributes>
</classpathentry>
<classpathentry output="bin/main" kind="src" path="src/main/resources">
    <attributes>
        <attribute name="gradle_scope" value="main"/>
        <attribute name="gradle_used_by_scope" value="main,test,integrationTest"/>
    </attributes>
</classpathentry>
<classpathentry output="bin/test" kind="src" path="src/test/java">
    <attributes>
        <attribute name="gradle_scope" value="test"/>
        <attribute name="gradle_used_by_scope" value="test"/>
    </attributes>
</classpathentry>
<classpathentry output="bin/test" kind="src" path="src/test/resources">
    <attributes>
        <attribute name="gradle_scope" value="test"/>
        <attribute name="gradle_used_by_scope" value="test"/>
    </attributes>
</classpathentry>

defaultOutputDir正如其字面意思一样,是为那些没有指定目标目录的源文件准备的。
Gradle目前给每个目标目录指定了目标目录,因此defaultOutputDir应该没有什么机会被用到

解决方案

classpath {
    defaultOutputDir = file('build/classes/java/main')
    file.whenMerged {
        entries.each {
            source ->
                // This seems kludgy.  If the second test is omitted, it fails processing a 'Project Dependency' entry
                if (source.kind == 'src' && source.toString().contains('output')) {
                    source.output = 'build/classes/java/main'
                }
        }
    }
}

其实想给所有源文件指定目标目录,更像是应该用outputBaseDir
outputBaseDir这个功能在Gradle的#3829票里面准备提供。

2. #4563(Closed)

内容和3825一样。因此被关闭

3. #3839(Open)

oehme提供了一个临时解决方案

classpath {
   file.whenMerged {
       entries.each { entry ->
            if (entry instanceof org.gradle.plugins.ide.eclipse.model.Output) {
               entry.path = entry.path.replace('bin', 'build/eclipse')
            }
       }
   }
}

今后可能会提供一个永久解决方案

classpath {
   outputBaseDir = 'build/eclipse'
}
apply plugin: 'java'
apply plugin: 'eclipse'

eclipse {
  classpath {
    file {
      //if you want to mess with the resulting XML in whatever way you fancy
      withXml {
        def node = it.asNode()
        node.appendNode('xml', 'is what I love')
      }

      //closure executed after .classpath content is loaded from existing file
      //从既存文件中载入.classpath的内容之后,这个闭包内容会被执行
      //but before gradle build information is merged
      //但是gradle build information被merge之前。
      //
      beforeMerged { classpath ->
        //you can tinker with the Classpath here
      }

      //closure executed after .classpath content is loaded from existing file
      //从既存文件中载入.classpath的内容之后,这个闭包内容会被执行(跟beforeMerged相同)
      //and after gradle build information is merged
      //但是gradle build information被merge之后。
      whenMerged { classpath ->
        //you can tinker with the Classpath here
      }
    }
  }
}

关键还是gradle build information的merge时间点,以及什么information。
从跑出来的一些例子看,这个information应该是指所有依赖关系

Gradle官方文档中关于defaultOutputDir的描述
缺省就是bin/default
所以一定要指定。

Groovy 中关于 eclipse plugin的一些理解

EclipseJdt

官方API文档

Eclipse plugin的详细内容进行优化调节

  1. 什么是Eclipse plugin
  2. Eclipse plugin的detail包括哪些内容
  3. 如何优化调节

首先看官方文档中例子

apply plugin: 'java'      //要用eclipse插件,当然需要申明引入java插件
apply plugin: 'eclipse'   //申明引入eclipse插件

//groovy语言的特点,都是用{}括起来的closure,针对eclipse这个closure进行配置
//引入了eclipse插件,自然就有了eclipse这个closure
eclipse {
  //eclipse里的jdt,下面有3个属性可以设置
  //1.file;2.sourceCompatibility;3.targetCompatibility
  jdt {
    //if you want to alter the java versions (by default they are configured with gradle java plugin settings):
    sourceCompatibility = 1.6
    targetCompatibility = 1.5

    file {
      //whenMerged closure is the highest voodoo
      //and probably should be used only to solve tricky edge cases.
      //the type passed to the closure is Jdt

      //closure executed after jdt file content is loaded from existing file
      //and after gradle build information is merged
      whenMerged { jdt
        //you can tinker with the Jdt here
      }
      
      //withProperties allows addition of properties not currently
      //modeled by Gradle
      withProperties { properties ->
          //you can tinker with the Properties here
      }
    }
  }
}
属性的详细
file

这是一个PropertiesFileContentMerger类型(只读)
具体参照,EclipseJdt这个closure的file的closure内容 什么是只读

sourceCompatibility

定义了java源文件的java版本,缺省是project.sourceCompatibility

targetCompatibility

定义了生成java字节码文件的jvm版本,缺省是project.targetCompatibility

Script blocks file:Enables advanced configuration like affecting the way existing jdt file content is merged with gradle build information 可以进一步配置

file{} Enables advanced configuration like affecting the way existing jdt file content is merged with gradle build information
The object passed to whenMerged{} and beforeMerged{} closures is of type Jdt
The object passed to withProperties{} closures is of type Properties
For example see docs for EclipseJdt

Groovy的Class

EclipseJdt
PropertiesFileContentMerger

DOMA的例子
eclipse {
    //it是Groovy的关键字,闭包内的唯一参数的隐式参数
    //为什么不直接用等号?
    jdt.file.withProperties { it['org.eclipse.jdt.core.compiler.processAnnotations'] = 'enabled' }
    //classpath定义了有什么用,跟上面sourceCompatibility有什么区别?
    classpath {
        containers = [
            'org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.8'
        ]
    }
}

这里只是将eclipse的注解编译器打开

eclipseJdt {
    def clos = []

    def fp = file('.factorypath')
    outputs.file fp
    clos += {
        fp.withWriter {
            def domaJar = configurations.compile.find {
                it.name.startsWith('doma-2')
            }
            new groovy.xml.MarkupBuilder(it).factorypath() {
                factorypathentry(kind:'EXTJAR', id:domaJar, enabled:true, runInBatchMode:false)
            }
        }
    }

    //定义一个prefs的方法
    def prefs = { name, contents ->
        //定义了一个文件的参数f
        def f = file(".settings/$name")
        //把contents的内容多行分解成string,赋值给f.text
        clos += {
            f.text = contents.stripMargin()
        }
        //输出内容到f这个参数定义的文件中去
        outputs.file f
    }
    //调用prefs这个方法,org.eclipse.jdt.apt.core.prefs这个文件里面,写下面四行内容。
    //第一行的“\”是什么意思→表示这行没有换行
    prefs 'org.eclipse.jdt.apt.core.prefs', """\
        |eclipse.preferences.version=1
        |org.eclipse.jdt.apt.aptEnabled=true
        |org.eclipse.jdt.apt.genSrcDir=${aptDir}
        |org.eclipse.jdt.apt.reconcileEnabled=true
        |"""

    doLast { clos*.run() }
}

Spring+DOMA开发过程中遇到的问题

DOMA使用过程中,无法扫描到类的原因

困扰了好几天的问题终于解决了。 在一个最简单的DOMA例子中,一直发生下列错误

***************************
APPLICATION FAILED TO START
***************************

Description:

Field customerRepository in com.apibot.service.CustomerService required a bean of type 'com.apibot.dao.CustomerRepository' that could not be found.


Action:

Consider defining a bean of type 'com.apibot.dao.CustomerRepository' in your configuration.

感觉是Repository类一直没有被扫描到的缘故,试过了在前面博客里面介绍过的方法。
【关于Autowire自动注入的类】
增加了 @SpringBootApplication(scanBasePackages = { "com.apibot" })
或是 @ComponentScan("com.apibot")
但是没有效果,最后还是在官网找到了答案。

使用DOMA时的编译问题

原因一:没有设置factory path,因此注解没有被解析。

问题是解决了,但原因还不是很明白,factory 里面设置的注解解析包,为什么要这么设置的原因,
留到今后寻找。

原因二:项目的缺省编译路径没有正确设置

错误路径:APIBot/bin/default
正确路径:APIBot/bin/main

这里也存在一个疑问没有明白,java和resource的路径虽然都已经是设置了,
但是DOMASQL没有使用这个路径,而是用了下面的缺省路径。
f:id:pinetree-chen:20180804211553p:plain

Spring Framework总结点滴

关于Autowire自动注入的类

Spring虽然会对有下面这些注解的类进行自动扫描,
@Component,@Controller,@Repository,@Service
但如果不在SpringApplication类相同目录下,是扫描不到的,
因此需要指定扫描目录。
扫描几种方法

对Application类的@SpringBootApplication注解指定扫描目录,
这样在这个目录以及这个目录的子目录里面类都会被扫描
@SpringBootApplication(scanBasePackages = { "com.apibot" })

对Application类增加@ComponentScan注解指定扫描目录

Spring+Doma进行数据库连接

DataSource的使用

Spring项目的application.yml一般会有下面一段关于DB的定义

spring:
  datasource:
    url: jdbc:postgresql://localhost:5432/apibot
    driver-class-name: org.postgresql.Driver
    username: apibot
    password: apibot
    platform: postgresql

Spring框架的DataSourceProperties类里面有以下定义:

@ConfigurationProperties(prefix = "spring.datasource")
public class DataSourceProperties implements BeanClassLoaderAware, InitializingBean {

Spring框架会自动扫描spring.datasource的定义项
具体有哪些定义项可以使用,可以根据DataSourceProperties类的成员变量推断出来。

DOMA的配置类(AppConfig.java

// ---------------------------
//  Doma用の設定
// ---------------------------
@Autowired
DataSourceProperties dataSourceProperties;  //←自动注入DatasourceProperties
DataSource realDataSource() {
    SimpleDataSource dataSource = new SimpleDataSource();
    dataSource.setUrl(dataSourceProperties.getUrl());
    dataSource.setUser(dataSourceProperties.getUsername());
    dataSource.setPassword(dataSourceProperties.getPassword());
    return dataSource;
}

通过Autowired自动注入了datasourceProperties
生成一个SimplieDataSource类实例,从DataSourceProperites中取出DB连接所需要的参数,赋值给DataSource
在这个上面的例子中给url,user,password

@Bean
DataSource dataSource() {
    /*
        TransactionAwareDataSourceProxyでラップしないとDomaのコネクションが
        Springの管理外になって実行時例外発生時にRollbackされない
    */
    return new TransactionAwareDataSourceProxy(
        new Log4jdbcProxyDataSource(
            realDataSource()));
}
@Bean的用法可以参考下方连接:
Doma的Transcation管理

Doma建立的DB连接,没有被TransactionAwareDataSourceProxy管理。
因此需要通过Wrapper,交给TransactionAwareDataSourceProxy管理。
也就是说,connection的建立,不是通过DataSource建立getconnection。
而是通过Log4jdbcProxyDataSource里的成员方法建立连接。
关于doma的连接需要进行Transaction管理一节,可以参考以下连接。

Warning
这是在测试环境下的,正式的生产环境还是用其他的jdbc

@Bean
Dialect dialect() {
    return new PostgresDialect();
}
@Bean
Config config() {
    return new Config() {

        @Override
        public DataSource getDataSource() {
            return dataSource();
        }

        @Override
        public Dialect getDialect() {
            return dialect();
        }

        @Override
        public SqlFileRepository getSqlFileRepository() {
            return sqlFileRepository();
        }
    };
}

Dialect是DB的方言消除Class,通过使用这个Class可以消除DB之间关于SQL文的语法差异。
SqlFileRepository是存放SQL的位置,可以用来指定SQL的存放位置
以上为标准写法。可以customize。

Gradle的一些理解

repositories可以使用在以下3个位置。

  1. buildscript:gradle脚本本身运行时需要的外部repository
  2. gradle.build文件本身:是指脚本运行时需要的外部repository
  3. allprojects:是指子工程需要的外部的外部repository

apply plugin: 'java'
build.gradle文件里一般都会需要这个plugin
增加java plugin的目的是为了增加java相关的各种task
下面这些命令就可以gradle -build,gradle -compile,

apply plugin: 'org.springframework.boot'
build.gradle文件里一般都会需要这个plugin
springframework最基本的插件,没有这个插件的话,会出现下面的错误
例:Could not resolve: org.springframework.boot:spring-boot-starter-web

apply plugin: 'io.spring.dependency-management'
build.gradle文件里一般都会需要这个plugin
版本管理最基本的插件,没有这个插件的话,会出现下面的错误
例:Could not resolve: org.springframework.boot:spring-boot-starter-jetty
During the build, one or more dependencies that were declared without a version failed to resolve:
org.springframework.boot:spring-boot-starter-jetty:

Gradle的结构

  1. buildscript
  2. plugin 本体
  3. dependencies依赖
buildscript {
    //定义外部变量
    ext {
        springBootVersion = '2.0.3.RELEASE'
    }
    //buildscript运行需要的repository
    repositories {
        mavenCentral()
    }
    //脚本执行时的依赖包
    dependencies {
        classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}")
    }
}

//增加compile,build等task
apply plugin: 'java'
//把java的路径,写到eclipse的.project文件里
apply plugin: 'eclipse'
//spring最基本的插件
apply plugin: 'org.springframework.boot'
//版本管理的插件
apply plugin: 'io.spring.dependency-management'
//编译的JRE版本
sourceCompatibility = 1.8
//jar包的名字
jar {
    baseName = 'apibot'
    version =  '0.0.1-SNAPSHOT'
}
//编译,打包,junit运行时需要的repository
repositories {
    mavenCentral()
    jcenter()
}

// Doma-Genで出力するJavaクラスとSQLファイルの出力先ディレクトリを同じにする
processResources.destinationDir = compileJava.destinationDir
processTestResources.destinationDir = compileTestJava.destinationDir

// Doma-Genでコンパイルより前にSQLファイルを出力先ディレクトリにコピーする
// ために依存関係を逆転する
compileJava.dependsOn processResources
compileTestJava.dependsOn processTestResources

dependencies {

    compile('org.springframework.boot:spring-boot-starter')
    compile('org.springframework.boot:spring-boot-starter-web')
    compile('org.springframework.boot:spring-boot-starter-jersey')
    compile('org.springframework.boot:spring-boot-starter-thymeleaf')
    compile('org.springframework.boot:spring-boot-starter-security')
    testCompile('org.springframework.boot:spring-boot-starter-test')
    compile("org.springframework.boot:spring-boot-starter-jetty")
    compile("org.springframework.boot:spring-boot-starter-actuator")
    compile("org.projectlombok:lombok:1.16.16")
    compile("org.seasar.doma:doma:${domaVersion}")

    /* DBアクセス関連 */
    // https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-jdbc
    compile("org.springframework.boot:spring-boot-starter-jdbc")
    // https://mvnrepository.com/artifact/org.seasar.doma.boot/doma-spring-boot-starter
    compile("org.seasar.doma.boot:doma-spring-boot-starter:${domaBootVersion}")

    compile("org.lazyluke:log4jdbc-remix:0.2.7")
    runtime(group: "org.postgresql", name: "postgresql")
}

关于Sublime Text3 使用MarkDown的配置

Sublime Text3 使用技巧

(一)如何使用Markdown插件

1. 安装Package Control

按[CTRL+`],在跳出的Console窗口中输入以下命令

import urllib.request,os,hashlib; h = '6f4c264a24d933ce70df5dedcf1dcaee' + 'ebe013ee18cced0ef93d5f746d80ef60'; pf = 'Package Control.sublime-package'; ipp = sublime.installed_packages_path(); urllib.request.install_opener( urllib.request.build_opener( urllib.request.ProxyHandler()) ); by = urllib.request.urlopen( 'http://packagecontrol.io/' + pf.replace(' ', '%20')).read(); dh = hashlib.sha256(by).hexdigest(); print('Error validating download (got %s instead of %s), please try manual install' % (dh, h)) if dh != h else open(os.path.join( ipp, pf), 'wb' ).write(by)

可以参照https://packagecontrol.io/installation

2. Sublime中的Markdown插件

安装完上述插件,就可以使用Markdown语言来写展示文档。 例如以下java代码

 public void static main( String args[]){

 }

例如以下标题3:

test