html 转换成 pdf

不久前有个需求,需要将 html 格式文件转换成 PDF 格式文件,学习 java 的都知道,有个组件可以帮我们完成这项工作——itext
但是请注意的是(亲测):itext 做简单的 html 转换还可以,但是如果页面相对比较复杂的时候,itext 表现的就不尽人意了。
然后上网查阅,找到一个比较不错的工具——wkhtmltopdf,和大家分享下

官网地址戳这里

wkhtmltopdfhtml 转成 pdf 很简单,只要在 windows 命令行中输入

1
c:\wkhtmltopdf.exe http://www.baidu.com c:\cnblogs.pdf

就可以把百度网页转成pdf,并保存到C盘根目录(路径可以自己指定,这个不像多说什么)。
Java 中调用 wkhtmltopdf 的命令

1
Runtime.getRuntime().exec("c:\wkhtmltopdf.exe http://www.cnblogs.com c:\cnblogs.pdf")

就可以实现转换。
下面把命令封装成java工具类,方便调用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
package com.thunisoft.dzjz.material.converter.html;

import java.io.File;

/**
* @author hanfeng on 2018-4-16.
* @version 4.0
*/
public class Html2Pdf {
// wkhtmltopdf 在本地的安装路径
private static final String toPdfTool = "D:\\wkhtmltopdf\\bin\\wkhtmltopdf.exe";

/**
* html转pdf
* @param srcPath html路径,可以是硬盘上的路径,也可以是网络路径
* @param destPath pdf保存路径
* @return 转换成功返回true
*/
public static boolean convert(String srcPath, String destPath){
File file = new File(destPath);
File parent = file.getParentFile();
//如果pdf保存路径不存在,则创建路径
if(!parent.exists()){
parent.mkdirs();
}

StringBuilder cmd = new StringBuilder();
cmd.append(toPdfTool);
cmd.append(" "); // 注意 前后的这个空命令行不能删,否则会报错
// cmd.append("--page-size A4 ");// 指定纸张大小
// cmd.append(" --header-line");//页眉下面的线
// cmd.append(" --header-center 这里是页眉这里是页眉这里是页眉这里是页眉 ");//页眉中间内容
//设置页面上边距 (default 10mm)
cmd.append(" --margin-top 0 ");
cmd.append(" --margin-bottom 0 ");
cmd.append(" --margin-left 0 ");
cmd.append(" --margin-right 0 ");
// cmd.append(" page background, #D48F44 ");
// cmd.append(" --header-spacing 10 ");// (设置页眉s和内容的距离,默认0)
cmd.append(srcPath);
cmd.append(" ");
cmd.append(destPath);

boolean result = true;
try{
Process proc = Runtime.getRuntime().exec(cmd.toString());
HtmlToPdfInterceptor error = new HtmlToPdfInterceptor(proc.getErrorStream());
HtmlToPdfInterceptor output = new HtmlToPdfInterceptor(proc.getInputStream());
error.start();
output.start();
proc.waitFor();
}catch(Exception e){
result = false;
e.printStackTrace();
}
return result;
}
/**
* 测试
*/
public static void main(String[] args) {
Html2Pdf.convert("e:/123.html", "e:/wkhtmltopdf_535.pdf");
}
}

小结

用这种方式进行转换相对简单方便,但是会存在个小问题,就是你在 html 中指定了大小之后,转完 pdf 不一定会完全一样,存在些误差,需要自己在做下调整。但相对 itext 要舒服的多,毕竟借用了三方插件。

  • 最后 附上 itext 实现传换的代码和 wkhtmltopdf 参数详解
    itext demo:
    1、 引入依赖
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    <dependency>
    <groupId>com.itextpdf</groupId>
    <artifactId>itextpdf</artifactId>
    <version>5.5.13</version>
    </dependency>
    <dependency>
    <groupId>com.itextpdf.tool</groupId>
    <artifactId>xmlworker</artifactId>
    <version>5.5.13</version>
    </dependency>

2、demo

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
import java.io.FileInputStream;
import java.io.FileOutputStream;

import com.itextpdf.text.Document;
import com.itextpdf.text.PageSize;
import com.itextpdf.text.pdf.PdfWriter;
import com.itextpdf.tool.xml.XMLWorkerHelper;
/**
* @author hanfeng on 2018-4-16.
* @version 4.0
*/
public class HtmlToPDF {
// html 模板路径
public static final String HTML = "D:/123.htm";
// pdf 保存路径
public static final String PDF = "D:/123.htm";
/**
* 测试
*/
public static void main(String[] args) {
// 1
Document document = new Document();
// 2
PdfWriter writer = PdfWriter.getInstance(document, newFileOutputStream(PDF));
// 3
document.open();
// 4
XMLWorkerHelper.getInstance().parseXHtml(writer, document,new FileInputStream(HTML));
// 5
document.close();
}

}

wkhtmltopdf 参数详解 :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
wkhtmltopdf [OPTIONS]... <input file> [More input files] <output file>
常规选项
--allow <path> 允许加载从指定的文件夹中的文件或文件(可重复)
--book* 设置一会打印一本书的时候,通常设置的选项
--collate 打印多份副本时整理
--cookie <name> <value> 设置一个额外的cookie(可重复)
--cookie-jar <path> 读取和写入的Cookie,并在提供的cookie jar文件
--copies <number> 复印打印成pdf文件数(默认为1)
--cover* <url> 使用HTML文件作为封面。它会带页眉和页脚的TOC之前插入
--custom-header <name> <value> 设置一个附加的HTTP头(可重复)
--debug-javascript 显示的javascript调试输出
--default-header* 添加一个缺省的头部,与页面的左边的名称,页面数到右边,例如: --header-left '[webpage]' --header-right '[page]/[toPage]' --header-line
--disable-external-links* 禁止生成链接到远程网页
--disable-internal-links* 禁止使用本地链接
--disable-javascript 禁止让网页执行JavaScript
--disable-pdf-compression* 禁止在PDF对象使用无损压缩
--disable-smart-shrinking* 禁止使用WebKit的智能战略收缩,使像素/ DPI比没有不变
--disallow-local-file-access 禁止允许转换的本地文件读取其他本地文件,除非explecitily允许用 --allow
--dpi <dpi> 显式更改DPI(这对基于X11的系统没有任何影响)
--enable-plugins 启用已安装的插件(如Flash
--encoding <encoding> 设置默认的文字编码
--extended-help 显示更广泛的帮助,详细介绍了不常见的命令开关
--forms* 打开HTML表单字段转换为PDF表单域
--grayscale PDF格式将在灰阶产生
--help Display help
--htmldoc 输出程序HTML帮助
--ignore-load-errors 忽略claimes加载过程中已经遇到了一个错误页面
--lowquality 产生低品质的PDF/ PS。有用缩小结果文档的空间
--manpage 输出程序手册页
--margin-bottom <unitreal> 设置页面下边距 (default 10mm)
--margin-left <unitreal> 将左边页边距 (default 10mm)
--margin-right <unitreal> 设置页面右边距 (default 10mm)
--margin-top <unitreal> 设置页面上边距 (default 10mm)
--minimum-font-size <int> 最小字体大小 (default 5)
--no-background 不打印背景
--orientation <orientation> 设置方向为横向或纵向
--page-height <unitreal> 页面高度 (default unit millimeter)
--page-offset* <offset> 设置起始页码 (default 1)
--page-size <size> 设置纸张大小: A4, Letter, etc.
--page-width <unitreal> 页面宽度 (default unit millimeter)
--password <password> HTTP验证密码
--post <name> <value> Add an additional post field (repeatable)
--post-file <name> <path> Post an aditional file (repeatable)
--print-media-type* 使用的打印介质类型,而不是屏幕
--proxy <proxy> 使用代理
--quiet Be less verbose
--read-args-from-stdin 读取标准输入的命令行参数
--readme 输出程序自述
--redirect-delay <msec> 等待几毫秒为JS-重定向(default 200)
--replace* <name> <value> 替换名称,值的页眉和页脚(可重复)
--stop-slow-scripts 停止运行缓慢的JavaScripts
--title <text> 生成的PDF文件的标题(第一个文档的标题使用,如果没有指定)
--toc* 插入的内容的表中的文件的开头
--use-xserver* 使用X服务器(一些插件和其他的东西没有X11可能无法正常工作)
--user-style-sheet <url> 指定用户的样式表,加载在每一页中
--username <username> HTTP认证的用户名
--version 输出版本信息退出
--zoom <float> 使用这个缩放因子 (default 1)

页眉和页脚选项
--header-center* <text> (设置在中心位置的页眉内容)
--header-font-name* <name> (default Arial) (设置页眉的字体名称)
--header-font-size* <size> (设置页眉的字体大小)
--header-html* <url> (添加一个HTML页眉,后面是网址)
--header-left* <text> (左对齐的页眉文本)
--header-line* (显示一条线在页眉下)
--header-right* <text> (右对齐页眉文本)
--header-spacing* <real> (设置页眉和内容的距离,默认0)
--footer-center* <text> (设置在中心位置的页脚内容)
--footer-font-name* <name> (设置页脚的字体名称)
--footer-font-size* <size> (设置页脚的字体大小default 11)
--footer-html* <url> (添加一个HTML页脚,后面是网址)
--footer-left* <text> (左对齐的页脚文本)
--footer-line* 显示一条线在页脚内容上)
--footer-right* <text> (右对齐页脚文本)
--footer-spacing* <real> (设置页脚和内容的距离)
./wkhtmltopdf --footer-right '[page]/[topage]' http://www.baidu.com baidu.pdf
./wkhtmltopdf --header-center '报表' --header-line --margin-top 2cm --header-line http://192.168.212.139/oma/ oma.pdf
表内容选项中
--toc-depth* <level> Set the depth of the toc (default 3)
--toc-disable-back-links* Do not link from section header to toc
--toc-disable-links* Do not link from toc to sections
--toc-font-name* <name> Set the font used for the toc (default Arial)
--toc-header-font-name* <name> The font of the toc header (if unset use --toc-font-name)
--toc-header-font-size* <size> The font size of the toc header (default 15)
--toc-header-text* <text> The header text of the toc (default Table Of Contents)
--toc-l1-font-size* <size> Set the font size on level 1 of the toc (default 12)
--toc-l1-indentation* <num> Set indentation on level 1 of the toc (default 0)
--toc-l2-font-size* <size> Set the font size on level 2 of the toc (default 10)
--toc-l2-indentation* <num> Set indentation on level 2 of the toc (default 20)
--toc-l3-font-size* <size> Set the font size on level 3 of the toc (default 8)
--toc-l3-indentation* <num> Set indentation on level 3 of the toc (default 40)
--toc-l4-font-size* <size> Set the font size on level 4 of the toc (default 6)
--toc-l4-indentation* <num> Set indentation on level 4 of the toc (default 60)
--toc-l5-font-size* <size> Set the font size on level 5 of the toc (default 4)
--toc-l5-indentation* <num> Set indentation on level 5 of the toc (default 80)
--toc-l6-font-size* <size> Set the font size on level 6 of the toc (default 2)
--toc-l6-indentation* <num> Set indentation on level 6 of the toc (default 100)
--toc-l7-font-size* <size> Set the font size on level 7 of the toc (default 0)
--toc-l7-indentation* <num> Set indentation on level 7 of the toc (default 120)
--toc-no-dots* Do not use dots, in the toc
轮廓选项
--dump-outline <file> 转储目录到一个文件
--outline 显示目录(文章中h1,h2来定)
--outline-depth <level> 设置目录的深度(默认为4)
页脚和页眉
* [page] 由当前正在打印的页的数目代替
* [frompage] 由要打印的第一页的数量取代
* [topage] 由最后一页要打印的数量取代
* [webpage] 通过正在打印的页面的URL替换
* [section] 由当前节的名称替换
* [subsection] 由当前小节的名称替换
* [date] 由当前日期系统的本地格式取代

Spring AOP的实现原理

原文出处: Listen

AOP(Aspect Orient Programming),我们一般称为面向方面(切面)编程,作为面向对象的一种补充,用于处理系统中分布于各个模块的横切关注点,比如事务管理、日志、缓存等等。AOP 实现的关键在于AOP框架自动创建的 AOP 代理,AOP 代理主要分为静态代理和动态代理,静态代理的代表为 AspectJ;而动态代理则以 Spring AOP 为代表。本文会分别对 AspectJSpring AOP 的实现进行分析和介绍。

一、使用 AspectJ 的编译时增强实现 AOP

之前提到,AspectJ 是静态代理的增强,所谓的静态代理就是 AOP 框架会在编译阶段生成 AOP 代理类,因此也称为编译时增强。

举个实例的例子来说。首先我们有一个普通的Hello类

1
2
3
4
5
6
7
8
9
10
public class Hello {
public void sayHello() {
System.out.println("hello");
}

public static void main(String[] args) {
Hello h = new Hello();
h.sayHello();
}
}

使用 AspectJ 编写一个 Aspect

1
2
3
4
5
6
7
public aspect TxAspect {
void around():call(void Hello.sayHello()){
System.out.println("开始事务 ...");
proceed();
System.out.println("事务结束 ...");
}
}

这里模拟了一个事务的场景,类似于 Spring 的声明式事务。使用 AspectJ 的编译器编译

1
ajc -d . Hello.java TxAspect.aj

编译完成之后再运行这个Hello类,可以看到以下输出

开始事务 ...
hello
事务结束 ...

显然,AOP已经生效了,那么究竟 AspectJ 是如何在没有修改Hello类的情况下为Hello类增加新功能的呢?
查看一下编译后的 Hello.class

1
2
3
4
5
6
7
8
9
10
11
12
13
public class Hello {
public Hello() {
}

public void sayHello() {
System.out.println("hello");
}

public static void main(String[] args) {
Hello h = new Hello();
sayHello_aroundBody1$advice(h, TxAspect.aspectOf(), (AroundClosure)null);
}
}

可以看到,这个类比原来的 Hello.java 多了一些代码,这就是 AspectJ 的静态代理,它会在编译阶段将 AspectJ 织入 Java 字节码中, 运行的时候就是经过增强之后的 AOP 对象。

1
2
3
4
5
public void ajc$around$com_listenzhangbin_aop_TxAspect$1$f54fe983(AroundClosure ajc$aroundClosure) {
System.out.println("开始事务 ...");
ajc$around$com_listenzhangbin_aop_TxAspect$1$f54fe983proceed(ajc$aroundClosure);
System.out.println("事务结束 ...");
}

从 Aspect 编译后的 class 文件可以更明显的看出执行的逻辑。proceed 方法就是回调执行被代理类中的方法。

二、使用Spring AOP

AspectJ 的静态代理不同,Spring AOP 使用的动态代理,所谓的动态代理就是说AOP框架不会去修改字节码,而是在内存中临时为方法生成一个 AOP 对象,这个 AOP 对象包含了目标对象的全部方法,并且在特定的切点做了增强处理,并回调原对象的方法。

Spring AOP 中的动态代理主要有两种方式,JDK 动态代理和 CGLIB 动态代理。JDK 动态代理通过反射来接收被代理的类,并且要求被代理的类必须实现一个接口。JDK 动态代理的核心是 InvocationHandler 接口和 Proxy 类。

如果目标类没有实现接口,那么 Spring AOP 会选择使用CGLIB来动态代理目标类。CGLIB(Code Generation Library),是一个代码生成的类库,可以在运行时动态的生成某个类的子类,注意,CGLIB是通过继承的方式做的动态代理,因此如果某个类被标记为 final,那么它是无法使用 CGLIB 做动态代理的。

为了验证以上的说法,可以做一个简单的测试。首先测试实现接口的情况。

定义一个接口

1
2
3
public interface Person {
String sayHello(String name);
}

实现类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Component
public class Chinese implements Person {

@Timer
@Override
public String sayHello(String name) {
System.out.println("-- sayHello() --");
return name + " hello, AOP";
}

public void eat(String food) {
System.out.println("我正在吃:" + food);
}

}

这里的 @Timer 注解是我自己定义的一个普通注解,用来标记 Pointcut。

定义Aspect

1
2
3
4
5
6
7
8
9
10
11
12
13
@Aspect
@Component
public class AdviceTest {

@Pointcut("@annotation(com.listenzhangbin.aop.Timer)")
public void pointcut() {
}

@Before("pointcut()")
public void before() {
System.out.println("before");
}
}

运行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@SpringBootApplication
@RestController
public class SpringBootDemoApplication {

//这里必须使用Person接口做注入
@Autowired
private Person chinese;

@RequestMapping("/test")
public void test() {
chinese.sayHello("listen");
System.out.println(chinese.getClass());
}

public static void main(String[] args) {
SpringApplication.run(SpringBootDemoApplication.class, args);
}
}

输出

1
2
3
before
-- sayHello() --
class com.sun.proxy.$Proxy53

可以看到类型是 com.sun.proxy.$Proxy53,也就是前面提到的 Proxy 类,因此这里 Spring AOP 使用了 JDK 的动态代理。

再来看看不实现接口的情况,修改 Chinese 类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Component
public class Chinese {

@Timer
// @Override
public String sayHello(String name) {
System.out.println("-- sayHello() --");
return name + " hello, AOP";
}

public void eat(String food) {
System.out.println("我正在吃:" + food);
}

}

运行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@SpringBootApplication
@RestController
public class SpringBootDemoApplication {

//直接用Chinese类注入
@Autowired
private Chinese chinese;

@RequestMapping("/test")
public void test() {
chinese.sayHello("listen");
System.out.println(chinese.getClass());
}

public static void main(String[] args) {
SpringApplication.run(SpringBootDemoApplication.class, args);
}
}

输出

1
2
3
before
-- sayHello() --
class com.listenzhangbin.aop.Chinese$$EnhancerBySpringCGLIB$$56b89168

可以看到类被 CGLIB 增强了,也就是动态代理。这里的 CGLIB 代理就是 Spring AOP 的代理,这个类也就是所谓的 AOP 代理,AOP 代理类在切点动态地织入了增强处理。

小结

AspectJ 在编译时就增强了目标对象,Spring AOP 的动态代理则是在每次运行时动态的增强,生成 AOP 代理对象,区别在于生成 AOP 代理对象的时机不同,相对来说 AspectJ 的静态代理方式具有更好的性能,但是 AspectJ 需要特定的编译器进行处理,而 Spring AOP 则无需特定的编译器处理。

spring boot+AOP+自定义注解记录日志

java 在 jdk1.5 中引入了注解,spring 框架也正好把 java 注解发挥得淋漓尽致。
下面会讲解 Spring 中自定义注解的简单流程。

前提

引入 maven 依赖:

1
2
3
4
 <dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>

一、自定义注解

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

/**
* 自定义 操作记录 注解
* 增加操作记录
* @author hanfeng
* @version 1.0
*/
@Target({ElementType.PARAMETER, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ActionRecord {
/**
* 自定义参数
* */
String parma;
/**
* 自定义参数 可以设置默认值
* */
String parma default "";

}

如上,一个自定义注解就已经定义结束,当然你也可以加上一些约束,比如只能加在类上,方法上等,这里不做举例。

二、解析注解

此处使用了spring 的 AOP(面向切面编程)特性,通过 @Aspect 注解使该类成为切面类,通过 @Pointcut 指定切入点 ,这里指定的切入点为 ActionRecord 注解类型,也就是被 @ActionRecord 注解修饰的方法,进入该切入点。

注解简述:

  • @Before 前置通知:在某连接点之前执行的通知,但这个通知不能阻止连接点之前的执行流程(除非它抛出一个异常)。
  • @Around 环绕通知:可以实现方法执行前后操作,需要在方法内执行point.proceed(); 并返回结果。
  • @AfterReturning 后置通知:在某连接点正常完成后执行的通知:例如,一个方法没有抛出任何异常,正常返回。
  • @AfterThrowing 异常通知:在方法抛出异常退出时执行的通知。
  • @After 后置通知:在某连接点正常完成后执行的通知:例如,一个方法没有抛出任何异常,正常返回。
  • 切入点参数说明:
    controllerAspect: 指定要扫描的包。
    @Pointcut中符号的解释:

    第一个 * 代表任意修饰符及任意返回值. 
    第二个 * 任意包名 
    第三个 * 代表任意方法. 
    第四个 * 定义在web包或者子包 
    第五个 * 任意方法 
    .. 匹配任意数量的参数.
    
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
import com.thunisoft.dzjz.commons.utils.UUIDUtils;
import com.thunisoft.dzjz.model.CzrzEntity;
import com.thunisoft.dzjz.service.CzrzService;
import javassist.*;
import javassist.bytecode.CodeAttribute;
import javassist.bytecode.LocalVariableAttribute;
import javassist.bytecode.MethodInfo;
import lombok.extern.log4j.Log4j;
import org.apache.commons.lang.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.MethodSignature;
import org.json.JSONArray;
import org.json.JSONObject;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Component;
import org.springframework.util.ObjectUtils;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.util.*;

/**
* 操作记录aop
*
* @author hanfeng
* @version 4.0
*/
@Aspect
@Component
@Log4j
public class CzrzAopAction {
//保证并发下 数据的纯洁
private static ThreadLocal<MetaData> metaDataContainer = ThreadLocal.withInitial(MetaData::new);

/**
* 获取开始时间
*/
private long BEGIN_TIME;
/**
* 获取结束时间
*/
private long END_TIME;
/**
* 获取监控的方法名称
*/
private String METHOD_NAME;
/**
* 定义本次log实体
*/
private List<CzrzEntity> czrzEntities = new ArrayList<>();

/**
* 切入点
*/
@Pointcut("execution(* com.thunisoft.dzjz.server.controller..*.*(..))")
private void controllerAspect() {
}

/**
* 前置通知
*/
@Before("controllerAspect()")
public void doBefore() {
BEGIN_TIME = System.currentTimeMillis();
}

/**
* 后置通知
*/
@After("controllerAspect()")
public void after() {
END_TIME = System.currentTimeMillis();
if (StringUtils.isNotBlank(METHOD_NAME)) {
log.debug("方法:【" + METHOD_NAME + "】运行时间为:" + (END_TIME - BEGIN_TIME));
}
}

/**
* 方法结束执行后的操作
*
* @param object response
*/
@AfterReturning(returning = "object", pointcut = "controllerAspect()")
public void doAfter(Object object) {
try {
//因为我的接口都是 RESTful 风格的 所以我才这么梳理 最终的验证视业务而定
ResponseEntity responseEntity = (ResponseEntity) object;
if (responseEntity.getStatusCode().is2xxSuccessful()) {
//保存记录 数据入库 不想用异步线程的 直接插入 保存方式视情况选择吧
CzrzRunner czrzRunner = new CzrzRunner(czrzEntities, czrzService);
czrzRunner.run();
for (CzrzEntity czrzEntity : czrzEntities) {
czrzService.addCzrz(czrzEntity);
}
}
} catch (Exception e) {
log.info("当前方法执行结束后拦截不到操作状态", e);
}

}

/**
* 方法有异常时的操作
*/
@AfterThrowing("controllerAspect()")
public void doAfterThrow() {
log.error("增加操作记录异常");
}

/**
* 环绕通知
*
* @param pjp 切片连接点
* @return 返回结果
* @throws Throwable 异常
*/
@Around("controllerAspect()")
public Object around(JoinPoint pjp) throws Throwable {
//自定义的当前线程的全局信息 可有可无 视业务增加
MetaData metaData = CzrzAopAction.metaDataContainer.get();
//获取被拦截的方法
Method method = getActionRecordMethod(metaData, pjp);
if (method == null) {
//log.info(……);
return;
}
//获取注解信息
ActionRecord actionRecord = method.getAnnotation(ActionRecord.class);
if (actionRecord == null) {
//log.info(……);
return;
}
// 默认不需要记录操作日志,如果有 ActionRecord 注解,说明需要记录日志
metaData.setNeedRecord(true);
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
metaData.fillReqInfo(request);

//获取请求中的参数 这里面包含了controller不需要的参数,需单独处理
String queryString = request.getQueryString();
//获取controller参数
Map<String, String[]> queryMap = dealQueryData(pjp, queryString);
//获取参数键值对
Map<String, String[]> queryRequestMap = request.getParameterMap();
//做去重处理 兼容各种参数形式
queryMap.putAll(queryRequestMap);
/**
*说明 :上面为什么样这么获取参数,是因为单存的用一种不能拦截到所有情况的参数。
*/

/**开始组装 日志实体信息 dosomeThing 这里不举例了
* 操作记录模板需要定制化的 要自己来实现模板数据的解析
* 如果 不同的功能记录不同的信息模板 则可以用下面的方式 否则只需要一条就ok
* 条件视业务定 如果定义了 metaData 需要将结果放入其中 如果没有 直接做入库操作即可
*/
switch (actionRecord.operation()) {
case 1:
dosomeThingToBuildczrzEntities;
metaData.czrzEntities.add(czrzEntity);
break;
case 2:
case 3:
dosomeThingToBuildczrzEntities;
metaData.czrzEntities.add(czrzEntity);
break;
default:
dosomeThingToBuildczrzEntities;
metaData.czrzEntities.add(czrzEntity);
break;
}
}
/**
* The type Meta data.
*/
@Data
private static class MetaData {……}

上面方法:getActionRecordMethod 的实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
/**
* 获取监控的方法
*
*
* @param metaData
* @param pjp 切片点
* @return 方法
*/
private Method getActionRecordMethod(MetaData metaData, JoinPoint pjp) {
// 拦截的方法名称。当前正在执行的方法
Signature sig = pjp.getSignature();
metaData.setMethodName(sig.getName());
// 拦截的放参数类型
if (!(sig instanceof MethodSignature)) {
throw new BadRequestException("添加操作记录失败。原因:@ActionRecord 该注解只能用于方法");
}
MethodSignature msig = (MethodSignature) sig;
Class[] parameterTypes = msig.getMethod().getParameterTypes();
try {
return pjp.getTarget().getClass().getMethod(sig.getName(), parameterTypes);
} catch (NoSuchMethodException e) {
log.warn("添加操作记录失败。原因:增加操作记录,监控方法时出现异常,方法名称:{}", sig.getName(), e);
return null;
}

}

上面方法: dealQueryData 的实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
 /**
* 处理拦截导弹参数和参数名称
*
* @param pjp 切片连接点
* @param queryString requset请求中参数
* @return 参数键值对
* @throws Exception 异常
*/
private Map<String, String[]> dealQueryData(JoinPoint pjp, String queryString) throws Exception {
//获取请求方法中的参数名称
String[] paramName = getFieldsName(pjp.getTarget().getClass().getName(), metaDataContainer.get().getMethodName());

// 拦截的controller方法参数值
Object[] args = pjp.getArgs();
if (ObjectUtils.isEmpty(paramName) || args.length != paramName.length) {
return new HashMap<>();
}
String paramterConnector = "&";
String paramterAssignment = "=";
Map<String, String[]> resultMap = new HashMap<>();
if (StringUtils.isNotBlank(queryString)) {
//这里获取到的结构是 key=value 的格式
String[] queryKeyAndValue = queryString.split(paramterConnector);
for (String parameter : queryKeyAndValue) {
//这里获取到的结构是 key & value 的值
String[] queryValue = parameter.split(paramterAssignment);
if (resultMap.containsKey(queryValue[0])) {
continue;
}
resultMap.put(queryValue[0], new String[] { queryValue[1] });
}
}
for (int i = 0; i < paramName.length; i++) {
if (resultMap.containsKey(paramName[i])) {
continue;
}
//做下特殊处理,有的解析后是个数组兼容下 可以视自己的业务来处理解析结果
if (args[i] instanceof String[]) {
resultMap.put(paramName[i], (String[]) args[i]);
} else if (args[i] instanceof String) {
resultMap.put(paramName[i], new String[] { args[i].toString() });
} else if(ObjectUtils.isEmpty(args[i])){
resultMap.put(paramName[i],new String[]{});
} else {
Object obj = args[i];
BeanInfo beanInfo = Introspector.getBeanInfo(obj.getClass());
PropertyDescriptor[] propertyDescriptors = beanInfo.getPropertyDescriptors();
for (PropertyDescriptor property : propertyDescriptors) {
String key = property.getName();
if (key.compareToIgnoreCase("class") == 0 || key.compareToIgnoreCase("writer") == 0) {
continue;
}
Method getter = property.getReadMethod();
Object value = getter != null ? getter.invoke(obj) : null;
String objV = ObjectUtils.isEmpty(value) ? "" : value.toString();
resultMap.put(key, new String[] { objV });
}
}
}
return resultMap;
}

方法:getFieldsName 的实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
/**
* 使用javassist来获取方法参数名称
*
* @param className 类名
* @param methodName 方法名
* @return 名字集合
* @throws Exception 异常
*/
private String[] getFieldsName(String className, String methodName) throws Exception {
Class<?> clazz = Class.forName(className);
String clazzName = clazz.getName();
ClassPool pool = ClassPool.getDefault();
ClassClassPath classPath = new ClassClassPath(clazz);
pool.insertClassPath(classPath);

CtClass ctClass = pool.get(clazzName);
CtMethod ctMethod = ctClass.getDeclaredMethod(methodName);
MethodInfo methodInfo = ctMethod.getMethodInfo();
CodeAttribute codeAttribute = methodInfo.getCodeAttribute();
LocalVariableAttribute attr = (LocalVariableAttribute) codeAttribute.getAttribute(LocalVariableAttribute.tag);
if (attr == null) {
String message = "添加操作记录失败。原因:拦截请求:【" + metaDataContainer.get().getMethodName() + " 】的方法中的参数名称失败";
log.warn(message);
throw new BadRequestException(message);
}
String[] paramsArgsName = new String[ctMethod.getParameterTypes().length];
int pos = Modifier.isStatic(ctMethod.getModifiers()) ? 0 : 1;
for (int i = 0; i < paramsArgsName.length; i++) {
paramsArgsName[i] = attr.variableName(i + pos);
}
if (ObjectUtils.isEmpty(paramsArgsName)) {
String message = "添加操作记录失败。原因:拦截请求:【" + metaDataContainer.get().getMethodName() + " 】的方法中的参数名称失败";
log.warn(message);
throw new BadRequestException(message);
}
return paramsArgsName;
}

小结

小编认为,如果没有特殊 @Before@Around@AfterReturning@AfterThrowing@After 这些功能只需要保留 @Before@AfterReturning@AfterThrowing 这三个就可以慢足需求,
@Before 中组装信息,
@AfterReturning 来确认数据是否需要保留,
@AfterThrowing 来处理异常情况。
使用 AOP 还是很方便的。

Thymeleaf API渲染模板

使用Thymeleaf API渲染模板生成静态页面

标签(空格分隔): Thymeleaf 静态页面


Thymeleaf 是新一代的Java模板引擎,它的语法对前端开发者友好可直接打开编辑,Spring Boot也建议使用它作为你的模板引擎,本文将演示如何使用它提供的API来渲染模板生成静态页面。

一、引入maven依赖

spring boot中引用:

1
2
3
4
5
<dependency>
<groupId>org.thymeleaf</groupId>
<artifactId>thymeleaf</artifactId>
<version>3.0.9.RELEASE</version>
</dependency>

或者:

1
2
3
4
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>

二、Java后台转换代码

  • 1、准备静态模板 templates/example.html
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    <!DOCTYPE html>
    <html lang="en">
    <head>
    <meta charset="UTF-8">
    <title>Title</title>
    </head>
    <body>
    <h1 th:text="${name}">列表名称</h1>
    <ul>
    <li th:each="item: ${array}" th:text="${item}">条目</li>
    </ul>
    </body>
    </html>
  • 2、获取静态资源 转换后 输出静态资源

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    //构造模板引擎
    ClassLoaderTemplateResolver resolver = new ClassLoaderTemplateResolver();
    //模板所在目录,相对于当前classloader的classpath。模板的路径
    resolver.setPrefix("templates/");
    //模板文件后缀
    resolver.setSuffix(".html");
    TemplateEngine templateEngine = new TemplateEngine();
    templateEngine.setTemplateResolver(resolver);

    //构造上下文(Model)
    Context context = new Context();
    context.setVariable("name", "蔬菜列表");
    context.setVariable("array", new String[]{"土豆", "番茄", "白菜", "芹菜"});

    //渲染模板 输出静态资源的位置
    FileWriter write = new FileWriter("result.html");

    //进行解析组装数据
    templateEngine.process("example", context, write);
  • 3、结果

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    <!DOCTYPE html>
    <html lang="en">
    <head>
    <meta charset="UTF-8">
    <title>Title</title>
    </head>
    <body>
    <h1>蔬菜列表</h1>
    <ul>
    <li>土豆</li>
    <li>番茄</li>
    <li>白菜</li>
    <li>芹菜</li>
    </ul>
    </body>
    </html>

三、直接返回属性值给前台页面,不需要后台转换

  • 1、准备静态页面 index.html

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    <!DOCTYPE html>
    <html lang="en">
    <head>
    <meta charset="UTF-8">
    <title>Title</title>
    </head>
    <body>
    <h1 th:text="${name}">列表名称</h1>
    <ul>
    <li th:each="item: ${array}" th:text="${item}">条目</li>
    </ul>
    </body>
    </html>
  • 2、后台控制器返回数据

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    import org.springframework.stereotype.Controller;
    import org.springframework.ui.Model;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RestController;

    import java.util.ArrayList;
    import java.util.List;
    @Controller
    @SpringBootApplication
    public class ThymeleafTestApplication {
    @RequestMapping("/")
    public String index(Model model){
    model.addAttribute("name","蔬菜列表");
    model.addAttribute("array",new String[]{"土豆", "番茄", "白菜", "芹菜"});
    return "index";
    }
    public static void main(String[] args) {
    SpringApplication.run(ThymeleafTestApplication.class, args);
    }
    }
  • 3、结果

四、错误解析

如果报以下错误,是以为缺少 ognl.jar 包,使得解析器在处理html中的表达式时失败。

  • 1、异常信息

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    java.lang.NoClassDefFoundError: ognl/PropertyAccessor

    at org.thymeleaf.standard.StandardDialect.getVariableExpressionEvaluator(StandardDialect.java:179)
    at org.thymeleaf.standard.StandardDialect.getExecutionAttributes(StandardDialect.java:393)
    at org.thymeleaf.DialectSetConfiguration.build(DialectSetConfiguration.java:263)
    at org.thymeleaf.EngineConfiguration.<init>(EngineConfiguration.java:123)
    at org.thymeleaf.TemplateEngine.initialize(TemplateEngine.java:336)
    at org.thymeleaf.TemplateEngine.process(TemplateEngine.java:1079)
    at org.thymeleaf.TemplateEngine.process(TemplateEngine.java:1067)
    at com.thunisoft.dzjz.conver.handle.impl.AbstractBuildCoverImpl.buildCover(AbstractBuildCoverImpl.java:92)
    at com.thunisoft.dzjz.conver.handle.IBuildCoverHandleTest.buildCover_volume(IBuildCoverHandleTest.java:73)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
    at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
    at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
    at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
    at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:26)
    at org.springframework.test.context.junit4.statements.RunBeforeTestMethodCallbacks.evaluate(RunBeforeTestMethodCallbacks.java:75)
    at org.springframework.test.context.junit4.statements.RunAfterTestMethodCallbacks.evaluate(RunAfterTestMethodCallbacks.java:86)
    at org.springframework.test.context.junit4.statements.SpringRepeat.evaluate(SpringRepeat.java:84)
    at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
    at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:252)
    at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:94)
    at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
    at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
    at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
    at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
    at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
    at org.springframework.test.context.junit4.statements.RunBeforeTestClassCallbacks.evaluate(RunBeforeTestClassCallbacks.java:61)
    at org.springframework.test.context.junit4.statements.RunAfterTestClassCallbacks.evaluate(RunAfterTestClassCallbacks.java:70)
    at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
    at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.run(SpringJUnit4ClassRunner.java:191)
    at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
    at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:68)
    at com.intellij.rt.execution.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:47)
    at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:242)
    at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:70)
    Caused by: java.lang.ClassNotFoundException: ognl.PropertyAccessor
    at java.net.URLClassLoader.findClass(URLClassLoader.java:381)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:424)
    at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:331)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
    ... 38 more
  • 2、解决方法 pom 中引入 ognl.jar 即可。

    1
    2
    3
    4
    5
    <dependency>
    <groupId>ognl</groupId>
    <artifactId>ognl</artifactId>
    <version>3.2.4</version>
    </dependency>

SMB 协议的使用(原创)

1、什么是 SMB

SMB(Server Message Block)通信协议是微软(Microsoft)和英特尔(Intel)在1987年制定的协议,主要是作为Microsoft网络的通讯协议。SMB 是在会话层(session layer)和表示层(presentation layer)以及小部分应用层(application layer)的协议。

SMB使用了NetBIOS的应用程序接口 (Application Program Interface,简称API)。另外,它是一个开放性的协议,允许了协议扩展——使得它变得更大而且复杂;大约有65个最上层的作业,而每个作业都超过120个函数,甚至Windows NT也没有全部支持到,最近微软又把 SMB 改名为 CIFS(Common Internet File System),并且加入了许多新的特色。

2、为什么使用 SMB 协议实现文件的读取

其实 SMB 协议使用非常简单,它方便了我们对共享文件(夹)的操作,我们不在需要知道具体的盘符,只要知道 IP(地址) PORT(端口) Folder(共享文件夹)即可。

3、SMB协议的两种连接方式

  • 1、访问和验证登录信息写在一起
    1
    smb://{user}:{password}@{host}/{path}

参数说明:

参数 含义
user 用户名(一般都是登录服务器所需的用户名)
password 密码(一般都是登录服务器所需的密码,和用户名配套)
host 服务器ip
path 要访问的资源名

eg:

1
2
String url="smb://hanfeng:123@192.168.0.22/share";
SmbFile smbFile = new SmbFile(url);

注意:这种方式存在一个坑,当你的用户名或者密码中存在特殊字符时,解析会有问题,
比如像我们通用的密码会有个 @ 符号,但是在 SmbFile 解析时,这个 @ 符号后面跟的应该是 host,这样就导致他解析的结果和我们实际想要连接的不符。
那么它是怎们解析的url呢?
SmbFile 中包含的方法:

2

3

4

SmbFile 我们所使用的构造器的源码:

1
2
3
public SmbFile( String url ) throws MalformedURLException {
this( new URL( null, url, Handler.SMB_HANDLER ));
}

URL 源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
public URL(URL context, String spec, URLStreamHandler handler)
throws MalformedURLException
{
String original = spec;
int i, limit, c;
int start = 0;
String newProtocol = null;
boolean aRef=false;
boolean isRelative = false;

// Check for permission to specify a handler
if (handler != null) {
SecurityManager sm = System.getSecurityManager();
if (sm != null) {
checkSpecifyHandler(sm);
}
}

try {
limit = spec.length();
while ((limit > 0) && (spec.charAt(limit - 1) <= ' ')) {
limit--; //eliminate trailing whitespace
}
while ((start < limit) && (spec.charAt(start) <= ' ')) {
start++; // eliminate leading whitespace
}

if (spec.regionMatches(true, start, "url:", 0, 4)) {
start += 4;
}
if (start < spec.length() && spec.charAt(start) == '#') {
/* we're assuming this is a ref relative to the context URL.
* This means protocols cannot start w/ '#', but we must parse
* ref URL's like: "hello:there" w/ a ':' in them.
*/
aRef=true;
}
for (i = start ; !aRef && (i < limit) &&
((c = spec.charAt(i)) != '/') ; i++) {
if (c == ':') {

String s = spec.substring(start, i).toLowerCase();
if (isValidProtocol(s)) {
newProtocol = s;
start = i + 1;
}
break;
}
}

// Only use our context if the protocols match.
protocol = newProtocol;
if ((context != null) && ((newProtocol == null) ||
newProtocol.equalsIgnoreCase(context.protocol))) {
// inherit the protocol handler from the context
// if not specified to the constructor
if (handler == null) {
handler = context.handler;
}

// If the context is a hierarchical URL scheme and the spec
// contains a matching scheme then maintain backwards
// compatibility and treat it as if the spec didn't contain
// the scheme; see 5.2.3 of RFC2396
if (context.path != null && context.path.startsWith("/"))
newProtocol = null;

if (newProtocol == null) {
protocol = context.protocol;
authority = context.authority;
userInfo = context.userInfo;
host = context.host;
port = context.port;
file = context.file;
path = context.path;
isRelative = true;
}
}

if (protocol == null) {
throw new MalformedURLException("no protocol: "+original);
}

// Get the protocol handler if not specified or the protocol
// of the context could not be used
if (handler == null &&
(handler = getURLStreamHandler(protocol)) == null) {
throw new MalformedURLException("unknown protocol: "+protocol);
}

this.handler = handler;

i = spec.indexOf('#', start);
if (i >= 0) {
ref = spec.substring(i + 1, limit);
limit = i;
}

/*
* Handle special case inheritance of query and fragment
* implied by RFC2396 section 5.2.2.
*/
if (isRelative && start == limit) {
query = context.query;
if (ref == null) {
ref = context.ref;
}
}

handler.parseURL(this, spec, start, limit);

} catch(MalformedURLException e) {
throw e;
} catch(Exception e) {
MalformedURLException exception = new MalformedURLException(e.getMessage());
exception.initCause(e);
throw exception;
}
}

parseURL 源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
protected void parseURL(URL u, String spec, int start, int limit) {
// These fields may receive context content if this was relative URL
String protocol = u.getProtocol();
String authority = u.getAuthority();
String userInfo = u.getUserInfo();
String host = u.getHost();
int port = u.getPort();
String path = u.getPath();
String query = u.getQuery();

// This field has already been parsed
String ref = u.getRef();

boolean isRelPath = false;
boolean queryOnly = false;

// FIX: should not assume query if opaque
// Strip off the query part
if (start < limit) {
int queryStart = spec.indexOf('?'); // 这里——hf
queryOnly = queryStart == start;
if ((queryStart != -1) && (queryStart < limit)) {
query = spec.substring(queryStart+1, limit);
if (limit > queryStart)
limit = queryStart;
spec = spec.substring(0, queryStart);
}
}

int i = 0;
// Parse the authority part if any
boolean isUNCName = (start <= limit - 4) &&
(spec.charAt(start) == '/') &&
(spec.charAt(start + 1) == '/') &&
(spec.charAt(start + 2) == '/') &&
(spec.charAt(start + 3) == '/'); // 这里——hf
if (!isUNCName && (start <= limit - 2) && (spec.charAt(start) == '/') &&
(spec.charAt(start + 1) == '/')) {
start += 2;
i = spec.indexOf('/', start);
if (i < 0) {
i = spec.indexOf('?', start);
if (i < 0)
i = limit;
}

host = authority = spec.substring(start, i);

int ind = authority.indexOf('@'); // 这里——hf
if (ind != -1) {
userInfo = authority.substring(0, ind);
host = authority.substring(ind+1);
} else {
userInfo = null;
}
if (host != null) {
// If the host is surrounded by [ and ] then its an IPv6
// literal address as specified in RFC2732
if (host.length()>0 && (host.charAt(0) == '[')) {
if ((ind = host.indexOf(']')) > 2) {

String nhost = host ;
host = nhost.substring(0,ind+1);
if (!IPAddressUtil.
isIPv6LiteralAddress(host.substring(1, ind))) {
throw new IllegalArgumentException(
"Invalid host: "+ host);
}

port = -1 ;
if (nhost.length() > ind+1) {
if (nhost.charAt(ind+1) == ':') {
++ind ;
// port can be null according to RFC2396
if (nhost.length() > (ind + 1)) {
port = Integer.parseInt(nhost.substring(ind+1));
}
} else {
throw new IllegalArgumentException(
"Invalid authority field: " + authority);
}
}
} else {
throw new IllegalArgumentException(
"Invalid authority field: " + authority);
}
} else {
ind = host.indexOf(':');
port = -1;
if (ind >= 0) {
// port can be null according to RFC2396
if (host.length() > (ind + 1)) {
port = Integer.parseInt(host.substring(ind + 1));
}
host = host.substring(0, ind);
}
}
} else {
host = "";
}
if (port < -1)
throw new IllegalArgumentException("Invalid port number :" +
port);
start = i;
// If the authority is defined then the path is defined by the
// spec only; See RFC 2396 Section 5.2.4.
if (authority != null && authority.length() > 0)
path = "";
}

if (host == null) {
host = "";
}

// Parse the file path if any
if (start < limit) {
if (spec.charAt(start) == '/') {
path = spec.substring(start, limit);
} else if (path != null && path.length() > 0) {
isRelPath = true;
int ind = path.lastIndexOf('/');
String seperator = "";
if (ind == -1 && authority != null)
seperator = "/";
path = path.substring(0, ind + 1) + seperator +
spec.substring(start, limit);

} else {
String seperator = (authority != null) ? "/" : "";
path = seperator + spec.substring(start, limit);
}
} else if (queryOnly && path != null) {
int ind = path.lastIndexOf('/');
if (ind < 0)
ind = 0;
path = path.substring(0, ind) + "/";
}
if (path == null)
path = "";

if (isRelPath) {
// Remove embedded /./
while ((i = path.indexOf("/./")) >= 0) {
path = path.substring(0, i) + path.substring(i + 2);
}
// Remove embedded /../ if possible
i = 0;
while ((i = path.indexOf("/../", i)) >= 0) {
/*
* A "/../" will cancel the previous segment and itself,
* unless that segment is a "/../" itself
* i.e. "/a/b/../c" becomes "/a/c"
* but "/../../a" should stay unchanged
*/
if (i > 0 && (limit = path.lastIndexOf('/', i - 1)) >= 0 &&
(path.indexOf("/../", limit) != 0)) {
path = path.substring(0, limit) + path.substring(i + 3);
i = 0;
} else {
i = i + 3;
}
}
// Remove trailing .. if possible
while (path.endsWith("/..")) {
i = path.indexOf("/..");
if ((limit = path.lastIndexOf('/', i - 1)) >= 0) {
path = path.substring(0, limit+1);
} else {
break;
}
}
// Remove starting .
if (path.startsWith("./") && path.length() > 2)
path = path.substring(2);

// Remove trailing .
if (path.endsWith("/."))
path = path.substring(0, path.length() -1);
}

setURL(u, protocol, host, port, authority, userInfo, path, query, ref);
}

可以看到上面注释 “这里——hf” 的地方,特殊符号是写死的。这就是为什么特殊字符会有坑的存在。

  • 2 访问和验证登录信息分开
    分别定义 userInfo 和 url。
    eg:
    1
    2
    3
    4
    5
    6
    String userName = "hanfeng";
    String passWord = "123";
    String domainIp = "172.16.192.156";
    String url = "Smb://172.16.192.156/share";
    NtlmPasswordAuthentication auth = new NtlmPasswordAuthentication(domainIp, userName, passWord);
    SmbFile smbFile = new SmbFile(url, auth);

构造器源码:

1
2
3
4
public SmbFile( String url, NtlmPasswordAuthentication auth )
throws MalformedURLException {
this( new URL( null, url, Handler.SMB_HANDLER ), auth );
}

this调用的源:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
public SmbFile( URL url, NtlmPasswordAuthentication auth ) {
super( url );
this.auth = auth == null ? new NtlmPasswordAuthentication( url.getUserInfo() ) : auth;

getUncPath0();
}
SmbFile( SmbFile context, String name, int type,
int attributes, long createTime, long lastModified, long size )
throws MalformedURLException, UnknownHostException {
this( context.isWorkgroup0() ?
new URL( null, "smb://" + name + "/", Handler.SMB_HANDLER ) :
new URL( context.url, name + (( attributes & ATTR_DIRECTORY ) > 0 ? "/" : "" )));

/* why was this removed before? DFS? copyTo? Am I going around in circles? */
auth = context.auth;


if( context.share != null ) {
this.tree = context.tree;
this.dfsReferral = context.dfsReferral;
}
int last = name.length() - 1;
if( name.charAt( last ) == '/' ) {
name = name.substring( 0, last );
}
if( context.share == null ) {
this.unc = "\\";
} else if( context.unc.equals( "\\" )) {
this.unc = '\\' + name;
} else {
this.unc = context.unc + '\\' + name;
}
/* why? am I going around in circles?
* this.type = type == TYPE_WORKGROUP ? 0 : type;
*/
this.type = type;
this.attributes = attributes;
this.createTime = createTime;
this.lastModified = lastModified;
this.size = size;
isExists = true;

attrExpiration = sizeExpiration =
System.currentTimeMillis() + attrExpirationPeriod;
}

4、实现文件的读写

那么上面我们已将连接好了我们想要连接的服务器目录,如何实现文件的读写呢?
要实现读写要有个前提,就是在目标服务器上给我们当前使用用户授予读写的权限,否则是会出问题的。

一般我们读取文件都是用 FileInputStream,那么smbFile可不可以使用 FileInputStream来读取呢?
6

那么怎么解决?????

在 InputStream 下有个子类(非 jdk 提供,或者可以说是个人实现的),叫 SmbFileInputStream
我们来试下这个是不是好用:
7
没有问题,是可以的,我们来看下 SmbFileInputStream 的实现:

8
我们可以清楚的看到 SmbFileInputStream 实际上也是继承了 InputStream
这样我们就可以根绝我们熟悉的 InputStream 去操作流了。

但是 SmbFile 不能用并发来实现,因为没有意义,他的底层全部使用了 synchronized ,即使你用并发,也是放在队列里,一条一条处理。想了解细节的可以去看下源码,这里就不过多说了。

5、最后附上完整的读写示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
 
package com.thunisoft.fy.dzjz.fiveimport.service;

import java.io.InputStream;
import java.io.OutputStream;

import jcifs.smb.NtlmPasswordAuthentication;
import jcifs.smb.SmbFile;
import jcifs.smb.SmbFileInputStream;
import jcifs.smb.SmbFileOutputStream;

import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.StringUtils;

public class SmbFileRead {

/**smb协议头*/
private final String SMB_URL_TOP = "smb://";

/**用户名*/
private final String SMB_USER = "hanfeng";

/**斜杠*/
private final String SMB_URL_SLANSH = "/";

/**密码*/
private final String SMB_PASSWORD = "123";

/**base folder*/
private final String SMB_BASE_FOLDER_NAME = "share";

/**IP*/
private String SMB_IP = "172.16.192.156";

/**url*/
private String SMB_URL = StringUtils.EMPTY;

/**用户信息*/
private NtlmPasswordAuthentication auth = null;

public void init() {
if (null == this.auth) {
this.auth = new NtlmPasswordAuthentication(SMB_IP, SMB_USER, SMB_PASSWORD);
}
if (StringUtils.isBlank(this.SMB_URL)) {
this.SMB_URL = SMB_URL_TOP + SMB_IP + SMB_URL_SLANSH + SMB_BASE_FOLDER_NAME;
}
}

/**
* 读
* @return
* @throws Exception
*/
public InputStream openFile() throws Exception {
init();
SmbFile smbFile = new SmbFile(this.SMB_URL, this.auth);
// SmbFile outSmbFile=new SmbFile(this.SMB_URL,this.auth);
// smbFile.createNewFile(); //创建文件
// smbFile.copyTo(outSmbFile); //将smbFile copy 到 outSmbFile
// smbFile.mkdirs(); //创建目录(还有 mkdir。mkdirs 支持多级创建,mkdir不支持)
// smbFile.listFiles(); //获取子文件/文件夹
return new SmbFileInputStream(smbFile);
}

public void writeFile() throws Exception {
InputStream in = openFile();
SmbFile smbFile = new SmbFile(this.SMB_URL, this.auth);//目标地址 这里我就不改了 人懒 ^V^.
/**
* 验证文件 或者文件夹是否存在 不存在先创建
*/
if (!smbFile.exists()) {
smbFile.createNewFile();
// 文件夹创建
smbFile.mkdirs();
//注:穿件文件不能包含目录创建,需要先创建目录在创建文件
}
OutputStream out = new SmbFileOutputStream(smbFile);
IOUtils.copy(in, out);
//因为 IOUtils 中没有给刷新输出流,也没有关流 ,所以需要自己来处理
out.flush();
out.close();
in.close();
/**
* 一般情况下是:先打开的后关闭,后打开的先关闭
* 另一种情况:看依赖关系,如果流a依赖流b,应该先关闭流a,再关闭流b
* 例如处理流a依赖节点流b,应该先关闭处理流a,再关闭节点流b
* 当然完全可以只关闭处理流,不用关闭节点流。处理流关闭的时候,会调用其处理的节点流的关闭方法
* 如果将节点流关闭以后再关闭处理流,会抛出IO异常
*/

}

}

转载请说明出处http://hanfeng.me/2018/01/21/SMB%20%E5%8D%8F%E8%AE%AE%E7%9A%84%E4%BD%BF%E7%94%A8%EF%BC%88%E5%8E%9F%E5%88%9B%EF%BC%89/;
欢迎拍砖。

Maven 常用命令


Maven库:http://repo2.maven.org/maven2/
Maven依赖查询:http://mvnrepository.com/

  1. 创建Maven的普通java项目:

    1
    2
    3
    mvn archetype:create 
    -DgroupId=packageName
    -DartifactId=projectName
  1. 创建Maven的Web项目:

    1
    2
    3
    4
    mvn archetype:create 
    -DgroupId=packageName
    -DartifactId=webappName
    -DarchetypeArtifactId=maven-archetype-webapp
  1. 编译源代码:

    1
    mvn compile
  2. 编译测试代码:

    1
    mvn test-compile
  3. 运行测试:

    1
    mvn test
  4. 产生site:

    1
    mvn site
  5. 打包:

    1
    mvn package
  6. 在本地Repository中安装jar:

    1
    mvn install
  7. 清除产生的项目:

    1
    mvn clean
  8. 生成eclipse项目:

    1
    mvn eclipse:eclipse
  9. 生成idea项目:

    1
    mvn idea:idea
  10. 组合使用goal命令,如只打包不测试:

    1
    mvn -Dtest package
  11. 编译测试的内容:

    1
    mvn test-compile
  12. 只打jar包:

    1
    mvn jar:jar
  13. 只测试而不编译,也不测试编译:

    1
    2
    mvn test -skipping compile -skipping test-compile 
    ( -skipping 的灵活运用,当然也可以用于其他组合命令)
  14. 清除eclipse的一些系统设置:

    1
    mvn eclipse:clean

一般使用情况是这样,首先通过 cvs 或 svn 下载代码到本机,然后执行mvn eclipse:eclipse生成ecllipse项目文件,然后导入到eclipse就行了;修改代码后执行mvn compile或mvn test检验,也可以下载eclipse的maven插件。

  1. Maven 的其他的命令
参数 含义
mvn -version/-v 显示版本信息
mvn archetype:generate 创建 mvn项目
mvn archetype:create -DgroupId=com.oreilly -DartifactId=my-app 创建 mvn项目
mvn package 生成target目录,编译、测试代码,生成测试报告,生成jar/war文件
mvn jetty:run 运行项目于jetty上,
mvn compile 编译
mvn test 编译并测试
mvn clean 清空生成的文件
mvn site 生成项目相关信息的网站
mvn -Dwtpversion=1.0 eclipse:eclipse 生成Wtp插件的Web项目
mvn -Dwtpversion=1.0 eclipse:clean 清除Eclipse项目的配置信息(Web项目)
mvn eclipse:eclipse 将项目转化为Eclipse项目
mvn -e 显示详细错误 信息.
mvn validate 验证工程是否正确,所有需要的资源是否可用。
mvn test-compile 编译项目测试代码。 。
mvn integration-test 在集成测试可以运行的环境中处理和发布包。
mvn verify 运行任何检查,验证包是否有效且达到质量标准。
mvn generate-sources 产生应用需要的任何额外的源代码,如xdoclet。
mvn help:describe -Dplugin=help 使用 help 插件的 describe 目标来输出Maven Help 插件的信息。
mvn help:describe -Dplugin=help -Dfull 使用Help插件输出完整的带有参数的目标列
mvn help:describe -Dplugin=compiler -Dmojo=compile -Dfull 获取单个目标的信息,设置 mojo 参数和 plugin 参数。此命令列出了Compiler插件的compile 目标的所有信息
mvn help:describe -Dplugin=exec -Dfull 列出所有 Maven Exec 插件可用的目标
mvn help:effective-pom 看这个“有效的 (effective)”POM,它暴露了Maven的默认设置
mvn archetype:create -DgroupId=org.sonatype.mavenbook.ch03 -DartifactId=simple -DpackageName=org.sonatype.mavenbook 创建Maven的普通java项目,在命令行使用Maven Archetype 插件
mvn exec:java -Dexec.mainClass=org.sonatype.mavenbook.weather.Main Exec 插件让我们能够在不往 classpath 载入适当的依赖的情况下,运行这个程序
mvn dependency:resolve 打印出已解决依赖的列表
mvn dependency:tree 打印整个依赖树
mvn install -X 想要查看完整的依赖踪迹,包含那些因为冲突或者其它原因而被拒绝引入的构件,打开 Maven 的调试标记运行
mvn install -Dmaven.test.skip=true 给任何目标添加maven.test.skip属性就能跳过测试
mvn install assembly:assembly 构建装配Maven Assembly插件是一个用来创建你应用程序特有分发包的插件
mvn jetty:run 调用 Jetty 插件的 Run 目标在 Jetty Servlet 容器中启动 web 应用
mvn compile 编译你的项目
mvn clean install 删除再编译
mvn hibernate3:hbm2ddl 使用 Hibernate3 插件构造数据库
  1. 在应用程序用使用多个存储库

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    <repositories>    
    <repository>
    <id>Ibiblio</id>
    <name>Ibiblio</name>
    <url>http://www.ibiblio.org/maven/</url>
    </repository>
    <repository>
    <id>PlanetMirror</id>
    <name>Planet Mirror</name>
    <url>http://public.planetmirror.com/pub/maven/</url>
    </repository>
    </repositories>
1
2
3
4
5
6
7
8
mvn deploy:deploy-file 
-DgroupId=com
-DartifactId=client
-Dversion=0.1.0
-Dpackaging=jar
-Dfile=d:\client-0.1.0.jar
-DrepositoryId=maven-repository-inner
-Durl=ftp://xxxxxxx/opt/maven/repository/
  1. 发布第三方Jar到本地库中
1
2
3
4
5
6
7
8
mvn install:install-file 
-DgroupId=com
-DartifactId=client
-Dversion=0.1.0
-Dpackaging=jar
-Dfile=d:\client-0.1.0.jar
-DdownloadSources=true
-DdownloadJavadocs=true

表达式语言_ spring

Spring Espresdsion Language

Spring表达式语言(简称SpEL)是一个支持查询并在运行时操纵一个对象图的功能强大的表达式语言。SpEL语言的语法类似于统一EL,但提供了更多的功能,最主要的是显式方法调用和基本字符串模板函数。

同很多可用的Java 表达式语言相比,例如OGNL,MVEL和JBoss EL,SpEL的诞生是为了给Spring社区提供一个可以给Spring目录中所有产品提供单一良好支持的表达式语言。其语言特性由Spring目录中的项目需求驱动,包括基于eclipse的SpringSource套件中的代码补全工具需求。也就是说,SpEL是一个基于技术中立的API允许需要时与其他表达式语言集成。

SpEL作为Spring目录中表达式求值的基础,它并不是直接依赖于Spring而是可以被独立使用。为了能够自包含,本章中的许多示例把SpEL作为一个独立的表达式语言来使用。这就需要创建一些如解析器的引导基础组件类。大多数Spring用户只需要为求值编写表达式字符串而不需要关心这些基础组件

SpEL 的功能特性:

  • 字符表达式
  • 布尔和关系操作符
  • 正则表达式
  • 类表达式
  • 访问properties,arrays,lists,maps
  • 方法调用
  • 关系操作符
  • 赋值
  • 调用构造器
  • 三元操作符
  • 变量
  • 用户自定义函数
  • 集合投影
  • 集合选择
  • 模板表达式

    使用 spring 表达式语言

  1. 新建一个Maven Web项目,添加依赖,pom.xml如下所示:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
<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.ceshi.test</groupId>
<artifactId>Spring053</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>

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

<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<spring.version>4.3.0.RELEASE</spring.version>
</properties>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
<version>4.10</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.8.9</version>
</dependency>
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.2.4</version>
</dependency>
</dependencies>
</project>
  1. 为了IOC,定义了用户类User.java与Order.java,如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
package com.ceshi.test.Spring053;

/**
* 订单类
*
*/
public class Order {
/**
* 订单名称
*/
private String orderName;
/*
* 用户姓名
*/
private String userName;
/**
* 用户对象
*/
private User customer;

public String getOrderName() {
return orderName;
}

public void setOrderName(String orderName) {
this.orderName = orderName;
}

public User getCustomer() {
return customer;
}

public void setCustomer(User customer) {
this.customer = customer;
}

public String getUserName() {
return userName;
}

public void setUserName(String userName) {
this.userName = userName;
}

@Override
public String toString() {
return "订单名:"+this.getOrderName()+",姓名:"+this.getUserName()+",编号:"+this.getCustomer().getId();
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
package com.ceshi.test.Spring053.spel01;

/**
* 订单类
*
*/
public class Order {
/**
* 订单名称
*/
private String orderName;
/*
* 用户姓名
*/
private String userName;
/**
* 用户对象
*/
private User customer;

public String getOrderName() {
return orderName;
}

public void setOrderName(String orderName) {
this.orderName = orderName;
}

public User getCustomer() {
return customer;
}

public void setCustomer(User customer) {
this.customer = customer;
}

public String getUserName() {
return userName;
}

public void setUserName(String userName) {
this.userName = userName;
}
}
  1. 编写容器初始化的配置文件spel01.xml,内容如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<?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:p="http://www.springframework.org/schema/p"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-4.3.xsd">

<bean id="gyl" class="com.ceshi.test.Spring053.spel01.User" p:id="9527">
<property name="name" value="张三">
</property>
</bean>

<bean id="order001" class="com.ceshi.test.Spring053.spel01.Order">
<property name="customer" ref="gyl"></property>
<property name="name" value="#{gyl.name}"></property>
<property name="orderName" value='#{"Apples".toUpperCase()}'></property>
</bean>

</beans>

复制代码
在配置文件中,出现了#{}形式的表达式,我们就称为Spel表达式。#{gyl.name}作用是找到名称为gyl的bean取出中间的name值;#{“Apples”.toUpperCase()}把字符串Apples转换成大写并输出。

  1. 取出bean测试
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    package com.ceshi.test.Spring053.spel01;

    import org.springframework.context.ApplicationContext;
    import org.springframework.context.support.ClassPathXmlApplicationContext;

    public class Test {

    public static void main(String[] args) {
    ApplicationContext ctx=new ClassPathXmlApplicationContext("spel01.xml");
    Order order=ctx.getBean("order001",Order.class);
    System.out.println(order);
    }
    }

    //运行结果:订单名:APPLES,姓名:张三,编号:9527

SpEL表达式Hello World!

Spring表达式语言(SpEL)从3.X开始支持,它是一种能够支持运行时查询和操作对象图的强大的表达式,其表达式语法类似于统一表达式语言。

SpEL支持如下表达式:

  • 基本表达式:字面量表达式、关系,逻辑与算数运算表达式、字符串连接及截取表达式、三目运算、正则表达式、括号优先级表达式;
  • 类相关表达式:类类型表达式、类实例化、instanceof表达式、变量定义及引用、赋值表达式、自定义函数、对象属性存取及安全导航表达式、对象方法调用、Bean引用;
  • 集合相关表达式:内联List、内联数组、集合,字典访问、列表,字典,数组修改、集合投影、集合选择;不支持多维内联数组初始化;不支持内联字典定义;
  • 其他表达式:模板表达式.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package com.ceshi.test.Spring053.spel02;

import org.springframework.expression.Expression;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;

public class Test {

public static void main(String[] args) {
//创建SpEL表达式的解析器
ExpressionParser parser=new SpelExpressionParser();
//解析表达式'Hello '+' World!'
Expression exp=parser.parseExpression("'Hello '+' World!'");
//取出解析结果
String result=exp.getValue().toString();
//输出结果
System.out.println(result);
}
}

SpEL 表达式的运用

支持的文字表达的类型是字符串,日期,数值(整型,实型,和十六进制),布尔和空。字符串是由单引号分隔。使用反斜杠字符转移把一个单引号字符本身放在字符串中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
//创建SpEL表达式的解析器
ExpressionParser ep= new SpelExpressionParser();
System.out.println(ep.parseExpression("'HelloWorld'").getValue());
System.out.println(ep.parseExpression("0xffffff").getValue());
System.out.println(ep.parseExpression("1.234345e+3").getValue());
System.out.println(ep.parseExpression("new java.util.Date()").getValue());

ExpressionParser parser=new SpelExpressionParser();
User user=new User(9527,"周星驰");
//解析表达式需要的上下文,解析时有一个默认的上下文
EvaluationContext ctx = new StandardEvaluationContext();
//在上下文中设置变量,变量名为user,内容为user对象
ctx.setVariable("user", user);
//从用户对象中获得id,获得解析后的值在ctx上下文中
//User类在前面已定义,这里增加了一个有参构造方法。上面的示例是调用方法,其实可以这样:引用对象属性,只需使用一个句点来表示一个嵌套的属性值
int id = (Integer) parser.parseExpression("#user.getId()").getValue(ctx);

System.out.println(id);

//运行结果HelloWorld
16777215
1234.345
Fri Jul 01 14:50:59 CST 2017

9527

数组:

1
2
3
4
5
String[] students=new String[]{"tom","jack","rose","mark","lucy"};
ctx.setVariable("students", students);
String student = parser.parseExpression("#students[3]").getValue(ctx,
String.class);
System.out.println(student);

大致使用都是类似的 parser.parseExpression(“ 表达式 “).getValue( 上下文, class);

parser.parseExpression(“ 表达式 “).getValue(class);

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
/**关系运算符*/
//true
boolean trueValue1 = parser.parseExpression("2 == 2").getValue(Boolean.class);
//false
boolean falseValue1 = parser.parseExpression("2 < -5.0").getValue(Boolean.class);
//true
boolean trueValue2 = parser.parseExpression("'black' < 'block'").getValue(Boolean.class);
//false,字符xyz是否为int类型
boolean falseValue2 = parser.parseExpression("'xyz' instanceof T(int)").getValue(Boolean.class);
//true,正则是否匹配
boolean trueValue3 =parser.parseExpression("'5.00' matches '^-?\\d+(\\.\\d{2})?$'").getValue(Boolean.class);
//false
boolean falseValue3=parser.parseExpression("'5.0067' matches '^-?\\d+(\\.\\d{2})?$'").getValue(Boolean.class);
/**逻辑运算*/
//false
boolean falseValue4 = parser.parseExpression("true and false").getValue(Boolean.class);
//true,isMember方法用于测试是否为某个对象的成员
String expression = "isMember('Nikola Tesla') and isMember('Mihajlo Pupin')";
boolean trueValue4 = parser.parseExpression(expression).getValue(Boolean.class);
// -- OR 或运算--
//true
boolean trueValue5 = parser.parseExpression("true or false").getValue(Boolean.class);
//true
String expression1 = "isMember('Nikola Tesla') or isMember('Albert Einstein')";
boolean trueValue6 = parser.parseExpression(expression).getValue( Boolean.class);
//false
boolean falseValue5 = parser.parseExpression("!true").getValue(Boolean.class);
//false
String expression2 = "isMember('Nikola Tesla') and !isMember('Mihajlo Pupin')";
boolean falseValue6 = parser.parseExpression(expression).getValue(Boolean.class);
// Addition
int two = parser.parseExpression("1 + 1").getValue(Integer.class);
// 2
String testString =
parser.parseExpression("'test' + ' ' + 'string'").getValue(String.class); // 'test string'
// Subtraction
int four = parser.parseExpression("1 - -3").getValue(Integer.class); // 4
double d = parser.parseExpression("1000.00 - 1e4").getValue(Double.class); // -9000
// Multiplication
int six = parser.parseExpression("-2 * -3").getValue(Integer.class); // 6
double twentyFour = parser.parseExpression("2.0 * 3e0 * 4").getValue(Double.class); // 24.0
// Division
int minusTwo = parser.parseExpression("6 / -3").getValue(Integer.class); // -2
double one = parser.parseExpression("8.0 / 4e0 / 2").getValue(Double.class); // 1.0
// Modulus
int three = parser.parseExpression("7 % 4").getValue(Integer.class); // 3
int one = parser.parseExpression("8 / 5 % 2").getValue(Integer.class); // 1
// Operator precedence
int minusTwentyOne = parser.parseExpression("1+2-3*8").getValue(Integer.class); // -21

基于 XML 配置定义 Bean

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<bean id="gyl" class="com.ceshi.test.Spring053.spel01.User" p:id="9527">
<property name="name" value="张三">
</property>
</bean>

<bean id="order001" class="com.ceshi.test.Spring053.spel01.Order">
<property name="customer" ref="gyl"></property>
<property name="userName" value="#{gyl.name}"></property>
<property name="orderName" value='#{"Apples".toUpperCase()}'></property>
</bean>

<bean id="numberGuess" class="org.spring.samples.NumberGuess">
<!--调用静态方法random() -->
<property name="randomNumber" value="#{ T(java.lang.Math).random() * 100.0 }" />
</bean>

<bean id="taxCalculator" class="org.spring.samples.TaxCalculator">
<property name="defaultLocale" value="#{ systemProperties['user.region'] }" />
</bean>

<bean id="numberGuess" class="org.spring.samples.NumberGuess">
<property name="randomNumber" value="#{ T(java.lang.Math).random() * 100.0 }" />
</bean>
<bean id="shapeGuess" class="org.spring.samples.ShapeGuess">
<property name="initialShapeSeed" value="#{ numberGuess.randomNumber }" />
</bean>

基于注解配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
package com.ceshi.test.Spring053.spel03;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

/**
* 用户类
*/
@Component("user1")
public class User {
/**
* 编号
*/
@Value("#{9527+100}")
private int id;
/**
* 姓名
*/
@Value("#{'Hello'.toUpperCase()}")
private String name;

public int getId() {
return id;
}

public void setId(int id) {
this.id = id;
}

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}
}

参见于:http://www.cnblogs.com/best/p/5748105.html

表达式语言_ jsp

JSP 中理想功能:

  1. 接收属性;
  2. 判断;
  3. 输出;
  4. 尽量少用scriptlet (脚本片段);
  5. 所以 JSP 中应该尽可能的把功能减少到这个程度。

表达式语言的介绍

表达式语言是为了能够让JSP中更少的包含Java代码;

用表达式语言可以方便地完成输出;

因此表达式语言的目的:

  • 更方便的显示;

  • JSP表达式语言(EL)使得访问存储在JavaBean中的数据变得非常简单。

  • JSPEL既可以用来创建算术表达式也可以用来创建逻辑表达式。

  • 在JSP EL表达式内可以使用整型数,浮点数,字符串,常量true、false,还有null。

一般表达式语言的形式:${…..}

简单示例

当您需要在 JSP 标签中指定一个属性值时,只需要简单地使用字符串即可,列如:

1
<jsp:setProperty name="box" property="perimeter" value="100"/>

但是,对于这个标签中的 value 我们还可以动态的来表示:

1
2
<jsp:setProperty name="box" property="perimeter" 
value="${testValue}"/>

其中 expr 指的是表达式。在 JSP EL 中通用的操作符是 . 和 {} 。这两个操作符允许您通过内嵌的JSP对象访问各种各样的JavaBean属性。
假如 testValue 的值是 Hello JSP! ,
当JSP编译器在属性中见到”${}”格式后,它会产生代码来计算这个表达式,并且产生一个替代品来代替表达式的值。
您也可以在标签的模板文本中使用表达式语言。比如jsp:text标签简单地将其主体中的文本插入到JSP输出中:

1
2
3
<jsp:text>
<h1>Hello JSP!</h1>
</jsp:text>

例二:我们设定在值是下面的一组,

request.setAttribute(“name”,”xiazdong”);

${name}即可进行显示;

传统获取属性的方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
<%@ page contentType="text/html" pageEncoding="GBK"%>  
<html>
<head>
<title>Ttitle</title>
</head>
<body>
<%
request.setAttribute("name","xiazdong");
%>
<%
if(request.getAttribute("name")!=null){
%>
<h3><%=request.getAttribute("name")%></h3>
<%
}
%>
</body>
</html>
// 上面代码也可以替换成如下代码
<%@ page contentType="text/html" pageEncoding="GBK"%>
<html>
<head>
<title>Ttitle</title>
</head>
<body>
<%
request.setAttribute("name","xiazdong");
%>
<h3>${name}</h3>
</body>
</html>

表达式语言的内置对象

  1. pageScope、requestScope、sessionScope、applicationScope内置对象

在取得内置对象设置的属性时有用。这些是为了取得page、request、session、application设置的属性,比如:

  • pageContext.setAttribute(“name”,”xiazdong”);
  • request.setAttribute(“name”,”xiazdong”);同时出现,则通过
  • ${pageScope.name}可以取得page的属性;
  • ${requestScope.name}可以取得request的属性;
  1. param和paramValues内置对象
  • 用来接收传来的参数;
  • param.a类似于request.getParameter(“a”);
  • paramValues.a类似于request.getParameterValues(“a”);
  • 如果想要取得第二个a的值,则通过${paramValues.a[1]};
  1. pageContext内置对象
  • pageContext对象可以取得request,session,application对象;
  • pageContext.request取得request内置对象;
  • pageContext.session取得session内置对象;
  • pageContext.application取得application对象;

还可以调用JSP内置对象的方法:举例:(都是通过反射调用)

  • (1)${pageContext.request.remoteAddr}; 调用getRemoteAddr();
  • (2)${pageContext.session.new};调用isNew()函数
  • (3)${pageContext.session.id};调用getId()函数
  1. 表达式语言用于集合

我们可以通过表达式语言很方便的操作集合,比如:

1
2
3
4
5
6
7
8
List<Integer> list = new ArrayList<Integer>();
request.setAttribute("a",list);
${a[0]} //表示取得队列中第一个元素;以此类推;

Map m = new HashMap();
request.setAttribute("map",m);
${map["a"]} //返回key=a的value;
${map.a} //返回key=a的value;

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
<%@ page contentType="text/html" pageEncoding="GBK" import="java.util.*"%>  
<html>
<head>
<title></title>
</head>
<body>
<%
application.setAttribute("info","application");
session.setAttribute("info","session");
request.setAttribute("info","request");
pageContext.setAttribute("info","page");
List<Integer> list = new ArrayList<Integer>();
list.add(1);
list.add(2);
pageContext.setAttribute("list",list);
Map<String,String>map = new HashMap<String,String>();
map.put("name","xiazdong");
pageContext.setAttribute("map",map);
%>
<h3>${applicationScope.info}</h3>
<h3>${sessionScope.info}</h3>
<h3>${requestScope.info}</h3>
<h3>${pageScope.info}</h3>
<h3>${pageContext.request.remoteAddr}</h3>
<h3>${pageContext.session.id}</h3>
<h3>${pageContext.session.new}</h3>
<h3>${param.name}</h3>
<h3>${paramValues.favor[0]}</h3>
<h3>${paramValues.favor[1]}</h3>
<h3>${paramValues.favor[2]}</h3>
<h3>${list[0]}</h3>
<h3>${map["name"]}</h3>

</body>
</html>
  1. 表达式语言用于MVC

MVC设计模式是通过Servlet将结果传递给JSP页面显示,如果按照一般的思路,则需要在JSP中导入VO包,因为需要通过VO对象得到属性;比如:

VO是Person类,则JSP中需要以Person per = (Person)request.getAttribute(“person”);

取得Person对象然后显示,从这句话可以看出,我们必须要导入Person类才可以,但是JSP最好只允许导入java.util.*包;
所以违背了要求;

如果根据表达式语言,则可以通过在Servlet中编写:

request.setAttribute(“VO”,VO对象);

设置属性,因为requestDispatcher是服务器跳转,因此可以在JSP中通过${VO.属性}取得结果;这样就不用导入VO包;

通过表达式语言可以方便的调用VO的属性;

1
2
3
class A{
private String a;
}
  • 传递对象
1
2
3
A a1 = new A();
request.setAttribute("obj",a1);
${obj.a} //通过反射获得a1对象的a属性;
  • 传递多个对象
1
2
3
4
5
6
7
8
9
10
List<A> as = new ArrayList<A>();
request.setAttribute("as",as);

在jsp页面中:
List as = (List)request.getAttribute("as");
Iterator iter = as.iterator();
while(iter.hasNext()){ //也可以使用 for 循环遍历
pageContext.setAttribute("obj",iter.next());
${obj.a} //调用A类的a属性
}

其实 JSP 上接收对象和我们在 Java 中遍历一个 list 是一样的。

  1. 运算符

EL表达式支持大部分Java所提供的算术和逻辑操作符:

操作符 描述
. row 1 col 2
. 访问一个Bean属性或者一个映射条目
[] 访问一个数组或者链表的元素
( ) 组织一个子表达式以改变优先级
+
- 减或负
*
/ or div
% or mod 取模
== or eq 测试是否相等
!= or ne 测试是否不等
< or lt 测试是否小于
> or gt 测试是否大于
<= or le 测试是否小于等于
>= or ge 测试是否大于等于
&& or and 测试逻辑与
丨丨 or or 测试逻辑或
! or not 测试取反
empty 测试是否空值

使用示例:

1
2
3
4
5
6
7
8
9
10
//例一:
<jsp:text>
Box Perimeter is: ${2*box.width + 2*box.height}
</jsp:text>
//例二:
request.setAttribute("num1",20);
request.setAttribute("num2",30);
${num1-num2} //表示减法运算
${num1>=num2} //表示比较运算
${ empty num2} //表示判断是否为空

本文参见于:

http://blog.csdn.net/xiazdong/article/details/6901497

http://www.runoob.com/jsp/jsp-expression-language.html

JNDI 是什么

JNDI 是什么

JNDI ( Java Naming and Directory Interface, Java 命名和目录接口 ) 是 SUN 公司提供的一种标准的 Java 命名系统接口,JNDI 提供统一的客户端 API,通过不同的访问提供者接口 JNDI 服务供应接口 ( SPI ) 的实现,由管理者将 JNDI API 映射为特定的命名服务和目录系统,使得 Java 应用程序可以和这些命名服务和目录服务之间进行交互。

没有 JNDI 的做法:

程序员开发时,知道要开发访问 MySQL 数据库的应用,于是将一个对 MySQL JDBC 驱动程序类的引用进行了编码,并通过使用适当的 JDBC URL 连接到数据库。
就像以下代码这样:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
Connection conn=null;
try {
Class.forName("com.mysql.jdbc.Driver",
true, Thread.currentThread().getContextClassLoader());
conn=DriverManager.getConnection("jdbc:mysql://MyDBServer?user=qingfeng&password=mingyue");
/* 使用conn并进行SQL操作 */
......
conn.close();
}
catch(Exception e) {
e.printStackTrace();
}
finally {
if(conn!=null) {
try {
conn.close();
} catch(SQLException e) {}
}
}

这是传统的做法,也是以前非Java程序员(如 Delphi、VB 等)常见的做法。这种做法一般在小规模的开发过程中不会产生问题,只要程序员熟悉 Java 语言、了解 JDBC 技术和 MySQL ,可以很快开发出相应的应用程序。

没有 JNDI 的做法存在的问题:

  1. 数据库服务器名称MyDBServer 、用户名和口令都可能需要改变,由此引发JDBC URL需要修改;
  2. 数据库可能改用别的产品,如改用DB2或者Oracle,引发JDBC驱动程序包和类名需要修改;
  3. 随着实际使用终端的增加,原配置的连接池参数可能需要调整;

    解决办法:

    程序员应该不需要关心“具体的数据库后台是什么?JDBC驱动程序是什么?JDBC URL格式是什么?访问数据库的用户名和口令是什么?”等等这些问题,程序员编写的程序应该没有对 JDBC 驱动程序的引用,没有服务器名称,没有用户名称或口令 —— 甚至没有数据库池或连接管理。而是把这些问题交给J2EE容器来配置和管理,程序员只需要对这些配置和管理进行引用即可。

由此,就有了JNDI。

用了JNDI之后的做法:

首先,在在J2EE容器中配置JNDI参数,定义一个数据源,也就是JDBC引用参数,给这个数据源设置一个名称;然后,在程序中,通过数据源名称引用数据源从而访问后台数据库。
具体操作如下(以JBoss为例):

1、配置数据源:

在JBoss的 D:/jboss420GA/docs/examples/jca 文件夹下面,有很多不同数据库引用的数据源定义模板。将其中的 mysql-ds.xml 文件Copy到你使用的服务器下,如 D:/jboss420GA/server/default/deploy。
修改 mysql-ds.xml 文件的内容,使之能通过JDBC正确访问你的MySQL数据库,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?xml version="1.0" encoding="UTF-8"?>
<datasources>
<local-tx-datasource>
<jndi-name>MySqlDS</jndi-name>
<connection-url>jdbc:mysql://localhost:3306/lw</connection-url>
<driver-class>com.mysql.jdbc.Driver</driver-class>
<user-name>root</user-name>
<password>rootpassword</password>
<exception-sorter-class-name>org.jboss.resource.adapter.jdbc.vendor.MySQLExceptionSorter</exception-sorter-class-name>
<metadata>
<type-mapping>mySQL</type-mapping>
</metadata>
</local-tx-datasource>
</datasources>

这里,定义了一个名为MySqlDS的数据源,其参数包括JDBC的URL,驱动类名,用户名及密码等。

2、在程序中引用数据源:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
Connection conn=null;
try {
Context ctx=new InitialContext();
Object datasourceRef=ctx.lookup("java:MySqlDS"); //引用数据源
DataSource ds=(Datasource)datasourceRef;
conn=ds.getConnection();
/* 使用conn进行数据库SQL操作 */
......
c.close();
}
catch(Exception e) {
e.printStackTrace();
}
finally {
if(conn!=null) {
try {
conn.close();
} catch(SQLException e) { }
}
}

直接使用 JDBC 或者通过 JNDI 引用数据源的编程代码量相差无几,但是现在的程序可以不用关心具体 JDBC 参数了。
在系统部署后,如果数据库的相关参数变更,只需要重新配置 mysql-ds.xml 修改其中的JDBC参数,只要保证数据源的名称不变,那么程序源代码就无需修改。

由此可见,JNDI避免了程序与数据库之间的紧耦合,使应用更加易于配置、易于部署。

JNDI的扩展:

JNDI 在满足了数据源配置的要求的基础上,还进一步扩充了作用:所有与系统外部的资源的引用,都可以通过 JNDI 定义和引用。

所以,在 J2EE 规范中,J2EE 中的资源并不局限于 JDBC 数据源。引用的类型有很多,其中包括资源引用(已经讨论过)、环境实体和 EJB 引用。特别是 EJB 引用,它暴露了 JNDI 在 J2EE 中的另外一项关键角色:查找其他应用程序组件。

EJB 的 JNDI 引用非常类似于 JDBC 资源的引用。在服务趋于转换的环境中,这是一种很有效的方法。可以对应用程序架构中所得到的所有组件进行这类配置管理,从 EJB 组件到 JMS 队列和主题,再到简单配置字符串或其他对象,这可以降低随时间的推移服务变更所产生的维护成本,同时还可以简化部署,减少集成工作。 外部资源”。

总结:

J2EE 规范要求所有 J2EE 容器都要提供 JNDI 规范的实现。JNDI 在 J2EE 中的角色就是“交换机” —— J2EE 组件在运行时间接地查找其他组件、资源或服务的通用机制。在多数情况下,提供 JNDI 供应者的容器可以充当有限的数据存储,这样管理员就可以设置应用程序的执行属性,并让其他应用程序引用这些属性(Java 管理扩展(Java Management Extensions,JMX)也可以用作这个目的)。JNDI 在 J2EE 应用程序中的主要角色就是提供间接层,这样组件就可以发现所需要的资源,而不用了解这些间接性。

在 J2EE 中,JNDI 是把 J2EE 应用程序合在一起的粘合剂,JNDI 提供的间接寻址允许跨企业交付可伸缩的、功能强大且很灵活的应用程序。这是 J2EE 的承诺,而且经过一些计划和预先考虑,这个承诺是完全可以实现的。

引用至:http://blog.csdn.net/zhaosg198312/article/details/3979435

JNDI数据源的配置:http://www.cnblogs.com/xdp-gacl/p/3951952.html

关于在Tomcat中如何配置DataSource,可以参考文档:http://tomcat.apache.org/tomcat-5.5-doc/jndi-datasource-examples-howto.html。

关于在Tomcat中如何配置其他JNDI服务,可以参考文档:http://tomcat.apache.org/tomcat-5.5-doc/jndi-resources-howto.html。

JDBC

什么是JDBC

JDBC(Java DataBase Connectivity,java数据库连接)是一种用于执行SQL语句的Java API,可以为多种关系数据库提供统一访问,它由一组用Java语言编写的类和接口组成。JDBC提供了一种基准,据此可以构建更高级的工具和接口,使数据库开发人员能够编写数据库应用程序,同时,JDBC也是个商标名。

JDBC 的使用

建立一个JDBC应用程序,本教程中以Java连接MySQL为一个示例:

  1. 导入包,在程序中包含数据库编程所需的JDBC类。大多数情况下,使用 import java.sql.*
1
import java.sql.*;
  1. 注册JDBC驱动程序,需要初始化驱动程序,这样就可以打开与数据库的通信。
1
Class.forName("com.mysql.jdbc.Driver");
  1. 打开一个连接,使用DriverManager.getConnection()方法来创建一个Connection对象,它代表一个数据库的物理连接。
1
2
3
4
static final String USER = "root"; //用户名
static final String PASS = "pwd123456"; //密码
System.out.println("Connecting to database...");
conn = DriverManager.getConnection(DB_URL,USER,PASS); //上面的名字可以自定义的 但是这里要写对

4.执行一个查询, 需要使用一个类型为Statement或PreparedStatement的对象,并提交一个SQL语句到数据库执行查询。

1
2
3
4
5
System.out.println("Creating statement...");
stmt = conn.createStatement();
String sql;
sql = "SELECT id, first, last, age FROM Employees";
ResultSet rs = stmt.executeQuery(sql);

如果要执行一个SQL语句:UPDATE,INSERT或DELETE语句,那么需要下面的代码:

1
2
3
4
5
System.out.println("Creating statement...");
stmt = conn.createStatement();
String sql;
sql = "DELETE FROM Employees";
ResultSet rs = stmt.executeUpdate(sql);
  1. 获取结果,可以使用适当的ResultSet.getXXX()方法来检索的数据结果。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    while(rs.next()){
    //Retrieve by column name
    int id = rs.getInt("id");
    int age = rs.getInt("age");
    String first = rs.getString("first");
    String last = rs.getString("last");

    //Display values
    System.out.print("ID: " + id);
    System.out.print(", Age: " + age);
    System.out.print(", First: " + first);
    System.out.println(", Last: " + last);
    }
  2. 清理环境资源,在使用JDBC与数据交互操作数据库中的数据后,应该明确地关闭所有的数据库资源以减少资源的浪费,对依赖于JVM的垃圾收集。

1
2
3
rs.close();
stmt.close();
conn.close();
  1. 完整示例。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
// 1. 
import java.sql.*;

public class FirstExample {
//注册驱动
static final String JDBC_DRIVER = "com.mysql.jdbc.Driver";
static final String DB_URL = "jdbc:mysql://localhost/test";

//数据库名和密码自己修改
static final String USER = "username";
static final String PASS = "password";

public static void main(String[] args) {
Connection conn = null;
Statement stmt = null;
try{
// 2.
Class.forName("com.mysql.jdbc.Driver");
//3.
conn = DriverManager.getConnection(DB_URL,USER,PASS);
//4.
stmt = conn.createStatement();
String sql;
sql = "SELECT id, first, last, age FROM Employees";
ResultSet rs = stmt.executeQuery(sql);
//5.
while(rs.next()){
int id = rs.getInt("id");
int age = rs.getInt("age");
String first = rs.getString("first");
String last = rs.getString("last");

System.out.print("ID: " + id);
System.out.print(", Age: " + age);
System.out.print(", First: " + first);
System.out.println(", Last: " + last);
}
//6.
rs.close();
stmt.close();
conn.close();
}catch(SQLException se){
// 异常不要这么处理 不好,要携带异常描述和原因。有日志要用日志输出。
se.printStackTrace();
}catch(Exception e){
e.printStackTrace();// 同上
}finally{
try{
if(stmt!=null)
stmt.close();
}catch(SQLException se2){
//自定义后续处理,这里不写了
}
try{
if(conn!=null)
conn.close();
}catch(SQLException se){
se.printStackTrace();
}
}
}
}

JDBC 的数据类型

SQL JDBC\JAVA setXXX updateXXX
VARCHAR java.lang.String setString updateString
CHAR java.lang.String setString updateString
LONGVARCHAR java.lang.String setString updateString
BIT boolean setBoolean updateBoolean
NUMERIC java.math.BigDecimal setBigDecimal updateBigDecimal
TINYINT byte setByte updateByte
SMALLINT short setShort updateShort
INTEGER int setInt updateInt
BIGINT long setLong updateLong
REAL float setFloat updateFloat
FLOAT float setFloat updateFloat
DOUBLE double setDouble updateDouble
VARBINARY byte[ ] setBytes updateBytes
BINARY byte[ ] setBytes updateBytes
DATE java.sql.Date setDate updateDate
TIME java.sql.Time setTime updateTime
TIMESTAMP java.sql.Timestamp setTimestamp updateTimestamp
CLOB java.sql.Clob setClob updateClob
BLOB java.sql.Blob setBlob updateBlob
ARRAY java.sql.Array setARRAY updateARRAY
REF java.sql.Ref SetRef updateRef
STRUCT java.sql.Struct SetStruct updateStruct

在JDBC3.0中增强支持BLOB,CLOB,ARRAY,REF等数据类型。ResultSet对象可调用UPDATEBLOB(),updateCLOB(),updateArray()和updateRef()方法,使您可以在数据库服务器上直接操作相应的数据。

对于setXXX()和updateXXX()方法,可以转换成特定的Java类型到特定的JDBC数据类型。而使用setObject()和updateObject()方法,几乎所有的Java类型映射到JDBC数据类型。

ResultSet对象提供相应的getXXX()方法为每个数据类型来检索列值。每一种类型方法,可以使用与列名或由列的序号位置来获取列的数据。