GeXiangDong

精通Java、SQL、Spring的拼写,擅长Linux、Windows的开关机

0%

@Value和@ConfigurationProperties两个注解都可以从配置文件获取值并设置到属性中,用法上有区别。

@Value适用一些比较简单的情形,用得比较普遍;而@ConfigurationProperties则可适用一些@Value无法处理更复杂的情形。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
myapp:
simple:
name: ABC
count: 15
countries: CN,US,UK
complex:
list:
- id: k1
value: hello
- id: k2
value: nihao
- id: k3
value: konichiha
nested:
name: John
address:
province: jiangsu
city: nanjing

例如上面的配置myapp.simple下的属性都可以用@value获取到,甚至myapp.simple.countries也可以被设置到String[]类型的属性上,但是下面的myapp.complex.list设置的配置,@Value就无法把它转成一个List<Map<String, Object>>类型的属性了,而@ConfigurationProperties可以。甚至myapp.complex.nested还可以被@ConfigurationProperties付值到带有两个属性(province, city)的子类中(参照下面的@ NestedConfigurationProperty注解)

@Value

可处理的类型

只可处理String、数值等简单类型,和简单的数组类型,不能处理map、List等复杂类型

位置

直接写在需要从配置文件读取值的成员变量上即可。
例如:

1
2
3
4
5
@Service
public class XxxxService{
@Value("${xxx.yyy}")
private String xxxYyy;
}

参数

只有一个字符型参数value,用于标记配置文件中的key

参数名称 类型 默认值 说明
value String 从配置文件中哪一项读取值,支持Spring EL

@ConfigurationProperties

可处理的类型

除了基本类型外,List、Map、 数组, 自定义POJO …

用法

@ConfigurationProperties注解需要写到类上,为该类所有有setter方法的属性从配置文件中读取并付值。@ConfigurationProperties 只能在类上使用,不能用在属性上。

如果类里面的某个属性是另外一个POJO,则在该属性上增加注解 @NestedConfigurationProperty , 这样就可嵌套解析。

例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@ConfigurationProperties
public class xxxYyy{
private String name;
@NestedConfigurationProperty Address address;

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

public void setAddress(Address address){
this.address = address;
}
}

public class Address{
private String province;
private String city;

//---略过setter方法
}

参数

参数名称 类型 默认值 说明
ignoreInvalidFields boolean false java中被绑定的属性类型与配置文件中设置的值类型不同时是否忽略
ignoreUnknownFields boolean true Flag to indicate that when binding to this object invalid fields should be ignored
prefix String The name prefix of the properties that are valid to bind to this object.
value String 同prefix,简写,支持Spring EL

前置条件

1、需要增加依赖

1
2
3
4
5
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>

2、需要先开始使用配置属性的功能(不开启不出错,但不会读取配置并设置到类上)

1
@EnableConfigurationProperties

@EnableConfigurationProperties需要写在Spring Boot的启动类或配置类上。

从其他配置文件中读取

如果信息配置在了自定义的配置文件中(不是applicaiton.yml),则可以使用@PropertySource注解来告诉spring从哪个文件读取配置信息,例如:

1
@PropertySource("classpath:configprops.properties")

@PropertySource 注解默认只能解析 properties 文件,如果是yml文件,需要自定义一个解析器

如果需要从其他yml文件读取配置,可以利用 @PropertySource 的 factory 属性,先写一个解析yml 文件的类,这很简单,因为spring中已经包含了解析yml格式的解析器,我们只需要包装一下。

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
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.config.YamlPropertiesFactoryBean;
import org.springframework.core.env.PropertiesPropertySource;
import org.springframework.core.env.PropertySource;
import org.springframework.core.io.support.EncodedResource;
import org.springframework.core.io.support.PropertySourceFactory;

import java.io.IOException;
import java.util.Properties;

public class YamlPropertySourceFactory implements PropertySourceFactory {
private static final Logger logger = LoggerFactory.getLogger(YamlPropertySourceFactory.class);

@Override
public PropertySource<?> createPropertySource(String name, EncodedResource encodedResource)
throws IOException {
YamlPropertiesFactoryBean factory = new YamlPropertiesFactoryBean();
factory.setResources(encodedResource.getResource());
Properties properties = factory.getObject();
logger.trace(
"read from {}, exists:{}",
encodedResource.getResource().getFile().getAbsoluteFile(),
encodedResource.getResource().exists());
return new PropertiesPropertySource(encodedResource.getResource().getFilename(), properties);
}
}

之后在使用 @PropertySource 注解时增加 factory 属性

1
@PropertySource(factory = YamlPropertySourceFactory.class, value = "classpath:yamlfile.yml")

背景

有时客户端给服务器传输的JSON比较大,几兆甚至几十兆,网络传输占用时间较多,而json都是文本格式,压缩率较高。因此考虑传输压缩后的格式。

HTTP协议在相应部分支持Content-Encoding:gzip,压缩response body,而没有压缩request body的设计,这也很合理,因为在客户端发起请求时并不知道服务器是否支持压缩。因此没法通过http自身解决,只能增加些程序。

压缩和解压都消耗CPU时间,需要根据实际情况决定是否使用压缩方案,一般内网(微服务)调用启用压缩后有可能得不偿失。在网速较慢的情况下才会有意义。

方案

考虑到通用性,仿效response的header Content-Encoding:gzip方式。

客户端把压缩过的json作为post-body传输,然后增加一个request header: Content-Encoding: gzip来告诉服务器端是压缩的格式。

服务端增加一个Filter,对request头进行检查,如果有Content-Encoding则解压缩后继续。这样不影响现有程序。

服务端还有另外一个解决方案,在NGINX反向代理处增加一个插件(需要去找实现这种功能的插件,官方未提供)把解压缩后的request body传给应用服务器(tomcat)。

参照 http://www.pataliebre.net/howto-make-nginx-decompress-a-gzipped-request.html#.XBToGi276i5

实现

服务器端(Spring)

增加2个类:

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
package cn.devmgr.springcloud;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
import org.springframework.web.filter.OncePerRequestFilter;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;


@Service
public class ContentEncodingFilter extends OncePerRequestFilter {
Logger logger = LoggerFactory.getLogger(ContentEncodingFilter.class);

@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response,
FilterChain chain) throws ServletException, IOException {

String conentEncoding = request.getHeader("Content-Encoding");
if(conentEncoding != null && ("gzip".equalsIgnoreCase(conentEncoding) || "deflate".equalsIgnoreCase(conentEncoding))){
logger.trace("Content-Encoding: {}", conentEncoding);
chain.doFilter(new GZIPRequestWrapper(request), response);
return;
}

chain.doFilter(request, response);
}
}

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
package cn.devmgr.springcloud;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.servlet.ReadListener;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.util.zip.DeflaterInputStream;
import java.util.zip.GZIPInputStream;

public class GZIPRequestWrapper extends HttpServletRequestWrapper {
private final static Logger logger = LoggerFactory.getLogger(GZIPRequestWrapper.class);

protected HttpServletRequest request;

public GZIPRequestWrapper(HttpServletRequest request){
super(request);
this.request = request;
}

@Override
public ServletInputStream getInputStream() throws IOException {
ServletInputStream sis = request.getInputStream();
InputStream is = null;
String conentEncoding = request.getHeader("Content-Encoding");
if("gzip".equalsIgnoreCase(conentEncoding)){
is = new GZIPInputStream(sis);
}else if("deflate".equalsIgnoreCase(conentEncoding)){
is = new DeflaterInputStream(sis);
}else{
throw new UnsupportedEncodingException(conentEncoding + " is not supported.");
}
final InputStream compressInputStream = is;
return new ServletInputStream(){
ReadListener readListener;

@Override
public int read() throws IOException {
int b = compressInputStream.read();
if(b == -1 && readListener != null) {
readListener.onAllDataRead();
}
return b;
}

@Override
public boolean isFinished(){
try {
return compressInputStream.available() == 0;
} catch (IOException e) {
logger.error("error", e);
if(readListener != null) {
readListener.onError(e);
}
return false;
}
}

@Override
public boolean isReady() {
try {
return compressInputStream.available() > 0;
} catch (IOException e) {
logger.error("error", e);
if(readListener != null) {
readListener.onError(e);
}
return false;
}
}

@Override
public void setReadListener(final ReadListener readListener) {
this.readListener = readListener;
sis.setReadListener(new ReadListener() {
@Override
public void onDataAvailable() throws IOException {
logger.trace("onDataAvailable");
if(readListener != null){
readListener.onDataAvailable();
}
}

@Override
public void onAllDataRead() throws IOException {
logger.trace("onAllDataRead");
}

@Override
public void onError(Throwable throwable) {
logger.error("onError", throwable);
if(readListener != null){
readListener.onError(throwable);
}
}
});
}
};
}
}

客户端

CURL的测试命令

1
2
3
echo '{"type": "json", "length": 2222}' | gzip > body.gz
curl -v -i http://127.0.0.1:8080/ss -H 'Content-Encoding: gzip' -H 'Content-Type:application/json' --data-binary @body.gz

RestTemplate

使用RestTemplate可以通过配置interceptor来统一增加压缩。实现代码如下:

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
package cn.devmgr.springcloud;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpRequest;
import org.springframework.http.client.ClientHttpRequestExecution;
import org.springframework.http.client.ClientHttpRequestInterceptor;
import org.springframework.http.client.ClientHttpResponse;
import org.springframework.util.CollectionUtils;
import org.springframework.web.client.RestTemplate;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.zip.GZIPOutputStream;

@Configuration
public class RestClientConfig {
private final static Logger logger = LoggerFactory.getLogger(RestClientConfig.class);

/**
* request body超过1M时,自动开启压缩模式;(需要服务端支持)
* 压缩和解压会增加程序处理时间,一般内网调用不压缩速度会更快;压缩只是在网络环境较差的情况下有使用意义。
* @return
*/
@Bean
public RestTemplate restTemplate() {
RestTemplate restTemplate = new RestTemplate();

List<ClientHttpRequestInterceptor> interceptors = restTemplate.getInterceptors();
if (CollectionUtils.isEmpty(interceptors)) {
interceptors = new ArrayList<>();
}
interceptors.add(new RestTemplateCompressInterceptor());

restTemplate.setInterceptors(interceptors);
return restTemplate;
}


class RestTemplateCompressInterceptor implements ClientHttpRequestInterceptor {

@Override
public ClientHttpResponse intercept(HttpRequest request, byte[] body,
ClientHttpRequestExecution execution) throws IOException {
logger.trace("RestTemplateCompressInterceptor:intercept() body-length: {}", body == null ? 0 : body.length);
byte[] newBody = null;
if(body == null || body.length < 1024 * 1024){
// 小于1M不开启压缩模式
logger.trace("{}不需要压缩", body == null ? 0 : body.length);
newBody = body;
}else{
logger.trace("{}开启压缩模式", body.length);
request.getHeaders().add("Content-Encoding", "gzip");
ByteArrayOutputStream baos = new ByteArrayOutputStream();
GZIPOutputStream gzip;
try {
gzip = new GZIPOutputStream(baos);
gzip.write(body);
gzip.close();
} catch (IOException e) {
logger.error("压缩request body出错.", e);
throw(e);
}
newBody = baos.toByteArray();
logger.trace("压缩后大小 {}", newBody.length);
}
ClientHttpResponse response = execution.execute(request, newBody);
return response;
}
}
}

windows

可以使用Windows service wrapper来把任何命令行执行的程序做成windows service.

Windows Service Wrapper简称WinSW,可以在https://github.com/kohsuke/winsw/releases 下载到

编写一个配置文件

编写一个xml文件,可参照下例:

1
2
3
4
5
6
7
8
<configuration>
<id>tutorial-service</id>
<name>Tutorial Service</name>
<description></description>
<executable>java</executable>
<arguments>-jar c:\tutorial\tutorial-section-01-1.0-SNAPSHOT.jar</arguments>
<logmode>rotate</logmode>
</configuration>

文件保存为 tutorial-service.xml,然后把winsw.exe拷贝到相同目录下,并改成和xml一样的名字(扩展名仍旧是exe不变)tutorial-service.exe

安装service和卸载service

在命令行下执行

1
tutorial-service.exe install

可把前面设置的xml中命令安装为windows service(执行上述命名需要具有windows管理员权限。

如果需要卸载可用

1
tutorial-service.exe uninstall

linux

ubuntu/debian下可以用sytemd。

配置文件

创建配置文件,内容如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
[Unit]  
Description=tutorial
After=syslog.target

[Service]
ExecStart=/usr/lib/jvm/java-8-oracle/bin/java -jar -Xms256m -Xmx1G -Dserver.port=8087 -Dautostart=true -Dlog.level.console=WARN /web/webapps/tutorial-1.0.0.jar --spring.profiles.active=development
SuccessExitStatus=143

User=www-data
Group=www-data
UMask=0007
RestartSec=10
Restart=always

[Install]
WantedBy=multi-user.target

配置文件放入/lib/systemd/system/tutorial.service

安装后台服务

然后执行:

1
2
systemctl enable tutorial.service
systemctl daemon-reload

管理后台服务

安装好之后可以使用

1
2
3
4
systemctl start tutorial
systemctl stop tutorial
systemctl restart tutorial
systemctl status tutorial

等命令来管理服务

如果你更习惯老版本linux下的service命令,也可以

1
service tutorial start

以上内容中tutorial应该替换成自己的项目名

实现Java打印功能有3种方式

javax.print.PrintService

使用javax.print包下的类来打印,可以打印文档,PDF等等文档

1
2
3
4
Doc doc = new SimpleDoc(new FileInputStream("tobeprint.pdf"), DocFlavor.BYTE_ARRAY.PDF, null);
PrintService ps = PrintServiceLookup.lookupDefaultPrintService();
DocPrintJob job = ps.createPrintJob();
job.print(doc, null);

java.awt.PrinterJob

awt的实现,可以通过Printable 打印 Graphics,自己程序绘制要打印的内容。

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
import java.awt.*;
import java.awt.print.*;


public class PrintToPrinter {

public static void main(String[] args) throws Exception {
Book book = new Book();
PageFormat pf = new PageFormat();
pf.setOrientation(PageFormat.PORTRAIT);

Paper p = new Paper();
p.setSize(590, 840);
p.setImageableArea(10, 10, 590, 840);
pf.setPaper(p);
book.append(new OneLabel(), pf);

PrinterJob job = PrinterJob.getPrinterJob();
job.setPageable(book);
job.print();
}

/**
* 实现Printable即可打印
*/
public static class OneLabel implements Printable {
@Override
public int print(Graphics graphics, PageFormat pageFormat, int pageIndex) throws PrinterException {
if (pageIndex != 0) {
// 此例子只有一页;
return NO_SUCH_PAGE;
}
Graphics2D g2 = (Graphics2D) graphics;
double scale = 72.0 / 300; //屏幕DPI是72;打印机DPI:300; 可以实现打印的更精细,特别是图片
g2.scale(scale, scale);

g2.setColor(Color.black);

double x = pageFormat.getImageableX();
double y = pageFormat.getImageableY();
System.out.println("左上角:" + x + "," + y + " 宽高: " + pageFormat.getWidth() + "," + pageFormat.getHeight());

String str = "中文字符串";
Font font = new Font("微软雅黑", Font.PLAIN, 10);
g2.setFont(font);
g2.drawString(str, (float) x, (float) (y + 20));

Font font2 = new Font("微软雅黑", Font.PLAIN, 20);
g2.setFont(font2);
g2.drawString(str, (float) x, (float) (y + 80));

return PAGE_EXISTS;
}
}
}

javafx.print.PrinterJob

这是javafx的实现,可以打印Canvas,类似awt的功能

1
2
3
4
5
final Canvas canvas = new Canvas(250,250);
GraphicsContext gc = canvas.getGraphicsContext2D();
gc.fillText("hello, world.", 10, 10);
PrinterJob printerJob = PrinterJob.createPrinterJob();
printerJob.printPage(canvas);

@RequestMapping 注解的 produce 参数

produces参数指明方法可返回给客户端的内容格式,spring会去和request头的Accept部分比较,如果发现相符合,则把方法返回值转换成相符合的格式(例如json, xml等),如果没有符合的,则返回406

RestController的方法上这么写

1
2
@PostMapping(consumes = {MediaType.TEXT_XML_VALUE}, 
produces = {MediaType.TEXT_XML_VALUE})

用下面的命令测试没问题,因为Accept和上面的相符合,会返回xml

1
2
3
4
5
curl -v \
-d '<TvSeriesDto><id>1</id><name>West Wrold</name><originRelease>2016-10-02</originRelease></TvSeriesDto>' \
-X POST -H 'Content-type:text/xml' \
-H 'Accept:text/xml' \
http://localhost:8080/tvseries

如果用下面的命令,则会返回406, Could not find acceptable representation。因为request头accept设置的信息和方法注解produces参数设置没有相符合(applicaiton/xml和text/xml被认为是不同的)

1
2
3
4
5
curl -v  \
-d '<TvSeriesDto><id>1</id><name>West Wrold</name><originRelease>2016-10-02</originRelease></TvSeriesDto>' \
-X POST -H 'Content-type:text/xml' \
-H 'Accept:application/xml' \
http://localhost:8080/tvseries

多个produces参数的顺序的影响

RestController上的RequestMapping的produces参数可以设置多个MediaType, 多个MediaType的顺序也会对结果有些影响,例如:

1
2
@PostMapping(consumes = {MediaType.TEXT_XML_VALUE}, 
produces = {MediaType.APPLICATION_JSON_VALUE, MediaType.TEXT_XML_VALUE})
1
2
3
4
curl -v -d '<TvSeriesDto><id>1</id><name>West Wrold</name><originRelease>2016-10-02</originRelease></TvSeriesDto>' \
-X POST \
-H 'Content-type:text/xml' \
-H 'Accept:*/*' http://localhost:8080/tvseries

得到的是JSON格式,如果把produces参数顺序调整成

1
2
@PostMapping(consumes = {MediaType.TEXT_XML_VALUE}, 
produces = {MediaType.TEXT_XML_VALUE, MediaType.APPLICATION_JSON_VALUE})

用同样的curl命令,则得到的是XML格式。

设置默认的格式

可以通过配置,修改默认的格式

1
2
3
4
5
6
7
8
9
10
@Configuration
public class WebAppConfigurer extends WebMvcConfigurationSupport {

@Override
protected void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
// 这里设置默认的返回格式
configurer.defaultContentType(MediaType.APPLICATION_JSON);
}

}

遇到的问题

Spring boot项目启动过一段较长时间后,在使用上传文件相关功能时,会出现错误。错误信息和下面的类似,临时目录不存在了

Could not parse multipart servlet request; 
nested exception is java.io.IOException: 
The temporary upload location [/tmp/tomcat.7313397276953595407.8090/work/Tomcat/localhost/ROOT] is not valid。

Could not parse multipart servlet request; 
nested exception is java.io.IOException: 
The temporary upload location [C:\Users\Administrator\AppData\Local\Temp\tomcat.7174298170552445669.8002\work\Tomcat\localhost\server] is not valid

原因

这是由于tomcat默认使用的临时目录是在系统临时目录下创建的,而系统临时目录被系统清理了,删除了tomcat的临时目录。

解决方案

知道原因后解决办法也就有了:把tomcat的临时目录设置到一个固定的不会被清理的目录下。

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

@Bean
MultipartConfigElement multipartConfigElement() {
MultipartConfigFactory factory = new MultipartConfigFactory();
String tempLocation = "c:\\temp";
File tmpFile = new File(tempLocation);
if (!tmpFile.exists()) {
tmpFile.mkdirs();
}
factory.setLocation(tempLocation);
return factory.createMultipartConfig();
}
}

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
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
import java.io.File;
import java.lang.reflect.Method;
import java.net.URI;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.Arrays;

import javax.tools.JavaCompiler;
import javax.tools.JavaFileObject;
import javax.tools.SimpleJavaFileObject;
import javax.tools.ToolProvider;

public class DynamicCompiler{

/**
* 动态编译传入的类源码并返回编译好的类
*/
public Class compile(String sourceCode) throws Exception{
File distDir = new File("target");
if (!distDir.exists()) {
distDir.mkdirs();
}
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
String className = getFullClassNameFromCode(sourceCode);
JavaFileObject javaFileObject = new CodeJavaFileObject(className, sourceCode);
JavaCompiler.CompilationTask task = compiler.getTask(null, null, null,
Arrays.asList("-d", distDir.getAbsolutePath()), null,
Arrays.asList(javaFileObject));
boolean compileSuccess = task.call();
if (!compileSuccess) {
System.out.println("compile failed");
return null;
} else {
//动态执行 (反射执行)
System.out.println("compile successed " + distDir.getAbsolutePath());
//URL 需要以 file:// 开始; 如果是目录需要以 / 结束;也可以是jar
URL[] urls = new URL[] {new URL("file://" + distDir.getAbsolutePath() + "/")};
URLClassLoader classLoader = new URLClassLoader(urls);
Class dynamicClass = classLoader.loadClass(className);
return dynamicClass;
}
}


class CodeJavaFileObject extends SimpleJavaFileObject{
private String code;

public CodeJavaFileObject(String className, String code){
super(URI.create(className.replaceAll("\\.", "/") + Kind.SOURCE.extension), Kind.SOURCE);
this.code = code;
}

@Override
public CharSequence getCharContent(boolean ignoreEncodingErrors) {
return code;
}
}

private String getFullClassNameFromCode(String code){
String t = code.substring(0, code.indexOf('{'));
String s = t.replaceAll("[\r\n\t]", " ").trim();
String[] ary = s.split("[;]");
String packageName = null;
String className = getLastPart((ary[ary.length - 1]));
if(ary[0].startsWith("package ")){
packageName = getLastPart(ary[0]);
return packageName + "." + className;
}else{
return className;
}
}

private String getLastPart(String s){
String[] ary = s.trim().split(" ");
return ary[ary.length - 1];
}

public static void main(String[] argvs) throws Exception{
StringBuffer buf = new StringBuffer();
buf.append("package a.b.c;\r\npublic class ANumber{")
.append("public int getNumber() {")
.append("System.out.println(\"Hello World in getNumber()!\"); return 999;")
.append("}")
.append("}");

DynamicCompiler dc = new DynamicCompiler();
Class cls = dc.compile(buf.toString());
System.out.println(cls.getName());
Object obj = cls.getDeclaredConstructor().newInstance();
Method method = cls.getDeclaredMethod("getNumber");
Object r = method.invoke(obj);
System.out.println("result is " + r);
}
}

使用java-jwt

创建和解析JWT,可以使用如下依赖:

1
2
3
4
5
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>3.3.0</version>
</dependency>

创建token

1
2
3
4
5
6
7
8
/** 生成JWT **/
public String createToken(String name, String userId, String email) throws IllegalArgumentException, UnsupportedEncodingException{
JWTCreator.Builder builder = JWT.create();
Algorithm algorithm = Algorithm.HMAC256("xxxxx"); //另外一端解析时也需要这个密码
String token = builder.withClaim("name", name.withClaim("user_id", userId).withClaim("email", email)
.withExpiresAt(new Date(new Date().getTime() + 24*3600*1000)).sign(algorithm);
return token;
}

解析token

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/** 解析JWT **/
public User parseJwt(String token){
User = new User();
try {
Algorithm algorithm = Algorithm.HMAC256("xxxxx"); // 另外一端解析时也需要这个密码
JWTVerifier verifier = JWT.require(algorithm).acceptExpiresAt(5).build(); // Reusable verifier instance
DecodedJWT jwt = verifier.verify(token);
String userId = jwt.getClaim("user_id").asString();
String name = jwt.getClaim("name").asString();
String email = jwt.getClaim("email").asString();
user.setId(userId);
user.setName(name);
user.setEmail(email);
} catch (JWTVerificationException | IllegalArgumentException | UnsupportedEncodingException e) {
log.error("无效的token" + token, e);
return null;
} catch (Throwable e) {
log.error("验证token异常:" + token, e);
return null;
}
return user;
}

上面是用了相同的密码做的签名,也可以用RSA的公钥私钥。

用CSS把SVG图片显示为背景图,可分为2种写法:

  1. 单独的.svg文件,css里用 background-image: url('xx.svg'); 去调用图片
  2. 使用url的data属性,把svg文件合并到css中

第2种写法直接把svg的xml内容拷贝到css中,有些浏览器会识别不出来,这是由于写法不规范造成的。

规范的写法把svg内容放到css内时需要encode,有两种encode方式: base64 和 URLEncode

base64

做base64的编码后,需要 background-image: url('data:image/svg+xml;base64,这里放base64后的svg')

urlencode

urlencode可以直接放 background-image: url('data:image/svg+xml;charset=utf-8,这里放urlencode后的svg)

但需要注意URLEncode有两种可能:

  • 空格变加号,这是在 content type 是 ‘application/x-www-form-urlencoded’时,也就是提交表单时,POST DATA的URLEncoding方式。
  • 空格变%20,这是标准的URL的encode, 在css中使用svg需要用这种encode方法。

如果你找到的工具编码出来空格是加号,可以把+都替换成%20即可。

例子

下面2个文件是个例子:

bg.svg

1
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0" y="0" width="100" height="100" viewBox="0, 0, 100, 100"><circle cx="50" cy="50" r="50"/></svg>

test.html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<!DOCTYPE html>
<html>
<head>
<title>svg background</title>
<style type="text/css">
div {margin: 50px; border: 1px solid #CDCDCD; padding:5px; height:150px; width:300px; background-repeat: no-repeat; background-position: center center; float:left;}
.bg1{background-image: url('bg.svg');}
.bg2{background-image: url('data:image/svg+xml;charset=utf-8,%3Csvg%20version%3D%221.1%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20xmlns%3Axlink%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink%22%20x%3D%220%22%20y%3D%220%22%20width%3D%22100%22%20height%3D%22100%22%20viewBox%3D%220%2C%200%2C%20100%2C%20100%22%20fill%3D%22%23ECECEC%22%3E%3Ccircle%20cx%3D%2250%22%20cy%3D%2250%22%20r%3D%2250%22%2F%3E%3C%2Fsvg%3E');}
.bg3{background-image: url('data:image/svg+xml;base64,PHN2ZyB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHg9IjAiIHk9IjAiIHdpZHRoPSIxMDAiIGhlaWdodD0iMTAwIiB2aWV3Qm94PSIwLCAwLCAxMDAsIDEwMCIgZmlsbD0iI0VDRUNFQyI+PGNpcmNsZSBjeD0iNTAiIGN5PSI1MCIgcj0iNTAiLz48L3N2Zz4=');}
.bg4{background-image: url('data:image/svg+xml;charset=utf-8,<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0" y="0" width="100" height="100" viewBox="0, 0, 100, 100" fill="#ECECEC"><circle cx="50" cy="50" r="50"/></svg>');}
</style>
</head>
<body>
<div class="bg1">使用独立的svg文件</div>
<div class="bg2">urlencode, urlencode编码,注意空格需要编码成%20,空格转义成加号的方式无效</div>
<div class="bg3">base64,用base64编码后的</div>
<div class="bg4">直接写,这是不规范的写法,可能有部分浏览器会生效,能看到背景图</div>
</body>
</html>

小工具

每次从网上下载的SVG的图片中,都带有一些注释、过多的无实际意义的 g 标签,id 属性等,这些也占用CSS空间,我这个有强迫症患者,每次都得手工清理,然后再找个工具base64编码,才能写到 CSS 内,今天我做了个小工具来把这些都自动化了,地址在: http://www.devmgr.cn/encodesvgtocssbackground.html

可以自动清理 SVG 中无实际意义的节点并 base64 encode,还把常用的css 属性也写到了一起。

出现的问题及原因

Spring boot 在 SpringMVC / REST 和Spring Security一起使用时,编写的RestController 或 Controller 中的方法,GET不出错, POST 403,这可能是CSRF保护导致的。

解决方法

可以通过 httpSecurity.csrf().disable() 关闭csrf防护。

但这不是推荐的做法。官方推荐的做法是增加csrf token

https://docs.spring.io/spring-security/site/docs/current/reference/htmlsingle/#csrf-include-csrf-token

SpringMVC中可以在form中增加:

1
<input type=”hidden” th:name=”${_csrf.parameterName}” th:value=”${_csrf.token}”/>

RESTful API需要把这2个值作为http header传递给后端。

CSRF

CSRF是网络攻击的一种,此处有详细说明