SpringBoot2 | 第二十五篇(四):SpringAOP实战

做个实战锻炼下自己

实战案例背景

  • 商家商品管理系统
  • 记录产品的修改记录
  • 什么人在什么时间修改了哪些产品的哪些字段以及修改前后的值

实现思路

  • 利用 aspect 去拦截增删方法
  • 利用反射去获取对象的新旧值
  • 利用 @Around 的 advice 去记录操作记录

领域模型

1542262541459

环境/版本一览:

  • 开发工具:Intellij IDEA 2018.2.2
  • springboot: 2.1.0.RELEASE
  • jdk:1.8.0_171
  • maven:3.3.9
  • spring-boot-starter-aop:2.1.0.RELEASE
  • spring-boot-starter-data-jpa:2.1.0.RELEASE
  • spring-boot-starter-data-mongodb:2.1.0.RELEASE

1、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
<dependencies>
<!-- 反射工具类`PropertyUtils`在此包 -->
<dependency>
<groupId>commons-beanutils</groupId>
<artifactId>commons-beanutils</artifactId>
<version>1.9.3</version>
</dependency>
<!-- spring data jpa -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.47</version>
</dependency>
<!-- MongoDB 数据库 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-mongodb</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.46</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.16.20</version>
</dependency>
</dependencies>

2、application.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
spring:
datasource:
driver-class-name: com.mysql.jdbc.Driver
# 基本属性
url: jdbc:mysql://localhost:3306/chapter25_4?useUnicode=true&characterEncoding=UTF-8&allowMultiQueries=true&serverTimezone=UTC&useSSL=false
username: root
password: 123456
jpa:
# 显示 sql
show-sql: true
# 数据库类型
database: mysql
# JPA 配置
hibernate:
ddl-auto: update
# 指定生成的表的引擎为InnoDB类型(默认是MyISAM,MyISAM不支持事务)
database-platform: org.hibernate.dialect.MySQL57InnoDBDialect
data:
mongodb:
# 默认uri`mongodb://localhost:27017/test`
# 数据库有用户名和密码可参考:mongodb://name:password@localhost:27017/db
# uri: mongodb://fatal:123456@localhost:27017/test
uri: mongodb://localhost:27017/test
username: fatal
password: 123456

3、annotation

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

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
* 自定义`数据记录`注解
* 功能:在类,方法或者属性上标注该注解,并赋予中文名称。后台自动通过反射
* 将中文名称存入数据库
* @author: Fatal
* @date: 2018/11/15 0015 11:23
*/
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD, ElementType.FIELD})
public @interface DataLog {

/** 中文名称 */
String name();

}

4、enums

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.fatal.enums;

import lombok.Getter;

@Getter
public enum ActionType {

/**
* 添加
*/
INSERT("添加", 1),

/**
* 更新
*/
UPDATE("更新", 2),

/**
* 删除
*/
DELETE("删除", 3);

private String name;

private int code;

ActionType(String name, int code) {
this.name = name;
this.code = code;
}
}

5、utils

DifferentUtil

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
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
package com.fatal.util;

import com.alibaba.fastjson.JSON;
import com.fatal.annotation.DataLog;
import com.fatal.entity.ChangeItem;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.beanutils.PropertyUtils;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;

import java.beans.BeanInfo;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.*;

@Slf4j
public class DifferentUtil {

public static Object getObjectById(Object target,Object id) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
Method findMethod = target.getClass().getDeclaredMethod("findById", Long.class);
Object oldObj = findMethod.invoke(target,id);
Optional optional = (Optional) oldObj;
return optional.get();
}

/**
* 获取新增操作的change item
* @param obj
* @return
*/
public static List<ChangeItem> getInsertChangeItems(Object obj){
Map<String,String> valueMap = getBeanSimpleFieldValueMap(obj,true);
Map<String,String> fieldCnNameMap = getFieldNameMap(obj.getClass());
List<ChangeItem> items = new ArrayList<>();
for(Map.Entry<String,String> entry : valueMap.entrySet()){
String fieldName = entry.getKey();
String value = entry.getValue();
ChangeItem changeItem = new ChangeItem();
//set old value empty
changeItem.setOldValue("");
changeItem.setNewValue(value);
changeItem.setField(fieldName);
String cnName = fieldCnNameMap.get(fieldName);
changeItem.setFieldShowName(StringUtils.isEmpty(cnName) ? fieldName : cnName);
items.add(changeItem);
}
return items;
}

/**
* 获取删除操作的change item
* @param obj
* @return
*/
public static ChangeItem getDeleteChangeItem(Object obj){
ChangeItem changeItem = new ChangeItem();
changeItem.setOldValue(JSON.toJSONString(obj));
changeItem.setNewValue("");
return changeItem;
}

/**
* 获取更新操作的change item
* @param oldObj
* @param newObj
* @return
*/
public static List<ChangeItem> getChangeItems(Object oldObj, Object newObj) {
Class cl = oldObj.getClass();
List<ChangeItem> changeItems = new ArrayList<>();
//获取字段中文名称
Map<String,String> fieldCnNameMap = getFieldNameMap(cl);
try {
// 获取实体信息
BeanInfo beanInfo = Introspector.getBeanInfo(cl, Object.class);

for (PropertyDescriptor propertyDescriptor : beanInfo.getPropertyDescriptors()) {
String field = propertyDescriptor.getName();
//获取字段旧值
String oldProp = getValue(PropertyUtils.getProperty(oldObj, field));
//获取字段新值
String newProp = getValue(PropertyUtils.getProperty(newObj, field));

//对比新旧值
if (!oldProp.equals(newProp)) {
ChangeItem changeItem = new ChangeItem();
changeItem.setField(field);
String cnName = fieldCnNameMap.get(field);
changeItem.setFieldShowName(StringUtils.isEmpty(cnName) ? field : cnName);
changeItem.setNewValue(newProp);
changeItem.setOldValue(oldProp);
changeItems.add(changeItem);
}
}
} catch (Exception e) {
log.error("There is error when convert change item", e);
}
return changeItems;
}

/**
* 不同类型转字符串的处理
* @param obj
* @return
*/
public static String getValue(Object obj) {
if (obj != null) {
if (obj instanceof Date) {
return formatDateW3C((Date) obj);
} else {
return obj.toString();
}
} else {
return "";
}
}

/**
* 从注解读取中文名
* @param clz
* @return
*/
public static Map<String,String> getFieldNameMap(Class<?> clz){
Map<String,String> map = new HashMap<>(16);
for (Field field : clz.getDeclaredFields()) {
if (field.isAnnotationPresent(DataLog.class)) {
DataLog datalog = field.getAnnotation(DataLog.class);
map.put(field.getName(),datalog.name());
}
}
return map;
}

/**
* 将date类型转为字符串形式
* @param date
* @return
*/
public static String formatDateW3C(Date date) {
DateFormat df = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ");
String text = df.format(date);
String result = text.substring(0, 22) + ":" + text.substring(22);
return result;
}

/**
* 获取bean的fieldName和value
* 只获取简单类型,不获取复杂类型,包括集合
* @param bean
* @return
*/
public static Map<String, String> getBeanSimpleFieldValueMap(Object bean, boolean filterNull) {
Map<String, String> map = new HashMap<>(16);
if (ObjectUtils.isEmpty(bean)) {
return map;
}
Class<?> clazz = bean.getClass();
try {
//不获取父类的字段
Field[] fields = clazz.getDeclaredFields();
for (int i = 0; i < fields.length; i++) {
Class<?> fieldType = fields[i].getType();
String name = fields[i].getName();
// 获取 getXXX()
Method method = clazz.getMethod("get" + name.substring(0, 1).toUpperCase() + name.substring(1));
Object value = method.invoke(bean);
if (filterNull && ObjectUtils.isEmpty(value)) {
continue;
}
if (isBaseDataType(fieldType)) {
// String strValue = getFieldStringValue(fieldType,value);
String strValue = getValue(value);
map.put(name,strValue);
}

}
} catch (Exception e) {
log.error(e.getMessage(), e);
}
return map;
}

/**
* 自定义不同类型的string值
* @param type
* @return
*/
public static String getFieldStringValue(Class type,Object value){
if(type.equals(Date.class)){
SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
return formatter.format((Date)value);
}
return value.toString();
}

/**
* 判断一个类是否为基本数据类型或包装类,或日期。
* @param clazz 要判断的类。
* @return true 表示为基本数据类型。
*/
public static boolean isBaseDataType(Class clazz) throws Exception {
return
(
clazz.equals(String.class) ||
clazz.equals(Integer.class) ||
clazz.equals(Byte.class) ||
clazz.equals(Long.class) ||
clazz.equals(Double.class) ||
clazz.equals(Float.class) ||
clazz.equals(Character.class) ||
clazz.equals(Short.class) ||
clazz.equals(BigDecimal.class) ||
clazz.equals(BigInteger.class) ||
clazz.equals(Boolean.class) ||
clazz.equals(Date.class) ||
clazz.isPrimitive()
);
}
}

ReflectUtil

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
package com.fatal.utils;

import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

/**
* @author Fatal
* @date 2019/8/9 0009 7:47
*/
public class ReflectUtil {

/**
* 属性集合(private + 继承的)
* 适用于实体
* @param clazz 实体字节码文件对象
* @return
*/
public static <T> List<String> getFields(Class<T> clazz) {
Field[] declaredFields = clazz.getDeclaredFields();
Class<? super T> superclass = clazz.getSuperclass();
Field[] fields = superclass.getDeclaredFields();
// 过滤出 protected 属性
List<Field> superProtectedFields = Arrays.stream(fields)
.filter(field -> (field.getModifiers() & Modifier.PROTECTED) != 0)
.collect(Collectors.toList());
// 过滤出 private 属性
List<Field> privateFields = Arrays.stream(declaredFields)
.filter(field -> (field.getModifiers() & Modifier.PRIVATE) != 0)
.collect(Collectors.toList());
// 子类属性集合(private + 继承的)
privateFields.addAll(superProtectedFields);
return privateFields.stream()
.map(Field::getName)
.collect(Collectors.toList());
}

}

6、entity

ChangeItem.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
package com.fatal.entity;

import lombok.Data;

/**
* `操作细项`实体
* @author: Fatal
* @date: 2018/11/15 0015 11:14
*/
@Data
public class ChangeItem {

/** 属性名 */
private String field;

/** 属性中文名 */
private String fieldShowName;

/** 原值 */
private String oldValue;

/** 新值 */
private String newValue;

}

Product.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
package com.fatal.entity;

import com.fatal.annotation.DataLog;
import lombok.Data;
import lombok.experimental.Accessors;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import java.util.Date;

/**
* Produt 实体
* @author: Fatal
* @date: 2018/11/15 0015 10:56
*/
@Data
@Entity
@Accessors(chain = true)
public class Product {

@Id
@GeneratedValue
private Long id;

@DataLog(name = "产品名称")
private String name;

@DataLog(name = "商品类别")
private String category;

@DataLog(name = "商品详情")
private String detail;

@DataLog(name = "买入价")
private Integer buyPrice;

@DataLog(name = "卖出价")
private Integer sellPrice;

@DataLog(name = "供应商")
private String provider;

@DataLog(name = "更新时间")
private Date updateTime;

@DataLog(name = "上线时间")
private Date onlineTime;

}

Action.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
package com.fatal.entity;

import com.fatal.enums.ActionType;
import lombok.Data;
import lombok.experimental.Accessors;

import java.util.ArrayList;
import java.util.Date;
import java.util.List;

/**
* `操作记录`实体
* @author: Fatal
* @date: 2018/11/15 0015 11:13
*/
@Data
@Accessors(chain = true)
public class Action {

private String id;

/** 操作对象的id,这里指的是Product的id */
private Long objectId;

/** 操作对象的Class,这里指的是Product的Class */
private String objectClass;

/** 操作人 */
private String operator;

/** 操作时间 */
private Date operateTime;

/** 操作类型 */
private ActionType actionType;

/** `操作细项`集合 */
private List<ChangeItem> changes = new ArrayList<>();

}

Fu.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package com.fatal.entity;

import lombok.Data;

/**
* @author Fatal
* @date 2019/8/8 0008 17:29
*/
@Data
public class Fu {

private String privateFu;

protected String fuName;

protected Integer fuAge;

}

zi.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package com.fatal.entity;

import lombok.Data;
import lombok.EqualsAndHashCode;

/**
* @author Fatal
* @date 2019/8/8 0008 17:30
*/
@Data
@EqualsAndHashCode(callSuper = false)
public class Zi extends Fu {

public String publicZi;

private String ziName;

private Integer ziAge;

}

7、dao

ProductDao.java

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

import com.fatal.entity.Product;
import org.springframework.data.jpa.repository.JpaRepository;

import java.util.Optional;

/**
* Product 数据库访问层
* @author: Fatal
* @date: 2018/11/15 0015 10:55
*/
public interface ProductDao extends JpaRepository<Product, Long> {

/**
* 后面反射要用到
* @param aLong
* @return
*/
@Override
Optional<Product> findById(Long aLong);
}

ActionDao.java

1
2
3
4
5
6
7
8
9
10
11
12
package com.fatal.dao.aop;

import com.fatal.entity.Action;
import org.springframework.data.mongodb.repository.MongoRepository;

/**
* @author: Fatal
* @date: 2018/11/15 0015 11:33
*/
public interface ActionDao extends MongoRepository<Action, String> {

}

8、aspect

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
package com.fatal.aspect;

import com.fatal.dao.ActionDao;
import com.fatal.entity.Action;
import com.fatal.entity.ChangeItem;
import com.fatal.enums.ActionType;
import com.fatal.util.DifferentUtil;
import org.apache.commons.beanutils.PropertyUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.util.ObjectUtils;

import java.util.Date;
import java.util.List;

/**
* @author: Fatal
* @date: 2018/11/15 0015 14:20
*/
@Aspect
@Component
public class ActionAspect {

/** 实体id */
private final String ID = "id";
/** 保存、更新的方法名 */
private final String SAVE = "save";
/** 删除的方法名 */
private final String DELETE = "deleteById";
/** 操作者 */
private final String ADMIN = "admin";

@Autowired
private ActionDao dao;

@Pointcut("execution(public * com.fatal.dao.ProductDao.save*(..))")
public void save() {}

@Pointcut("execution(public * com.fatal.dao.ProductDao.delete*(..))")
public void delete() {}

/**
* 1、判断操作的类型 -- 增加、删除或者更新(增加和更新通过id区分)
* 2、获取ChangeItem
* @return
*/
@Around("save() || delete()")
public Object addOperateLog(ProceedingJoinPoint joinPoint) throws Throwable {

Action action = new Action();
ActionType actionType = null;
Object oldObj = null;
// 获得方法名
String method = joinPoint.getSignature().getName();
// 获取切点参数。update -> Product, delete -> id
Object joinPointArg = joinPoint.getArgs()[0];

Long id = null;
if (method.contains(DELETE)) {
id = (Long) joinPointArg;
} else {
// 先判断有没有id
Object property = PropertyUtils.getProperty(joinPointArg, ID);
if (!ObjectUtils.isEmpty(property)) {
id = Long.valueOf(property.toString());
}
}

if (ObjectUtils.isEmpty(id)) {
// 新增
actionType = ActionType.INSERT;
action.setObjectClass(joinPointArg.getClass().getName());
} else {
if (method.contains(SAVE)) {
// 更新
actionType = ActionType.UPDATE;
} else if (method.contains(DELETE)) {
// 删除
actionType = ActionType.DELETE;
}
// 通过切点的父类反射获得 findById()。然后拿id通过反射去数据库查原始数据
oldObj = DifferentUtil.getObjectById(joinPoint.getTarget(), id);
action.setObjectClass(oldObj.getClass().getName());
action.setObjectId(id);
}

// ====== 实际方法的调用 begin ======
Object result = joinPoint.proceed(joinPoint.getArgs());
// ========= end =========

if (ObjectUtils.isEmpty(id)) {
// 新增后的实体中是存在id的
Long newId = Long.valueOf(PropertyUtils.getProperty(result, ID).toString());
action.setObjectId(newId);
List<ChangeItem> changeItems = DifferentUtil.getInsertChangeItems(joinPointArg);
action.getChanges().addAll(changeItems);
} else {
if (SAVE.equals(method)) {
// 更新
List<ChangeItem> changeItems = DifferentUtil.getChangeItems(oldObj, joinPointArg);
action.getChanges().addAll(changeItems);
} else if (DELETE.equals(method)) {
// 删除
ChangeItem changeItem = DifferentUtil.getDeleteChangeItem(oldObj);
action.getChanges().add(changeItem);
}
}
action.setOperateTime(new Date());
action.setOperator(ADMIN);
action.setActionType(actionType);
// 保存操作数据
dao.save(action);
return result;
}

}

9、Test

Chapter254ApplicationTests

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

import com.fatal.dao.ProductDao;
import com.fatal.entity.Product;
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.data.domain.Example;
import org.springframework.test.context.junit4.SpringRunner;

import java.util.Date;

@RunWith(SpringRunner.class)
@SpringBootTest
public class Chapter254ApplicationTests {

@Autowired
private ProductDao productDao;

@Test
public void testInsert() {
Product product = new Product()
.setDetail("detail")
.setName("童年·在人间·我的大学")
.setBuyPrice(2950)
.setSellPrice(3500)
.setProvider("fatal")
.setCategory("book")
.setOnlineTime(new Date())
.setUpdateTime(new Date());
productDao.save(product);
System.out.println("new product id:"+product.getId());
}

@Test
public void testUpdate(){
Product product = new Product().setId(1L);
product = productDao.findOne(Example.of(product)).orElse(null);

if (product != null) {
product.setName("偷影子的人")
.setBuyPrice(2350)
.setOnlineTime(new Date());
productDao.save(product);
}
}

@Test
public void testDelete(){
productDao.deleteById(1L);
}

}

ReflectTests

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.fatal.reflect;

import com.fatal.entity.Zi;
import com.fatal.utils.ReflectUtil;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

import java.lang.reflect.Method;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import java.util.stream.Collectors;

/**
* @author Fatal
* @date 2019/8/9 0009 7:49
*/
@RunWith(SpringRunner.class)
@SpringBootTest
public class ReflectTests {

@Test
public void getBeanInfo() {
Zi zi = new Zi();
zi.setZiName("zi");
zi.setZiAge(18);
zi.setFuName("fu");
zi.setFuAge(42);
Class<? extends Zi> ziClass = zi.getClass();
List<String> fields = ReflectUtil.getFields(ziClass);
/*
* 实体属性值映射(这里没有考虑复杂类型,需要的话自行调整)
*/
Map<String, Object> map = fields.stream()
.collect(Collectors.toMap(Function.identity(), field -> {
try {
Method method = ziClass.getMethod("get" + field.substring(0, 1).toUpperCase() + field.substring(1));
return method.invoke(zi);
} catch (Exception e) {
throw new RuntimeException();
}
}));
map.entrySet().forEach(System.out::println);
}

}

显示

  1. 运行 Chapter254ApplicationTests.testInsert()

    • 控制台

    1542446272983

    • mongodb图形化界面

    1542446038908

    • mysql数据库

      1542446344229

  2. 运行Chapter254ApplicationTests.testUpdate()

    • 控制台

      1542446516892

    • mongodb图形化界面

      1542446599557

    • mysql数据库

      1542446623080

  3. 运行Chapter254ApplicationTests.testDelete()

    • 控制台

      1542452140155

    • mongodb图形化界面

      1542452321036

    • mysql数据库

      1542452159967

  4. 运行 ReflectTests.getBeanInfo()

    • 控制台

      1565308583525

笔记

  • 利用反射获取新旧值
  • 利用Aroud的advice去记录修改记录
  • 利用注解去增加中文名称字段名

反射知识复习:

  • getFields():获得某个类的所有的公共(public)的字段,包括父类中的字段。
  • getDeclaredFields():获得某个类的所有声明的字段,即包括public、private和protected,但是不包括父类的声明字段。

参考资料

探秘Spring AOP

总结

SpringBoot的知识已经有前辈在我们之前探索了。比较喜欢的博主有:唐亚峰 | Battcn方志朋的专栏程序猿DD纯洁的微笑。对这门技术感兴趣的可以去他们的博客逛逛。谢谢他们的分享~~

以上文章是我用来学习的Demo,都是基于 SpringBoot2.x 版本。

源码地址: https://github.com/ynfatal/springboot2-learning/tree/master/chapter25_4

学习 apollo_0001 前辈的经验