java Agent 简单学习

java_logo

前言

java.lang.instrument包是Java中来增强JVM上的应用的一种方式,机制是在JVM启动前或启动后attach上去进行修改方法字节码的方式。
instrument包的用途很多,主要体现在对代码侵入低的优点上,例如一些监控不方便修改业务代码,但是可以使用这种方式在方法中植入特定逻辑,这种方式能够直接修改JVM中加载的字节码的内容,而不需要在像Spring AOP实现中创建新的代理类,所以在底层侵入更高,但是对开发者更透明。用于自动添加getter/setter方法的工具lombok就使用了这一技术。另外btrace和housemd等动态诊断工具也是用了instrument技术。

创建一个简单的Agent

关于premain的介绍:

通常agent的包里面MATE-INF目录下的MANIFEST.MF中会有这样一段声明

Premain-Class: cn.org.javaweb.test.Agent

在你通过启动命令添加-javaagent:xxx.jar 的时候,JVM会去xxx.jar中找其中Premain-Class是你在MANIFEST.MF中声明的cn.org.javaweb.test.Agent这个类中的public static void premain(String agentOps, Instrumentation instrumentation)或者public static void premain(String agentOps)方法.

注意这里,public static void premain(String agentOps, Instrumentation instrumentation)public static void premain(String agentOps)这两个是有优先级的,其中当premain有两个参数,也就是public static void premain(String agentOps, Instrumentation instrumentation)的时候优先级最高,如果两个都存在,public static void premain(String agentOps)则会被忽略。

其中agentOps将获得程序的参数,会随着-javaagent一起传入,如下:

java -javaagent:agent-0.0.1.jar="Hello World.?"  -jar agent-1.0-SNAPSHOT.jar

将会将Hello World.?传入进去,和main方法不一样的是,该处传入的是一串完整的字符串,并不会传入解析以后的字符串,所以此处如果有需要的话,那么此处传入字符串将由agent完成解析

其中instrumentationjava.lang.instrument.Instrumentation 的实例,由 JVM 自动传入。java.lang.instrument.Instrumentationinstrument 包中定义的一个接口,也是这个包的核心部分,集中了其中几乎所有的功能方法,例如类定义的转换和操作等等。


了解了以上知识以后,我们来创建一个agent。

创建agent

首先创建一个名为cn.org.javaweb.test的包,然后在该包下创建一个为Agent的类,
如下:

package cn.org.javaweb.test;
/*
 * Copyright sky 2018-11-20 Email:sky@03sec.com.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/**
 * @author sky
 */
public class Agent {
}

然后写一个premain方法

public static void premain(String agentOps, Instrumentation inst) {
        System.out.println("=======this is agent premain function=======");
    }

将其使用maven打包为jar文件,pom.xml文件中build的内容为:

<plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-jar-plugin</artifactId>
                <version>3.0.2</version>
                <configuration>
                    <archive>
                        <manifest>
                            <addClasspath>true</addClasspath>
                        </manifest>
                        <manifestEntries>
                            <Premain-Class>
                                cn.org.javaweb.test.Agent
                            </Premain-Class>
                            <can-redefine-classes>
                                true
                            </can-redefine-classes>
                        </manifestEntries>
                    </archive>
                </configuration>
            </plugin>
</plugins>

然后使用maven中的clean install命令安装即可(或者使用mvn clean package),将会生成一个agent.jar的文件。

创建测试程序

此时我们随便新建一个带有main方法的程序,

package cn.org.javaweb.test;
/*
 * Copyright sky 2018-11-20 Email:sky@03sec.com.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/**
 * @author sky
 */
public class TestAgent {

    public static void main(String[] args) {
        System.out.print("Hello,This is TestAgent Program!");
    }
}

pom.xml文件中build的内容是


        <plugins>
            <plugin>
                <artifactId>maven-shade-plugin</artifactId>
                <groupId>org.apache.maven.plugins</groupId>
                <version>1.2.1</version>
                <executions>
                    <execution>
                        <phase>package</phase>
                        <goals>
                            <goal>shade</goal>
                        </goals>
                        <configuration>
                            <transformers>
                                <transformer
                                        implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
                                    <mainClass>cn.org.javaweb.test.TestAgent</mainClass>
                                </transformer>
                            </transformers>
                        </configuration>
                    </execution>
                </executions>
            </plugin>
</plugins>

然后使用maven生成jar文件。

测试agent

这时候我们会得到两个jar文件,

agent.jar --> agent生成的jar文件
test.jar  --> 测试程序

我们先来运行下测试程序,看是否正常

java -jar test.jar 

输出

Hello,This is TestAgent Program!

然后我们加入agent运行试试看

java -javaagent:agent.jar -jar test.jar

输出

=======this is agent premain function=======
Hello,This is TestAgent Program!

可以看到premain会在程序的main方法之前运行,此时我们可以简单的看下jvm中加入agent以及不加入agent的运行过程。

不加入agent的运行过程

加入agent的运行过程

可以简单明了的看到agent会在运行程序之前运行,但是其后面涉及到其他复杂的逻辑,这里不做解释。

使用instrumentation参数

在上面我们只是进入了premain方法,其中premain方法的instrumentation参数我们并没有用到,这里我们将简单的介绍使用instrumentation参数。

关于Transformer解释

此方法的实现可以转换提供的类文件,并返回一个新的替换类文件。
有两种装换器,由 Instrumentation.addTransformer(ClassFileTransformer,boolean)canRetransform 参数确定:

  • 可重转换 转换器,将 canRetransform 设为 true 可添加这种转换器
  • 不可重转换 转换器,将 canRetransform 设为 false 或者使用 Instrumentation.addTransformer(ClassFileTransformer)可添加这种转换器

在转换器使用 addTransformer 注册之后,每次定义新类和重定义类时都将调用该转换器。
每次重转换类时还将调用可重转换转换器。
对新类定义的请求通过 ClassLoader.defineClass或其本机等价方法进行。
对类重定义的请求通过 Instrumentation.redefineClasses 或其本机等价方法进行。
对类重转换的请求将通过Instrumentation.retransformClasses 或其本机等价方法进行。
转换器是在验证或应用类文件字节之前的请求处理过程中调用的。
当存在多个转换器时,转换将由 transform 调用链组成。 也就是说,一个 transform 调用返回的 byte 数组将成为下一个调用的输入(通过 classfileBuffer 参数)。

转换将按以下顺序应用:

  • 不可重转换转换器
  • 不可重转换本机转换器
  • 可重转换转换器
  • 可重转换本机转换器

对于重转换,不会调用不可重转换转换器,而是重用前一个转换的结果。对于所有其他情况,调用此方法。在每个这种调用组中,转换器将按照注册的顺序调用。本机转换器由 Java 虚拟机 Tool 接口中的 ClassFileLoadHook 事件提供。

ClassLoader loader                  ----> 这个没什么解释的,就是classloader
String className                    ----> 包名(jvm中包名是/而不是.)
Class<?> classBeingRedefined        ----> 如果是被重定义或重转换触发,则为重定义或重转换的类;如果是类加载,则为 null
ProtectionDomain protectionDomain   ----> 保护域
byte[] classfileBuffer              ----> 二进制字节码

添加一个新的Transformer类

我们新建一个名为TestTransformer的类,内容入下:

/*
 * Copyright sky 2018-11-20 Email:sky@03sec.com.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package cn.org.javaweb.test;

import java.lang.instrument.ClassFileTransformer;
import java.lang.instrument.IllegalClassFormatException;
import java.security.ProtectionDomain;

/**
 * @author sky
 */
public class TestTransformer implements ClassFileTransformer {

    @Override
    public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined,
                            ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException {

        System.out.println(className.replace("/", "."));
        return classfileBuffer;
    }
}

在Agent.java中修改premain方法为

System.out.println("=======this is agent premain function=======");
inst.addTransformer(new TestTransformer());

将我们新建的TestTransformer注册进去
这时候我们在重新生成agent后运行如下命令

java -javaagent:agent.jar -jar test.jar

将会输出

=======this is agent premain function=======
sun.launcher.LauncherHelper
cn.org.javaweb.test.TestAgent
java.lang.Void
Hello,This is TestAgent Program!
java.lang.Shutdown
java.lang.Shutdown$Lock

可以看到会输出很多包名,这是因为我们在TestTransformer中将包名打印了出来。

结束语

关于agent的简单使用就介绍到了此处,此文仅抛砖引玉,更多用法可以发挥想象,比如性能监控,日志监控、管理会话、安全过滤、请求管理等。

文章中涉及到的代码全部都在下面地址:

https://gitlab.com/iiusky/javaagent

参考:

PHPdocX7 Source code

====PHPdocX 7 by 2mdc.com==== https://www.phpdocx.com/

PHPDOCX is a PHP library designed to dynamically generate reports in
Word format (WordprocessingML).

The reports may be built from any available data source like a MySQL
database or a spreadsheet. The resulting documents remain fully
editable in Microsoft Word (or any other compatible software like
OpenOffice) and therefore the final users are able to modify them as
necessary.

The formatting capabilities of the library allow the programmers to
generate dynamically and programmatically all the standard rich
formatting of a typical word processor.

This library also provides an easy method to generate documents in
other standard formats such as PDF or HTML.


phpdocx 可以使用word模板将数据填充到word里面并且生成新的word文档。具体使用方式看官方文档

https://www.phpdocx.com/documentation

下载地址:phpdocx7.zip

git地址:phpdocx7-git

Spring中注解大全和应用

@Controller
@RestController:
@Service
@Autowired
@RequestMapping
@RequestParam
@ModelAttribute
@Cacheable
@CacheEvict
@Resource
@PostConstruct
@PreDestroy
@Repository
@Component
@Scope
@SessionAttributes
@Required
@Qualifier

@Controller

标识一个该类是Spring MVC controller处理器,用来创建处理http请求的对象.

@Controller
public class TestController {
        @RequestMapping("/test")
        public String test(Map<String,Object> map){

            return "hello";
        }
}

@RestController

Spring4之后加入的注解,原来在@Controller中返回json需要@ResponseBody来配合,如果直接用@RestController替代@Controller就不需要再配置@ResponseBody,默认返回json格式。

@RestController
public class TestController {
        @RequestMapping("/test")
        public String test(Map<String,Object> map){

            return "hello";
        }
}

@Service

用于标注业务层组件,说白了就是加入你有一个用注解的方式把这个类注入到spring配置中

@Autowired

用来装配bean,都可以写在字段上,或者方法上。
默认情况下必须要求依赖对象必须存在,如果要允许null值,可以设置它的required属性为false,例如:@Autowired(required=false)

@RequestMapping

类定义处: 提供初步的请求映射信息,相对于 WEB 应用的根目录。
方法处: 提供进一步的细分映射信息,相对于类定义处的 URL。
用过RequestMapping的同学都知道,他有非常多的作用,因此详细的用法
我会在下一篇文章专门讲述,请关注公众号哦,以免错过。

@RequestParam

用于将请求参数区数据映射到功能处理方法的参数上
例如

public Resp test(@RequestParam Integer id){
        return Resp.success(customerInfoService.fetch(id));
    }

这个id就是要接收从接口传递过来的参数id的值的,如果接口传递过来的参数名和你接收的不一致,也可以如下

public Resp test(@RequestParam(value="course_id") Integer id){
        return Resp.success(customerInfoService.fetch(id));
    }

其中course_id就是接口传递的参数,id就是映射course_id的参数名

@ModelAttribute

使用地方有三种:

1. 标记在方法上。

标记在方法上,会在每一个@RequestMapping标注的方法前执行,如果有返回值,则自动>将该返回值加入到ModelMap中。

A.在有返回的方法上:

当ModelAttribute设置了value,方法返回的值会以这个value为key,以参数接受到的值作为value,存入到Model中,如下面的方法执行之后,最终相当于 model.addAttribute("user_name", name);假如 @ModelAttribute没有自定义value,则相当于
model.addAttribute("name", name);

@ModelAttribute(value="user_name")
    public String before2(@RequestParam(required = false) String name, Model model) {
        System.out.println("进入了2:" + name);
        return name;
    }

B.在没返回的方法上:

需要手动model.add方法

@ModelAttribute
    public void before(@RequestParam(required = false) Integer age, Model model) {
        model.addAttribute("age", age);
        System.out.println("进入了1:" + age);
    }

我们在当前类下建一个请求方法:

@RequestMapping(value="/mod")
     public Resp mod(
             @RequestParam(required = false) String name,
             @RequestParam(required = false) Integer age, 
             Model model){
         System.out.println("进入mod");
         System.out.println("参数接受的数值{name="+name+";age="+age+"}");
         System.out.println("model传过来的值:"+model);
         return Resp.success("1");
    }

在浏览器中输入访问地址并且加上参数:
http://localhost:8081/api/test/mod?name=我是小菜&age=12

最终输出如下:

1进入了1:40
2进入了2:我是小菜
3进入mod
4参数接受的数值{name=我是小菜;age=12}
5model传过来的值:{age=40, user_name=我是小菜}

2. 标记在方法的参数上。

标记在方法的参数上,会将客户端传递过来的参数按名称注入到指定对象中,并且会将这个对象自动加入ModelMap中,便于View层使用.
我们在上面的类中加入一个方法如下

@RequestMapping(value="/mod2")
     public Resp mod2(@ModelAttribute("user_name") String user_name, 
             @ModelAttribute("name") String name,
             @ModelAttribute("age") Integer age,Model model){
         System.out.println("进入mod2");
         System.out.println("user_name:"+user_name);
         System.out.println("name:"+name);
         System.out.println("age:"+age);
         System.out.println("model:"+model);
        return Resp.success("1");
    }

```在浏览器中输入访问地址并且加上参数:
http://localhost:8081/api/test/mod2?name=我是小菜&age=12
最终输出:

1进入了1:40
2进入了2:我是小菜
3进入mod2
4user_name:我是小菜
5name:我是小菜
6age:40
7model:{user_name=我是小菜, 
org.springframework.validation.BindingResult.user_name=org.springframework.validation.BeanPropertyBindingResult: 0 errors, name=我是小菜, org.springframework.validation.BindingResult.name=org.springframework.validation.BeanPropertyBindingResult: 0 errors, age=40, org.springframework.validation.BindingResult.age=org.springframework.validation.BeanPropertyBindingResult: 0 errors}
从结果就能看出,用在方法参数中的@ModelAttribute注解,实际上是一种接受参数并且自动放入Model对象中,便于使用。

@Cacheable

用来标记缓存查询。可用用于方法或者类中,

当标记在一个方法上时表示该方法是支持缓存的,
当标记在一个类上时则表示该类所有的方法都是支持缓存的。
参数列表

参数 解释 例子
value 名称 @Cacheable(value={”c1”,”c2”}
key key @Cacheable(value=”c1”,key=”#id”)
condition 条件 @Cacheable(value=”c1”,condition=”#id=1”)比如@Cacheable(value="UserCache") 标识的是当调用了标记了这个注解的方法时,逻辑默认加上从缓存中获取结果的逻辑,如果缓存中没有数据,则执行用户编写查询逻辑,查询成功之后,同时将结果放入缓存中。但凡说到缓存,都是key-value的形式的,因此key就是方法中的参数(id),value就是查询的结果,而命名空间UserCache是在spring*.xml中定义.
@Cacheable(value="UserCache")// 使用了一个缓存名叫 accountCache   
public Account getUserAge(int id) {  
     //这里不用写缓存的逻辑,直接按正常业务逻辑走即可,
     //缓存通过切面自动切入  
    int age=getUser(id);   
     return age;   
} 

@CacheEvict

用来标记要清空缓存的方法,当这个方法被调用后,即会清空缓存。 @CacheEvict(value=”UserCache”)

参数列表

参数 解释 例子
value 名称 @CachEvict(value={”c1”,”c2”}
key key @CachEvict(value=”c1”,key=”#id”)
condition 缓存的条件,可以为空
allEntries 是否清空所有缓存内容 @CachEvict(value=”c1”,allEntries=true)
beforeInvocation 是否在方法执行前就清空 @CachEvict(value=”c1”,beforeInvocation=true)

@Resource

@Resource的作用相当于@Autowired
只不过@Autowired按byType自动注入,
而@Resource默认按 byName自动注入罢了。

@Resource有两个属性是比较重要的,分是name和type,Spring将@Resource注解的name属性解析为bean的名字,而type属性则解析为bean的类型。所以如果使用name属性,则使用byName的自动注入策略,而使用type属性时则使用byType自动注入策略。如果既不指定name也不指定type属性,这时将通过反射机制使用byName自动注入策略。

@Resource装配顺序:
如果同时指定了name和type,则从Spring上下文中找到唯一匹配的bean进行装配,找不到则抛出异常
如果指定了name,则从上下文中查找名称(id)匹配的bean进行装配,找不到则抛出异常

如果指定了type,则从上下文中找到类型匹配的唯一bean进行装配,找不到或者找到多个,都会抛出异常

如果既没有指定name,又没有指定type,则自动按照byName方式进行装配;如果没有匹配,则回退为一个原始类型进行匹配,如果匹配则自动装配;

@PostConstruct

用来标记是在项目启动的时候执行这个方法。用来修饰一个非静态的void()方法
也就是spring容器启动时就执行,多用于一些全局配置、数据字典之类的加载

被@PostConstruct修饰的方法会在服务器加载Servlet的时候运行,并且只会被服务器执行一次。PostConstruct在构造函数之后执行,init()方法之前执行。PreDestroy()方法在destroy()方法执行执行之后执

@PreDestroy

被@PreDestroy修饰的方法会在服务器卸载Servlet的时候运行,并且只会被服务器调用一次,类似于Servlet的destroy()方法。被@PreDestroy修饰的方法会在destroy()方法之后运行,在Servlet被彻底卸载之前

@Repository

用于标注数据访问组件,即DAO组件

@Component

泛指组件,当组件不好归类的时候,我们可以使用这个注解进行标注

@Scope

用来配置 spring bean 的作用域,它标识 bean 的作用域。
默认值是单例

singleton:单例模式,全局有且仅有一个实例
prototype:原型模式,每次获取Bean的时候会有一个新的实例

request:request表示该针对每一次HTTP请求都会产生一个新的bean,同时该bean仅在当前HTTP request内有效

session:session作用域表示该针对每一次HTTP请求都会产生一个新的bean,同时该bean仅在当前HTTP session内有效

global session:只在portal应用中有用,给每一个 global http session 新建一个Bean实例。

@SessionAttributes

默认情况下Spring MVC将模型中的数据存储到request域中。当一个请求结束后,数据就失效了。如果要跨页面使用。那么需要使用到session。而@SessionAttributes注解就可以使得模型中的数据存储一份到session域中

参数:

names:这是一个字符串数组。里面应写需要存储到session中数据的名称。
types:根据指定参数的类型,将模型中对应类型的参数存储到session中
value:和names是一样的。

@Controller
 @SessionAttributes(value={"names"},types={Integer.class})
 public class ScopeService {
         @RequestMapping("/testSession")
         public String test(Map<String,Object> map){
             map.put("names", Arrays.asList("a","b","c"));
             map.put("age", 12);
             return "hello";
         }
}

@Required

适用于bean属性setter方法,并表示受影响的bean属性必须在XML配置文件在配置时进行填充。否则,容器会抛出一个BeanInitializationException异常。

@Qualifier

当你创建多个具有相同类型的 bean 时,并且想要用一个属性只为它们其中的一个进行装配,在这种情况下,你可以使用 @Qualifier 注释和 @Autowired 注释通过指定哪一个真正的 bean 将会被装配来消除混乱。

Idea 中使用Groovy 脚本自动生成Entitys类

原生成脚本来自园长:from:http://javaweb.org

import com.intellij.database.model.DasTable
import com.intellij.database.model.ObjectKind
import com.intellij.database.util.Case
import com.intellij.database.util.DasUtil

/*
 * 默认可用的 context 绑定:
 *   SELECTION   Iterable<DasObject>
 *   PROJECT     project
 *   FILES       files helper
 */

packageName = ""
typeMapping = [
        (~/(?i)tinyint|smallint|mediumint/)      : "Integer",
        (~/(?i)int/)                             : "Long",
        (~/(?i)bool|bit/)                        : "Boolean",
        (~/(?i)float|double|decimal|real/)       : "Double",
        (~/(?i)datetime|timestamp|date|time/)    : "Date",
        (~/(?i)blob|binary|bfile|clob|raw|image/): "InputStream",
        (~/(?i)/)                                : "String"
]

FILES.chooseDirectoryAndSave("Choose directory", "Choose where to store generated files") { dir ->
    SELECTION.filter { it instanceof DasTable && it.getKind() == ObjectKind.TABLE }.each { generate(it, dir) }
}

def generate(table, dir) {
    def className = javaName(table.getName(), true)
    def fields = calcFields(table)
    packageName = getPackageName(dir)
    new File(dir, className + ".java").withPrintWriter { out -> generate(out, className, fields) }
}

/**
 * 获取包名称
 * @param dir 实体类所在目录
 * @return
 */
def getPackageName(dir) {
    return dir.toString().replaceAll("/", ".").replaceAll("^.*src(\\.main\\.java\\.)?", "") + ";"
}

/**
 * 实体类代码生成
 * @param out
 * @param className
 * @param fields
 * @return
 */
def generate(out, className, fields) {
    out.println "package $packageName"
    out.println ""

    Set<String> types = new HashSet<String>()

    fields.each() {
        types.add(it.type)
    }


    out.println "import com.alibaba.fastjson.annotation.JSONField;"
    out.println "import javax.persistence.Column;"
    if (types.contains("Date")) {
        out.println "import java.util.Date;"
    }

    if (types.contains("InputStream")) {
        out.println "import java.io.InputStream;"
    }
    out.println "import java.io.Serializable;"

    out.println ""
    out.println "public class $className implements Serializable {"
    fields.each() {
        out.println ""
        if (it.annos != "") out.println "  ${it.annos}"

        // 输出注释
        if (isNotEmpty(it.commoent)) {
            out.println "\t/**"
            out.println "\t * ${it.commoent}"
            out.println "\t */"
        }

        // 输出成员变量
        out.println "\tprivate ${it.type} ${it.name};"
    }

    // 输出get/set方法
    fields.each() {
        out.println ""

        if (isNotEmpty(it.comment)) {
            out.println "\t/**"
            out.println "\t * 获取${it.comment}"
            out.println "\t * @return"
            out.println "\t */"
        }

        def actionName = "Boolean".equals(it.type.toString()) ? "is" : "get"

        out.println "\tpublic ${it.type} ${actionName}${it.name.capitalize()}() {"
        out.println "\t\treturn this.${it.name};"
        out.println "\t}"
        out.println ""

        if (isNotEmpty(it.comment)) {
            out.println "\t/**"
            out.println "\t * 设置${it.comment}"
            out.println "\t * @param ${it.name}"
            out.println "\t */"
        }
        out.println "\tpublic void set${it.name.capitalize()}(${it.type} ${it.name}) {"
        out.println "\t\tthis.${it.name} = ${it.name};"
        out.println "\t}"
    }
    out.println ""
    out.println "}"
}

def calcFields(table) {
    DasUtil.getColumns(table).reduce([]) { fields, col ->
        def spec = Case.LOWER.apply(col.getDataType().getSpecification())
        def typeStr = typeMapping.find { p, t -> p.matcher(spec).find() }.value
        fields += [[
                           colName : col.getName(),
                           name    : javaName(col.getName(), false),
                           type    : typeStr,
                           comment: col.getComment(),
                           annos   : ""]]
    }
}

def javaName(str, capitalize) {
    def s = str.split(/[^\p{Alnum}]/).collect { def s = Case.LOWER.apply(it).capitalize() }.join("")
    capitalize ? s : Case.LOWER.apply(s[0]) + s[1..-1]
}

def isNotEmpty(content) {
    return content != null && content.toString().trim().length() > 0
}

增强版本

增加Serializable ID
增加@JSONField以及@Column注解生成方式
增加添加@Entity注解以及添加@Table 名

import com.intellij.database.model.DasTable
import com.intellij.database.model.ObjectKind
import com.intellij.database.util.Case
import com.intellij.database.util.DasUtil

/*
 * 默认可用的 context 绑定:
 *   SELECTION   Iterable<DasObject>
 *   PROJECT     project
 *   FILES       files helper
 */

packageName = ""
tableName = ""
typeMapping = [
        (~/(?i)tinyint|smallint|mediumint/)      : "Integer",
        (~/(?i)int/)                             : "Long",
        (~/(?i)bool|bit/)                        : "Boolean",
        (~/(?i)float|double|decimal|real/)       : "Double",
        (~/(?i)datetime|timestamp|date|time/)    : "Date",
        (~/(?i)blob|binary|bfile|clob|raw|image/): "InputStream",
        (~/(?i)/)                                : "String"
]

FILES.chooseDirectoryAndSave("Choose directory", "Choose where to store generated files") { dir ->
    SELECTION.filter { it instanceof DasTable && it.getKind() == ObjectKind.TABLE }.each { generate(it, dir) }
}

def generate(table, dir) {
    def className = javaName(table.getName(), true)
    def fields = calcFields(table)
    packageName = getPackageName(dir)
    tableName = table.getName()
    new File(dir, className + ".java").withPrintWriter { out -> generate(out, className, fields) }
}

/**
 * 获取包名称
 * @param dir 实体类所在目录
 * @return
 */
def getPackageName(dir) {
    return dir.toString().replaceAll("/", ".").replaceAll("^.*src(\\.main\\.java\\.)?", "") + ";"
}

/**
 * 实体类代码生成
 * @param out
 * @param className
 * @param fields
 * @return
 */
def generate(out, className, fields) {
    out.println "package $packageName"
    out.println ""

    Set<String> types = new HashSet<String>()

    fields.each() {
        types.add(it.type)
    }


    out.println "import com.alibaba.fastjson.annotation.JSONField;"
    out.println "import javax.persistence.Column;"
    out.println "import javax.persistence.Entity;"
    out.println "import javax.persistence.Table;"
    if (types.contains("Date")) {
        out.println "import java.util.Date;"
    }

    if (types.contains("InputStream")) {
        out.println "import java.io.InputStream;"
    }
    out.println "import java.io.Serializable;"

    out.println ""
    out.println "@Entity"
    out.println "@Table(name = \"$tableName\")"
    out.println "public class $className implements Serializable {"
    fields.each() {
        out.println ""
        if (it.annos != "") out.println "  ${it.annos}"

        // 输出注释
        if (isNotEmpty(it.commont)) {
            out.println "\t/**"
            out.println "\t * ${it.commont}"
            out.println "\t */"
        }

        def matcher = ("${it.colName}" =~ /[0-9]/);

        if (matcher.getCount() > 0) {
            //输出Column
            out.println "\t@Column(name = \"${it.colName}\")"
        }

        //输出JSONField
        out.println "\t@JSONField(name = \"${it.colName}\")"
        // 输出成员变量
        out.println "\tprivate ${it.type} ${it.name};"
    }

    // 输出get/set方法
    fields.each() {
        out.println ""

        if (isNotEmpty(it.comment)) {
            out.println "\t/**"
            out.println "\t * 获取${it.comment}"
            out.println "\t * @return"
            out.println "\t */"
        }

        def actionName = "Boolean".equals(it.type.toString()) ? "is" : "get"

        out.println "\tpublic ${it.type} ${actionName}${it.name.capitalize()}() {"
        out.println "\t\treturn this.${it.name};"
        out.println "\t}"
        out.println ""

        if (isNotEmpty(it.comment)) {
            out.println "\t/**"
            out.println "\t * 设置${it.comment}"
            out.println "\t * @param ${it.name}"
            out.println "\t */"
        }
        out.println "\tpublic void set${it.name.capitalize()}(${it.type} ${it.name}) {"
        out.println "\t\tthis.${it.name} = ${it.name};"
        out.println "\t}"
    }
    out.println ""
    out.println "}"
}

def calcFields(table) {
    DasUtil.getColumns(table).reduce([]) { fields, col ->
        def spec = Case.LOWER.apply(col.getDataType().getSpecification())
        def typeStr = typeMapping.find { p, t -> p.matcher(spec).find() }.value
        fields += [[
                           colName: col.getName(),
                           name   : javaName(col.getName(), false),
                           type   : typeStr,
                           comment: col.getComment(),
                           annos  : ""]]
    }
}

def javaName(str, capitalize) {
    def s = str.split(/[^\p{Alnum}]/).collect { def s = Case.LOWER.apply(it).capitalize() }.join("")
    capitalize ? s : Case.LOWER.apply(s[0]) + s[1..-1]
}

def isNotEmpty(content) {
    return content != null && content.toString().trim().length() > 0
}