通过brew安装的程序,可以通过下列命令来查找:
1 | brew info XXX |
Mac系统自己还可以通过type
和which
命令来查找命令对应的路径:
1 | type XXX |
通过brew安装的程序,可以通过下列命令来查找:
1 | brew info XXX |
Mac系统自己还可以通过type
和which
命令来查找命令对应的路径:
1 | type XXX |
Log4j2 是一个常用的日志工具。本文把试用结果和遇到的问题记录一下。
Java代码如下:
1 | package com.shizhihua.example; |
log4j2默认在console上输出ERROR级别的信息。为了显示INFO信息,需要通过配置更改默认设置。试用中采用XML文件来进行设置。log4j2.xml
的具体设置内容如下(Root level设置为INFO):
1 | <?xml version="1.0" encoding="UTF-8"?> |
代码的组织按照gradle/maven的默认结构。
Q1: 程序找不到log4j2的配置文件?
A1: 配置文件必须放在src\main\resources
下面,否则运行时需要指定配置文件。
build.gradle文件内容如下:
1 | apply plugin: 'java' |
Q2: 运行jar文件,找不到依赖包?
A2: Gradle能自动解决编译过程中的第三方依赖,但是我们直接从命令后直接运行程序时,还是要指定第三方依赖才行。为了能直接运行jar文件,上述脚本中在jar文件生成配置中增加了一行代码,这样就把第三方依赖包也打包起来了,形成了“胖”jar。
1 | from { configurations.compile.collect { it.isDirectory() ? it : zipTree(it) } } |
直接执行下面命令:
1 | gradle build |
得倒下面的输出(与时间有关)
1 | 07:33:56.484 [main] INFO com.shizhihua.example.TestLog - Hello, world! |
Spring 4.0含有20个独立的模块(modules),每个模块含有三个文件:
这20个独立模块可以分为6大类。
在一个Spring-based application中,程序中的对象都生存在Spring Container
中。由这一Container来负责这些对象的生成、关联、配置和管理等等。
在Spring中,存在多个容器的实现,大体可以分为两大类:
org.springframework.beans.factory.BeanFactory
定义org.springframework.context.ApplicationContext
定义在实际应用中,由于bean factories
过于底层,不便于应用。所以,在大部分程序中,一般更倾向于采用application contexts
。
常见的一些application contexts
:
AnnotationConfigApplicationContext
:Loads a Spring application context from one or more Java-based configuration classesAnnotationConfigWebApplicationContext
:Loads a Spring web application context from one or more Java-based configuration classesClassPathXmlApplicationContext
:Loads a context definition from one ormore XML files located in the classpath, treating context-definition files as classpathFileSystemXmlApplicationContext
:Loads a context definition from one or more XML files in the filesystemXmlWebApplicationContext
:Loads context definitions from one or more XML files contained in a web application两个例子:
1 | ApplicationContext context = new FileSystemXmlApplicationContext("c:/knight.xml"); |
基于《Spring In Action》第四版。
为了替代笨重的Enterprise JavaBeans (EJB),人们设计了轻量级的框架Spring。 因此Spring的设计目的就是:简化java开发。 Spring simplifies Java development。
为了降低Java开发复杂度,Spring采用了四方面的策略
后续四部分会分别针对这四个方面,以实际例子来说明其含义。
先记录一些常用的名词:
有一些Java框架在使用时,开发人员需要在自己的程序中扩展或者实现框架的的一些interface或者class。
Spring框架不会让程序代码和他的API混在一起。Spring almost never forces you to implement a Sprint-specific interface or extend a Spring-specific class。
通常,使用Spring时,往往感受不到在使用Spring。 最差情况是会使用一些Spring’s annotation。下面是一个例子:
1 | // Spring doesn’t make any unreasonable demands on HelloWorldBean |
从上面例子可以看到,这就是一个简单Java类(POJO),没有Spring的组件直接体现出来。这样的好处是:
同样的代码可以在其他非Spring程序中使用(in a non-Spring application)。
Dependency Injection (DI) 的用处主要是解耦不同的class/object。
一个典型应用场景是:
下面分三部分通过例子来
1 | package com.springinaction.knights; |
上述代码,存在两方面的问题:
DamselRescuingKnight
与RescueDamselQuest
紧密联系在一起。如果要一个屠龙的knight,则上述代码都得重新写,很难直接利用。quest.embark()
,目前没有简洁的方法可以进行验证。使用DI,则有一个第三方来负责协同两个类。看下面例子(constructor injection):
1 | package com.springinaction.knights; |
上面的BraveKnight
就比较通用,可以接收各种Quest
:RescueDamselQuest
, SlayDragonQuest
, MakeRoundTableRounderQuest
。
因此BraveKnight
已经与Quest
的各种具体实现完全解耦了,不再依赖于具体实现。 (设计模式原则之一:基于接口编程。策略模式)
采用constructor injection后,很容易使用Mock来进行单元测试:
1 | package com.springinaction.knights; |
通过采用调用embarkOnQuest()
,可以利用Mockito
来验证Quest
的embark()
是被调用了1次。
首先实现被注入的类SlayDragonQuest
:
1 | package com.springinaction.knights; |
可以看到,SlayDragonQuest
也使用了构造注入(constructor injection)。
在Spring中,可以使用XML来把SlayDragonQuest
传递给BraveKnight
:
1 | <?xml version="1.0" encoding="UTF-8"?> |
在上述XML文件中,两个类都被声明为Bean了。每个Bean都配置了构造函数输入参数,从而关联的对应的类(wire beans)。
在Spring中,可以用Java代码来等价替换上述XML文件:
1 | package com.springinaction.knights.config; |
在Spring application中,一个application context
来读取bean定义,并把他们wire在一起。
Spring提供了application context的多种实现,其差别仅仅在于如何load configuration。 本处以XML文件为例,需要使用ClassPathXmlApplicationContext
(Java配置文件时,需要使用AnnotationConfigApplicationContext
):
1 | package com.springinaction.knights; |
DI实现了模块间的解耦(松耦合),AOP可以帮组我们提高功能模块的可重用性。
系统的不同功能分为不同的模块。但在实际过程中,一些模块除了自己的核心功能以外,还有一些其他的辅助功能,而这些辅助的功能可能会涉及多个模块。这就造成,修改一些代码,可能会涉及到系统很多部分。
定义一个类Minstrel
,其有两个函数,分别在knight做一个quest(embark on a quest)之前和之后执行:
1 | package com.springinaction.knights; |
直接在某个具体骑士的类中调用minstrel
的两个函数:
1 | package com.springinaction.knights; public class BraveKnight implements Knight { private Quest quest; private Minstrel minstrel; public BraveKnight(Quest quest, Minstrel minstrel) { |
在这个例子中,两个类紧紧耦合在一起了。
在Spring中,AOP处理方式对应的XML配置如下:
1 | <?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:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation="http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.2.xsd http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> |
在上述配置中,首先把Minstrel
声明为bean,然后通过<aop:aspect>
来配置AOP。在这儿,Minstrel
仍然是一个POJO。
Eliminating boilerplate code with templates
JDBC相关程序,会产生很多例行(冗余)代码,很多代码不是程序的核心功能,而是辅助性的代码。
1 | public Employee getEmployeeById(long id) { Connection conn = null; PreparedStatement stmt = null; ResultSet rs = null; try { conn = dataSource.getConnection(); stmt = conn.prepareStatement( "select id, firstname, lastname, salary from " + "employee where id=?"); stmt.setLong(1, id); rs = stmt.executeQuery(); Employee employee = null; if (rs.next()) { employee = new Employee(); employee.setId(rs.getLong("id")); employee.setFirstName(rs.getString("firstname")); employee.setLastName(rs.getString("lastname")); employee.setSalary(rs.getBigDecimal("salary")); } return employee; } catch (SQLException e) { } finally { // Clean up mess if(rs != null) { try { rs.close(); } catch(SQLException e) {} } if(stmt != null) { try { stmt.close(); } catch(SQLException e) {} } if(conn != null) { try { conn.close(); } catch(SQLException e) {} } } return null; } |
使用Spring中的模板SimpleJdbcTemplate
,只要写核心功能相关的代码,和JDBC API相关的一些辅助性代码都交由模板处理,不需要额外写代码。
1 | public Employee getEmployeeById(long id) { return jdbcTemplate.queryForObject( "select id, firstname, lastname, salary " + "from employee where id=?", new RowMapper<Employee>() { public Employee mapRow(ResultSet rs, int rowNum) throws SQLException { Employee employee = new Employee(); employee.setId(rs.getLong("id")); employee.setFirstName(rs.getString("firstname")); employee.setLastName(rs.getString("lastname")); employee.setSalary(rs.getBigDecimal("salary")); return employee; } }, id); } |
下列内容来自《Building and Testing with Gradle》。
Gradle运行生命周期如下:
在Gradle中,task是build activity的基本单元,由一系列的构建指令组合而成。Task是first-class object。
Task Action使用task
和<<
来定义:
1 | task hello << { |
其中<<
在此处表示为hello
任务添加一系列的操作(append a code block to the list of actions a task performs)。
上述表达式可以用下面的命令来等价:
1 | task hello |
Task的定义与配置很像,差别在于task配置中不使用<<
。
1 | task initializeDatabase |
上述第1条声明一个task,第2条添加operation到task,第3条是task的配置。
运行上述build文件,可以得到下列结果:
1 | $ gradle -b scratch.gradle initializeDatabase |
Tasks are Objects
。和不同的对象一样,a task object可以含有方法(methods)和属性(properties)。
前述定义的Task,属于DefaultTask
,有以下一些方法。
被依赖的task会先执行。可以有不同的表示方法,结果是等效的。下面示例中定义的task依赖于两个其它task(只显示了4种方法):
1 | task world(dependsOn: [compileTestClasses, createSchema]) |
1 | task world |
1 | task world { |
1 | task world { |
doFirst
中的代码会在对应的task action之前执行;多个doFirst
出现时,后出现的先运行,即遵照FILO规则。
1 | task setupDatabaseTest << { |
1 | task setupDatabaseTest << { |
doLast
中的代码会在对应的task action之后执行。多个doLast
出现时,先出现的先运行,即遵照FIFO规则。doLast
用法与doFirst
类似。
1 | task setupDatabaseTest << { |
除了DefaultTask
,task还有其它一些type。在task名字后面添加(type: typeName)
即可指定task type。
Copy task从一个目录拷贝文件到另一个目录。
1 | task copyFiles(type: Copy) { from 'resources' into 'target' include '**/*.xml', '**/*.txt', '**/*.properties' } |
从源文件生成jar
文件
1 | apply plugin: 'java' |
Gradle通用命令格式:
1 | gradle [option ...] [task ...] |
运行gradle相关命令后,可以看到依次执行的tasks:
1 | > gradle build |
帮助命令:
1 | gradle help |
执行完整的项目构建(生成class文件、jar文件、执行单元测试等):
1 | gradle build |
编译源代码,并生成jar文件(不执行单元测试):
1 | gradle assemble |
删除构建目录:
1 | gradle clean |
编译源代码:
1 | gradle classes |
编译测试类的代码:
1 | gradle testClasses |
生成jar文件:
1 | gradle jar |
生成Javadoc API文档:
1 | gradle javadoc |
显示项目的properties
:
1 | gradle properties |
显示当前项目的可运行task的完整列表:
1 | gradle tasks |
通过这种方式,不需要阅读构建脚本,就能对项目进行大致的浏览。由于Java插件在构建中自动加入了很多任务,因此上述命令可以看到build文件中之外的其它一些task。
在某个项目下,上述命令运行后,terminal显示如下信息:
1 | :tasks |
build.gradle
如下:
1 | apply plugin: 'java' |
上述配置指定仓库后,不需要额外再安装Junit4了。
Calculator.java
:
1 | package com.shizhihua.example; |
CalculatorTest.java
:
Test
@Test
表示需要运行的测试函数1 | package com.shizhihua.example; |
为了Gradle,项目结构需要按照下面约定。注意被测的源java文件和测试例的java文件目录相对应:
src/main/java
目录包含项目源代码src/main/resources
目录包含项目的资源src/test/java
目录包含项目测试类src/test/resource
目录包含项目测试资源执行测试:
1 | gradle test |
运行上述命令后,整个项目的目录结构如下:
打开上述index.html
可以看到测试结果的统计报告。
当项目中的测试例过多,需要通过并行的方法来加快运行速度。但是,如果每个unit test都需要自己独立的JVM,则系统overhead消耗大。Gradle采用了一种这种方法:设定maxParallelForks
,限定最大的并发JVMs。
如果一个JVM不断运行unit test,可能会引起性能问题(e.g.,leak
)。Gradle设定forkEvery
,使得一个test-running JVM运行完设定的test数目后会结束,并启动一个新的test-running JVM来替代。
1 | apply plugin: 'java' |
1 | brew install gradle |
查看版本
1 | gradle -v |
本机安装版本为Gradle 2.12
。
在当前目录下生成文件build.gradle
:
1 | apply plugin: 'java' |
项目的目录结构需要按照下列固定约束(最里面几层对应package):
src/main/java
目录包含项目源代码src/main/resources
目录包含项目的资源src/test/java
目录包含项目测试类src/test/resource
目录包含项目测试资源本例中,没有测试相关内容,因此后两个目录没有。
Java文件:
1 | package com.shizhihua.example; |
1 | gradle build |
执行构建后,Gradle会生出一个目录build
:
classes
目录包含编译生成的.class
文件libs
目录包含生成的jar
或war
文件运行程序时,必须通过-cp
指定CLASSPATH
:
1 | java -cp build/classes/main/ com/shizhihua/example/OReillyByName |
上面得到的jar
文件运行时需要指定入口,即
在libs
中的jar
文件,需要设置Main-Class
属性的值,指定程序的入口点。为了运行简单,可以在gradle文件中直接配置好入口,即
1 | apply plugin: 'java' |
通过运行gradle build
生产的jar
文件,即可直接运行:
1 | java -jar build/libs/testGradle.jar |
Homebrew,简称为brew
,是Mac上的软件包管理工具,类似于Debain中的apt-get
。
Homebrew的官网地址为: Homebrew
安装时不需要使用sudo
。Homebrew不需要sudo
可以正常安装软件,因为其认为sudo
不完全。
安装命令如下:
1 | ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)" |
卸载命令如下:
1 | ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/uninstall)" |
通用命令
1 | brew command [--verbose|-v] [options] [formula] ... |
formula
就是对应要安装的软件。formula
是ruby
脚本,定义了安装对应软件的具体步骤和操作命令。
安装软件
1 | brew install formula |
卸载软件可以用下列两条命令之一
1 | brew remove formula |
重装软件
1 | brew reinstall formula |
更新软件
1 | brew upgrade [--cleanup] [formulae] |
cleanup软件旧版本
1 | brew cleanup |
查看版本
1 | brew --version |
显示formula脚本代码
1 | brew cat formula |
更新Homebrew
1 | brew update |
罗列已安装的formula
1 | brew list |
查找软件
1 | brew search text |
其中text可以是正则表达式。
通过brew安装的文件,可以在目录/usr/local/Cellar/
下面找到。