大狗哥传奇

  • 首页

  • 关于

  • 标签

  • 分类

  • 归档

  • 搜索

Spring上传文件

发表于 2019-08-21 更新于 2020-06-30 分类于 spring

MultipartResolver

DispatcherServlet用于处理所有请求,doDispatch方法中会判断请求参数中是否包含文件。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
HttpServletRequest processedRequest = request;
HandlerExecutionChain mappedHandler = null;
boolean multipartRequestParsed = false;

WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);

try {
ModelAndView mv = null;
Exception dispatchException = null;

try {
processedRequest = checkMultipart(request);
multipartRequestParsed = (processedRequest != request);

// Determine handler for the current request.
mappedHandler = getHandler(processedRequest);
if (mappedHandler == null) {
noHandlerFound(processedRequest, response);
return;
}

...
}

查看checkMultipart细节

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
protected HttpServletRequest checkMultipart(HttpServletRequest request) throws MultipartException {
if (this.multipartResolver != null && this.multipartResolver.isMultipart(request)) {
if (WebUtils.getNativeRequest(request, MultipartHttpServletRequest.class) != null) {
if (request.getDispatcherType().equals(DispatcherType.REQUEST)) {
logger.trace("Request already resolved to MultipartHttpServletRequest, e.g. by MultipartFilter");
}
}
else if (hasMultipartException(request)) {
logger.debug("Multipart resolution previously failed for current request - " +
"skipping re-resolution for undisturbed error rendering");
}
else {
try {
return this.multipartResolver.resolveMultipart(request);
}
catch (MultipartException ex) {
if (request.getAttribute(WebUtils.ERROR_EXCEPTION_ATTRIBUTE) != null) {
logger.debug("Multipart resolution failed for error dispatch", ex);
// Keep processing error dispatch with regular request handle below
}
else {
throw ex;
}
}
}
}
// If not returned before: return original request.
return request;
}

首先我们了解下this.multipartResolver是如何被加载的

我们可以看到DispatcherServlet的方法中有初始化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
private void initMultipartResolver(ApplicationContext context) {
try {
this.multipartResolver = context.getBean(MULTIPART_RESOLVER_BEAN_NAME, MultipartResolver.class);
if (logger.isTraceEnabled()) {
logger.trace("Detected " + this.multipartResolver);
}
else if (logger.isDebugEnabled()) {
logger.debug("Detected " + this.multipartResolver.getClass().getSimpleName());
}
}
catch (NoSuchBeanDefinitionException ex) {
// Default is no multipart resolver.
this.multipartResolver = null;
if (logger.isTraceEnabled()) {
logger.trace("No MultipartResolver '" + MULTIPART_RESOLVER_BEAN_NAME + "' declared");
}
}
}

通过查看MultipartResolver的实现类,我们可以看到如下两个实现类

  1. CommonsMultipartResolver
  2. StandardServletMultipartResolver

其中StandardServletMultipartResolver在下述自动配置类中有被加载

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
package org.springframework.boot.autoconfigure.web.servlet;

import javax.servlet.MultipartConfigElement;
import javax.servlet.Servlet;

import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication.Type;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.multipart.MultipartResolver;
import org.springframework.web.multipart.commons.CommonsMultipartResolver;
import org.springframework.web.multipart.support.StandardServletMultipartResolver;
import org.springframework.web.servlet.DispatcherServlet;

@Configuration
@ConditionalOnClass({ Servlet.class, StandardServletMultipartResolver.class, MultipartConfigElement.class })
@ConditionalOnProperty(prefix = "spring.servlet.multipart", name = "enabled", matchIfMissing = true)
@ConditionalOnWebApplication(type = Type.SERVLET)
@EnableConfigurationProperties(MultipartProperties.class)
public class MultipartAutoConfiguration {

private final MultipartProperties multipartProperties;

public MultipartAutoConfiguration(MultipartProperties multipartProperties) {
this.multipartProperties = multipartProperties;
}

@Bean
@ConditionalOnMissingBean({ MultipartConfigElement.class, CommonsMultipartResolver.class })
public MultipartConfigElement multipartConfigElement() {
return this.multipartProperties.createMultipartConfig();
}

@Bean(name = DispatcherServlet.MULTIPART_RESOLVER_BEAN_NAME)
@ConditionalOnMissingBean(MultipartResolver.class)
public StandardServletMultipartResolver multipartResolver() {
StandardServletMultipartResolver multipartResolver = new StandardServletMultipartResolver();
multipartResolver.setResolveLazily(this.multipartProperties.isResolveLazily());
return multipartResolver;
}

}

可以看出若没有指定MultipartResolver时,会默认加载StandardServletMultipartResolver。我们查看其isMultipart放过可知 当请求ContentType类型以multipart/则会认为是文件上传操作。

1
2
3
4
@Override
public boolean isMultipart(HttpServletRequest request) {
return StringUtils.startsWithIgnoreCase(request.getContentType(), "multipart/");
}

解决上传文件大小限制

同时我们也可以得出,若没有指定的MultipartConfigElement时,会使用默认的this.multipartProperties.createMultipartConfig()

通过查看this.multipartProperties

1
2
3
4
@ConfigurationProperties(prefix = "spring.servlet.multipart", ignoreUnknownFields = false)
public class MultipartProperties {
...
}

MultipartProperties被自动EnableConfigurationProperties所引用

1
2
3
4
5
6
7
@Configuration
@ConditionalOnClass({ Servlet.class, StandardServletMultipartResolver.class, MultipartConfigElement.class })
@ConditionalOnProperty(prefix = "spring.servlet.multipart", name = "enabled", matchIfMissing = true)
@ConditionalOnWebApplication(type = Type.SERVLET)
@EnableConfigurationProperties(MultipartProperties.class)
public class MultipartAutoConfiguration {
}

则可通过修改对应SpringBoot可以配置application.yml

1
2
3
4
5
spring:
servlet:
multipart:
max-file-size: 150MB
max-request-size: 150MB

EnableConfigurationProperties原理

通过查看注解EnableConfigurationProperties被调用处,即查找EnableConfigurationProperties.class出现处。我们可以看到在EnableConfigurationPropertiesImportSelector的方法

1
2
3
4
5
private List<Class<?>> getTypes(AnnotationMetadata metadata) {
MultiValueMap<String, Object> attributes = metadata
.getAllAnnotationAttributes(EnableConfigurationProperties.class.getName(), false);
return collectClasses((attributes != null) ? attributes.get("value") : Collections.emptyList());
}

所有注解了EnableConfigurationProperties的类被自动注入到容器中了。

1
2
3
4
@Override
public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
getTypes(metadata).forEach((type) -> register(registry, (ConfigurableListableBeanFactory) registry, type));
}
1
2
3
4
5
6
7
private void register(BeanDefinitionRegistry registry, ConfigurableListableBeanFactory beanFactory,
Class<?> type) {
String name = getName(type);
if (!containsBeanDefinition(beanFactory, name)) {
registerBeanDefinition(registry, name, type);
}
}

一步一步回溯可以最终可以看到被AbstractApplicationContext的refresh方法调用。

静态内部类的泛型与建造者模式

发表于 2019-08-20 更新于 2020-06-30 分类于 设计模式

静态内部类的泛型和外部类的泛型没有任何关系,即使使用同一个字母。

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
package com.li.springboot.bean;

import java.util.List;

public class LayuiResponse<T> {

private final int code;
private final String msg;
private final List<T> data;
private final Integer count;

public int getCode() {
return code;
}

public String getMsg() {
return msg;
}

public List<T> getData() {
return data;
}

public int getCount() {
return count;
}

public static <T> Builder<T> builder() {
return new Builder<T>();
}

private LayuiResponse(Builder<T> builder) {
this.code = builder.code;
this.msg = builder.msg;
this.data = builder.data;
this.count = builder.count;
}

public static class Builder<R> {
private int code;
private String msg;
private List<R> data;
private Integer count;

public Builder<R> code(int code) {
this.code = code;
return this;
}

public Builder<R> msg(String msg) {
this.msg = msg;
return this;
}

public Builder<R> data(List<R> data) {
this.data = data;
return this;
}

public Builder<R> count(int count) {
this.count = count;
return this;
}

public LayuiResponse<R> build() {
return new LayuiResponse<>(this);
}
}
}

使用时,需指定内部类的泛型

1
2
3
 List<String> list = new ArrayList<>();
list.add("123");
LayuiResponse<String> ok = LayuiResponse.<String>builder().code(0).msg("ok").data(list).build();

使用构造器模式,主要是为了使属性在构建时一次性初始化好,不允许中间过程去设置属性,保持bean的状态一致性。同时也是为了清晰化构建过程。推荐使用final来修饰被构建对象的属性值, 确保成员属性不会被赋值。

SpringBoot自定义扫描器

发表于 2019-08-18 更新于 2020-06-30 分类于 spring

实现自定义注解扫描器,将被JsonBean注解的类,注入到spring容器中,当由spring生成时,自动根据对应的json文件自动生成。

首先是两个注解

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

import java.lang.annotation.*;

@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface JsonBean {
String value();
}



import com.li.ivr.test.scanner.JsonBeanRegistrar;
import org.springframework.context.annotation.Import;

import java.lang.annotation.*;

@Target( ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(JsonBeanRegistrar.class)
public @interface JsonBeanScanner {
String 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
package com.li.ivr.test.expression;


import com.li.ivr.test.annotation.JsonBean;

import java.util.Map;

@JsonBean("/data.json")
public class Session {

private Map<String,?> avaya;

public Map<String, ?> getAvaya() {
return avaya;
}

public void setAvaya(Map<String, ?> avaya) {
this.avaya = avaya;
}

@Override
public String toString() {
return "Session{" +
"avaya=" + avaya +
'}';
}
}

核心代码,即JsonBeanScanner中import的类JsonBeanRegistrar

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
package com.li.ivr.test.scanner;

import com.li.ivr.test.advise.ProxyFactoryBean;
import com.li.ivr.test.annotation.JsonBean;
import com.li.ivr.test.annotation.JsonBeanScanner;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider;
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.core.type.filter.AnnotationTypeFilter;

import java.util.Objects;

public class JsonBeanRegistrar implements ImportBeanDefinitionRegistrar {
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
String path =
(String) Objects.requireNonNull(importingClassMetadata.getAnnotationAttributes(JsonBeanScanner.class.getName())).get(
"value");
createComponentScanner().findCandidateComponents(path).forEach(beanDefinition -> {
beanDefinition.getConstructorArgumentValues().addGenericArgumentValue(Objects.requireNonNull(beanDefinition.getBeanClassName()));
beanDefinition.setBeanClassName(ProxyFactoryBean.class.getName());
registry.registerBeanDefinition(Objects.requireNonNull(beanDefinition.getBeanClassName()), beanDefinition);
});

}

private ClassPathScanningCandidateComponentProvider createComponentScanner() {
// Don't pull default filters (@Component, etc.):
ClassPathScanningCandidateComponentProvider provider
= new ClassPathScanningCandidateComponentProvider(false);
provider.addIncludeFilter(new AnnotationTypeFilter(JsonBean.class));
return provider;
}
}

其中ProxyFactoryBean为生成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
package com.li.ivr.test.advise;

import com.google.gson.Gson;
import com.li.ivr.test.annotation.JsonBean;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.util.StreamUtils;

import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;

public class ProxyFactoryBean<T> implements FactoryBean {
private Class<T> interfaceClass;

public ProxyFactoryBean(Class<T> interfaceClass) {
this.interfaceClass = interfaceClass;
}

@Override
public T getObject() throws IOException {
JsonBean annotation = interfaceClass.getAnnotation(JsonBean.class);
InputStream in = ProxyFactoryBean.class.getResourceAsStream(annotation.value());
String json = StreamUtils.copyToString(in, StandardCharsets.UTF_8);
return new Gson().fromJson(json, interfaceClass);
}

@Override
public Class<?> getObjectType() {
return interfaceClass;
}
}

我们在任意配置类中设置扫描路径即可

1
2
3
4
@@Configuration
@JsonBeanScanner("com.li")
public class Application {
}

java中使用js

发表于 2019-08-18 更新于 2020-06-30 分类于 java

背景

使用SpringBoot构建的项目

注入js代码

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
function objectToString(obj) {
try {
var result = "";
result += getobjectprops("", obj);
if (result.charAt(0) == '|') {
result = result.substring(1);
}
} catch (errMsg) {
return ("undefined");
}
return result;
}

function getArray(arrayName, item) {
try {
var len = eval(arrayName + '.length;');
var result = "";
for (var i = 0; i < len; i++) {
if (i > 0) {
result += " |";
}
var temp = arrayName + "[" + i + "]." + item;
result += eval(temp);
}
return result;
} catch (errMsg) {
return ('unknown');
}
}

function getRedirect(item) {
return (getArray('session.connection.redirect', item));
}

f
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
package com.li.ivr.test.expression;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.core.io.Resource;
import org.springframework.stereotype.Component;
import org.springframework.util.StreamUtils;

import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import javax.script.ScriptException;
import java.io.IOException;
import java.nio.charset.StandardCharsets;

/**
*
*/
@Component
public class JS {

@Value("classpath:rootxml.js")
private Resource js;

@Bean
public ScriptEngine scriptEngine() throws IOException, ScriptException {
ScriptEngineManager manager = new ScriptEngineManager();
ScriptEngine engine = manager.getEngineByName("javascript");
engine.eval(StreamUtils.copyToString(js.getInputStream(), StandardCharsets.UTF_8));
return engine;
}
}

测试

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
package com.li.ivr.test.expression;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

import javax.script.ScriptEngine;
import javax.script.ScriptException;
import java.io.IOException;

@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest
public class JSTest {

@Autowired
ScriptEngine js;

@Test
public void test() throws ScriptException, IOException {
Object eval = js.eval("objectToString(1)");
System.out.println(eval);
}
}

gradle入门

发表于 2019-08-18 更新于 2020-06-30 分类于 java

刷新依赖

1
gradle build --refresh-dependencies

vim

发表于 2019-08-11 更新于 2020-12-02 分类于 linux

从本质上讲,我们的工作是重复性的 。不论是在几个不同的地方做相同的小改动,还是在文档的相似结构间移动,我们都会重复很多操作,凡是可以简化重复性操作的方式,都会成倍地节省我们的事件

减少无关的操作,以最少的命令完成任务

查找

f
行内查找下一个指定字符,光标位于字符处,;查找下一个,,查找上一个。F反向

t
行内查找下一个指定字符,光标位于字符前;查找下一个,,查找上一个。T反向

#
普通模式下输入#寻找游标所在处的单词,*是反向

ma
标记当前为止为 a

`a 引用标记的为止 a,跳转到 a

'' 跳转到光标上次停靠的地方, 是两个', 而不是一个"

%
匹配对应的括号

(
光标移至句首,)反向

b
光标左移一个字至字首,跳过空格,B,跳转上一个空格前

移动{motion}

^ 行首第一个单词前

e
光标跳转到下一个单词尾,E跳转至下一个空格前的一个单词尾,不包含空行

b
光标跳转到上一个单词头,B,跳转上一个空格后的一个单词头,包括空行

w
光标跳转到下一个单词头,W,跳转下一个空格后的一个单词头

{
转到上一个空行 }反向

编辑

a 光标之后追加,A,行尾追加

i 光标处插入,I,行首追加

s
删除当前字符并进入插入模式,S删除当前行并进入插入模式

r 替换当前字符,R 进入替换模式,直到按下 esc

操作符{operator}

组合命令配合移动或查找命令一起使用,一般操作符命令连续调用两次时,它会作用与当前行

c
删除并进入插入模式,C等同于cc

d
删除,D等同于dd

> 增加缩进 <减少缩进

v 选择模式, V行选择模式

g 前缀字符,用于更改其后面的按键行为,可将其视为一整个操作符

gU
转换大写,例如:gUU操作整行,gUaw更高当前单词

gu
转换小写

g~ 大小写反转

gc 切换注释状态

gD,gd
跳转到局部变量的定义处

gv重选上次的高亮选区

{operator}i{motion},{operator}a{motion}

以当前位置进行操作,配合可以选择范围的移动命令,i和a的区别在于是否选中移动命令的关键词如{,(等。 例如:di{,di(,diw,da{,gUaw

其他

. 重复执行上次命令,即每次进入插入模式的那一刻(例如:输入 i),直到返回普通模式为止(输入<Esc>),Vim 会记录每一个按键操作。做出这样一个修改后在用.命令,它将会重新执行所有这些按键操作。

ZZ
命令模式下保存当前文件所做的修改后退出 vi;

EX 命令

[range]为可选参数,表示行的范围,

  • .表示当前行(缺省值)
  • .+3表示当前行后三行(缺省值)
  • 'm 包含位置标记 m 的行
  • n 表示指定行
  • n,m表示 n 到 m 行
  • $可表示最后一行
  • %表示所有行
  • <高亮选取的起始行
  • >高亮选取的结束行

[line]可选参数 指定行,默认为当前行 [x]寄存器为一位字符,可通过:registers(简称:reg)查看寄存器内容 [address]指定行,也可以使用$表最后一行

常用的操作缓冲区问的 ex 命令

  1. :[range] delete [x]
    删除指定范围的行到寄存器 X,简写d
  2. :[range] yank [x]
    删除指定范围的行到寄存器 X,简写y
  3. :[line] put [x]
    删除指定范围的行到寄存器 X,简写y
  4. :[range] copy {address}
    复制指定范围内的行到某行 简写co
  5. :[range] move {address}
    移动指定范围内的行到某行,简写m
  6. :[range] join
    连接指定范围内的行,简写j
  7. :[range]/substiture/{pattern}/{string}/{flags}
    把指定范围内出现的{pattern}替换为{string} 例如:
    • :%s/vivian/sky/g 替换每一行中所有 vivian 为 sky
    • :1,$ s/$/WORLD/g 替换行尾
    • :1,$ s/^/HELLO/g 替换行首
    • :g/^s*$/d 删除所有空格
    • `:s/vivian/sky/g 替换当前行所有 vivian 为 sky
    • :%s/ /\r/g替换空格为换行
  8. :[range]global/{pattern}/[cmd]
    对指定范围内匹配{pattern}的行执行 cmd,简称g
  9. :[range]normal {commands} 对指定范围内的行执行普通命令。例如
    • :% normal A; 在每行后面添加;,在做修改时自动进入插入模式,修改后自动返回正常模式
    • :% normal @q 在每行执行宏命令
    • :% normal . 在每行执行最近修改命令

命令

:E
浏览当前目录 :f
在命令模式下,用于显示当前的文件名、光标所在行的行号以及显示比例; :find 通过文件名查找文件,支持 tab 补全

翻页

H
光标移至屏幕顶行
L
光标移至屏幕最后行
M
光标移至屏幕中间行
Ctrl+b
向文件首翻一屏;
Ctrl+d
向文件尾翻半屏;
Ctrl+f
向文件尾翻一屏;
Ctrl+u
向文件首翻半屏;

配置文件

vim 的配置在~/.vimrc中。

  • set paste 进入 paste 模式以后,可以在插入模式下粘贴内容,不会有任何变形,解决粘贴乱行问题

宏模式

在命令模式下按下qa开始记录,指导在命令模式下再次按下q结束记录。 可通过@a,重复执行命令,n@a重复执行n次。

查看颜色代码

1
:hi

例如 vim_2020-06-03-11-36-27.png

其中关于搜索高亮的颜色是配置项 Search,我们可以临时修改

1
:hi Search term=reverse ctermfg=0 ctermbg=3 guifg=#000000 guibg=#FFE792

也可在.vimrc中配置,使其永久生效

1
hi Search term=reverse ctermfg=0 ctermbg=3 guifg=#000000 guibg=#FFE792

查看字节

使用 16 进制查看文件的 byte

1
2
vim -b XXX.class
:%!xxd

显示特殊字符

在 vi 中

1
:set list

但是不会显示 dos 中的特殊符号^M,我们可使用dos2unix命令快速转换为 unix 格式,会自动移除掉^M

tomcat

发表于 2019-08-10 更新于 2020-06-30 分类于 java

配置 JNDI

确保 tomcat 下有相关jar

tomcat 目录下/conf/context.xml中增加配置

1
2
3
4
5
6
7
8
9
10
11
12
<Resource name = "jdbc/mysql"
auth = "Container"
type = "javax.sql.DataSource"
driverClassName = "com.mysql.jdbc.Driver"
url = "jdbc:mysql://localhost:3306/app"
factory="com.li.jndi.EncryptedDataSourceFactory"
username = "root"
password = "xxxx"
maxActive = "200"
maxIdle = "30"
maxWait = "5000"
/>

确保 tomcat 目录下有driver的jar包

/lib/mysql-connector-java-8.0.16.jar

factory标签是指定BasicDataSourceFactory工厂类,可以用来解密password密文。需要如下依赖

1
2
3
4
5
<dependency>
<groupId>commons-dbcp</groupId>
<artifactId>commons-dbcp</artifactId>
<version>1.2.2</version>
</dependency>

EncryptedDataSourceFactory代码如下

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
package com.li.jndi;

import org.apache.commons.dbcp.BasicDataSourceFactory;
import org.apache.naming.ResourceRef;

import javax.naming.*;
import java.util.Enumeration;
import java.util.Hashtable;

public class EncryptedDataSourceFactory extends BasicDataSourceFactory {

@Override
public Object getObjectInstance(Object obj, Name name, Context nameCtx, Hashtable environment) throws Exception {
if (obj instanceof ResourceRef) {
decode("password", (Reference) obj);
}
return super.getObjectInstance(obj, name, nameCtx, environment);
}

private String decode(String old) throws Exception {
return "root";
}

private int find(String addrType, Reference ref) throws Exception {
Enumeration enu = ref.getAll();
for (int i = 0; enu.hasMoreElements(); i++) {
RefAddr addr = (RefAddr) enu.nextElement();
if (addr.getType().compareTo(addrType) == 0) {
return i;
}
}
throw new Exception("The \"" + addrType
+ "\" name/value pair was not found"
+ " in the Reference object. The reference Object is" + " "
+ ref.toString());
}

private void decode(String refType, Reference ref) throws Exception {
int index = find(refType, ref);
RefAddr refAddr = ref.get(index);
Object content = refAddr.getContent();
if (content instanceof String) {
ref.remove(index);
ref.add(index, new StringRefAddr(refType, decode((String) content)));
}
}

}

使用jndi服务

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package com.li.jndi;

import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.SQLException;

public class DBConn {

private static DataSource dataSource;

static {
try {
Context context = new InitialContext();
dataSource = (DataSource)context.lookup("java:comp/env/jdbc/mysql");
}
catch (NamingException e) {
e.printStackTrace();
}
}
}

tomcat 类加载顺序

tomcat 优先加载应用内的 jar 包,然后在加载共享 lib 目录下的 jar 包

java-bridge-method

发表于 2019-08-09 更新于 2020-06-30 分类于 java

桥接方法

泛型类型擦除的影响,以及 bridge 方法介绍

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class Node<T> {

public T data;

public Node(T data) { this.data = data; }

public void setData(T data) {
System.out.println("Node.setData");
this.data = data;
}
}

public class MyNode extends Node<Integer> {
public MyNode(Integer data) { super(data); }

public void setData(Integer data) {
System.out.println("MyNode.setData");
super.setData(data);
}
}

当做如下使用时

1
2
Node node = new MyNode(5);
n.setData("Hello");

我们的子类中实际是没有 setData(Object.class)的方法的,java编译器在进行类型擦除的时候会自动生成一个synthetic方法,也叫bridge方法,我们通过生成的字节码可以看到实际bridge方法,首先校验类型是否为Integer,然后在调用setData(Integer.class)因此,上述代码会抛出ClassCastException

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
public void setData(java.lang.Integer);
descriptor: (Ljava/lang/Integer;)V
flags: ACC_PUBLIC
Code:
stack=2, locals=2, args_size=2
0: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
3: ldc #3 // String MyNode.setNode
5: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
8: aload_0
9: aload_1
10: invokespecial #5 // Method com/li/springboot/bridge/Node.setData:(Ljava/lang/Object;)V
13: return
LineNumberTable:
line 11: 0
line 12: 8
line 13: 13
LocalVariableTable:
Start Length Slot Name Signature
0 14 0 this Lcom/li/springboot/bridge/MyNode;
0 14 1 integer Ljava/lang/Integer;
MethodParameters:
Name Flags
integer


public void setData(java.lang.Object);
descriptor: (Ljava/lang/Object;)V
flags: ACC_PUBLIC, ACC_BRIDGE, ACC_SYNTHETIC
Code:
stack=2, locals=2, args_size=2
0: aload_0
1: aload_1
2: checkcast #11 // class java/lang/Integer
5: invokevirtual #12 // Method setData:(Ljava/lang/Integer;)V
8: return
LineNumberTable:
line 3: 0
LocalVariableTable:
Start Length Slot Name Signature
0 9 0 this Lcom/li/springboot/bridge/MyNode;
MethodParameters:
Name Flags
integer synthetic

桥接子类获取泛型

父类泛型可以使用

1
2
3
ParameterizedTypeImpl type = (ParameterizedTypeImpl) MyNode.class.getGenericSuperclass();
//具体每个泛型
type.getActualTypeArguments()

接口泛型

1
ParameterizedTypeImpl[] types = (ParameterizedTypeImpl[]) MyNode.class.getGenericInterfaces();

Spring 注入桥接子类注意

1
2
3
4
5
6
public interface Generic<T,R> {}
@Component
public class G1 implements Generic<Object, Collection> {}
public class G2 implements Generic<Object, List> {}
public class G3<T> implements Generic<T, List> {}
public class G4 implements Generic<String, List> {}
1
2
3
4
5
6
7
8
9
10
11

@Autowired
List<Generic> generics; //G1 G2 G3 G4
@Autowired
List<Generic<?, ? extends Collection>> generics; //G1 G2 G3 G4
@Autowired
List<Generic<?, Collection>> generics;//G1
@Autowired
List<Generic<Object, ? extends Collection>> generics; //G1 G2 G3
@Autowired
List<Generic<Object, Collection>> generics; //G1 G2

restTemplate使用

发表于 2019-08-09 更新于 2020-12-02 分类于 spring

基础

推荐使用RestTemplateBuilder构建RestTemplate

我们看下RestTemplate的基础成员变量

  • requestFactory: ClientHttpRequestFactory
  • defaultUriVariables: Map<String, ?>
  • uriTemplateHandler: UriTemplateHandler
  • interceptors: List<ClientHttpRequestInterceptor>
  • messageConverters: List<HttpMessageConverter<?>>
  • errorHandler: ResponseErrorHandler

下面我们主要针对这些属性做一些分析

requestFactory

指定使用的HTTP请求方式, 设置http请求工厂类,处理超时,线程,异常处理等情况。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
RestTemplate restTemplate() {
return new RestTemplateBuilder().requestFactory(requestFactory()).build();
}

private Supplier<ClientHttpRequestFactory> requestFactory() {
return () -> {
HttpComponentsClientHttpRequestFactory requestFactory = new HttpComponentsClientHttpRequestFactory();
requestFactory.setConnectionRequestTimeout(3000);
requestFactory.setConnectTimeout(3000);
requestFactory.setReadTimeout(8000);
requestFactory.setHttpClient(httpClient());
return requestFactory;
};
}

private HttpClient httpClient() {
HttpClientBuilder httpClientBuilder = HttpClientBuilder.create();
httpClientBuilder.setMaxConnTotal(50);
httpClientBuilder.setMaxConnPerRoute(5);
return HttpClientBuilder.create().build();
}

defaultUriVariables

当被赋值时就不能使用自定义uriTemplateHandler。这是就使用默认的uriTemplateHandler即DefaultUriBuilderFactory。 假设现在defaultUriVariables包含default=demo。

例如:

1
2
3
4
5
6
7
String url = "http://localhost:8080/{default}/{replace}"

Map<String,String> uriVariables = new HashMap<>();
uriVariables.put("replace","someuri");

public <T> T postForObject(String url, @Nullable Object request, Class<T> responseType,
Map<String, ?> uriVariables)

在实际请求时: {default}会被替换为demo
{replace}会被替换为someuri
替换后的值中包含/会无法正常解析。

uriTemplateHandler

默认的 uriTemplateHandler 会将 uriVariables 中的 value 进行转译,因此无法'/'会被解析为 ascii 符号。根据需要,生成满足自己需要的 URI。

1
2
3
4
public interface UriTemplateHandler {
URI expand(String uriTemplate, Map<String, ?> uriVariables);
URI expand(String uriTemplate, Object... uriVariables);
}

返回的 URI 经过自定义处理器会将{xxx},替换为uriVariables中对应的值,这个可以正常解析/

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
RestTemplate rest = restTemplate();
rest.setUriTemplateHandler(new UriTemplateHandler() {
@Override
public URI expand(String uriTemplate, Map<String, ?> uriVariables) {
return new UriTemplate(uriTemplate).expand(uriVariables);
}

@Override
public URI expand(String uriTemplate, Object... uriVariables) {
return new UriTemplate(uriTemplate).expand(uriVariables);
}
});

Map<String, Object> uriVariables = new HashMap<>();
uriVariables.put("replace", "response/test");
String hello = rest.postForObject("http://localhost:8080/{replace}", "", String.class, uriVariables);

interceptors

类似AOP切面,在HTTP请求前后进行拦截,比如统一加`headers

1
2
3
4
5
6
7
8
9
public interface ClientHttpRequestInterceptor {

ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution)
throws IOException;
}

ClientHttpRequestInterceptor interceptor = (request, body, execution) -> {
return execution.execute(request, body);
};

messageConverters

与SpringMVC的messageConverters原理是一样的。HTTP交易时数据传递是通过二进制byte传递的。而我们使用RestTemplate时,一般请求返回都使用javaBean,那就需要messageConverters来统一处理。

我们看下RestTemplate的静态方法

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
private static boolean romePresent;

private static final boolean jaxb2Present;

private static final boolean jackson2Present;

private static final boolean jackson2XmlPresent;

private static final boolean jackson2SmilePresent;

private static final boolean jackson2CborPresent;

private static final boolean gsonPresent;

private static final boolean jsonbPresent;

static {
ClassLoader classLoader = RestTemplate.class.getClassLoader();
romePresent = ClassUtils.isPresent("com.rometools.rome.feed.WireFeed", classLoader);
jaxb2Present = ClassUtils.isPresent("javax.xml.bind.Binder", classLoader);
jackson2Present =
ClassUtils.isPresent("com.fasterxml.jackson.databind.ObjectMapper", classLoader) &&
ClassUtils.isPresent("com.fasterxml.jackson.core.JsonGenerator", classLoader);
jackson2XmlPresent = ClassUtils.isPresent("com.fasterxml.jackson.dataformat.xml.XmlMapper", classLoader);
jackson2SmilePresent = ClassUtils.isPresent("com.fasterxml.jackson.dataformat.smile.SmileFactory", classLoader);
jackson2CborPresent = ClassUtils.isPresent("com.fasterxml.jackson.dataformat.cbor.CBORFactory", classLoader);
gsonPresent = ClassUtils.isPresent("com.google.gson.Gson", classLoader);
jsonbPresent = ClassUtils.isPresent("javax.json.bind.Jsonb", classLoader);
}

再看下RestTemplate构造器,当classpath中有对应的class时,可以看到RestTemplate会自动加载

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
public RestTemplate() {
this.messageConverters.add(new ByteArrayHttpMessageConverter());
this.messageConverters.add(new StringHttpMessageConverter());
this.messageConverters.add(new ResourceHttpMessageConverter(false));
try {
this.messageConverters.add(new SourceHttpMessageConverter<>());
}
catch (Error err) {
// Ignore when no TransformerFactory implementation is available
}
this.messageConverters.add(new AllEncompassingFormHttpMessageConverter());

if (romePresent) {
this.messageConverters.add(new AtomFeedHttpMessageConverter());
this.messageConverters.add(new RssChannelHttpMessageConverter());
}

if (jackson2XmlPresent) {
this.messageConverters.add(new MappingJackson2XmlHttpMessageConverter());
}
else if (jaxb2Present) {
this.messageConverters.add(new Jaxb2RootElementHttpMessageConverter());
}

if (jackson2Present) {
this.messageConverters.add(new MappingJackson2HttpMessageConverter());
}
else if (gsonPresent) {
this.messageConverters.add(new GsonHttpMessageConverter());
}
else if (jsonbPresent) {
this.messageConverters.add(new JsonbHttpMessageConverter());
}

if (jackson2SmilePresent) {
this.messageConverters.add(new MappingJackson2SmileHttpMessageConverter());
}
if (jackson2CborPresent) {
this.messageConverters.add(new MappingJackson2CborHttpMessageConverter());
}

this.uriTemplateHandler = initUriTemplateHandler();
}

errorHandler

仅在HTTP成功返回后才会被执行,决定当前HTTP请求是否成功。hasError返回true时才会调用handleError方法。

1
2
3
4
5
6
7
public interface ResponseErrorHandler {
boolean hasError(ClientHttpResponse response) throws IOException;
void handleError(ClientHttpResponse response) throws IOException;
default void handleError(URI url, HttpMethod method, ClientHttpResponse response) throws IOException {
handleError(response);
}
}

rootUri

通过RestTemplateBuilder设置rootUri,进行HTTP请求时,若非http开头,则会自动加上rootUri

1
2
RestTemplate rest = new RestTemplateBuilder().rootUri("http://localhost:8080").build();
rest.postForObject("/test", "", String.class, variables);

POST 请求参数

上传文件的请求模拟

1
2
3
4
5
RestTemplate restTemplate = new RestTemplate();
MultiValueMap multiValueMap = new LinkedMultiValueMap();
FileSystemResource resource = new FileSystemResource(new File("/Users/li/Documents/Blog/db.json"));
multiValueMap.add("file",resource);
String msg = restTemplate.postForObject("http://localhost:8082/upload/any", multiValueMap, String.class);

上传文件的错误

错误信息

The request was rejected because no multipart boundary was found

通过查找该报错信息打印处可以定位

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
FileItemIteratorImpl(RequestContext ctx)
throws FileUploadException, IOException {
if (ctx == null) {
throw new NullPointerException("ctx parameter");
}

String contentType = ctx.getContentType();
if ((null == contentType)
|| (!contentType.toLowerCase(Locale.ENGLISH).startsWith(MULTIPART))) {
throw new InvalidContentTypeException(String.format(
"the request doesn't contain a %s or %s stream, content type header is %s",
MULTIPART_FORM_DATA, MULTIPART_MIXED, contentType));
}


final long requestSize = ((UploadContext) ctx).contentLength();

InputStream input; // N.B. this is eventually closed in MultipartStream processing
if (sizeMax >= 0) {
if (requestSize != -1 && requestSize > sizeMax) {
throw new SizeLimitExceededException(String.format(
"the request was rejected because its size (%s) exceeds the configured maximum (%s)",
Long.valueOf(requestSize), Long.valueOf(sizeMax)),
requestSize, sizeMax);
}
// N.B. this is eventually closed in MultipartStream processing
input = new LimitedInputStream(ctx.getInputStream(), sizeMax) {
@Override
protected void raiseError(long pSizeMax, long pCount)
throws IOException {
FileUploadException ex = new SizeLimitExceededException(
String.format("the request was rejected because its size (%s) exceeds the configured maximum (%s)",
Long.valueOf(pCount), Long.valueOf(pSizeMax)),
pCount, pSizeMax);
throw new FileUploadIOException(ex);
}
};
} else {
input = ctx.getInputStream();
}

String charEncoding = headerEncoding;
if (charEncoding == null) {
charEncoding = ctx.getCharacterEncoding();
}

boundary = getBoundary(contentType);
if (boundary == null) {
IOUtils.closeQuietly(input); // avoid possible resource leak
throw new FileUploadException("the request was rejected because no multipart boundary was found");
}

notifier = new MultipartStream.ProgressNotifier(listener, requestSize);
try {
multi = new MultipartStream(input, boundary, notifier);
} catch (IllegalArgumentException iae) {
IOUtils.closeQuietly(input); // avoid possible resource leak
throw new InvalidContentTypeException(
String.format("The boundary specified in the %s header is too long", CONTENT_TYPE), iae);
}
multi.setHeaderEncoding(charEncoding);

skipPreamble = true;
findNextItem();
}

通过查看getBoundary

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
protected byte[] getBoundary(String contentType) {
ParameterParser parser = new ParameterParser();
parser.setLowerCaseNames(true);
// Parameter parser can handle null input
Map<String,String> params =
parser.parse(contentType, new char[] {';', ','});
//Content-Type中包含boundary=xxx字段
String boundaryStr = params.get("boundary");

if (boundaryStr == null) {
return null;
}
byte[] boundary;
boundary = boundaryStr.getBytes(StandardCharsets.ISO_8859_1);
return boundary;
}

根据上一节的说明,我们知道上送文件是使用MultiValueMap来上送的,那么我们只要知道MultiValueMap请求的Content-Type是什么即可。

根据前面章节messageConverters的加载介绍,我们知道RestTemplate会选择一个合适的处理器来处理, 其中AllEncompassingFormHttpMessageConverter的父类FormHttpMessageConverter

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
public void doWithRequest(ClientHttpRequest httpRequest) throws IOException {
super.doWithRequest(httpRequest);
Object requestBody = this.requestEntity.getBody();
if (requestBody == null) {
HttpHeaders httpHeaders = httpRequest.getHeaders();
HttpHeaders requestHeaders = this.requestEntity.getHeaders();
if (!requestHeaders.isEmpty()) {
requestHeaders.forEach((key, values) -> httpHeaders.put(key, new LinkedList<>(values)));
}
if (httpHeaders.getContentLength() < 0) {
httpHeaders.setContentLength(0L);
}
}
else {
//request的类型需要继承自MultiValueMap或者HttpEntity<MultiValueMap>
Class<?> requestBodyClass = requestBody.getClass();
Type requestBodyType = (this.requestEntity instanceof RequestEntity ?
((RequestEntity<?>)this.requestEntity).getType() : requestBodyClass);
HttpHeaders httpHeaders = httpRequest.getHeaders();
HttpHeaders requestHeaders = this.requestEntity.getHeaders();
MediaType requestContentType = requestHeaders.getContentType();
for (HttpMessageConverter<?> messageConverter : getMessageConverters()) {
if (messageConverter instanceof GenericHttpMessageConverter) {
GenericHttpMessageConverter<Object> genericConverter =
(GenericHttpMessageConverter<Object>) messageConverter;
if (genericConverter.canWrite(requestBodyType, requestBodyClass, requestContentType)) {
if (!requestHeaders.isEmpty()) {
requestHeaders.forEach((key, values) -> httpHeaders.put(key, new LinkedList<>(values)));
}
logBody(requestBody, requestContentType, genericConverter);
genericConverter.write(requestBody, requestBodyType, requestContentType, httpRequest);
return;
}
}
//requestContentType需满足如下之一
// null
// multipart/form-data
// */*
//application/x-www-form-urlencoded
else if (messageConverter.canWrite(requestBodyClass, requestContentType)) {
if (!requestHeaders.isEmpty()) {
requestHeaders.forEach((key, values) -> httpHeaders.put(key, new LinkedList<>(values)));
}
logBody(requestBody, requestContentType, messageConverter);
((HttpMessageConverter<Object>) messageConverter).write(
requestBody, requestContentType, httpRequest);
return;
}
}
String message = "No HttpMessageConverter for " + requestBodyClass.getName();
if (requestContentType != null) {
message += " and content type \"" + requestContentType + "\"";
}
throw new RestClientException(message);
}
}

查看具体FormHttpMessageConverter的write方法

1
2
3
4
5
6
7
8
9
10
public void write(MultiValueMap<String, ?> map, @Nullable MediaType contentType, HttpOutputMessage outputMessage)
throws IOException, HttpMessageNotWritableException {
//上送文件肯定使用的writeMultipart
if (!isMultipart(map, contentType)) {
writeForm((MultiValueMap<String, Object>) map, contentType, outputMessage);
}
else {
writeMultipart((MultiValueMap<String, Object>) map, outputMessage);
}
}
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
private void writeMultipart(final MultiValueMap<String, Object> parts, HttpOutputMessage outputMessage)
throws IOException {

final byte[] boundary = generateMultipartBoundary();
Map<String, String> parameters = new LinkedHashMap<>(2);
if (!isFilenameCharsetSet()) {
parameters.put("charset", this.charset.name());
}
//这里我们可以看到生成的boundary信息
parameters.put("boundary", new String(boundary, StandardCharsets.US_ASCII));

MediaType contentType = new MediaType(MediaType.MULTIPART_FORM_DATA, parameters);
HttpHeaders headers = outputMessage.getHeaders();
headers.setContentType(contentType);

if (outputMessage instanceof StreamingHttpOutputMessage) {
StreamingHttpOutputMessage streamingOutputMessage = (StreamingHttpOutputMessage) outputMessage;
streamingOutputMessage.setBody(outputStream -> {
writeParts(outputStream, parts, boundary);
writeEnd(outputStream, boundary);
});
}
else {
writeParts(outputMessage.getBody(), parts, boundary);
writeEnd(outputMessage.getBody(), boundary);
}
}

所以我们定位一下具体发生错误的情况下使用的是何种messageConverter即可

messageConverter有关问题

当请求的api返回的content-type不是标准的application/json时,默认的messageConverter不支持处理

1
2
3
4
5
6
7
8
9
List<HttpMessageConverter<?>> messageConverters = new ArrayList<HttpMessageConverter<?>>();
//Add the Jackson Message converter
MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();

// Note: here we are making this converter to process any kind of response,
// not only application/*json, which is the default behaviour
converter.setSupportedMediaTypes(Collections.singletonList(MediaType.ALL));
messageConverters.add(converter);
restTemplate.setMessageConverters(messageConverters);

spring自定义注解改变方法

发表于 2019-08-09 更新于 2020-06-30 分类于 spring

版本说明

jdk:1.8.0_131
springboot:2.1.6.RELEAS
maven:3.6.1
lombok插件

概述

仅仅示范简单用法不涉及源码介绍,仅仅是一些实现方式,不一定是最优实现方式。这里仅仅只做打印参数,不做其他特殊操作, 可以根据需要,可以实现的包括异步调用,远程调用,缓存等等。

标记注解类

1
2
3
4
5
6
7
8
9
10
import org.slf4j.event.Level;

import java.lang.annotation.*;

@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface LogAnnotation {
Level value() default Level.DEBUG;
}

被注解bean

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

import org.slf4j.event.Level;
import org.springframework.stereotype.Component;

@Component
public class LogBean {
@LogAnnotation
public void debug() {

}

@LogAnnotation(Level.ERROR)
public void error() {

}

public void normal(){

}
}

方案一

使用BeanPostProcessor,扫描方法或类是否有LogAnnotation注解,如果有,则替换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
38
39
40
41
42
43
44
45
46
47
48
49
import lombok.extern.slf4j.Slf4j;
import org.slf4j.event.Level;
import org.springframework.aop.support.AopUtils;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

import java.util.Arrays;

@Component
@Order(Integer.MAX_VALUE)
@Slf4j
public class LogBeanPostProcessor implements BeanPostProcessor {
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {

Class<?> targetClass = AopUtils.getTargetClass(bean);
//判断类是否有注解LogAnnotation
if (AnnotatedElementUtils.findMergedAnnotation(targetClass, LogAnnotation.class) != null || Arrays.stream(targetClass.getMethods()).anyMatch(method -> AnnotatedElementUtils.findMergedAnnotation(method, LogAnnotation.class) != null)) {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(targetClass);
//注册切面方法
enhancer.setCallback((MethodInterceptor) (o, method, args, methodProxy) -> {
LogAnnotation annotation = AnnotatedElementUtils.findMergedAnnotation(targetClass, LogAnnotation.class);
if (annotation == null) {
annotation = AnnotatedElementUtils.findMergedAnnotation(method, LogAnnotation.class);
}
if (annotation == null) {
return method.invoke(bean, args);
}
//包含有注解的方法,或者类上有注解的方法,打印日志
Level level = annotation.value();
if (log.isDebugEnabled()) {
log.debug("[" + level + "]" + method.getName() + " " + Arrays.toString(args));
} else if (log.isErrorEnabled()) {
log.debug("[" + level + "]" + method.getName() + " " + Arrays.toString(args));
}
return method.invoke(bean, args);
});
//生成代理类
return enhancer.create();
}
return bean;
}
}

方案二

使用spring提供的切面功能,针对被注解的类进行切面

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
package com.li.springboot.other;

import com.li.springboot.advice.MyPointcutAdvisor;
import lombok.extern.slf4j.Slf4j;
import org.aopalliance.aop.Advice;
import org.aopalliance.intercept.MethodInterceptor;
import org.slf4j.event.Level;
import org.springframework.aop.Pointcut;
import org.springframework.aop.framework.AbstractAdvisingBeanPostProcessor;
import org.springframework.aop.support.AbstractPointcutAdvisor;
import org.springframework.aop.support.AopUtils;
import org.springframework.aop.support.ComposablePointcut;
import org.springframework.aop.support.annotation.AnnotationMatchingPointcut;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.core.BridgeMethodResolver;
import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import org.springframework.util.ClassUtils;

import java.lang.reflect.Method;
import java.util.Arrays;

@Component
@Order(Integer.MAX_VALUE)
@Slf4j
public class LogBeanAdvisingBeanPostProcessor extends AbstractAdvisingBeanPostProcessor implements BeanFactoryAware {
@Override
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
setBeforeExistingAdvisors(true);
MyPointcutAdvisor advisor = new MyPointcutAdvisor();
this.advisor = new AbstractPointcutAdvisor() {
@Override
public Advice getAdvice() {
return (MethodInterceptor) invocation -> {
//获取目标类
Class<?> targetClass = (invocation.getThis() != null ? AopUtils.getTargetClass(invocation.getThis()) : null);
//获取指定方法
Method specificMethod = ClassUtils.getMostSpecificMethod(invocation.getMethod(), targetClass);
//获取真正执行的方法,可能存在桥接方法
final Method userDeclaredMethod = BridgeMethodResolver.findBridgedMethod(specificMethod);
//获取方法上注解
LogAnnotation annotation = AnnotatedElementUtils.findMergedAnnotation(userDeclaredMethod,
LogAnnotation.class);
if (annotation == null) {
annotation = AnnotatedElementUtils.findMergedAnnotation(targetClass, LogAnnotation.class);
}
//包含注解则打印参数
if (annotation != null) {
Level level = annotation.value();
if (log.isDebugEnabled()) {
log.debug("[" + level + "]" + specificMethod.getName() + " " + Arrays.toString(invocation.getArguments()));
} else if (log.isErrorEnabled()) {
log.debug("[" + level + "]" + specificMethod.getName() + " " + Arrays.toString(invocation.getArguments()));
}
}
//执行具体业务逻辑
return invocation.proceed();
};
}

@Override
public Pointcut getPointcut() {
ComposablePointcut result = null;
//类级别
Pointcut cpc = new AnnotationMatchingPointcut(LogAnnotation.class, true);
//方法级别
Pointcut mpc = AnnotationMatchingPointcut.forMethodAnnotation(LogAnnotation.class);
//对于类和方法上都可以添加注解的情况
//类上的注解,最终会将注解绑定到每个方法上
result = new ComposablePointcut(cpc);
return result.union(mpc);
}
};
}
}
1…111213…15

lijun

与其感慨路难行,不如马上就出发
145 日志
30 分类
117 标签
© 2019 – 2020 lijun