Java最佳实践
- 1: AES加解密
- 2: fastjson
- 3: Integer比较
- 4: Java的参数是值传递
- 5: Optional
- 6: PDF转图片
- 7: Serializable序列化
- 8: SimpleFTPClient
- 9: Spring专栏
- 9.1: 多个网卡时指定注册ip段.md
- 9.2: 如何在pom文件中管理多个环境
- 9.3: Spring 上传文件的配置
- 9.4: SpringBoot单元测试
- 10: Stream API示例
- 11: String API
- 12: TryCache到底怎么返回值
- 13: Valid参数校验
- 14: ZIPUtils
- 15: 遍历List
- 16: 遍历Map
- 17: 从mybatis的sql日志自动填充sql占位符
- 18: 代理模式实现
- 18.1: ByteBuddy动态代理
- 18.2: CGLIB动态代理
- 18.3: 动态代理
- 18.4: 静态代理
- 19: 定义一个由builder来构造的实体类
- 20: 多线程
- 20.1: 方法一: 继承Thread
- 20.2: 方法二实现Runnable接口
- 20.3: 方法三实现Callable接口
- 20.4: 方法四Executors框架
- 20.5: 线程池
- 20.6: 线程交替执行的实践
- 20.7: Callable捕获异常
- 21: 根据对象层次路径动态获取和设置值
- 22: 基于BigDecimal的高精度数字运算
- 23: 基于序列化的深拷贝
- 24: 时间日期
- 25: 使用抽象类来实现接口
- 26: 文件拷贝
- 27: 下载图片
- 28: 原生Java发送Post请求
1 - AES加解密
package cn.anzhongwei.lean.demo.encryption;
import java.security.Key;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;
import java.util.Base64;
import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
/**
* AES加密类
*
*/
public class AESUtils {
/**
* 密钥算法
*/
private static final String KEY_ALGORITHM = "AES";
private static final String DEFAULT_CIPHER_ALGORITHM = "AES/ECB/PKCS5Padding";
/**
* 初始化密钥
*
* @return byte[] 密钥
* @throws Exception
*/
public static byte[] initSecretKey() {
// 返回生成指定算法的秘密密钥的 KeyGenerator 对象
KeyGenerator kg = null;
try {
kg = KeyGenerator.getInstance(KEY_ALGORITHM);
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
return new byte[0];
}
// 初始化此密钥生成器,使其具有确定的密钥大小
// AES 要求密钥长度为 128
kg.init(128);
// 生成一个密钥
SecretKey secretKey = kg.generateKey();
return secretKey.getEncoded();
}
/**
* 转换密钥
*
* @param key
* 二进制密钥
* @return 密钥
*/
public static Key toKey(byte[] key) {
// 生成密钥
return new SecretKeySpec(key, KEY_ALGORITHM);
}
/**
* 加密
*
* @param data
* 待加密数据
* @param key
* 密钥
* @return byte[] 加密数据
* @throws Exception
*/
public static byte[] encrypt(byte[] data, Key key) throws Exception {
return encrypt(data, key, DEFAULT_CIPHER_ALGORITHM);
}
/**
* 加密
*
* @param data
* 待加密数据
* @param key
* 二进制密钥
* @return byte[] 加密数据
* @throws Exception
*/
public static byte[] encrypt(byte[] data, byte[] key) throws Exception {
return encrypt(data, key, DEFAULT_CIPHER_ALGORITHM);
}
/**
* 加密
*
* @param data
* 待加密数据
* @param key
* 二进制密钥
* @param cipherAlgorithm
* 加密算法/工作模式/填充方式
* @return byte[] 加密数据
* @throws Exception
*/
public static byte[] encrypt(byte[] data, byte[] key, String cipherAlgorithm) throws Exception {
// 还原密钥
Key k = toKey(key);
return encrypt(data, k, cipherAlgorithm);
}
/**
* 加密
*
* @param data
* 待加密数据
* @param key
* 密钥
* @param cipherAlgorithm
* 加密算法/工作模式/填充方式
* @return byte[] 加密数据
* @throws Exception
*/
public static byte[] encrypt(byte[] data, Key key, String cipherAlgorithm) throws Exception {
// 实例化
Cipher cipher = Cipher.getInstance(cipherAlgorithm);
// 使用密钥初始化,设置为加密模式
cipher.init(Cipher.ENCRYPT_MODE, key);
// 执行操作
return cipher.doFinal(data);
}
/**
* 解密
*
* @param data
* 待解密数据
* @param key
* 二进制密钥
* @return byte[] 解密数据
* @throws Exception
*/
public static byte[] decrypt(byte[] data, byte[] key) throws Exception {
return decrypt(data, key, DEFAULT_CIPHER_ALGORITHM);
}
/**
* 解密
*
* @param data
* 待解密数据
* @param key
* 密钥
* @return byte[] 解密数据
* @throws Exception
*/
public static byte[] decrypt(byte[] data, Key key) throws Exception {
return decrypt(data, key, DEFAULT_CIPHER_ALGORITHM);
}
/**
* 解密
*
* @param data
* 待解密数据
* @param key
* 二进制密钥
* @param cipherAlgorithm
* 加密算法/工作模式/填充方式
* @return byte[] 解密数据
* @throws Exception
*/
public static byte[] decrypt(byte[] data, byte[] key, String cipherAlgorithm) throws Exception {
// 还原密钥
Key k = toKey(key);
return decrypt(data, k, cipherAlgorithm);
}
/**
* 解密
*
* @param data
* 待解密数据
* @param key
* 密钥
* @param cipherAlgorithm
* 加密算法/工作模式/填充方式
* @return byte[] 解密数据
* @throws Exception
*/
public static byte[] decrypt(byte[] data, Key key, String cipherAlgorithm) throws Exception {
// 实例化
Cipher cipher = Cipher.getInstance(cipherAlgorithm);
// 使用密钥初始化,设置为解密模式
cipher.init(Cipher.DECRYPT_MODE, key);
// 执行操作
return cipher.doFinal(data);
}
public static String showByteArray(byte[] data) {
if (null == data) {
return null;
}
StringBuilder sb = new StringBuilder("{");
for (byte b : data) {
sb.append(b).append(",");
}
sb.deleteCharAt(sb.length() - 1);
sb.append("}");
return sb.toString();
}
/**
* 将16进制转换为二进制
*
* @param hexStr
* @return
*/
public static byte[] parseHexStr2Byte(String hexStr) {
if (hexStr.length() < 1)
return null;
byte[] result = new byte[hexStr.length() / 2];
for (int i = 0; i < hexStr.length() / 2; i++) {
int high = Integer.parseInt(hexStr.substring(i * 2, i * 2 + 1), 16);
int low = Integer.parseInt(hexStr.substring(i * 2 + 1, i * 2 + 2), 16);
result[i] = (byte) (high * 16 + low);
}
return result;
}
/**
* 将二进制转换成16进制
*
* @param buf
* @return
*/
public static String parseByte2HexStr(byte buf[]) {
StringBuffer sb = new StringBuffer();
for (int i = 0; i < buf.length; i++) {
String hex = Integer.toHexString(buf[i] & 0xFF);
if (hex.length() == 1) {
hex = '0' + hex;
}
sb.append(hex.toUpperCase());
}
return sb.toString();
}
/**
* @param str
* @param key
* @return
* @throws Exception
*/
public static String aesEncrypt(String str, String key) throws Exception {
if (str == null || key == null)
return null;
Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(key.getBytes("utf-8"), "AES"));
byte[] bytes = cipher.doFinal(str.getBytes("utf-8"));
return Base64.getEncoder().encodeToString(bytes);
}
public static String aesDecrypt(String str, String key) throws Exception {
if (str == null || key == null)
return null;
Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
cipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(key.getBytes("utf-8"), "AES"));
byte[] bytes = Base64.getDecoder().decode(str);
bytes = cipher.doFinal(bytes);
return new String(bytes, "utf-8");
}
public static void main(String[] args) throws Exception {
// 指定key
String base64Str1 = "cmVmb3JtZXJyZWZvcm1lcg==";
byte[] bs = Base64.getDecoder().decode(base64Str1);
System.out.println("base64Str1解密:"+ Arrays.toString(bs));
System.out.println("base64Str1解密:" + showByteArray(Base64.getDecoder().decode(base64Str1)));
Key key1 = toKey(Base64.getDecoder().decode(base64Str1));
String data1 = "{\"aaa\":\"bbb\"}";
System.out.println("明文:" + data1);
byte[] encryptData1 = encrypt(data1.getBytes(), key1);
String encryptStr1=parseByte2HexStr(encryptData1);
System.out.println("加密后数据1: byte[]:" + showByteArray(encryptData1));
System.out.println("加密后数据1: Byte2HexStr:" + encryptStr1);
System.out.println();
byte[] encryptStrByte1 = parseHexStr2Byte(encryptStr1);
byte[] decryptData1 = decrypt(encryptStrByte1, key1);
System.out.println("解密后数据: byte[]:" + showByteArray(decryptData1));
System.out.println("解密后数据: string:" + new String(decryptData1));
System.out.println("--------------------------------------------------------");
byte[] initKey = initSecretKey();
System.out.println("key:" + Arrays.toString(Base64.getEncoder().encode(initKey)));
System.out.println("key:" + showByteArray(initKey));
Key key2 = toKey(initKey);
String data2 = "{\"ccc\":\"ddd\"}";
System.out.println("明文:" + data2);
byte[] encryptData2 = encrypt(data2.getBytes(), key2);
String encryptStr2=parseByte2HexStr(encryptData2);
System.out.println("加密后数据1: byte[]:" + showByteArray(encryptData2));
System.out.println("加密后数据1: Byte2HexStr:" + encryptStr2);
System.out.println();
byte[] encryptStrByte2 = parseHexStr2Byte(encryptStr2);
byte[] decryptData2 = decrypt(encryptStrByte2, key2);
System.out.println("解密后数据: byte[]:" + showByteArray(decryptData2));
System.out.println("解密后数据: string:" + new String(decryptData2));
}
}
2 - fastjson
package cn.anzhongwei.lean.demo.alibabafastjsondemo;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import java.util.Map;
public class JsonToMapDemo1 {
public static void main(String[] args){
String str = "{\"0\":\"zhangsan\",\"1\":\"lisi\",\"2\":\"wangwu\",\"3\":\"maliu\"}";
//第一种方式
Map maps = (Map) JSON.parse(str);
System.out.println("这个是用JSON类来解析JSON字符串!!!");
for (Object map : maps.entrySet()){
System.out.println(((Map.Entry)map).getKey()+" " + ((Map.Entry)map).getValue());
}
//第二种方式
Map mapTypes = JSON.parseObject(str);
System.out.println("这个是用JSON类的parseObject来解析JSON字符串!!!");
for (Object obj : mapTypes.keySet()){
System.out.println("key为:"+obj+"值为:"+mapTypes.get(obj));
}
//第三种方式
Map mapType = JSON.parseObject(str,Map.class);
System.out.println("这个是用JSON类,指定解析类型,来解析JSON字符串!!!");
for (Object obj : mapType.keySet()){
System.out.println("key为:"+obj+"值为:"+mapType.get(obj));
}
//第四种方式
Map json = (Map) JSONObject.parse(str);
System.out.println("这个是用JSONObject类的parse方法来解析JSON字符串!!!");
for (Object map : json.entrySet()){
System.out.println(((Map.Entry)map).getKey()+" "+((Map.Entry)map).getValue());
}
//第五种方式
JSONObject jsonObject = JSONObject.parseObject(str);
System.out.println("这个是用JSONObject的parseObject方法来解析JSON字符串!!!");
for (Object map : json.entrySet()){
System.out.println(((Map.Entry)map).getKey()+" "+((Map.Entry)map).getValue());
}
//第六种方式
Map mapObj = JSONObject.parseObject(str,Map.class);
System.out.println("这个是用JSONObject的parseObject方法并执行返回类型来解析JSON字符串!!!");
for (Object map: json.entrySet()){
System.out.println(((Map.Entry)map).getKey()+" "+((Map.Entry)map).getValue());
}
String strArr = "{{\"0\":\"zhangsan\",\"1\":\"lisi\",\"2\":\"wangwu\",\"3\":\"maliu\"}," +
"{\"00\":\"zhangsan\",\"11\":\"lisi\",\"22\":\"wangwu\",\"33\":\"maliu\"}}";
// JSONArray.parse()
System.out.println(json);
}
}
最终输出结果为:
这个是用JSON类来解析JSON字符串!!!
0 zhangsan
1 lisi
2 wangwu
3 maliu
这个是用JSON类的parseObject来解析JSON字符串!!!
key为:0值为:zhangsan
key为:1值为:lisi
key为:2值为:wangwu
key为:3值为:maliu
这个是用JSON类,指定解析类型,来解析JSON字符串!!!
key为:0值为:zhangsan
key为:1值为:lisi
key为:2值为:wangwu
key为:3值为:maliu
这个是用JSONObject类的parse方法来解析JSON字符串!!!
0 zhangsan
1 lisi
2 wangwu
3 maliu
这个是用JSONObject的parseObject方法来解析JSON字符串!!!
0 zhangsan
1 lisi
2 wangwu
3 maliu
这个是用JSONObject的parseObject方法并执行返回类型来解析JSON字符串!!!
0 zhangsan
1 lisi
2 wangwu
3 maliu
{"0":"zhangsan","1":"lisi","2":"wangwu","3":"maliu"}
3 - Integer比较
package cn.anzhongwei.lean.demo.intandinteger;
// java 9 以后 此初始化已经标记为废弃,推荐使用 Integer.valueOf("100");
public class IntegerTest {
public static void main(String[] args) {
int a = 100;
Integer b = 100;
if ( a == b ) {
//原因是由于此区间的只都是由IntegerCache.cache而来,具体查看Integer.valueOf(n)方法
//其中IntegerCache.cache static final Integer cache[];
System.out.println("-128 - 127 之间的int类型是在常量池中, ");
}
// 但是 Integer.valueOf("100");
Integer c = Integer.valueOf("300");
Integer d = Integer.valueOf("300");
if ( c != d ) {
System.out.println("Integer.valueOf(\"100\") 初始化的127以上的数字是放到推空间的");
}
Integer e = Integer.valueOf("100");
Integer f = Integer.valueOf("100");
if ( e == f ) {
System.out.println("Integer.valueOf(\"100\") 初始化的127以内的还是从常量池中获取");
}
Integer g = Integer.valueOf("400");
int h = 400;
if ( g == h ) {
System.out.println("Integer会向下转型");
}
}
}
4 - Java的参数是值传递
class User
package cn.anzhongwei.lean.demo.java传参;
public class User {
private String name;
private String Gender;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getGender() {
return Gender;
}
public void setGender(String gender) {
Gender = gender;
}
}
package cn.anzhongwei.lean.demo.java传参;
/**
* 要理解java的传参
* 结果:
主类中实例化user1-hash:cn.anzhongwei.demo.java传参.User@4d7e1886
主类中实例化参数:Name=Hollis
方法中接受到参数user-hash:cn.anzhongwei.demo.java传参.User@4d7e1886 // 与main函数同一个对象
方法中重新实例化参数user-hash:cn.anzhongwei.demo.java传参.User@3cd1a2f1 // 产生新对象,但是只在局部有效
方法中重新实例化参数:Name=hollischuang
经过方法后的user1-hashcn.anzhongwei.demo.java传参.User@4d7e1886 //user1并没有因为在函数中局部参数实例化而发生变化
经过方法后的参数xiaoming //实例属性会被函数中的更改而发生变化
*/
public class PassOn {
public void pass(User user) {
System.out.println("方法中接受到参数user-hash:"+user);
user.setName("xiaoming");
user = new User();
user.setName("hollischuang");
System.out.println("方法中重新实例化参数user-hash:"+user);
System.out.println("方法中重新实例化参数:Name="+user.getName());
}
public static void main(String[] args) {
PassOn pt = new PassOn();
User user1 = new User();
System.out.println("主类中实例化user1-hash:"+user1);
user1.setName("Hollis");
user1.setGender("Male");
System.out.println("主类中实例化参数:Name="+user1.getName());
pt.pass(user1);
System.out.println("经过方法后的user1-hash" + user1);
System.out.println("经过方法后的参数" + user1.getName());
}
}
5 - Optional
package cn.anzhongwei.lean.demo.optional;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.Optional;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Person {
private String name;
private Integer age;
private Address address;
public Person(String name, Integer age) {
this.name = name;
this.age = age;
}
public Optional<String> optionalGetName() {
return Optional.ofNullable(this.name);
}
}
package cn.anzhongwei.lean.demo.optional;
import lombok.Data;
@Data
public class Address {
private String city;
private String addInfo;
}
package cn.anzhongwei.lean.demo.optional;
import org.junit.Test;
import java.util.*;
import java.util.stream.Collectors;
public class OptionalTest {
/**
* Optional(T value),empty(),of(T value),ofNullable(T value)
* Optional(T value) 是private的属于内部方法
*/
@Test
public void test1() {
//of(T value) 是实际上调用了Optional(T value) 而其内部的requireNonNull方法源码中判断value==null时会抛出空指针异常
//在开发中如果不想隐藏空指针异常时 使用of函数, 不过这种情况几乎不用
//Optional<Person> p1 = Optional.of(null);
//所以如果要创建一个value为null的Optional对象则应该使用 empty()
Optional<Person> pnull = Optional.empty();
//而ofNullable(T value) 则是一个三元表达式 return value == null ? empty() : of(value);
Optional<Person> p0 = Optional.ofNullable(null);
Optional<Person> p1 = Optional.ofNullable(new Person("zhangs", 20));
}
/**
* orElse(T other),orElseGet(Supplier other)和orElseThrow(Supplier exceptionSupplier)
* 这三个函数放一组进行记忆,都是在构造函数传入的value值为null时,进行调用的。
* orElse 和 orElseGet 没区别 可以认为时完全一样的代码
*
* orElseThrow 则会当value为空时抛出一个自定义异常
*/
@Test
public void test2() {
Person p = null;
//当p值不为null时,orElse函数依然会执行createUser()方法
p = Optional.ofNullable(p).orElse(new Person("zhangsan", 20));
System.out.println("调用orElse 当p为null时:"+p);
//而当p不为空,则相当于使用get()方法
p = Optional.ofNullable(p).orElse(new Person("王五", 60));
System.out.println("调用orElse 此时p不为null时:"+p);
//
p = null;
//重新赋值p=null
System.out.println("重新赋值p=:"+p);
p = Optional.ofNullable(p).orElseGet(() -> new Person("lisi", 30));
System.out.println("orElseGet 当p为null时:"+p);
p = Optional.ofNullable(p).orElseGet(() -> new Person("zhaoliu", 34));
System.out.println("orElseGet 此时p不为null时:"+p);
}
@Test
public void testOrElseThrow() {
Person p = new Person("lisi", 30);
p = Optional.ofNullable(p).orElseThrow(()-> new RuntimeException("用户不存在"));
System.out.println("orElseThrow 此时p不为null时:"+p);
p = null;
p = Optional.ofNullable(p).orElseThrow(()-> new RuntimeException("用户不存在"));
}
/**
* map(Function mapper)和flatMap(Function> mapper)
* 这两个函数做的是转换值的操作
*/
@Test
public void test3() {
Person p = new Person("lisi", 30);
String name = Optional.ofNullable(p).map( person -> person.getName()).get();
System.out.println("name="+name);
String optionalName = Optional.ofNullable(p).flatMap(person -> person.optionalGetName()).get();
System.out.println("optionalName="+optionalName);
}
/**
* isPresent()和ifPresent(Consumer consumer)
* isPresent即判断value值是否为空,而ifPresent就是在value值不为空时,做一些操作。
*/
@Test
public void test4() {
Person p = null;
Optional.ofNullable(p).ifPresent(person -> {
});
}
/**
* filter(Predicate predicate)
*/
@Test
public void test5() {
Person p = null;
//如果 p.name 长度小于6则返回, 否则返回EMPTY
Optional<Person> p1 = Optional.ofNullable(p).filter(u -> u.getName().length()<6);
}
/**
* 实战写法1
*/
public String getCity(Person person) throws Exception{
if(person!=null){
if(person.getAddress()!=null){
Address address = person.getAddress();
if(address.getCity()!=null){
return address.getCity();
}
}
}
throw new RuntimeException("取值错误");
}
public String getCity2(Person person) throws Exception{
return Optional.ofNullable(person)
.map(u-> u.getAddress())
.map(a->a.getCity())
.orElseThrow(()->new Exception("取值错误"));
}
/**
* 实战写法2
*/
public void dosomething(Person person) {
if (Objects.nonNull(person)) {
//......
}
Optional.ofNullable(person).ifPresent(person1 -> {
//......
});
}
/**
* 实战写法3
*/
public Person getUser1(Person user){
if(user!=null){
String name = user.getName();
if(!"zhangsan".equals(name)){
user = new Person();
user.setName("zhangsan");
}
}else{
user = new Person();
user.setName("zhangsan");
}
return user;
}
public Person getUser2(Person user) {
return Optional.ofNullable(user)
.filter(u->"zhangsan".equals(u.getName()))
.orElseGet(()-> {
Person user1 = new Person();
user1.setName("zhangsan");
return user1;
});
}
public Person getUser3(Person user){
if(user!=null){
String name = user.getName();
if(!"zhangsan".equals(name)){
user.setName("zhangsan");
}
}else{
user = new Person();
user.setName("zhangsan");
}
return user;
}
public Person getUser4(Person user) {
//这个逻辑一般来说不会这么写, 大多数情况下都是不为空且不为zhangsan时, 将name赋值成zhangsan,而不是重新实例化
return Optional.ofNullable(user)
.filter(u->"zhangsan".equals(u.getName()))
.orElseGet(()-> {
if (Objects.nonNull(user)) {
user.setName("zhangsan");
return user;
} else {
Person user1 = new Person();
user1.setName("zhangsan");
return user1;
}
});
}
@Test
public void test6() {
//getUser1 和 getUser2 只要name不等于 zhangsan, 就重新初始化
Person p = new Person("wangwu", 26);
Person p1 = getUser1(p);
System.out.println("p1:"+p1);
p = new Person("lisi", 23);
Person p2 = getUser2(p);
System.out.println("p2:"+p2);
//getUser3 和 getUser4 name不等于 zhangsan, 只改变name=张三, 其他内容不变
p = new Person("zhaosi", 26);
Person p3 = getUser3(p);
System.out.println("p3:"+p3);
p = new Person("liuqi", 26);
Person p4 = getUser4(p);
System.out.println("p4:"+p4);
}
@Test
public void test7() {
List<Person> personList = new ArrayList<>();
Person p1 = new Person("zhangsan", 22);personList.add(p1);
Person p2 = new Person("zhaosi", 29);personList.add(p2);
Person p3 = new Person("wangwu", 22);personList.add(p3);
Person p4 = new Person("lisen", 35);personList.add(p4);
Person p5 = new Person("liuming", 248);personList.add(p5);
Optional<Person> personOptional = personList.stream().filter(p -> p.getAge() > 22).findFirst();
Person pp1 = personOptional.orElseGet(() -> personList.get(0));
System.out.println(pp1);
Person pp2 = personOptional.orElse(null);
System.out.println(pp2);
List<Person> personList2 = Optional.ofNullable(personList).orElseGet(ArrayList::new);
System.out.println(personList2);
Map<String, List<Person>> pMap = new HashMap<>();
// List<Person> personList3 = Optional.ofNullable(pMap.get("随便写"))
// //这种写法主要为了后面stream()的流式处理方式不会有空指针
// //这样会空指针
// .get()
// .stream().collect(Collectors.toList());
// System.out.println(personList3);
List<Person> personList4 = Optional.ofNullable(pMap.get("随便写"))
//这种写法主要为了后面stream()的流式处理方式不会有空指针
.orElseGet(ArrayList::new)
.stream().collect(Collectors.toList());
System.out.println(personList4);
}
}
6 - PDF转图片
package cn.anzhongwei.lean.demo.pdftoimg;
import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.rendering.ImageType;
import org.apache.pdfbox.rendering.PDFRenderer;
import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
public class PdfUtil {
public static void main(String[] args) {
String source = "C:\\Home\\22.pdf";
String desFileName = "invoice";
String desFilePath = "C:\\Home\\ddd";
String imageType = "png";
Long t1 = System.currentTimeMillis();
List<String> pair = PdfUtil.pdfToImg(source, desFilePath, desFileName, imageType);
Long t2 = System.currentTimeMillis();
System.out.println(t2-t1);
pair.forEach(System.out::println);
}
public static List<String> pdfToImg(String source, String desFilePath, String desFileName, String imageType) {
//通过给定的源路径名字符串创建一个File实例
File file = new File(source);
if (!file.exists()) {
throw new RuntimeException("文件不存在,无法转化");
}
//目录不存在则创建目录
File destination = new File(desFilePath);
if (!destination.exists()) {
boolean flag = destination.mkdirs();
System.out.println("创建文件夹结果:" + flag);
}
PDDocument doc = null;
try {
//加载PDF文件
doc = PDDocument.load(file);
PDFRenderer renderer = new PDFRenderer(doc);
//获取PDF文档的页数
int pageCount = doc.getNumberOfPages();
System.out.println("文档一共" + pageCount + "页");
List<String> fileList = new ArrayList<>();
for (int i = 0; i < pageCount; i++) {
//只有一页的时候文件名为传入的文件名,大于一页的文件名为:文件名_自增加数字(从1开始)
String realFileName = pageCount > 1 ? desFileName + "_" + (i + 1) : desFileName;
//每一页通过分辨率和颜色值进行转化
BufferedImage bufferedImage = renderer.renderImageWithDPI(i, 96*2, ImageType.RGB);
String filePath = desFilePath + File.separator + realFileName + "." + imageType;
//写入文件
ImageIO.write(bufferedImage, imageType, new File(filePath));
//文件名存入list
fileList.add(filePath);
}
return fileList;
} catch (IOException e) {
e.printStackTrace();
throw new RuntimeException("PDF转化图片异常,无法转化");
} finally {
try {
if (doc != null) {
doc.close();
}
} catch (IOException e) {
System.out.println("关闭文档失败");
e.printStackTrace();
}
}
}
}
7 - Serializable序列化
- Person
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.ToString;
import java.io.Serializable;
@Data
@ToString
@AllArgsConstructor
public class Person implements Serializable {
private String name;
private int age;
}
- Teacher
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.ToString;
import java.io.Serializable;
@Data
@ToString
@AllArgsConstructor
public class Teacher implements Serializable {
private String name;
private Person person;
}
- 写序列化到文件
import java.io.FileOutputStream;
import java.io.ObjectOutputStream;
// TODO 还没测试成功
public class WriteObject {
public static void main(String[] args) {
// try (//创建一个ObjectOutputStream输出流
// ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("C:\\Home\\teacher.txt"))) {
// //将对象序列化到文件s
// Person person = new Person("9龙", 23);
// Teacher teacher = new Teacher("路飞", person);
// oos.writeObject(teacher);
// } catch (Exception e) {
// e.printStackTrace();
// }
try (//创建一个ObjectOutputStream输出流
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("D:\\teacher2.txt"))) {
//将对象序列化到文件s
Person person = new Person("路飞", 20);
Teacher t1 = new Teacher("雷利", person);
Teacher t2 = new Teacher("红发香克斯", person);
//依次将4个对象写入输入流
oos.writeObject(t1);
oos.writeObject(t2);
System.out.println(t2.hashCode());
oos.writeObject(person);
oos.writeObject(t2);
} catch (Exception e) {
e.printStackTrace();
}
}
}
- 从文件读序列化并实例化
import java.io.FileInputStream;
import java.io.ObjectInputStream;
public class ReadObject {
public static void main(String[] args) {
// try (//创建一个ObjectInputStream输入流
// ObjectInputStream ois = new ObjectInputStream(new FileInputStream("C:\\Home\\teacher.txt"))) {
// Teacher teacher = (Teacher) ois.readObject();
// System.out.println(teacher);
// } catch (Exception e) {
// e.printStackTrace();
// }
try (//创建一个ObjectInputStream输入流
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("D:\\teacher2.txt"))) {
Teacher t1 = (Teacher) ois.readObject();
Teacher t2 = (Teacher) ois.readObject();
Person p = (Person) ois.readObject();
Teacher t3 = (Teacher) ois.readObject();
System.out.println(t1 == t2);
System.out.println(t1.getPerson() == p);
System.out.println(t2.getPerson() == p);
System.out.println(t2 == t3);
System.out.println(t2.hashCode());
System.out.println(t3.hashCode());
System.out.println(t1.getPerson() == t2.getPerson());
} catch (Exception e) {
e.printStackTrace();
}
}
}
8 - SimpleFTPClient
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Inet4Address;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.util.Arrays;
import java.util.Base64;
import java.util.LinkedList;
import java.util.List;
import org.apache.commons.net.ftp.FTP;
import org.apache.commons.net.ftp.FTPClient;
import org.apache.commons.net.ftp.FTPFile;
import org.apache.commons.net.ftp.FTPReply;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
<!--apacheftp-->
<!--导入commons-net依赖-->
<!--https://mvnrepository.com/artifact/commons-net/commons-net-->
<dependency>
<groupId>commons-net</groupId>
<artifactId>commons-net</artifactId>
<version>3.6</version>
</dependency>
<!-- 引入Apache的commons-lang3包,方便操作字符串-->
<!--https://mvnrepository.com/artifact/org.apache.commons/commons-lang3-->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.8</version>
</dependency>
<!--引入Apachecommons-io包,方便操作文件-->
<!--https://mvnrepository.com/artifact/commons-io/commons-io-->
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.6</version>
</dependency>
*/
public class SimpleFTPClient {
private static final Logger logger = LoggerFactory.getLogger(SimpleFTPClient.class);
private static FTPClient connectFtpServer(String url,String userName,String password){
String str = url.replaceFirst("ftp://", "").replaceFirst("FTP://", "");
str=str.replaceAll("/+", "/");
int endIndex = str.indexOf("/");
str = str.substring(0,endIndex);
url = str;//"ftp://"+str+"/";
String[] addr = url.split(":");
FTPClient ftpClient = new FTPClient();
ftpClient.setConnectTimeout(1000*5);//设置连接超时时间
// ftpClient.setControlEncoding("GBK");
ftpClient.setControlEncoding("utf-8");//设置ftp字符集
ftpClient.enterLocalPassiveMode();//设置被动模式,文件传输端口设置
try {
ftpClient.connect(addr[0],Integer.parseInt(addr[1]));
if(null==userName||"".equals(userName)){
userName = "anonymous";
password = "password";
}
boolean isLoginSuccess = ftpClient.login(userName,password);
logger.info("isLoginSuccess="+isLoginSuccess);
ftpClient.setFileType(FTP.BINARY_FILE_TYPE);//设置文件传输模式为二进制,可以保证传输的内容不会被改变
int replyCode = ftpClient.getReplyCode();
if (!FTPReply.isPositiveCompletion(replyCode)){
logger.error("connect ftp {} failed",url);
ftpClient.abort();
ftpClient.disconnect();
return null;
}
logger.info("replyCode==========={}",replyCode);
} catch (IOException e) {
logger.error("connect fail ------->>>{}",e);
return null;
}
return ftpClient;
}
/**
*
* @param inputStream 待上传文件的输入流
* @param originName 文件保存时的名字
*/
public void uploadFile(String url,String userName,String password,InputStream inputStream, String originName){
FTPClient ftpClient = connectFtpServer(url,userName,password);
if (ftpClient == null){
return;
}
/* try {
ftpClient.changeWorkingDirectory(remoteDir);//进入到文件保存的目录
Boolean isSuccess = ftpClient.storeFile(originName,inputStream);//保存文件
if (!isSuccess){
throw new BusinessException(ResponseCode.UPLOAD_FILE_FAIL_CODE,originName+"---》上传失败!");
}
logger.info("{}---》上传成功!",originName);
ftpClient.logout();
} catch (IOException e) {
logger.error("{}---》上传失败!",originName);
throw new BusinessException(ResponseCode.UPLOAD_FILE_FAIL_CODE,originName+"上传失败!");
}finally {
if (ftpClient.isConnected()){
try {
ftpClient.disconnect();
} catch (IOException e) {
logger.error("disconnect fail ------->>>{}",e.getCause());
}
}
} */
}
/**
* 读ftp上的文件,并将其转换成base64
* @param remoteFileName ftp服务器上的文件名
* @return
*/
public static String readFileToBase64(String url,String userName,String password){
logger.info("url={}",url);
FTPClient ftpClient = connectFtpServer(url,userName,password);
if (ftpClient == null){
return null;
}
logger.info("ftpClient={}",ftpClient.isAvailable()&&ftpClient.isConnected());
String base64 = "";
InputStream inputStream = null;
ByteArrayOutputStream os=null;
String remoteDir;
String str = url.replaceFirst("ftp://", "").replaceFirst("FTP://", "");
str=str.replaceAll("/+", "/");
int beginIndex = str.indexOf("/");
int endIndex = str.lastIndexOf("/");
remoteDir=str.substring(beginIndex,endIndex+1);
logger.info("remoteDir={}",remoteDir);
String remoteFileName = str.substring(endIndex+1);
logger.info("remoteFileName={}",remoteFileName);
try {
boolean chDirRes = ftpClient.changeWorkingDirectory(remoteDir);
logger.info("changeWorkingDirectory={}",chDirRes);
ftpClient.enterLocalPassiveMode();
FTPFile[] ftpFiles = ftpClient.listFiles(remoteDir);
logger.info("ftpFiles.length={}",ftpFiles.length);
Boolean flag = false;
//遍历当前目录下的文件,判断要读取的文件是否在当前目录下
for (FTPFile ftpFile:ftpFiles){
if (ftpFile.getName().equals(remoteFileName)){
flag = true;
break;
}
}
if (!flag){
logger.error("directory:{}下没有 {}",remoteDir,remoteFileName);
return null;
}
/* // 下载ftp上指定文件到本地
File localFile = new File("D:" + File.separator +"abc_"+ remoteFileName);
boolean downloaded = ftpClient.retrieveFile(remoteFileName,
new FileOutputStream(localFile));
System.out.println(ftpClient.getReplyString()); */
logger.info("开始获取文件流");
//获取待读文件输入流
inputStream = ftpClient.retrieveFileStream(remoteFileName); // remoteDir+remoteFileName
// //inputStream.available() 获取返回在不阻塞的情况下能读取的字节数,正常情况是文件的大小
logger.info("完成获取文件流");
byte[] bytes = new byte[1024];
os = new ByteArrayOutputStream();
int len=-1;
while( (len=inputStream.read(bytes))!=-1){//将文件数据读到字节数组中
os.write(bytes, 0, len);
}
os.flush();
byte[] fileBytes = os.toByteArray();
/* File file = new File("d:/"+remoteFileName);
OutputStream fos = new FileOutputStream(file);
try {
fos.write(fileBytes);
fos.flush();
} catch (Exception e) {
//TODO: handle exception
}
finally{
fos.close();
} */
int fileSize = fileBytes.length;
logger.info("fileSize={}",fileSize);
Base64.Encoder encoder = Base64.getEncoder();
base64 = encoder.encodeToString(fileBytes);
logger.debug("base64 img="+base64);
logger.info("read file {} success",remoteFileName);
if(!ftpClient.completePendingCommand()) {
ftpClient.logout();
ftpClient.disconnect();
logger.error("File transfer failed.");
return null;
}
ftpClient.logout();
} catch (IOException e) {
logger.error("read file fail ----->>>{}",e.getCause());
return null;
}finally {
if (ftpClient.isConnected()){
try {
ftpClient.disconnect();
} catch (IOException e) {
logger.error("disconnect fail ------->>>{}",e.getCause());
}
}
if (inputStream != null){
try {
inputStream.close();
} catch (IOException e) {
logger.error("inputStream close fail -------- {}",e.getCause());
}
}
if (os != null){
try {
os.close();
} catch (IOException e) {
logger.error("os close fail -------- {}",e.getCause());
}
}
}
return base64;
}
/**
* 文件下载
* @param remoteFileName ftp上的文件名
* @param localFileName 本地文件名
*/
public void download(String url,String userName,String password,String remoteFileName,String localFileName){
FTPClient ftpClient = connectFtpServer(url,userName,password);
if (ftpClient == null){
return ;
}
OutputStream outputStream = null;
/* try {
ftpClient.changeWorkingDirectory(remoteDir);
FTPFile[] ftpFiles = ftpClient.listFiles(remoteDir);
Boolean flag = false;
//遍历当前目录下的文件,判断是否存在待下载的文件
for (FTPFile ftpFile:ftpFiles){
if (ftpFile.getName().equals(remoteFileName)){
flag = true;
break;
}
}
if (!flag){
logger.error("directory:{}下没有 {}",remoteDir,remoteFileName);
return ;
}
outputStream = new FileOutputStream(localDir+localFileName);//创建文件输出流
Boolean isSuccess = ftpClient.retrieveFile(remoteFileName,outputStream); //下载文件
if (!isSuccess){
logger.error("download file 【{}】 fail",remoteFileName);
}
logger.info("download file success");
ftpClient.logout();
} catch (IOException e) {
logger.error("download file 【{}】 fail ------->>>{}",remoteFileName,e.getCause());
}finally {
if (ftpClient.isConnected()){
try {
ftpClient.disconnect();
} catch (IOException e) {
logger.error("disconnect fail ------->>>{}",e.getCause());
}
}
if (outputStream != null){
try {
outputStream.close();
} catch (IOException e) {
logger.error("outputStream close fail ------->>>{}",e.getCause());
}
}
} */
}
public static void main(String[] args) {
// String ftpPath = "ftp://192.168.1.3:8010/recordsImg/2019-05-31/STRANGERBABY_1559265983072.jpg";
// readFileToBase64(ftpPath,"","");
String url="ftp://106.12.195.197:21/";
FTPClient ftpClient = connectFtpServer(url,"myftp","123456");
if (ftpClient == null){
return ;
}
logger.info("ftpClient={}",ftpClient.isAvailable()&&ftpClient.isConnected());
String base64 = "";
InputStream inputStream = null;
ByteArrayOutputStream os=null;
String remoteDir;
String str = url.replaceFirst("ftp://", "").replaceFirst("FTP://", "");
str=str.replaceAll("/+", "/");
int beginIndex = str.indexOf("/");
// int endIndex = str.lastIndexOf("/");
// remoteDir=str.substring(beginIndex,endIndex+1);
String remotePath =str.substring(beginIndex);
logger.info("remotePath={}",remotePath);
String status="nil";
try {
status=ftpClient.getStatus(remotePath);
} catch (Exception e) {
//TODO: handle exception
e.printStackTrace();
}
logger.info("ftpClient.getStatus={}",status);
try {
// boolean chDirRes = ftpClient.changeWorkingDirectory(remoteDir);
// logger.info("changeWorkingDirectory={}",chDirRes);
// ftpClient.enterLocalPassiveMode();
/* FTPFile[] ftpFiles = ftpClient.listFiles(remoteDir);
logger.info("ftpFiles.length={}",ftpFiles.length);
Boolean flag = false;
//遍历当前目录下的文件,判断要读取的文件是否在当前目录下
for (FTPFile ftpFile:ftpFiles){
logger.info("ftpFiles------{}",ftpFile.getName());
} */
// boolean renameRes = ftpClient.rename("/imgfacebak/bb1.png", "/test/a.png");
// logger.info("renameRes={}",renameRes);
// if(!ftpClient.completePendingCommand()) {
// ftpClient.logout();
// ftpClient.disconnect();
// logger.error("File transfer failed.");
// return ;
// }
ftpClient.logout();
} catch (IOException e) {
logger.error("read file fail ----->>>{}",e.getCause());
return ;
}finally {
if (ftpClient.isConnected()){
try {
ftpClient.disconnect();
} catch (IOException e) {
logger.error("disconnect fail ------->>>{}",e.getCause());
}
}
}
}
}
9 - Spring专栏
9.1 - 多个网卡时指定注册ip段.md
添加配置项
cloud:
inetutils:
preferred-networks: 192.168.*
9.2 - 如何在pom文件中管理多个环境
9.3 - Spring 上传文件的配置
spring:
servlet:
multipart:
#配置文件传输
enabled: true
file-size-threshold: 0B
#单个文件的最大上限
max-file-size: 100MB
#单个请求的文件总大小上限
max-request-size: 1000MB
9.4 - SpringBoot单元测试
简介
1. 完全脱离上下文(纯单元测试)
-
实现方式:
Mockito+JUnit5(无Spring注解) -
特点:
-
仅测试类自身逻辑
-
执行速度最快(毫秒级)
-
-
适用场景:Service工具类、纯POJO逻辑测试
-
代码示例:
@ExtendWith(MockitoExtension.class)
class GoodsControllerTest {
private MockMvc mockMvc;
// 需要模拟的控制器
@InjectMocks // 可以注入要模拟的类
private GoodsController goodsController;
// 需要打桩的Service
@Mock
GoodsService goodsService;
@BeforeEach
public void initOne() {
// 通过goodsController构建mvc模拟对象
mockMvc = MockMvcBuilders.standaloneSetup(goodsController).build();
}
}
2. 部分加载上下文(切片测试)
-
实现方式:
@WebMvcTest/@DataJpaTest等 -
特点:
-
仅加载相关模块组件(如MVC层/JPA层)
-
启动较快(秒级)
-
-
适用场景:控制器测试、Repository接口测试
-
代码示例:
@WebMvcTest(HomeController.class) // HomeController要测试那个控制器
class GoodsControllerTest {
@Autowired
private MockMvc mockMvc;
// 需要打桩的Service
@MockitoBean
GoodsService goodsService;
}
3. 完全加载上下文(集成测试)
-
实现方式:
@SpringBootTest -
特点:
-
加载完整Spring容器
-
支持真实数据库交互
-
启动慢(10秒+)
-
-
适用场景:端到端测试、多组件协同测试
-
代码示例:
@AutoConfigureMockMvc
@SpringBootTest
class GoodsControllerTest {
@Autowired
private MockMvc mockMvc;
// 需要模拟的控制器
@InjectMocks // 可以注入要模拟的类
private GoodsController goodsController;
// 需要打桩的Service
@MockitoBean
GoodsService goodsService;
}
测试用例
测试用例项目源代码: https://gitee.com/azhw/rookiedev-example/tree/master/编程/编程语言/Java/最佳实践/unittest/SpringBoot3UnitTest
-
Service测试: no01servicetest
-
Controller测试: no02ControllerTest
-
Controller对Service打桩&Service对Mapper打桩的测试: no03MockitoService
-
对文件上传下载的测试: no04FileUploadDownload
-
使用H2数据库对数据写入进行实际测试: no05H2DBTest
在 no03MockitoService 其实已经包含了该测试内容
-
测试Controller 对 Cookie 、 Session 、 Header 的读写: no06CookieSessionHeaderTest
-
使用嵌入式redis服务器(embedded-redis)对Redis进行测试: no07RedisTest
这种场景只有使用完整的spring容器方式进行单元测试能进行测试, 其他两种(完全脱离spring和部分启动的方式只能对redisTemplate进行打桩测试)
-
测试SrpingBoot的Filter: no08SpringFilterTest
-
演示controller使用thymeleaf模板而不是返回json时的单元测试: no09ThymeleafTest
-
异步调用测试: no10AsyncTest
-
Service超时测试: no11TimeoutTest
-
普通的类中(不是spring bean) 静态方法和非静态方法的打桩测试:no12NoSpringBeanMethodMockTest
10 - Stream API示例
import lombok.Data;
import org.junit.Test;
import java.util.*;
import java.util.stream.Collectors;
public class StreamAPI {
/**
*
* Comparator.reverseOrder()是让某个条件进行倒序排序.
* reversed()是让他前面的字段进行倒序。
*
* 例如:Comparator.comparing(名称).reversed(),此时名称倒序;
* Comparator.comparing(名称).thenComparing(状态).reversed(),此时名称和状态进行倒序。
*
* Comparator.comparing(名称).thenComparing(状态).reversed().thenComparing(年龄),此时名称和状态会倒序,年龄会升序。
*
* 加多个.reversed()
* Comparator.comparing(状态).reversed().thenComparing(年龄).reversed(),最终结果是只有年龄倒排,状态未排序
*/
/**
* 练习1:让年龄进行倒序排序。
*/
@Test
public void test1() {
/*
* Comparator.comparing(放实体类名称::放列名):添加排序字段;
* reversed():倒序。
* collect(Collectors.toList()):转成集合。
* forEach(System.out::println):打印循环。
*/
List<User> userList = generatorUserList();
// userList.stream()
// .sorted(Comparator.comparing(User::getAge).reversed())//reversed()是让其前面得所有字段都倒排
// .collect(Collectors.toList())
// .forEach(System.out::println);
// List<User> userList2 = generatorUserList();
// userList2.stream()
// .sorted(Comparator.comparing(User::getAge, Comparator.reverseOrder()))//Comparator.reverseOrder()是让某个条件进行倒序排序
// .collect(Collectors.toList()).forEach(System.out::println);
userList.stream().sorted(Comparator.comparing(User::getAge).reversed())
.collect(Collectors.toList()).forEach(System.out::println);
}
/**
* 练习2:让使用状态进行升序排序。
*/
@Test
public void test2() {
List<User> userList = generatorUserList();
//默认是升序,所以只需要把排序字段放进去就行了。
userList.stream().sorted(Comparator.comparing(User::getAge)).collect(Collectors.toList()).forEach(System.out::println);
}
/**
* 练习3:让使用先状态和后年龄都进行倒序排序。
*/
@Test
public void test3() {
List<User> userList = generatorUserList();
userList.stream()
//先根据使用状态倒序,而王五使用状态为2,所以王五排在第一。
//而李四和张三使用状态都是1,就会触发另一个排序条件根据年龄排序
.sorted(Comparator.comparing(User::getState).thenComparing(User::getAge).reversed())
.collect(Collectors.toList())
.forEach(System.out::println);
//写法2
List<User> userList2 = generatorUserList();
userList2.stream()
.sorted(
Comparator.comparing(User::getState, Comparator.reverseOrder())//根据state倒排
.thenComparing(User::getAge, Comparator.reverseOrder())//根据age倒排
)
.forEach(System.out::println);
}
/**
* 练习4:让使用状态和年龄都进行升序排序。
*/
@Test
public void test4() {
List<User> userList = generatorUserList();
//默认就是升序
userList.stream().sorted(Comparator.comparing(User::getState).thenComparing(User::getAge)).forEach(System.out::println);
}
/**
* 练习5:让使用状态进行升序排序,年龄进行倒序排序,创建时间进行倒序排序。名字正排序
* 注意:假如多个排序条件的排序方向不一致,需要倒序的字段应该用Comparator.reverseOrder(),每个倒排得字段单独写。
*/
@Test
public void test5() {
List<User> userList = generatorUserList();
userList.stream()
.sorted(
Comparator.comparing(User::getState)
.thenComparing(User::getAge, Comparator.reverseOrder())
.thenComparing(User::getCreateTime,Comparator.reverseOrder())
.thenComparing(User::getName)
)
.forEach(System.out::println);
}
/**
* 练习6,按 State 分组 组成 Map<Ingeter, List<User>>的结构
*/
@Test
public void test6() {
List<User> userList = generatorUserList();
Map<Integer, List<User>> stateToUserList = userList.stream().collect(Collectors.groupingBy(User::getState));
System.out.println(stateToUserList);
}
/**
* 联系7 根据姓名作为key, User对象为val,组成map<String, User>
* 如果是根据state, 因为元素不唯一, 所以会抛出异常
* 根据姓名为key, age为val组成 map<String, Ingeter>
*/
@Test
public void test7() {
List<User> userList = generatorUserList();
Map<String, User> nameToUser = userList.stream().collect(Collectors.toMap(User::getName, user -> user));
//下面这样也行
// Map<String, User> nameToUser = userList.stream().collect(Collectors.toMap(User::getName, user -> {return user;}));
System.out.println(nameToUser);
//在映射时, 把年龄加1再返回
Map<String, User> nameToAge0 = userList.stream().collect(Collectors.toMap(User::getName, user -> { user.setAge(user.age+1); return user;}));
System.out.println(nameToAge0);
//那么如果分组的字段是一个非唯一的,比如state, 会报错
// Map<Integer, User> stateToUser = userList.stream().collect(Collectors.toMap(User::getState, user -> user));
// System.out.println(stateToUser);
//根据姓名为key, age为val组成 map<String, Ingeter>
//写法1
Map<String, Integer> nameToAge = userList.stream().collect(Collectors.toMap(User::getName, User::getAge));
System.out.println(nameToAge);
//写法2
Map<String, Integer> nameToAge2 = userList.stream().collect(Collectors.toMap(User::getName, user -> {return user.age;}));
System.out.println(nameToAge2);
Map<String, Integer> nameToAge3 = userList.stream().collect(Collectors.toMap(User::getName, user -> { user.setAge(user.age+1); return user.age;}));
System.out.println(nameToAge3);
}
/**
* 练习8 按名字去重
*/
@Test
public void test8() {
List<User> userList = generatorUserList();
Set<String> collect1 = userList.stream().distinct().map(User::getName).collect(Collectors.toSet());
System.out.println(collect1);
return;
}
/**
* 按名字统计数量
*/
@Test
public void test9() {
List<User> userList = generatorUserList();
Map<String, Long> collect = userList.stream().collect(Collectors.groupingBy(User::getName, Collectors.counting()));
System.out.println(collect);
return;
}
/**
* 求年龄最大值、最小值
*/
@Test
public void test10() {
List<User> userList = generatorUserList();
//如果有年龄一样的,会按list顺序取第一条, 所以除非能唯一, 否则多维度需要使用排序来获取值
Optional<User> min = userList.stream().min(Comparator.comparing(User::getAge));
System.out.println("min = " + min);
}
/**
* 获取某个字段的 最大值、最小值、求和、统计、计数
*/
@Test
public void test11() {
List<User> userList = generatorUserList();
IntSummaryStatistics collect = userList.stream().collect(Collectors.summarizingInt(User::getAge));
double average = collect.getAverage();
int max = collect.getMax();
int min = collect.getMin();
long sum = collect.getSum();
long count = collect.getCount();
System.out.println("collect = " + collect);
//单独对某个字段汇总
int sum1 = userList.stream().mapToInt(User::getAge).sum();
System.out.println("sum = " + sum1);
double avg1 = userList.stream().collect(Collectors.averagingDouble(User::getAge));
System.out.println("avg1 = " + avg1);
OptionalDouble avg2 = userList.stream().mapToDouble(User::getAge).average();
if (avg2.isPresent()) {
System.out.println("avg2 = " + avg2);
}
}
/**
* 按名字分组, 统计年龄相关的 计数, 总数,最大,最小,平均
*/
@Test
public void test12() {
List<User> userList = generatorUserList();
Map<String, IntSummaryStatistics> collect = userList.stream().collect(Collectors.groupingBy(User::getName, Collectors.summarizingInt(User::getAge)));
for(Map.Entry<String, IntSummaryStatistics> entry : collect.entrySet()) {
System.out.println(entry.getKey()+"-----"+entry.getValue());
}
}
/**
* 生成用户示例用户列表
* @return
*/
private List<User> generatorUserList() {
List<User> list = new ArrayList<>();
User user1 = new User();
user1.setName("19");
user1.setAge(18);
user1.setState(1);
user1.setCreateTime(new Date());
list.add(user1);
User user2 = new User();
user2.setName("1");
user2.setAge(22);
user2.setState(1);
user2.setCreateTime(new Date());
list.add(user2);
User user3 = new User();
user3.setName("2");
user3.setAge(15);
user3.setState(2);
user3.setCreateTime(new Date());
list.add(user3);
User user4 = new User();
user4.setName("23");
user4.setAge(15);
user4.setState(2);
user4.setCreateTime(new Date());
list.add(user4);
User user5 = new User();
user5.setName("23");
user5.setAge(18);
user5.setState(2);
user5.setCreateTime(new Date());
list.add(user5);
return list;
}
@Data
class User{
private String name;
private Integer age;
private Integer state;
private Date createTime;
}
}
11 - String API
/**
* public final class String
* String 是包装类型,且为final修饰不能被继承
* 一下提供字符串拼接和字符串反转的方法
*/
public class StringUtils {
public static void main(String[] args) {
StringUtils.stringJoin();
StringUtils.stringReverse();
}
public static void stringJoin(){
//StringBuffer字符串连接 线程安全的
StringBuffer buffer = new StringBuffer();
buffer.append("a");
buffer.append("b");
buffer.append("c");
buffer.append("d");
System.out.println(buffer.toString());
//StringBuilder字符串连接, 非线程安全的,但是速度快
StringBuilder builder = new StringBuilder();
builder.append("a");
builder.append("b");
builder.append("c");
builder.append("d");
System.out.println(builder.toString());
}
//字符串反转
public static void stringReverse(){
// StringBuffer reverse
StringBuffer stringBuffer = new StringBuffer();
stringBuffer.append("abcdefg");
System.out.println(stringBuffer.reverse()); // gfedcba
// StringBuilder reverse
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.append("abcdefg");
System.out.println(stringBuilder.reverse()); // gfedcba
}
}
12 - TryCache到底怎么返回值
package cn.anzhongwei.lean.demo;
public class TestTryCatch {
public static void main(String[] args) {
System.out.println(getInt1());
System.out.println(getInt2());
}
public static int getInt1() {
int a = 10;
try {
System.out.println(a / 0);
a = 20;
} catch (ArithmeticException e) {
a = 30;
return a;
/*
* return a 在程序执行到这一步的时候,这里不是return a 而是 return 30;这个返回路径就形成了
* 但是呢,它发现后面还有finally,所以继续执行finally的内容,a=40
* 再次回到以前的路径,继续走return 30,形成返回路径之后,这里的a就不是a变量了,而是常量30
*/
} finally {
a = 40;
}
return a;
}
public static int getInt2() {
int a = 10;
try {
System.out.println(a / 0);
a = 20;
} catch (ArithmeticException e) {
a = 30;
return a;
/*
* return a 在程序执行到这一步的时候,这里不是return a 而是 return 30;这个返回路径就形成了
* 但是呢,它发现后面还有finally,所以继续执行finally的内容,a=40
* 再次回到以前的路径,继续走return 30,形成返回路径之后,这里的a就不是a变量了,而是常量30
*/
} finally {
a = 40;
return a; //如果这样,就又重新形成了一条返回路径,由于只能通过1个return返回,所以这里直接返回40
}
// return a;
}
}
13 - Valid参数校验
简单使用
Java API规范(JSR303)定义了Bean校验的标准validation-api,但没有提供实现。hibernate validation是对这个规范的实现,并增加了校验注解如@Email、@Length等。
Spring Validation是对hibernate validation的二次封装,用于支持spring mvc参数自动校验。接下来,我们以spring-boot项目为例,介绍Spring Validation的使用。
引入依赖
如果spring-boot版本小于2.3.x,spring-boot-starter-web会自动传入hibernate-validator依赖。如果spring-boot版本大于2.3.x,则需要手动引入依赖:
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-validator</artifactId>
<version>6.0.1.Final</version>
</dependency>
对于web服务来说,为防止非法参数对业务造成影响,在Controller层一定要做参数校验的!大部分情况下,请求参数分为如下两种形式:
-
POST、PUT请求,使用requestBody传递参数;
-
GET请求,使用requestParam/PathVariable传递参数。
下面我们简单介绍下requestBody和requestParam/PathVariable的参数校验实战!
requestBody参数校验
POST、PUT请求一般会使用requestBody传递参数,这种情况下,后端使用DTO对象进行接收。只要给DTO对象加上@Validated注解就能实现自动参数校验。比如,有一个保存User的接口,要求userName长度是2-10,account和password字段长度是6-20。
如果校验失败,会抛出MethodArgumentNotValidException异常,Spring默认会将其转为400(Bad Request)请求。
DTO表示数据传输对象(Data Transfer Object),用于服务器和客户端之间交互传输使用的。在spring-web项目中可以表示用于接收请求参数的Bean对象。
在DTO字段上声明约束注解
@Data
public class UserDTO {
private Long userId;
@NotNull
@Length(min = 2, max = 10)
private String userName;
@NotNull
@Length(min = 6, max = 20)
private String account;
@NotNull
@Length(min = 6, max = 20)
private String password;
}
在方法参数上声明校验注解
@PostMapping("/save")
public Result saveUser(@RequestBody @Validated UserDTO userDTO) {
// 校验通过,才会执行业务逻辑处理
return Result.ok();
}
这种情况下,使用@Valid和@Validated都可以。
requestParam/PathVariable参数校验
GET请求一般会使用requestParam/PathVariable传参。如果参数比较多(比如超过6个),还是推荐使用DTO对象接收。
否则,推荐将一个个参数平铺到方法入参中。在这种情况下,必须在Controller类上标注@Validated注解,并在入参上声明约束注解(如@Min等)。如果校验失败,会抛出ConstraintViolationException异常。
代码示例如下:
@RequestMapping("/api/user")
@RestController
@Validated
public class UserController {
// 路径变量
@GetMapping("{userId}")
public Result detail(@PathVariable("userId") @Min(10000000000000000L) Long userId) {
// 校验通过,才会执行业务逻辑处理
UserDTO userDTO = new UserDTO();
userDTO.setUserId(userId);
userDTO.setAccount("11111111111111111");
userDTO.setUserName("xixi");
userDTO.setAccount("11111111111111111");
return Result.ok(userDTO);
}
// 查询参数
@GetMapping("getByAccount")
public Result getByAccount(@Length(min = 6, max = 20) @NotNull String account) {
// 校验通过,才会执行业务逻辑处理
UserDTO userDTO = new UserDTO();
userDTO.setUserId(10000000000000003L);
userDTO.setAccount(account);
userDTO.setUserName("xixi");
userDTO.setAccount("11111111111111111");
return Result.ok(userDTO);
}
}
统一异常处理
前面说过,如果校验失败,会抛出MethodArgumentNotValidException或者ConstraintViolationException异常。在实际项目开发中,通常会用统一异常处理来返回一个更友好的提示。
比如我们系统要求无论发送什么异常,http的状态码必须返回200,由业务码去区分系统的异常情况。
@RestControllerAdvice
public class CommonExceptionHandler {
@ExceptionHandler({MethodArgumentNotValidException.class})
@ResponseStatus(HttpStatus.OK)
@ResponseBody
public Result handleMethodArgumentNotValidException(MethodArgumentNotValidException ex) {
BindingResult bindingResult = ex.getBindingResult();
StringBuilder sb = new StringBuilder("校验失败:");
for (FieldError fieldError : bindingResult.getFieldErrors()) {
sb.append(fieldError.getField()).append(":").append(fieldError.getDefaultMessage()).append(", ");
}
String msg = sb.toString();
return Result.fail(BusinessCode.参数校验失败, msg);
}
@ExceptionHandler({ConstraintViolationException.class})
@ResponseStatus(HttpStatus.OK)
@ResponseBody
public Result handleConstraintViolationException(ConstraintViolationException ex) {
return Result.fail(BusinessCode.参数校验失败, ex.getMessage());
}
}
进阶使用
分组校验
在实际项目中,可能多个方法需要使用同一个DTO类来接收参数,而不同方法的校验规则很可能是不一样的。这个时候,简单地在DTO类的字段上加约束注解无法解决这个问题。因此,spring-validation支持了分组校验的功能,专门用来解决这类问题。
还是上面的例子,比如保存User的时候,UserId是可空的,但是更新User的时候,UserId的值必须>=10000000000000000L;其它字段的校验规则在两种情况下一样。这个时候使用分组校验的代码示例如下:
约束注解上声明适用的分组信息groups
@Data
public class UserDTO {
@Min(value = 10000000000000000L, groups = Update.class)
private Long userId;
@NotNull(groups = {Save.class, Update.class})
@Length(min = 2, max = 10, groups = {Save.class, Update.class})
private String userName;
@NotNull(groups = {Save.class, Update.class})
@Length(min = 6, max = 20, groups = {Save.class, Update.class})
private String account;
@NotNull(groups = {Save.class, Update.class})
@Length(min = 6, max = 20, groups = {Save.class, Update.class})
private String password;
/**
* 保存的时候校验分组
*/
public interface Save {
}
/**
* 更新的时候校验分组
*/
public interface Update {
}
}
@Validated注解上指定校验分组
@PostMapping("/save")
public Result saveUser(@RequestBody @Validated(UserDTO.Save.class) UserDTO userDTO) {
// 校验通过,才会执行业务逻辑处理
return Result.ok();
}
@PostMapping("/update")
public Result updateUser(@RequestBody @Validated(UserDTO.Update.class) UserDTO userDTO) {
// 校验通过,才会执行业务逻辑处理
return Result.ok();
}
嵌套校验
前面的示例中,DTO类里面的字段都是基本数据类型和String类型。但是实际场景中,有可能某个字段也是一个对象,这种情况先,可以使用嵌套校验。
比如,上面保存User信息的时候同时还带有Job信息。需要注意的是,此时DTO类的对应字段必须标记@Valid注解。
@Data
public class UserDTO {
@Min(value = 10000000000000000L, groups = Update.class)
private Long userId;
@NotNull(groups = {Save.class, Update.class})
@Length(min = 2, max = 10, groups = {Save.class, Update.class})
private String userName;
@NotNull(groups = {Save.class, Update.class})
@Length(min = 6, max = 20, groups = {Save.class, Update.class})
private String account;
@NotNull(groups = {Save.class, Update.class})
@Length(min = 6, max = 20, groups = {Save.class, Update.class})
private String password;
@NotNull(groups = {Save.class, Update.class})
@Valid
private Job job;
@Data
public static class Job {
@Min(value = 1, groups = Update.class)
private Long jobId;
@NotNull(groups = {Save.class, Update.class})
@Length(min = 2, max = 10, groups = {Save.class, Update.class})
private String jobName;
@NotNull(groups = {Save.class, Update.class})
@Length(min = 2, max = 10, groups = {Save.class, Update.class})
private String position;
}
/**
* 保存的时候校验分组
*/
public interface Save {
}
/**
* 更新的时候校验分组
*/
public interface Update {
}
}
嵌套校验可以结合分组校验一起使用。还有就是嵌套集合校验会对集合里面的每一项都进行校验,例如List<Job>字段会对这个list里面的每一个Job对象都进行校验
集合校验
如果请求体直接传递了json数组给后台,并希望对数组中的每一项都进行参数校验。此时,如果我们直接使用java.util.Collection下的list或者set来接收数据,参数校验并不会生效!我们可以使用自定义list集合来接收参数:
包装List类型,并声明@Valid注解
public class ValidationList<E> implements List<E> {
@Delegate // @Delegate是lombok注解
@Valid // 一定要加@Valid注解
public List<E> list = new ArrayList<>();
// 一定要记得重写toString方法
@Override
public String toString() {
return list.toString();
}
}
@Delegate注解受lombok版本限制,1.18.6以上版本可支持。如果校验不通过,会抛出NotReadablePropertyException,同样可以使用统一异常进行处理。
比如,我们需要一次性保存多个User对象,Controller层的方法可以这么写:
@PostMapping("/saveList")
public Result saveList(@RequestBody @Validated(UserDTO.Save.class) ValidationList<UserDTO> userList) {
// 校验通过,才会执行业务逻辑处理
return Result.ok();
}
自定义校验
业务需求总是比框架提供的这些简单校验要复杂的多,我们可以自定义校验来满足我们的需求。
自定义spring validation非常简单,假设我们自定义加密id(由数字或者a-f的字母组成,32-256长度)校验,主要分为两步:
自定义约束注解
@Target({METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER})
@Retention(RUNTIME)
@Documented
@Constraint(validatedBy = {EncryptIdValidator.class})
public @interface EncryptId {
// 默认错误消息
String message() default "加密id格式错误";
// 分组
Class<?>[] groups() default {};
// 负载
Class<? extends Payload>[] payload() default {};
}
实现ConstraintValidator接口编写约束校验器
public class EncryptIdValidator implements ConstraintValidator<EncryptId, String> {
private static final Pattern PATTERN = Pattern.compile("^[a-f\\d]{32,256}$");
@Override
public boolean isValid(String value, ConstraintValidatorContext context) {
// 不为null才进行校验
if (value != null) {
Matcher matcher = PATTERN.matcher(value);
return matcher.find();
}
return true;
}
}
这样我们就可以使用@EncryptId进行参数校验了!
编程式校验
上面的示例都是基于注解来实现自动校验的,在某些情况下,我们可能希望以编程方式调用验证。这个时候可以注入javax.validation.Validator对象,然后再调用其api。
@Autowired
private javax.validation.Validator globalValidator;
// 编程式校验
@PostMapping("/saveWithCodingValidate")
public Result saveWithCodingValidate(@RequestBody UserDTO userDTO) {
Set<ConstraintViolation<UserDTO>> validate = globalValidator.validate(userDTO, UserDTO.Save.class);
// 如果校验通过,validate为空;否则,validate包含未校验通过项
if (validate.isEmpty()) {
// 校验通过,才会执行业务逻辑处理
} else {
for (ConstraintViolation<UserDTO> userDTOConstraintViolation : validate) {
// 校验失败,做其它逻辑
System.out.println(userDTOConstraintViolation);
}
}
return Result.ok();
}
快速失败(Fail Fast)
Spring Validation默认会校验完所有字段,然后才抛出异常。可以通过一些简单的配置,开启Fali Fast模式,一旦校验失败就立即返回。
@Bean
public Validator validator() {
ValidatorFactory validatorFactory = Validation.byProvider(HibernateValidator.class)
.configure()
// 快速失败模式
.failFast(true)
.buildValidatorFactory();
return validatorFactory.getValidator();
}
@Valid和@Validated区别
实现原理
requestBody参数校验实现原理
在spring-mvc中,RequestResponseBodyMethodProcessor是用于解析@RequestBody标注的参数以及处理@ResponseBody标注方法的返回值的。显然,执行参数校验的逻辑肯定就在解析参数的方法resolveArgument()中:
public class RequestResponseBodyMethodProcessor extends AbstractMessageConverterMethodProcessor {
@Override
public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {
parameter = parameter.nestedIfOptional();
//将请求数据封装到DTO对象中
Object arg = readWithMessageConverters(webRequest, parameter, parameter.getNestedGenericParameterType());
String name = Conventions.getVariableNameForParameter(parameter);
if (binderFactory != null) {
WebDataBinder binder = binderFactory.createBinder(webRequest, arg, name);
if (arg != null) {
// 执行数据校验
validateIfApplicable(binder, parameter);
if (binder.getBindingResult().hasErrors() && isBindExceptionRequired(binder, parameter)) {
throw new MethodArgumentNotValidException(parameter, binder.getBindingResult());
}
}
if (mavContainer != null) {
mavContainer.addAttribute(BindingResult.MODEL_KEY_PREFIX + name, binder.getBindingResult());
}
}
return adaptArgumentIfNecessary(arg, parameter);
}
}
可以看到,resolveArgument()调用了validateIfApplicable()进行参数校验。
protected void validateIfApplicable(WebDataBinder binder, MethodParameter parameter) {
// 获取参数注解,比如@RequestBody、@Valid、@Validated
Annotation[] annotations = parameter.getParameterAnnotations();
for (Annotation ann : annotations) {
// 先尝试获取@Validated注解
Validated validatedAnn = AnnotationUtils.getAnnotation(ann, Validated.class);
//如果直接标注了@Validated,那么直接开启校验。
//如果没有,那么判断参数前是否有Valid起头的注解。
if (validatedAnn != null || ann.annotationType().getSimpleName().startsWith("Valid")) {
Object hints = (validatedAnn != null ? validatedAnn.value() : AnnotationUtils.getValue(ann));
Object[] validationHints = (hints instanceof Object[] ? (Object[]) hints : new Object[] {hints});
//执行校验
binder.validate(validationHints);
break;
}
}
}
看到这里,大家应该能明白为什么这种场景下@Validated、@Valid两个注解可以混用。我们接下来继续看WebDataBinder.validate()实现。
@Override
public void validate(Object target, Errors errors, Object... validationHints) {
if (this.targetValidator != null) {
processConstraintViolations(
//此处调用Hibernate Validator执行真正的校验
this.targetValidator.validate(target, asValidationGroups(validationHints)), errors);
}
}
最终发现底层最终还是调用了Hibernate Validator进行真正的校验处理。
方法级别的参数校验实现原理
上面提到的将参数一个个平铺到方法参数中,然后在每个参数前面声明约束注解的校验方式,就是方法级别的参数校验。
实际上,这种方式可用于任何Spring Bean的方法上,比如Controller/Service等。其底层实现原理就是AOP,具体来说是通过MethodValidationPostProcessor动态注册AOP切面,然后使用MethodValidationInterceptor对切点方法织入增强。
public class MethodValidationPostProcessor extends AbstractBeanFactoryAwareAdvisingPostProcessorimplements InitializingBean {
@Override
public void afterPropertiesSet() {
//为所有`@Validated`标注的Bean创建切面
Pointcut pointcut = new AnnotationMatchingPointcut(this.validatedAnnotationType, true);
//创建Advisor进行增强
this.advisor = new DefaultPointcutAdvisor(pointcut, createMethodValidationAdvice(this.validator));
}
//创建Advice,本质就是一个方法拦截器
protected Advice createMethodValidationAdvice(@Nullable Validator validator) {
return (validator != null ? new MethodValidationInterceptor(validator) : new MethodValidationInterceptor());
}
}
接着看一下MethodValidationInterceptor:
public class MethodValidationInterceptor implements MethodInterceptor {
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
//无需增强的方法,直接跳过
if (isFactoryBeanMetadataMethod(invocation.getMethod())) {
return invocation.proceed();
}
//获取分组信息
Class<?>[] groups = determineValidationGroups(invocation);
ExecutableValidator execVal = this.validator.forExecutables();
Method methodToValidate = invocation.getMethod();
Set<ConstraintViolation<Object>> result;
try {
//方法入参校验,最终还是委托给Hibernate Validator来校验
result = execVal.validateParameters(
invocation.getThis(), methodToValidate, invocation.getArguments(), groups);
}
catch (IllegalArgumentException ex) {
...
}
//有异常直接抛出
if (!result.isEmpty()) {
throw new ConstraintViolationException(result);
}
//真正的方法调用
Object returnValue = invocation.proceed();
//对返回值做校验,最终还是委托给Hibernate Validator来校验
result = execVal.validateReturnValue(invocation.getThis(), methodToValidate, returnValue, groups);
//有异常直接抛出
if (!result.isEmpty()) {
throw new ConstraintViolationException(result);
}
return returnValue;
}
}
实际上,不管是requestBody参数校验还是方法级别的校验,最终都是调用Hibernate Validator执行校验,Spring Validation只是做了一层封装。
各种检查注解示意
空检查
@Null 验证对象是否为null
@NotNull 验证对象是否不为null, 无法查检长度为0的字符串
@NotBlank 检查约束字符串是不是Null还有被Trim的长度是否大于0,只对字符串,且会去掉前后空格.
@NotEmpty 检查约束元素是否为NULL或者是EMPTY. 验证注解的元素值不为null且不为空(字符串长度不为0、集合大小不为0)
Booelan检查
@AssertTrue 验证 Boolean 对象是否为 true
@AssertFalse 验证 Boolean 对象是否为 false
长度检查
@Size(min=, max=) 验证对象(Array,Collection,Map,String)长度是否在给定的范围之内
@Length(min=, max=) Validates that the annotated string is between min and max included.
日期检查
@Past 验证 Date 和 Calendar 对象是否在当前时间之前
@Future 验证 Date 和 Calendar 对象是否在当前时间之后
@Pattern 验证 String 对象是否符合正则表达式的规则 数值检查,建议使用在Stirng,Integer类型,不建议使用在int类型上,因为表单值为“”时无法转换为int,但可以转换为Stirng为"",Integer为null
@Min 验证 Number 和 String 对象是否大等于指定的值
@Max 验证 Number 和 String 对象是否小等于指定的值
@DecimalMax 被标注的值必须不大于约束中指定的最大值. 这个约束的参数是一个通过BigDecimal定义的最大值的字符串表示.小数存在精度
@DecimalMin 被标注的值必须不小于约束中指定的最小值. 这个约束的参数是一个通过BigDecimal定义的最小值的字符串表示.小数存在精度
@Digits 验证 Number 和 String 的构成是否合法 @Digits(integer=,fraction=) 验证字符串是否是符合指定格式的数字,interger指定整数精度,fraction指定小数精度。
@Range(min=, max=) 检查数字是否介于min和max之间.
@Range(min=10000,max=50000,message="range.bean.wage")
private BigDecimal wage;
@Valid 递归的对关联对象进行校验, 如果关联对象是个集合或者数组,那么对其中的元素进行递归校验,如果是一个map,则对其中的值部分进行校验.(是否进行递归验证)
@CreditCardNumber信用卡验证
@Email 验证是否是邮件地址,如果为null,不进行验证,算通过验证。
@ScriptAssert(lang= ,script=, alias=)
@URL(protocol=,host=, port=,regexp=, flags=)
最后再附上一个demo
- 被校验的类 User
import lombok.Data;
import javax.validation.constraints.Email;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
@Data
public class User {
@NotNull(message = "主键不能为空", groups = {CheckGroup1.class})
private String id;
@Email(message = "邮箱格式不正确")
private String email;
@Size(min = 4, max = 16, message = "昵称长度为4~16个字符之间", groups = {CheckGroup1.class})
private String nickName;
@NotNull(message = "密码不能为空")
@Size(min = 8, max = 24, message = "密码长度为8~24个字符之间")
private String pwd;
}
- 校验结果实体ValidationResult
import lombok.Data;
import lombok.ToString;
import java.text.MessageFormat;
import java.util.Map;
/**
* @Description 实体校验结果
*/
@Data
@ToString
public class ValidationResult {
/**
* 是否有异常
*/
private boolean hasErrors;
/**
* 异常消息记录
*/
private Map<String, String> errorMsg;
/**
* 获取异常消息组装
*
* @return
*/
public String getMessage() {
if (errorMsg == null || errorMsg.isEmpty()) {
return "";
}
StringBuilder message = new StringBuilder();
errorMsg.forEach((key, value) -> {
message.append(MessageFormat.format("{0}:{1} \r\n", key, value));
});
return message.toString();
}
}
- 校验Util类
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import org.apache.commons.collections4.CollectionUtils;
import org.hibernate.validator.HibernateValidator;
import javax.validation.*;
import javax.validation.groups.Default;
/**
*
*/
public class ValidateUtils {
private ValidateUtils() {
}
// 线程安全的,直接构建也可以,这里使用静态代码块一样的效果
private static Validator validator;
static {
ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
validator = factory.getValidator();
}
public static <T> void validate(T t) {
Set<ConstraintViolation<T>> constraintViolations = validator.validate(t);
if (constraintViolations.size() > 0) {
StringBuilder validateError = new StringBuilder();
for (ConstraintViolation<T> constraintViolation : constraintViolations) {
validateError.append(constraintViolation.getMessage()).append(";");
}
throw new ValidationException(validateError.toString());
}
}
public static <T> void validate(T t, Class clazz) {
Set<ConstraintViolation<T>> constraintViolations = validator.validate(t, clazz);
if (constraintViolations.size() > 0) {
StringBuilder validateError = new StringBuilder();
for (ConstraintViolation<T> constraintViolation : constraintViolations) {
validateError.append(constraintViolation.getMessage()).append(";");
}
throw new ValidationException(validateError.toString());
}
}
public static <T> void validate1(T t) {
Validator validator = Validation.byProvider(HibernateValidator.class).configure().failFast(false) // 为true,则有一个错误就结束校验
.buildValidatorFactory().getValidator();
Set<ConstraintViolation<T>> constraintViolations = validator.validate(t);
StringBuilder validateError = new StringBuilder();
for (ConstraintViolation<T> constraintViolation : constraintViolations) {
validateError.append(constraintViolation.getMessage()).append(";");
}
throw new ValidationException(validateError.toString());
}
// 自定义校验结果返回
/**
* 校验实体,返回实体所有属性的校验结果
*
* @param obj
* @param <T>
* @return
*/
public static <T> ValidationResult validateEntity(T obj) {
// 解析校验结果
Set<ConstraintViolation<T>> validateSet = validator.validate(obj, Default.class);
return buildValidationResult(validateSet);
}
/**
* 校验指定实体的指定属性是否存在异常
*
* @param obj
* @param propertyName
* @param <T>
* @return
*/
public static <T> ValidationResult validateProperty(T obj, String propertyName) {
Set<ConstraintViolation<T>> validateSet = validator.validateProperty(obj, propertyName, Default.class);
return buildValidationResult(validateSet);
}
/**
* 将异常结果封装返回
*
* @param validateSet
* @param <T>
* @return
*/
private static <T> ValidationResult buildValidationResult(Set<ConstraintViolation<T>> validateSet) {
ValidationResult validationResult = new ValidationResult();
if (!CollectionUtils.isEmpty(validateSet)) {
validationResult.setHasErrors(true);
Map<String, String> errorMsgMap = new HashMap<>();
for (ConstraintViolation<T> constraintViolation : validateSet) {
errorMsgMap.put(constraintViolation.getPropertyPath().toString(), constraintViolation.getMessage());
}
validationResult.setErrorMsg(errorMsgMap);
}
return validationResult;
}
public static <T> ValidationResult validateEntity1(T obj) {
ValidationResult result = new ValidationResult();
Set<ConstraintViolation<T>> set = validator.validate(obj, Default.class);
if (!CollectionUtils.isEmpty(set)) {
result.setHasErrors(true);
Map<String, String> errorMsg = new HashMap<String, String>();
for (ConstraintViolation<T> cv : set) {
errorMsg.put(cv.getPropertyPath().toString(), cv.getMessage());
}
result.setErrorMsg(errorMsg);
}
return result;
}
public static <T> ValidationResult validateEntity1(T obj, Class clazz) {
ValidationResult result = new ValidationResult();
Set<ConstraintViolation<T>> set = validator.validate(obj, clazz);
if (!CollectionUtils.isEmpty(set)) {
result.setHasErrors(true);
Map<String, String> errorMsg = new HashMap<String, String>();
for (ConstraintViolation<T> cv : set) {
errorMsg.put(cv.getPropertyPath().toString(), cv.getMessage());
}
result.setErrorMsg(errorMsg);
}
return result;
}
public static <T> ValidationResult validateProperty1(T obj, String propertyName) {
ValidationResult result = new ValidationResult();
Set<ConstraintViolation<T>> set = validator.validateProperty(obj, propertyName, Default.class);
if (!CollectionUtils.isEmpty(set)) {
result.setHasErrors(true);
Map<String, String> errorMsg = new HashMap<String, String>();
for (ConstraintViolation<T> cv : set) {
errorMsg.put(propertyName, cv.getMessage());
}
result.setErrorMsg(errorMsg);
}
return result;
}
}
- 分组校验的交验组定义
public interface CheckGroup1 {
}
- 测试客户端
import java.util.Map;
public class Client {
public static void main(String[] args) {
User user = new User();
ValidationResult validRes = ValidateUtils.validateEntity1(user, CheckGroup1.class);
if (validRes.isHasErrors()) {
for (Map.Entry<String, String> entry : validRes.getErrorMsg().entrySet()) {
System.out.println(entry.getValue());
}
}
}
}
14 - ZIPUtils
package cn.anzhongwei.lean.demo.zip;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class ZipUtilFileInfo {
private String name;
private String url;
private Boolean Local;
}
package cn.anzhongwei.lean.demo.zip;
import org.apache.commons.lang.ArrayUtils;
import org.apache.pdfbox.io.IOUtils;
import java.io.*;
import java.net.HttpURLConnection;
import java.net.URL;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.zip.*;
public class ZipUtils2 {
private static final int BUFFER = 256;
public static void main(String[] args) throws IOException {
String hallFilePath = "d:/" + "qwe";
compress(Paths.get(hallFilePath).toString(), hallFilePath + ".zip");
List<ZipUtilFileInfo> downFileInfos = new ArrayList<>();
ZipUtilFileInfo d1 = new ZipUtilFileInfo( "价格导入模板.xlsx", "http://网络地址", false);
ZipUtilFileInfo d2 = new ZipUtilFileInfo( "订单导入_仓储.xlsx", "http://网络地址", false);
downFileInfos.add(d1);
downFileInfos.add(d2);
File zipfile1 = gerenatorZIPFileFromUrl(downFileInfos, "t1.zip");
File zipfile2 = gerenatorZIPFileFromUrl(downFileInfos, "t2.zip");
File zipfile3 = gerenatorZIPFileFromUrl(downFileInfos, "t3.zip");
List<ZipUtilFileInfo> downFileInfos2 = new ArrayList<>();
ZipUtilFileInfo dd1 = new ZipUtilFileInfo( zipfile1.getName(), zipfile1.getAbsolutePath(), true);
ZipUtilFileInfo dd2 = new ZipUtilFileInfo( zipfile2.getName(), zipfile1.getAbsolutePath(), true);
ZipUtilFileInfo dd3 = new ZipUtilFileInfo( zipfile3.getName(), zipfile1.getAbsolutePath(), true);
downFileInfos2.add(dd1);
downFileInfos2.add(dd2);
downFileInfos2.add(dd3);
File ff2 = gerenatorZIPFileFromUrl(downFileInfos2, "tt1.zip");
System.out.println(zipfile1.getAbsolutePath());
InputStream inputStream = new FileInputStream(ff2);
File tempFile = new File("C:\\testFile\\");
if (!tempFile.exists()) {
tempFile.mkdirs();
}
OutputStream os = new FileOutputStream(tempFile.getPath() + File.separator + "shishi.zip");
byte[] bs = new byte[1024];
int len;
while ((len = inputStream.read(bs)) != -1) {
os.write(bs, 0, len);
}
}
//文件或目录打包成zip
public static void compress(String fromPath, String toPath) throws IOException {
File fromFile = new File(fromPath);
File toFile = new File(toPath);
if (!fromFile.exists()) {
throw new RuntimeException(fromPath + "不存在!");
}
try (FileOutputStream outputStream = new FileOutputStream(toFile);
CheckedOutputStream checkedOutputStream = new CheckedOutputStream(outputStream, new CRC32());
ZipOutputStream zipOutputStream = new ZipOutputStream(checkedOutputStream)) {
String baseDir = "";
compress(fromFile, zipOutputStream, baseDir);
}
}
private static void compress(File file, ZipOutputStream zipOut, String baseDir) throws IOException {
if (file.isDirectory()) {
compressDirectory(file, zipOut, baseDir);
} else {
compressFile(file, zipOut, baseDir);
}
}
private static void compressFile(File file, ZipOutputStream zipOut, String baseDir) throws IOException {
if (!file.exists()) {
return;
}
try (BufferedInputStream bis = new BufferedInputStream(new FileInputStream(file))) {
ZipEntry entry = new ZipEntry(baseDir + file.getName());
zipOut.putNextEntry(entry);
int count;
byte[] data = new byte[BUFFER];
while ((count = bis.read(data, 0, BUFFER)) != -1) {
zipOut.write(data, 0, count);
}
}
}
private static void compressDirectory(File dir, ZipOutputStream zipOut, String baseDir) throws IOException {
File[] files = dir.listFiles();
if (files != null && ArrayUtils.isNotEmpty(files)) {
for (File file : files) {
compress(file, zipOut, baseDir + dir.getName() + File.separator);
}
}
}
private static File gerenatorZIPFileFromUrl(List<ZipUtilFileInfo> downFileInfos, String zipFileName) {
try {
File zipFile = File.createTempFile(zipFileName, ".zip");
FileOutputStream f = new FileOutputStream(zipFile);
CheckedOutputStream csum = new CheckedOutputStream(f, new Adler32());
ZipOutputStream zos = new ZipOutputStream(csum);
for (ZipUtilFileInfo downFileInfo : downFileInfos) {
InputStream inputStream;
if (Objects.isNull(downFileInfo.getLocal())) {
throw new RuntimeException("url类型未指定是否本地,请检查代码");
}
if (downFileInfo.getLocal()) {
inputStream = new FileInputStream(downFileInfo.getUrl());
} else {
inputStream = getStreamByUrl(downFileInfo.getUrl());
}
zos.putNextEntry(new ZipEntry(downFileInfo.getName()));
int bytesRead = 0;
// 向压缩文件中输出数据
while((bytesRead = inputStream.read()) != -1){
zos.write(bytesRead);
}
inputStream.close();
zos.closeEntry();
}
zos.close();
return zipFile;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
public static InputStream getStreamByUrl(String strUrl){
HttpURLConnection conn = null;
try {
URL url = new URL(strUrl);
conn = (HttpURLConnection)url.openConnection();
conn.setRequestMethod("GET");
conn.setConnectTimeout(20 * 1000);
final ByteArrayOutputStream output = new ByteArrayOutputStream();
IOUtils.copy(conn.getInputStream(),output);
return new ByteArrayInputStream(output.toByteArray());
} catch (Exception e) {
e.printStackTrace();
}finally {
try{
if (conn != null) {
conn.disconnect();
}
}catch (Exception e){
e.printStackTrace();
}
}
return null;
}
}
15 - 遍历List
package cn.anzhongwei.lean.demo.list.arraylist;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
public class Demo {
public static void main(String[] args) {
Object obj = new Object();
List<Object> list = new ArrayList<>();
int objCount = 1000;
for (int i = 0; i < objCount; i++) {
list.add(obj);
}
int testTimes = 5;
for (int i = 0; i < testTimes; i++) {
testFor(list);
testForEnhanced(list);
testForEach(list);
testIterator(list);
System.out.println("\n");
}
System.out.println("---------------------------------------------------------\n");
List<Object> list2 = new ArrayList<>();
for (int i = 0; i < objCount; i++) {
list2.add(obj);
}
for (int i = 0; i < testTimes; i++) {
testFor(list2);
testForEnhanced(list2);
testForEach(list2);
testIterator(list2);
System.out.println();
}
}
private static void testFor(List<Object> list) {
long startTime = 0L;
long endTime = 0L;
startTime = System.nanoTime();
for (int i = 0; i < list.size(); i++) {
Object o = list.get(i);
}
endTime = System.nanoTime();
System.out.println("for所用时间(ns) :" + (endTime - startTime));
}
private static void testForEnhanced(List<Object> list) {
long startTime = 0L;
long endTime = 0L;
startTime = System.nanoTime();
for (Object o : list) {
Object value = o;
}
endTime = System.nanoTime();
System.out.println("增强for所用时间(ns) :" + (endTime - startTime));
}
private static void testForEach(List<Object> list) {
long startTime = 0L;
long endTime = 0L;
startTime = System.nanoTime();
list.forEach(o->{Object obj = o;});
endTime = System.nanoTime();
System.out.println("forEach所用时间(ns) :" + (endTime - startTime));
}
private static void testIterator(List<Object> list) {
long startTime = 0L;
long endTime = 0L;
startTime = System.nanoTime();
Iterator<Object> iterator = list.iterator();
while (iterator.hasNext()) {
Object o = iterator.next();
}
endTime = System.nanoTime();
System.out.println("iterator所用时间(ns) :" + (endTime - startTime));
}
}
16 - 遍历Map
package cn.anzhongwei.lean.demo.map;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
public class MapTest {
public static void main(String[] args) {
Map<String, String> map = new HashMap<String, String>();
map.put("1", "value1");
map.put("2", "value2");
map.put("3", "value3");
//第一种:普遍使用,二次取值
System.out.println("通过Map.keySet遍历key和value:");
for (String key : map.keySet()) {
System.out.println("key= "+ key + " and value= " + map.get(key));
}
//第二种
System.out.println("通过Map.entrySet使用iterator遍历key和value:");
Iterator<Map.Entry<String, String>> it = map.entrySet().iterator();
while (it.hasNext()) {
Map.Entry<String, String> entry = it.next();
System.out.println("key= " + entry.getKey() + " and value= " + entry.getValue());
}
//第三种:推荐,尤其是容量大时
System.out.println("通过Map.entrySet遍历key和value");
for (Map.Entry<String, String> entry : map.entrySet()) {
System.out.println("key= " + entry.getKey() + " and value= " + entry.getValue());
}
//第四种
System.out.println("通过Map.values()遍历所有的value,但不能遍历key");
for (String v : map.values()) {
System.out.println("value= " + v);
}
}
}
17 - 从mybatis的sql日志自动填充sql占位符
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* 从mybatis的sql日志自动填充sql占位符
*/
public class SqlReplaceUtil extends JFrame {
private static final Pattern HUMP_PATTERN = Pattern.compile("\\?");
JPanel jp;
JButton b1;
JTextArea sqlTextField;
JTextArea paramTextField;
JTextArea resultTextField;
JLabel lb3;
JLabel lb4;
SqlReplaceUtil() {
jp = new JPanel();
b1 = new JButton("替换");
sqlTextField = new JTextArea("输入待处理的SQL,比如:insert into test (id,name,age) values (?,?,?)",10,90);
paramTextField = new JTextArea("输入待处理参数,比如:100(Integer),zhangsan(String),null",10,90);
resultTextField = new JTextArea(10,90);
lb3 = new JLabel("结果为:");
lb4 = new JLabel("");
b1.addActionListener(new ActionListener() {//响应按钮的事件
public void actionPerformed(ActionEvent e) {
try {
lb4.setText("");
String sql = sqlTextField.getText();
String param = paramTextField.getText();
resultTextField.setText(replaceSql(sql, param));
} catch (Exception ex) {
lb4.setText(ex.getMessage());
}
}
});
jp.add(sqlTextField);
jp.add(paramTextField);
jp.add(b1);
jp.add(resultTextField);
jp.add(lb4);
add(jp);
setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
setLocationRelativeTo(null);
setLocation(600, 200);
setVisible(true);
pack();
setSize(1050, 700);
setLocationRelativeTo(null);
setVisible(true);
setResizable(false);
}
public static void main(String[] args) {
SqlReplaceUtil e = new SqlReplaceUtil();
Container contentPane = e.getContentPane();
contentPane.setLayout(new BoxLayout(contentPane, BoxLayout.Y_AXIS));
}
private String replaceSql(String sql, String param) {
String[] split = param.split(",");
//校验数量
int paramCount = split.length;
int matcherCount = 0;
Matcher matcher = HUMP_PATTERN.matcher(sql);
while (matcher.find()) {
matcherCount++;
}
if (paramCount != matcherCount) {
throw new RuntimeException("待替换参数和参数数量不一致");
}
//处理参数
for (int i = 0; i < split.length; i++) {
split[i] = split[i].trim();
int index = split[i].lastIndexOf('(');
if (split[i].equals("null")||index<0) {
continue;
}
split[i] = "'" + split[i].substring(0, index) + "'";
}
StringBuffer sb = new StringBuffer();
Matcher matcher2 = HUMP_PATTERN.matcher(sql);
int index = 0;
while (matcher2.find()) {
matcher2.appendReplacement(sb, split[index]);
index++;
}
matcher2.appendTail(sb);
return sb.toString();
}
}
18 - 代理模式实现
18.1 - ByteBuddy动态代理
- pom文件添加依赖
<dependency>
<groupId>net.bytebuddy</groupId>
<artifactId>byte-buddy</artifactId>
<!-- 我用的最新的, 一般1.14.0以上都没问题 -->
<version>1.15.1</version>
</dependency>
- 先创建要被代理的类
public class TargetClass {
public void performAction() {
System.out.println("原始方法: performAction");
}
public String sayHello(String name) {
return "Hello, " + name;
}
}
- 创建拦截器
import net.bytebuddy.implementation.bind.annotation.*;
import java.lang.reflect.Method;
import java.util.concurrent.Callable;
/**
* 通用方法拦截器
*/
public class GenericInterceptor {
@RuntimeType
public static Object intercept(@Origin Method method,
@AllArguments Object[] args,
@This Object obj,
@SuperCall Callable<?> callable) throws Exception {
// 获取动态添加的字段值
String proxyField = (String) obj.getClass().getField("proxyField").get(obj);
System.out.println("[拦截] proxyField 值: " + proxyField);
// 修改字段值
System.out.println("[通用拦截] 修改字段proxyField值: Modified by interceptor" );
obj.getClass().getField("proxyField").set(obj, "Modified by interceptor");
System.out.println("[通用拦截] 方法开始: " + method.getName());
Object result = callable.call(); // 执行原始方法
System.out.println("[通用拦截] 方法结束: " + method.getName());
return result;
}
}
- 代理工具
import net.bytebuddy.ByteBuddy;
import net.bytebuddy.description.modifier.Visibility;
import net.bytebuddy.implementation.MethodDelegation;
import net.bytebuddy.matcher.ElementMatchers;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
/**
* 通用 Byte Buddy 动态代理工具类
*/
public class ByteBuddyProxy {
/**
* 创建一个代理对象
*
* @param targetClass 目标类类型
* @param interceptor 拦截器类(需使用 Byte Buddy 注解定义逻辑)
* @param <T> 泛型
* @return 代理实例
* @throws NoSuchMethodException
* @throws IllegalAccessException
* @throws InvocationTargetException
* @throws InstantiationException
*/
public static <T> T createProxy(Class<T> targetClass, Class<?> interceptor) throws Exception {
Class<?> dynamicType = new ByteBuddy()
.subclass(targetClass)
// ElementMatchers.any() 拦截所有方法
.method(ElementMatchers.any())
// MethodDelegation.to(interceptor) 拦截器类 方法增强
.intercept(MethodDelegation.to(interceptor))
// 动态的添加字段。这里可以放到参数里面,然后由调用方传递进来要添加什么参数
.defineField("proxyField", String.class, Visibility.PUBLIC) // 可选:添加字段
.defineField("abc", String.class, Visibility.PUBLIC) // 可选:添加字段
.make()
.load(targetClass.getClassLoader())
.getLoaded();
// 支持构造实例
// .getDeclaredConstructor()
// .newInstance();
Constructor<?> constructor = dynamicType.getDeclaredConstructor();
Object instance = constructor.newInstance();
// 现在就可以 调用方法触发拦截逻辑 了
// ((TargetClass) instance).performAction();
// 设置字段值
System.out.println("[通用拦截] 设置字段proxyField值: Hello Dynamic Field" );
dynamicType.getField("proxyField").set(instance, "Hello Dynamic Field");
System.out.println("[通用拦截] 设置字段abc值: def" );
dynamicType.getField("abc").set(instance, "def");
return (T) instance;
}
}
- 一个测试类用来测试动态代理
public class ByteBuddyGenericExample {
public static void main(String[] args) throws Exception {
// 创建代理对象
TargetClass proxyInstance = ByteBuddyProxy
.createProxy(TargetClass.class, GenericInterceptor.class);
// 调用方法
proxyInstance.performAction();
String result = proxyInstance.sayHello("World");
System.out.println("返回值: " + result);
String abc = (String) proxyInstance.getClass().getField("abc").get(proxyInstance);
System.out.println("last在打印一遍proxyField: " + abc);
}
}
18.2 - CGLIB动态代理
maven项目需要添加依赖
<!-- https://mvnrepository.com/artifact/cglib/cglib -->
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.3.0</version>
</dependency>
1 0.1 CGLIB包结构
-
*net.sf.cglib.core *底层字节码处理类。
-
*net.sf.cglib.transform *该包中的类用于class文件运行时转换或编译时转换。
-
*net.sf.cglib.proxy *该包中的类用于创建代理和方法拦截。
-
*net.sf.cglib.reflect *该包中的类用于快速反射,并提供了C#风格的委托。
-
*net.sf.cglib.util *集合排序工具类。
-
*net.sf.cglib.beans *JavaBean工具类。
1 使用CGLIB实现动态代理
2 1.1 CGLIB代理相关的类
- net.sf.cglib.proxy.Enhancer 主要的增强类。
- net.sf.cglib.proxy.MethodInterceptor 主要的方法拦截类,它是Callback接口的子接口,需要用户实现。
- net.sf.cglib.proxy.MethodProxy JDK的java.lang.reflect.Method类的代理类,可以方便的实现对源对象方法的调用。
cglib是通过动态的生成一个子类去覆盖所要代理类的非final方法,并设置好callback,则原有类的每个方法调用就会转变成调用用户定义的拦截方法(interceptors)。
CGLIB代理相关的常用API如下图所示:

net.sf.cglib.proxy.Callback接口在CGLIB包中是一个重要的接口,所有被net.sf.cglib.proxy.Enhancer类调用的回调(callback)接口都要继承这个接口。
net.sf.cglib.proxy.MethodInterceptor能够满足任何的拦截(interception )需要。对有些情况下可能过度。为了简化和提高性能,CGLIB包提供了一些专门的回调(callback)类型:
- net.sf.cglib.proxy.FixedValue 为提高性能,FixedValue回调对强制某一特别方法返回固定值是有用的。
- net.sf.cglib.proxy.NoOp NoOp回调把对方法调用直接委派到这个方法在父类中的实现。
- net.sf.cglib.proxy.LazyLoader 当实际的对象需要延迟装载时,可以使用LazyLoader回调。一旦实际对象被装载,它将被每一个调用代理对象的方法使用。
- net.sf.cglib.proxy.Dispatcher Dispathcer回调和LazyLoader回调有相同的特点,不同的是,当代理方法被调用时,装载对象的方法也总要被调用。
- net.sf.cglib.proxy.ProxyRefDispatcher ProxyRefDispatcher回调和Dispatcher一样,不同的是,它可以把代理对象作为装载对象方法的一个参数传递。
3 1.2 CGLIB动态代理的基本原理
CGLIB动态代理的原理就是用Enhancer生成一个原有类的子类,并且设置好callback到proxy, 则原有类的每个方法调用都会转为调用实现了MethodInterceptor接口的proxy的intercept() 函数,如图

在intercept()函数里,除执行代理类的原因方法,在原有方法前后加入其他需要实现的过程,改变原有方法的参数值,即可以实现对原有类的代理了。这似于AOP中的around advice。
4 1.3 使用MethodInterceptor接口实现方法回调
当对代理中所有方法的调用时,都会转向MethodInterceptor类型的拦截(intercept)方法,在拦截方法中再调用底层对象相应的方法。下面我们举个例子,假设你想对目标对象的所有方法调用进行权限的检查,如果没有经过授权,就抛出一个运行时的异常。
net.sf.cglib.proxy.MethodInterceptor接口是最通用的回调(callback)类型,它经常被基于代理的AOP用 来实现拦截(intercept)方法的调用。
MethodInterceptor接口只定义了一个方法:
public Object intercept(Object object, java.lang.reflect.Method method, Object[] args, MethodProxy proxy) throws Throwable;
参数Object object是被代理对象,不会出现死循环的问题。
参数java.lang.reflect.Method method是java.lang.reflect.Method类型的被拦截方法。
参数Object[] args是被被拦截方法的参数。
参数MethodProxy proxy是CGLIB提供的MethodProxy 类型的被拦截方法。
注意:
1、若原方法的参数存在基本类型,则对于第三个参数Object[] args会被转化成类的类型。如原方法的存在一个参数为int,则在intercept方法中,对应的会存在一个Integer类型的参数。
2、若原方法为final方法,则MethodInterceptor接口无法拦截该方法。
4.1 1.3.1 实现MethodInterceptor接口
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
class MethodInterceptorImpl implements MethodInterceptor {
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
System.out.println("Before invoke " + method);
Object result = proxy.invokeSuper(obj, args);
System.out.println("After invoke" + method);
return result;
}
}
Object result=proxy.invokeSuper(o,args); 表示调用原始类的被拦截到的方法。这个方法的前后添加需要的过程。在这个方法中,我们可以在调用原方法之前或之后注入自己的代码。
由于性能的原因,对原始方法的调用使用CGLIB的net.sf.cglib.proxy.MethodProxy对象,而不是反射中一般使用java.lang.reflect.Method对象。
5 1.4 使用CGLIB代理最核心类Enhancer生成代理对象
net.sf.cglib.proxy.Enhancer中有几个常用的方法:
- void setSuperclass(java.lang.Class superclass) 设置产生的代理对象的父类。
- void setCallback(Callback callback) 设置CallBack接口的实例。
- void setCallbacks(Callback[] callbacks) 设置多个CallBack接口的实例。
- void setCallbackFilter(CallbackFilter filter) 设置方法回调过滤器。
- Object create() 使用默认无参数的构造函数创建目标对象。
- Object create(Class[], Object[]) 使用有参数的构造函数创建目标对象。参数Class[] 定义了参数的类型,第二个Object[]是参数的值。
注意:在参数中,基本类型应被转化成类的类型。
基本代码:
public Object createProxy(Class targetClass) {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(targetClass);
enhancer.setCallback(new MethodInterceptorImpl ());
return enhancer.create();
}
createProxy方法返回值是targetClass的一个实例的代理。
6 1.5 使用CGLIB继进行动态代理示例
例1:使用CGLIB生成代理的基本使用。
import java.lang.reflect.Method;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
public class TestMain {
public static void main(String[] args) {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(Cglib.class);
enhancer.setCallback(new HelloProxy());
Cglib cglibProxy = (Cglib)enhancer.create();
cglibProxy.cglib();
}
}
class Cglib{
public void cglib(){
System.out.println("CGLIB");
}
}
class HelloProxy implements MethodInterceptor{
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
System.out.println("Hello");
Object object = proxy.invokeSuper(obj, args);
System.out.println("Powerful!");
return object;
}
}
输出内容:
Hello
CGLIB
Powerful!
例2:使用CGLIB创建一个Dao工厂,并展示一些基本特性。
public interface Dao {
void add(Object o);
void add(int i);
void add(String s);
}
public class DaoImpl implements Dao {
@Override
public void add(Object o) {
System.out.println("add(Object o)");
}
@Override
public void add(int i) {
System.out.println("add(int i)");
}
public final void add(String s) {
System.out.println("add(String s)");
}
}
public class Proxy implements MethodInterceptor {
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
System.out.println("拦截前...");
// 输出参数类型
for (Object arg : args) {
System.out.print(arg.getClass() + ";");
}
Object result = proxy.invokeSuper(obj, args);
System.out.println("拦截后...");
return result;
}
}
public class DaoFactory {
public static Dao create() {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(DaoImpl.class);
enhancer.setCallback(new Proxy());
Dao dao = (Dao) enhancer.create();
return dao;
}
}
public class TestMain {
public static void main(String[] args) {
Dao dao = DaoFactory.create();
dao.add(new Object());
dao.add(1);
dao.add("1");
}
}
输出内容:
拦截前...
class java.lang.Object;add(Object o)
拦截后...
拦截前...
class java.lang.Integer;add(int i)
拦截后...
add(String s)
2 回调过滤器CallbackFilter
net.sf.cglib.proxy.CallbackFilter有选择的对一些方法使用回调。
CallbackFilter可以实现不同的方法使用不同的回调方法。所以CallbackFilter称为"回调选择器"更合适一些。
CallbackFilter中的accept方法,根据不同的method返回不同的值i,这个值是在callbacks中callback对象的序号,就是调用了callbacks[i]。
import java.lang.reflect.Method;
import net.sf.cglib.proxy.Callback;
import net.sf.cglib.proxy.CallbackFilter;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import net.sf.cglib.proxy.NoOp;
public class CallbackFilterDemo {
public static void main(String[] args) {
// 回调实例数组
Callback[] callbacks = new Callback[]{new MethodInterceptorImpl(), NoOp.INSTANCE};
// 使用enhancer,设置相关参数。
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(User.class);
enhancer.setCallbacks(callbacks);
enhancer.setCallbackFilter(new CallbackFilterImpl());
// 产生代理对象
User proxyUser = (User) enhancer.create();
proxyUser.pay(); // 买
proxyUser.eat(); // 吃
}
}
/**
* 回调过滤器类。
*/
public class CallbackFilterImpl implements CallbackFilter {
@Override
public int accept(Method method) {
String methodName = method.getName();
if ("eat".equals(methodName)) {
return 1; // eat()方法使用callbacks\[1\]对象拦截。
} else if ("pay".equals(methodName)) {
return 0; // pay()方法使用callbacks\[0\]对象拦截。
}
return 0;
}
}
/**
* 自定义回调类。
*/
public class MethodInterceptorImpl implements MethodInterceptor {
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
System.out.println("Before invoke " + method);
Object result = proxy.invokeSuper(obj, args); // 原方法调用。
System.out.println("After invoke" + method);
return result;
}
}
class User {
public void pay() {
System.out.println("买东西");
}
public void eat() {
System.out.println("吃东西");
}
}
输出结果:
Before invoke public void sjq.cglib.filter.User.pay()
pay()
After invokepublic void sjq.cglib.filter.User.pay()
eat()
3 CGLIB对Mixin的支持
CGLIB的代理包net.sf.cglib.proxy.Mixin类提供对Minix编程的支持。Minix允许多个对象绑定到一个单个的大对象上。在代理中对方法的调用委托到下面相应的对象中。 这是一种将多个接口混合在一起的方式 , 实现了多个接口。
Minix是一种多继承的替代方案, 很大程度上解决了多继承的很多问题 , 实现和理解起来都比较容易。
import net.sf.cglib.proxy.Mixin;
public class MixinDemo {
public static void main(String[] args) {
//接口数组
Class<?>[] interfaces = new Class[] { MyInterfaceA.class, MyInterfaceB.class };
//实例对象数组
Object[] delegates = new Object[] { new MyInterfaceAImpl(), new MyInterfaceBImpl() };
//Minix组合为o对象。
Object o = Mixin.create(interfaces, delegates);
MyInterfaceA a = (MyInterfaceA) o;
a.methodA();
MyInterfaceB b = (MyInterfaceB) o;
b.methodB();
System.out.println("\\r\\n 输出Mixin对象的结构...");
Class clazz = o.getClass();
Method[] methods = clazz.getDeclaredMethods();
for (int i = 0; i < methods.length; i++) {
System.out.println(methods[i].getName());
}
System.out.println(clazz);
}
}
interface MyInterfaceA {
public void methodA();
}
interface MyInterfaceB {
public void methodB();
}
class MyInterfaceAImpl implements MyInterfaceA {
@Override
public void methodA() {
System.out.println("MyInterfaceAImpl.methodA()");
}
}
class MyInterfaceBImpl implements MyInterfaceB {
@Override
public void methodB() {
System.out.println("MyInterfaceBImpl.methodB()");
}
}
输出结果:
MyInterfaceAImpl.methodA()
MyInterfaceBImpl.methodB()
输出Mixin对象的结构...
methodA
methodB
newInstance
class sjq.cglib.mixin.MyInterfaceA
M ixin ByC GLI B MixinByCGLIB
d1f6261a
4 CGLIB用来对象之间拷贝属性
package sjq.cglib.bean.copy;
import net.sf.cglib.beans.BeanCopier;
public class PropertyCopyDemo {
public static void main(String[] args) {
//两个对象
Other other = new Other("test", "1234");
Myth myth = new Myth();
System.out.println(other);
System.out.println(myth);
//构建BeanCopier,并copy对象的属性值。
BeanCopier copier = BeanCopier.create(Other.class, Myth.class, false);
copier.copy(other, myth, null);
System.out.println(other);
System.out.println(myth);
}
}
class Other {
private String username;
private String password;
private int age;
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public Other(String username, String password) {
super();
this.username = username;
this.password = password;
}
@Override
public String toString() {
return "Other: " + username + ", " + password + ", " + age;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
class Myth {
private String username;
private String password;
private String remark;
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
@Override
public String toString() {
return "Myth: " + username + ", " + password + ", " + remark;
}
public void setRemark(String remark) {
this.remark = remark;
}
public String getRemark() {
return remark;
}
}
运行结果如下:
Other: test, 1234, 0
Myth: null, null, null
Other: test, 1234, 0
Myth: test, 1234, null
5 使用CGLIB动态生成Bean
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import net.sf.cglib.beans.BeanGenerator;
import net.sf.cglib.beans.BeanMap;
/**
* 动态实体bean
*/
public class CglibBean {
/**
* 实体Object
*/
public Object object = null;
/**
* 属性map
*/
public BeanMap beanMap = null;
public CglibBean() {
super();
}
@SuppressWarnings("unchecked")
public CglibBean(Map<String, Class> propertyMap) {
this.object = generateBean(propertyMap);
this.beanMap = BeanMap.create(this.object);
}
/**
* 给bean属性赋值
* @param property属性名
* @param value值
*/
public void setValue(String property, Object value) {
beanMap.put(property, value);
}
/**
* 通过属性名得到属性值
* @param property属性名
*/
public Object getValue(String property) {
return beanMap.get(property);
}
/**
* 得到该实体bean对象。
*/
public Object getObject() {
return this.object;
}
/**
* 生成Bean
* @param propertyMap
* @return
*/
@SuppressWarnings("unchecked")
private Object generateBean(Map<String, Class> propertyMap) {
BeanGenerator generator = new BeanGenerator();
Set keySet = propertyMap.keySet();
for (Iterator i = keySet.iterator(); i.hasNext();) {
String key = (String) i.next();
generator.addProperty(key, (Class) propertyMap.get(key));
}
return generator.create();
}
}
测试并使用动态Bean
import java.lang.reflect.Method;
import java.util.HashMap;
/**
* Cglib测试类
*/
public class CglibTest {
@SuppressWarnings("unchecked")
public static void main(String[] args) throws ClassNotFoundException {
// 设置类成员属性
HashMap<String, Class> propertyMap = new HashMap<String, Class>();
propertyMap.put("id", Class.forName("java.lang.Integer"));
propertyMap.put("name", Class.forName("java.lang.String"));
propertyMap.put("address", Class.forName("java.lang.String"));
// 生成动态Bean
CglibBean bean = new CglibBean(propertyMap);
// 给Bean设置值
bean.setValue("id", new Integer(123));
bean.setValue("name", "454");
bean.setValue("address", "789");
// 从Bean中获取值,当然了获得值的类型是Object
System.out.println(">>id=" + bean.getValue("id"));
System.out.println(">>name=" + bean.getValue("name"));
System.out.println(">>address=" + bean.getValue("address"));// 获得bean的实体
Object object = bean.getObject();
// 通过反射查看所有方法名
Class clazz = object.getClass();
Method[] methods = clazz.getDeclaredMethods();
for (int i = 0; i < methods.length; i++) {
System.out.println(methods[i].getName());
}
}
}
输出:
\>>id=123
\>>name=454
\>>address=789
setId
getAddress
getName
getId
setName
setAddress
class net.sf.cglib.empty.Object
B eanG ener ator ByC GLI B BeanGeneratorByCGLIB
1d39cfaa
18.3 - 动态代理
- 定义一个接口
public interface IRegisterService {
void register(String name, String pwd);
}
- 一个接口的实现类
public class RegisterServiceImpl implements IRegisterService {
@Override
public void register(String name, String pwd) {
System.out.println(String.format("【向数据库中插入数据】name:%s,pwd:%s", name, pwd));
}
}
- 一个实现了InvocationHandler接口的类
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
class InsertDataHandler implements InvocationHandler {
// 持有一个被代理类的引用, 但是这个被代理类不是具体的类。而是超类
Object obj;
public Object getProxy(Object obj){
this.obj = obj;
return Proxy.newProxyInstance(obj.getClass().getClassLoader(), obj.getClass().getInterfaces(), this);
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
doBefore();
// 在这里调用被代理的方法
Object result = method.invoke(obj, args);
doAfter();
return result;
}
private void doBefore() {
System.out.println("[Proxy]一些前置处理");
}
private void doAfter() {
System.out.println("[Proxy]一些后置处理");
}
}
- 测试
public class DynamicProxy {
public static void main(String[] args) {
// 实例化被代理类
IRegisterService iRegisterService = new RegisterServiceImpl();
// 实例化代理类
InsertDataHandler insertDataHandler = new InsertDataHandler();
// 通过Proxy获取代理类
IRegisterService proxy = (IRegisterService)insertDataHandler.getProxy(iRegisterService);
// 执行方法
proxy.register("RyanLee", "123");
}
}
18.4 - 静态代理
- 定义一个接口 IRegisterService
public interface IRegisterService {
void register(String name, String pwd);
}
- 创建被代理的类,并实现 接口
public class RegisterServiceImpl implements IRegisterService {
@Override
public void register(String name, String pwd) {
System.out.println(String.format("【向数据库中插入数据】name:%s,pwd:%s", name, pwd));
}
}
- 创建代理类,并实现接口
class RegisterServiceProxy implements IRegisterService {
// 代理类持有被代理类对象的引用
IRegisterService iRegisterService;
public RegisterServiceProxy(IRegisterService iRegisterService) {
this.iRegisterService = iRegisterService;
}
@Override
public void register(String name, String pwd) {
System.out.println("[Proxy]一些前置处理");
//这里调用被代理类的方法,也是静态指定的
iRegisterService.register(name, pwd);
System.out.println("[Proxy]一些后置处理");
}
}
- 测试
public class StaticProxy {
public static void main(String[] args) {
IRegisterService iRegisterService = new RegisterServiceImpl();
IRegisterService proxy = new RegisterServiceProxy(iRegisterService);
proxy.register("RyanLee", "123");
}
}
19 - 定义一个由builder来构造的实体类
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* description: Java Builder模式练习
* @version v1.0
* @author w
* @date 2021年7月7日上午11:37:49
**/
@AllArgsConstructor
@Data
@NoArgsConstructor
public class User {
private String id ;
private String name ;
private Integer age ;
public static Builder builder(){
return new Builder();
}
public static class Builder{
private String id ;
private String name ;
private Integer age ;
public Builder id(String id) {
this.id = id ;
return this;
}
public Builder name(String name) {
this.name = name ;
return this ;
}
public Builder age(Integer age) {
this.age = age ;
return this;
}
public User build() {
return new User(this);
}
public User build2() {
return new User(this.id , this.name , this.age);
}
}
public User(Builder builder) {
this.id = builder.id;
this.name = builder.name ;
this.age = builder.age;
}
}
public class TestBuilder {
public static void main(String[] args) {
User user = User.builder().id("1").name("xiaoming").age(18).build();
System.out.println(user);
}
}
20 - 多线程
20.1 - 方法一: 继承Thread
import java.io.IOException;
/**
* 方法一: 继承Thread
*/
public class M01ExtentThread {
public static void main(String[] args) throws IOException {
M01ExtentThread test = new M01ExtentThread();
MyThread thread1 = test.new MyThread();
MyThread thread2 = test.new MyThread();
thread1.setName("线程1");
thread2.setName("线程2");
thread1.start();
thread2.start();
System.out.println("主线程输出");
}
class MyThread extends Thread{
@Override
public void run() {
for(int i = 0; i < 3; i++) {
System.out.println(Thread.currentThread().getName()+"线程输出"+i);
}
}
}
}
20.2 - 方法二实现Runnable接口
/**
* 方法2实现Runnable接口
*/
public class M02ImplentRunnable {
public static void main(String[] args) {
Runnable runnable = new Runnable() {
@Override
public void run() {
for(int i = 0; i < 3; i++) {
System.out.println("runnablex线程输出"+i);
}
}
};
new Thread(runnable).start();
System.out.println("主线程输出");
}
}
20.3 - 方法三实现Callable接口
package cn.anzhongwei.lean.demo.thread;
import java.util.Random;
import java.util.concurrent.*;
/**
* 和Runnable接口不一样,Callable接口提供了一个call()方法作为线程执行体,call()方法比run()方法功能要强大。
*
* call()方法可以有返回值
*
* call()方法可以声明抛出异常
*
* Java5提供了Future接口来代表Callable接口里call()方法的返回值,并且为Future接口提供了一个实现类FutureTask,这个实现类既实现了Future接口,还实现了Runnable接口。
* 因此可以作为Thread类的target。在Future接口里定义了几个公共方法来控制它关联的Callable任务。
* public interface Future<V> {
* //视图取消该Future里面关联的Callable任务
* boolean cancel(boolean mayInterruptIfRunning);
* //如果在Callable任务正常完成前被取消,返回True
* boolean isCancelled();
* //若Callable任务完成,返回True
* boolean isDone();
* //返回Callable里call()方法的返回值,调用这个方法会导致程序阻塞,必须等到子线程结束后才会得到返回值
* V get() throws InterruptedException, ExecutionException;
* //返回Callable里call()方法的返回值,最多阻塞timeout时间,经过指定时间没有返回抛出TimeoutException
* V get(long timeout, TimeUnit unit)
* throws InterruptedException, ExecutionException, TimeoutException;
* }
*
* 介绍了相关的概念之后,创建并启动有返回值的线程的步骤如下:
*
* 1】创建Callable接口的实现类,并实现call()方法,然后创建该实现类的实例(从java8开始可以直接使用Lambda表达式创建Callable对象)。
*
* 2】使用FutureTask类来包装Callable对象,该FutureTask对象封装了Callable对象的call()方法的返回值
*
* 3】使用FutureTask对象作为Thread对象的target创建并启动线程(因为FutureTask实现了Runnable接口)
*
* 4】调用FutureTask对象的get()方法来获得子线程执行结束后的返回值
*/
public class M03ImplementCallable01 {
public static void main(String[] args) {
//1. 此处使用的是匿名类方式
// Callable<String> callable = new Callable<String>() {
// public String call() throws Exception {
// for(int i = 0; i < 3; i++) {
// System.out.println("callable线程输出"+i);
// }
// return "返回随机数"+(new Random().nextInt(100));
// }
// };
//2. 使用静态内部类
Callable<String> callable = new InnerStaticCallable();
//3. 使用外部实现类。略
FutureTask<String> future = new FutureTask<String>(callable);
Thread thread = new Thread(future);
thread.start();
try {
String resI = future.get();
System.out.println(resI);
} catch (InterruptedException ie) {
ie.printStackTrace();
} catch (ExecutionException ee) {
ee.printStackTrace();
}
//此种方法可以用, 但是不明确应用场景, 返回资源的值是在new FutureTask中传入的,
//runnable接口实现直接使用Thread的start方法运行就行,传到FutureTask中有啥用
Runnable runnable = new Runnable() {
@Override
public void run() {
for(int i = 0; i < 3; i++) {
System.out.println("Runnable线程输出"+i);
}
}
};
FutureTask<Integer> target = new FutureTask<>(runnable, 12);
Thread thread1 = new Thread(target);
thread1.start();
try {
Integer resI1 = target.get();
System.out.println(resI1);
} catch (InterruptedException ie) {
ie.printStackTrace();
} catch (ExecutionException ee) {
ee.printStackTrace();
}
System.out.println("主线程输出");
}
//也可以使用内部类方式
static class InnerStaticCallable implements Callable<String> {
@Override
public String call() {
for(int i = 0; i < 3; i++) {
System.out.println("callable线程输出"+i);
}
return "返回随机数"+(new Random().nextInt(100));
}
}
}
20.4 - 方法四Executors框架
import java.util.concurrent.*;
/**
* 1.5后引入的Executor框架的最大优点是把任务的提交和执行解耦。
* 要执行任务的人只需把Task描述清楚,然后提交即可。
* 这个Task是怎么被执行的,被谁执行的,什么时候执行的,提交的人就不用关心了。
* 具体点讲,提交一个Callable对象给ExecutorService(如最常用的线程池ThreadPoolExecutor),将得到一个Future对象,调用Future对象的get方法等待执行结果就好了。
* Executor框架的内部使用了线程池机制,它在java.util.cocurrent 包下,通过该框架来控制线程的启动、执行和关闭,可以简化并发编程的操作。
* 因此,在Java 5之后,通过Executor来启动线程比使用Thread的start方法更好,除了更易管理,效率更好(用线程池实现,节约开销)外,
* 还有关键的一点:有助于避免this逃逸问题——如果我们在构造器中启动一个线程,因为另一个任务可能会在构造器结束之前开始执行,此时可能会访问到初始化了一半的对象用Executor在构造器中。
*
* 关于ExecutorService执行submit和execute的区别
* 1、接收的参数不一样
* 2、submit有返回值,而execute没有
* 3、submit方便Exception处理
* 如果你在你的task里会抛出checked或者unchecked exception,而你又希望外面的调用者能够感知这些exception并做出及时的处理,那么就需要用到submit,通过捕获Future.get抛出的异常。
*/
public class M04ExccutorCallable {
public static void main(String[] args) throws ExecutionException, InterruptedException {
ExecutorService executorService1 = Executors.newSingleThreadExecutor();
ExecutorService executorService2 = Executors.newSingleThreadExecutor();
Runnable runnable = new Runnable(){
@Override
public void run() {
for(int i = 0; i < 3; i++) {
System.out.println("Runnable接口"+i);
}
}
};
//执行Runnable接口,无返回值
executorService1.execute(runnable);
executorService1.shutdown();
//执行Callable接口,有返回值
Callable<String> stringCallable = new Callable(){
@Override
public String call() throws Exception {
for(int i = 0; i < 3; i++) {
System.out.println("Callable接口"+i);
}
return "Hello World";
}
};
Future<String> submit2 = executorService2.submit(stringCallable);
executorService2.shutdown();
//get操作会造成执行线程的阻塞
String submit2Res = submit2.get();
System.out.println(submit2Res);
System.out.println("main Thread output");
}
}
20.5 - 线程池
import java.io.IOException;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;
public class TestThreadPoolExecutor {
/*
corePoolSize:指定了线程池中的线程数量,它的数量决定了添加的任务是开辟新的线程去执行,还是放到workQueue任务队列中去;
当提交一个任务到线程池时,线程池会创建一个线程来执行任务,即使其他空闲的基本线程能够执行新任务也会创建线程,等到需要执行的任务数大于线程池基本大小时就不再创建。
如果调用了线程池的prestartAllCoreThreads方法,线程池会提前创建并启动所有基本线程。
maximumPoolSize:指定了线程池中的最大线程数量,这个参数会根据你使用的workQueue任务队列的类型,决定线程池会开辟的最大线程数量;
如果队列满了,并且已创建的线程数小于最大线程数,则线程池会再创建新的线程执行任务。
值得注意的是,如果使用了无界的任务队列这个参数就没用了
执行:
1、2任务直接压入1、2线程直接创建并执行,
3任务放入缓冲队列,最大线程数未满,创建新线程执行3任务
*/
public static void main(String[] args) throws InterruptedException, IOException {
int corePoolSize = 2;
int maximumPoolSize = 10;
long keepAliveTime = 10;
TimeUnit unit = TimeUnit.SECONDS;
BlockingQueue<Runnable> workQueue = new ArrayBlockingQueue<>(2);
ThreadFactory threadFactory = new NameTreadFactory();
RejectedExecutionHandler handler = new MyIgnorePolicy();
ThreadPoolExecutor executor = new ThreadPoolExecutor(corePoolSize, maximumPoolSize, keepAliveTime, unit,
workQueue, threadFactory);
executor.prestartAllCoreThreads(); // 预启动所有核心线程
for (int i = 1; i <= 10; i++) {
// Thread.sleep(1);
MyTask task = new MyTask(String.valueOf(i));
executor.execute(task);
System.out.println("当前队列数两"+executor.getQueue().size());
}
System.out.println(executor.getActiveCount());
Thread.sleep(10000);
System.out.println(executor.getActiveCount());
System.in.read(); //阻塞主线程
}
static class NameTreadFactory implements ThreadFactory {
private final AtomicInteger mThreadNum = new AtomicInteger(1);
@Override
public Thread newThread(Runnable r) {
// try {
// Thread.sleep(200);
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
Thread t = new Thread(r, "my-thread-" + mThreadNum.getAndIncrement());
System.out.println(t.getName() + " has been created");
return t;
}
}
public static class MyIgnorePolicy implements RejectedExecutionHandler {
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
doLog(r, e);
}
private void doLog(Runnable r, ThreadPoolExecutor e) {
// 可做日志记录等
System.err.println( r.toString() + " rejected");
// System.out.println("completedTaskCount: " + e.getCompletedTaskCount());
}
}
static class MyTask implements Runnable {
private String name;
public MyTask(String name) {
this.name = name;
}
@Override
public void run() {
try {
System.out.println(this.toString() + " is running!");
Thread.sleep(1000); //让任务执行慢点
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public String getName() {
return name;
}
@Override
public String toString() {
return "MyTask [name=" + name + "]";
}
}
}
20.6 - 线程交替执行的实践
- 只能保证交替执行, 至于先输出1 还是先输出A 谁先抢到o锁谁就输出
public class Syncwaitnotify1 {
public static void main(String[] args) {
final Object o = new Object();
char[] aI = "123456789".toCharArray();
char[] aC = "ABCDEFGHI".toCharArray();
new Thread(()->{
//1. 先拿到o锁
synchronized (o) {
for(char c : aI) {
//2. 执行
System.out.print(c);
try {
o.notify();//唤醒线程队列中得下一个线程, 这里只有两个,就相当于唤醒另一个
o.wait();//让出锁,重新进入等待队列
}catch (InterruptedException e) {
e.printStackTrace();
}
}
o.notify();
}
}, "t1").start();
new Thread(()->{
//1. 和上面得Thread强锁
synchronized (o) {
for(char i : aC) {
System.out.print(i);
try {
o.notify();
o.wait();
}catch (InterruptedException e) {
e.printStackTrace();
}
}
o.notify();
}
}, "t2").start();
}
}
- 先等待一直等到CountDownLatch计数器为0, 所以这个程序一定先输出A
import java.util.concurrent.CountDownLatch;
public class Syncwaitnotify2 {
/*
CountDownLatch latch = new CountDownLatch(n);
latch.countDown();每执行一次,n-1
*/
private static CountDownLatch latch = new CountDownLatch(1);
public static void main(String[] args) {
final Object o = new Object();
char[] aI = "123456789".toCharArray();
char[] aC = "ABCDEFGHI".toCharArray();
new Thread(()->{
try{
latch.await();
}catch (InterruptedException e) {
e.printStackTrace();
}
//1. 先拿到o锁
synchronized (o) {
for(char c : aI) {
//2. 执行
System.out.print(c);
try {
o.notify();//唤醒线程队列中得下一个线程, 这里只有两个,就相当于唤醒另一个
o.wait();//让出锁,重新进入等待队列
}catch (InterruptedException e) {
e.printStackTrace();
}
}
o.notify();
}
}, "t1").start();
new Thread(()->{
//1. 和上面得Thread强锁
synchronized (o) {
for(char i : aC) {
System.out.print(i);
latch.countDown();
try {
o.notify();
o.wait();
}catch (InterruptedException e) {
e.printStackTrace();
}
}
o.notify();
}
}, "t2").start();
}
}
- 通过volatile 实现 volatile是原子操作并且线程可见,也可以保证顺序执行
/**
* 如果t1先拿到锁,此时t25 = false 在while中不断得会让出锁
* 等t2拿到锁后, 设置t25=true就可以正常交替执行了
*/
public class Syncwaitnotify3 {
private static volatile boolean t25 = false;
public static void main(String[] args) {
final Object o = new Object();
char[] aI = "123456789".toCharArray();
char[] aC = "ABCDEFGHI".toCharArray();
new Thread(()->{
//1. 如果t1先拿到锁
synchronized (o) {
//2. 此时t25=false
while (!t25) {
try {
//3. 让出线程, 进入等待队列
o.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
for(char c : aI) {
System.out.print(c);
try {
o.notify();//唤醒线程队列中得下一个线程, 这里只有两个,就相当于唤醒另一个
o.wait();//让出锁,重新进入等待队列
}catch (InterruptedException e) {
e.printStackTrace();
}
}
o.notify();
}
}, "t1").start();
new Thread(()->{
//4. 此时t2后拿到锁
synchronized (o) {
for(char i : aC) {
System.out.print(i);
t25 = true;
try {
o.notify();
o.wait();
}catch (InterruptedException e) {
e.printStackTrace();
}
}
o.notify();
}
}, "t2").start();
}
}
- 指定线程锁,相互唤醒,第一次也使用latch.await(); 保证, 和2差不多
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class LockCondition {
public static void main(String[] args) {
char[] aI = "123456789".toCharArray();
char[] aC = "ABCDEFGHI".toCharArray();
Lock lock = new ReentrantLock();
Condition conditionT1 = lock.newCondition();
Condition conditionT2 = lock.newCondition();
CountDownLatch latch = new CountDownLatch(1);
new Thread(()->{
try {
//1. 启动线程直接进入等待
latch.await();
}catch (InterruptedException e) {
e.printStackTrace();
}
lock.lock();
try {
for(char c : aI) {
System.out.print(c);
conditionT2.signal();
conditionT1.await();
}
conditionT2.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}, "t1").start();
new Thread(()->{
lock.lock();
try {
//2. 线程启动直接进入循环进行输出
for(char c : aC) {
System.out.print(c);
//3. 标记完成
latch.countDown();
//4. 唤醒conditionT1队列
conditionT1.signal();
conditionT2.await();
}
conditionT1.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}, "t2").start();
}
}
- LockSupport的实现
import java.util.concurrent.locks.LockSupport;
public class LockSupport1 {
static Thread t1=null, t2=null;
public static void main(String[] args) {
char[] aI = "123456789".toCharArray();
char[] aC = "ABCDEFGHI".toCharArray();
t1 = new Thread(() -> {
for(char c: aI) {
LockSupport.park();
System.out.print(c);
LockSupport.unpark(t2);
}
},"t1");
t2 = new Thread(() -> {
for(char c: aC) {
System.out.print(c);
LockSupport.unpark(t1);
LockSupport.park();
}
},"t2");
t1.start();
t2.start();
}
}
- TransferQueue的实现
import java.util.concurrent.LinkedTransferQueue;
public class TransferQueue1 {
public static void main(String[] args) {
char[] aI = "123456789".toCharArray();
char[] aC = "ABCDEFGHI".toCharArray();
java.util.concurrent.TransferQueue<Character> queue = new LinkedTransferQueue<>();
new Thread(() ->{
try{
for(char c: aI) {
System.out.print(queue.take());
queue.transfer(c);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}, "t1").start();
new Thread(() ->{
try{
for(char c: aC) {
queue.transfer(c);
System.out.print(queue.take());
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}, "t2").start();
}
}
20.7 - Callable捕获异常
import java.util.concurrent.*;
/**
* Callable捕获异常
*/
public class CallableGetThrow {
public static void main(String[] args){
int timeout = 2;
ExecutorService executor = Executors.newSingleThreadExecutor();
Boolean result = false;
Future<Boolean> future = executor.submit(new TaskThread("发送请求"));//将任务提交给线程池
try {
System.out.println("try");
result = future.get(timeout, TimeUnit.SECONDS);//获取结果
future.cancel(true);
System.out.println("发送请求任务的返回结果:"+result); //2
} catch (InterruptedException e) {
System.out.println("线程中断出错。"+e);
future.cancel(true);// 中断执行此任务的线程
} catch (ExecutionException e) {
System.out.println("线程服务出错。");
future.cancel(true);
} catch (TimeoutException e) {// 超时异常
System.out.println("超时。");
future.cancel(true);
}finally{
System.out.println("线程服务关闭。");
executor.shutdown();
}
}
static class TaskThread implements Callable<Boolean> {
private String t;
public TaskThread(String temp){
this.t= temp;
}
/*
try
继续执行..........
发送请求任务的返回结果: true
线程服务关闭。
*/
// public Boolean call() throws InterruptedException {
// Thread.currentThread().sleep(1000);
// System.out.println("继续执行..........");
// return true;
// }
/*
try
超时。
线程服务关闭。
*/
// public Boolean call() throws InterruptedException {
// Thread.currentThread().sleep(3000);
// System.out.println("继续执行..........");
// return true;
// }
/*
* try
* start
* 线程服务出错。
* 线程服务关闭。
*/
public Boolean call() throws InterruptedException {
System.out.println("start");
throw new InterruptedException();
}
}
}
21 - 根据对象层次路径动态获取和设置值
先看一段json
{
"wmPlans":
[
{
"skuInfos":
[
{
"customerItemNO":"no-03",
"goCargoId":"go03"
},
{
"customerItemNO":"no-04",
"goCargoId":"go04",
"extensionParameter":
{
"invNo":"abcd01",
"bu":"BM"
}
}
],
"warehouseType":"wmsHouse1",
"warehousedocumentType":"wmsDocument1"
},
{
"skuInfos":
[
{
"customerItemNO":"no-03",
"goCargoId":"go03"
},
{
"customerItemNO":"no-04",
"goCargoId":"go04"
}
],
"warehouseType":"wmsHouse2",
"warehousedocumentType":"wmsDocument2"
}
],
"goOrder":
{
"extionParam":
{
"bbb":"ccc"
},
"skuInfos":
[
{
"customerItemNO":"no-01",
"goCargoId":"go01"
},
{
"customerItemNO":"no-02",
"goCargoId":"go02"
}
],
"i3":20,
"mark3":"mark3",
"consignorCode":"ConsignorCode01"
}
}
我希望能根据一个路径
例如
goOrder.skuInfos.customerItemNO 获取到两个值 no-01 no-02
goOrder.skuInfos[0].customerItemNO 获取到 no-01
wmPlans.skuInfos.customerItemNO 获取到 no-03 no-04 no-03 no-04
wmPlans[0].skuInfos.customerItemNO 获取到 no-03 no-04
wmPlans.skuInfos[0].customerItemNO 获取到 no-03 no-03
不仅仅是获取, 我还希望可以对这个深层次的对象进行赋值 所以有了这个小例子
先定义数据结构
- OrderInfoDTO
import lombok.Data;
import lombok.ToString;
import java.util.List;
@Data
@ToString
public class OrderInfoDTO {
private GoOrderDTO goOrder;
private List<WmsPlanDTO> wmPlans;
}
- GoOrderDTO
import lombok.Data;
import lombok.ToString;
import java.util.List;
import java.util.Map;
@Data
@ToString
public class GoOrderDTO {
private String consignorCode;
private String mark3;
private Integer i3;
private List<SkuInfoDTO> skuInfos;
private Map<String, String> extionParam;
}
- WmsPlanDTO
import lombok.Data;
import lombok.ToString;
import java.util.List;
@Data
@ToString
public class WmsPlanDTO {
private String warehouseType;
private String warehousedocumentType;
private List<SkuInfoDTO> skuInfos;
}
- SkuInfoDTO
import lombok.Data;
import lombok.ToString;
import java.util.Map;
@Data
@ToString
public class SkuInfoDTO {
private String customerItemNO;
private String goCargoId;
private Map<String, String> extensionParameter;
}
定义反射方法这是我们的核心
- ReflectGetPathValRes
import lombok.Data;
@Data
public class ReflectGetPathValRes {
private Boolean success;
private String val;
public ReflectGetPathValRes(Boolean success, String val) {
this.success = success;
this.val = val;
}
}
- ReflectUtil 这个类方法里面很多重复代码,可以考虑封装成函数
import cn.hutool.json.JSONUtil;
import org.apache.commons.lang.StringUtils;
import java.lang.reflect.Field;
import java.util.*;
public class ReflectUtil {
/**
* 通过反射,对rootObj 对象 对应conditionPath路径 赋值为val
* @param rootObj
* @param conditionPath
* @param val
* @return
*/
public static boolean reflectAssignEvaluate(Object rootObj, String conditionPath, Object val) {
if (conditionPath.isEmpty()) {
System.out.println("条件比较路径发生空值错误错误");
return false;
}
String[] paths = StringUtils.split(conditionPath, '.');
int pathsLen = paths.length;
Object currObj = rootObj;
Object[] objArr = new Object[pathsLen+1];
objArr[0] = rootObj;
for(int i=0; i <= pathsLen-1; i++) {
try{
Class currObjClass = currObj.getClass();
String currGetDeclaredPath = paths[i];
// 虽然命名是mapKey 但是他也会获取到list的索引值
String mapKey = "";
if (StringUtils.contains(currGetDeclaredPath, '[')) {
currGetDeclaredPath = paths[i].substring(0, paths[i].indexOf('['));
mapKey = paths[i].substring(paths[i].indexOf('[')+1, paths[i].indexOf(']'));
}
Field currObjField = currObjClass.getDeclaredField(currGetDeclaredPath);
currObjField.setAccessible(true);
currObj = currObjField.get(currObj);
if(currObj != null) {
objArr[i+1] = currObj;
}
//如果获取到当前对象为空,并且路径不是最后一级,则为错误
if (currObj == null && i < pathsLen-1) {
System.out.println(currObjField.getType().getTypeName()+"."+currObjField.getName()+"为空,下一级属性无法获取及赋值");
return false;
}
//赋值操作
if(i == pathsLen-1) {
return objAssignEvaluate(currObjField, objArr[i], val, mapKey);
}
if (currObjField.getType().getTypeName().contains("List")) {
List srcList=(List)currObj;
String newPaths = StringUtils.join(arraySub(paths, i+1, pathsLen),'.');
if (StringUtils.isNotBlank(mapKey) && StringUtils.isNumeric(mapKey)) {
int mapKeyIndex = Integer.parseInt(mapKey);
srcList.get(mapKeyIndex);
return reflectAssignEvaluate(srcList.get(mapKeyIndex), newPaths, val);
} else {
boolean assignEvaluateRes = true;
for(Object src : srcList) {
if (!reflectAssignEvaluate(src, newPaths, val)) {
assignEvaluateRes = false;
}
}
return assignEvaluateRes;
}
}
}catch (NoSuchFieldException | IllegalAccessException e) {
System.out.println(e.getMessage());
return false;
}
}
return true;
}
/**
* 利用反射,校验条件路径下对象中的值是否与期望值相同
* 期望值都是String类型,因为是从数据库中获取到的,所以无法判定具体类型
* @param rootObj
* @param conditionPath
* @param expVal
* @return
*/
public static boolean reflectCheckCondition(Object rootObj, String conditionPath, String expVal) {
ReflectGetPathValRes getValRes = reflectGetPathVal(rootObj, conditionPath);
if (getValRes.getSuccess()) {
if (Objects.isNull(getValRes.getVal())) {
getValRes.setVal("");
}
return StringUtils.equals(getValRes.getVal(), expVal);
}
return false;
}
/**
* 通过反射,对rootObj 对象 对应conditionPath路径 代表得值
* 本方法支持map对应得key值
* list获取对应索引后得内容
* @param rootObj
* @param conditionPath
* @return
*/
public static ReflectGetPathValRes reflectGetPathVal(Object rootObj, String conditionPath) {
if (conditionPath.isEmpty()) {
System.out.println("条件比较路径发生空值错误错误");
return new ReflectGetPathValRes(false,"");
}
String[] paths = StringUtils.split(conditionPath, '.');
int pathsLen = paths.length;
Object currObj = rootObj;
Object[] objArr = new Object[pathsLen+1];
objArr[0] = rootObj;
for(int i=0; i <= pathsLen-1; i++) {
try{
Class currObjClass = currObj.getClass();
String currGetDeclaredPath = paths[i];
String mapKey = "";
if (StringUtils.contains(currGetDeclaredPath, '[')) {
currGetDeclaredPath = paths[i].substring(0, paths[i].indexOf('['));
mapKey = paths[i].substring(paths[i].indexOf('[')+1, paths[i].indexOf(']'));
}
Field currObjField = currObjClass.getDeclaredField(currGetDeclaredPath);
currObjField.setAccessible(true);
currObj = currObjField.get(currObj);
if(currObj != null) {
objArr[i+1] = currObj;
}
//如果获取到当前对象为空,并且路径不是最后一级,则为错误
if (currObj == null && i < pathsLen-1) {
System.out.println(currObjField.getType().getTypeName()+"."+currObjField.getName()+"为空,下一级属性无法获取及赋值");
return new ReflectGetPathValRes(false,"");
}
//赋值操作
if(i == pathsLen-1) {
return new ReflectGetPathValRes(true,fromObjGetFieldValue(currObjField, objArr[i], mapKey));
}
if (currObjField.getType().getTypeName().contains("List")) {
List srcList=(List)currObj;
String newPaths = StringUtils.join(arraySub(paths, i+1, pathsLen),'.');
if (StringUtils.isNotBlank(mapKey)) {
return reflectGetPathVal(srcList.get(Integer.parseInt(mapKey)),newPaths);
}
List<Object> resList = new ArrayList<>();
for(Object src : srcList) {
// return reflectGetPathVal(src,newPaths);
ReflectGetPathValRes getPathValRes = reflectGetPathVal(src,newPaths);
// 检查 getPathValRes.getVal() 是否是一个jsonarray
if (getPathValRes.getVal().startsWith("[")) {
resList.addAll(JSONUtil.toList(JSONUtil.parseArray(getPathValRes.getVal()), Object.class));
} else {
resList.add(getPathValRes.getVal());
}
}
return new ReflectGetPathValRes(true, JSONUtil.toJsonStr(resList));
}
}catch (NoSuchFieldException | IllegalAccessException e) {
e.printStackTrace();
return new ReflectGetPathValRes(false,"");
}
}
return new ReflectGetPathValRes(false,"");
}
public static boolean objAssignEvaluate(Field field, Object rootObj, Object val, String mapKey) throws IllegalAccessException {
if (field.getType().getName().contains("Integer")) {
field.set(rootObj, Integer.parseInt(String.valueOf(val)));return true;
}else if (field.getType().getName().contains("String")) {
field.set(rootObj, String.valueOf(val));
} else if (field.getType().getName().contains("Map")) {
Map<String, String> currMap = (Map<String, String>) field.get(rootObj);
if (currMap == null) {
currMap = new HashMap<>();
}
currMap.put(mapKey, String.valueOf(val));
field.set(rootObj, currMap);
} else if (field.getType().getName().contains("List")) {
List srcList=(List)field.get(rootObj);
if (srcList == null) {
srcList = new ArrayList();
}
if (StringUtils.isNotBlank(mapKey) && StringUtils.isNumeric(mapKey)) {
int index = Integer.valueOf(mapKey);
srcList.set(index, val);
// field.set(rootObj, srcList);
} else {
field.set(rootObj, val);
}
}
//其他类型的强制转换
return true;
}
/**
* 从rootObj中获取字段field得值
* @param field
* @param rootObj
* @param mapKey
* @return
* @throws IllegalAccessException
*/
public static String fromObjGetFieldValue(Field field, Object rootObj, String mapKey) throws IllegalAccessException {
if (field.getType().getName().contains("Integer")) {
Integer realVal = (Integer)field.get(rootObj);
return String.valueOf(realVal);
} else if (field.getType().getName().contains("String")) {
return (String)field.get(rootObj);
} else if (field.getType().getName().contains("Map")) {
Map<String, String> currMap = (Map<String, String>) field.get(rootObj);
return currMap.get(mapKey);
} else if (field.getType().getName().contains("List")) {
List srcList=(List)field.get(rootObj);
// mapkey不是空并且他还是一个数字
if (StringUtils.isNotBlank(mapKey) && StringUtils.isNumeric(mapKey)) {
int index = Integer.valueOf(mapKey);
return JSONUtil.toJsonStr(srcList.get(index));
}
return JSONUtil.toJsonStr(srcList);
}
//其他类型的强制转换
return "";
}
public static String[] arraySub(String[] data, int start, int end) {
String[] c = new String[end-start];
int j = 0;
for(int i=start; i<end; i++){
c[j] = data[i];
j++;
}
return c;
}
//对对象中所有字符串类型的空对象赋值空字符串
public static void javaBeanNullStrToEmptyStr(Object obj){
Class currObjClass = obj.getClass();
Field[] fields = currObjClass.getDeclaredFields();
for(Field field : fields) {
field.setAccessible(true);
if (field.getType().getName().contains("String")) {
try {
String currValue = (String) field.get(obj);
if (StringUtils.isBlank(currValue)) {
objAssignEvaluate(field, obj, "", "");
}
} catch (IllegalAccessException e) {
e.printStackTrace();
System.out.println(e.toString());
}
}
}
}
}
接下来我们进行测试
TestMain.java
import cn.hutool.json.JSONUtil;
import java.util.*;
public class TestMain {
public static void main(String[] args) {
OrderInfoDTO orderInfoDTO = initData();
System.out.println(JSONUtil.formatJsonStr(JSONUtil.toJsonStr(orderInfoDTO)));
/********************************************************************************/
// 第一次测试 一个正常的属性路径
// String path1 = "goOrder.consignorCode";
// String expValTrue="ConsignorCode01";
// // 检查一个正确的值
// boolean res11 = ReflectUtil.reflectCheckCondition(orderInfoDTO, path1, expValTrue);//true
// System.out.println(res11); // true
//
// // 重新赋值
// ReflectUtil.reflectAssignEvaluate(orderInfoDTO, path1, "ConsignorCode03");
// System.out.println(ReflectUtil.reflectGetPathVal(orderInfoDTO, path1)); // ConsignorCode03
/********************************************************************************/
// 第二次测试 一个arraylist的属性路径
// String path2 = "goOrder.skuInfos";
// String expValTrue="[{\"customerItemNO\":\"no-01\",\"goCargoId\":\"go01\"},{\"customerItemNO\":\"no-02\",\"goCargoId\":\"go02\"}]";
// boolean resTrue = ReflectUtil.reflectCheckCondition(orderInfoDTO, path2, expValTrue);
// System.out.println(resTrue);
// List<SkuInfoDTO> skuList = new ArrayList<>();
// SkuInfoDTO skuInfoDTO1 = new SkuInfoDTO();
// skuInfoDTO1.setGoCargoId("goSku01");
// skuInfoDTO1.setCustomerItemNO("noSKU01");
//
// SkuInfoDTO skuInfoDTO2 = new SkuInfoDTO();
// skuInfoDTO2.setGoCargoId("goSku02");
// skuInfoDTO2.setCustomerItemNO("noSku02");
// skuList.add(skuInfoDTO1);
// skuList.add(skuInfoDTO2);
// ReflectUtil.reflectAssignEvaluate(orderInfoDTO, path2, skuList);
// System.out.println(ReflectUtil.reflectGetPathVal(orderInfoDTO, path2));
/********************************************************************************/
// 第三次测试对数组中的某个元素的属性路径
// String path3 = "goOrder.skuInfos[0].goCargoId";
// String expValTrue="go01";
//
// boolean resTrue = ReflectUtil.reflectCheckCondition(orderInfoDTO, path3, expValTrue);
// System.out.println(resTrue); // true
// ReflectUtil.reflectAssignEvaluate(orderInfoDTO, path3, "goCargo01");
// System.out.println(JSONUtil.toJsonStr(orderInfoDTO)); // goCargo01
/********************************************************************************/
// 第四次测试 数组的指定索引进行赋值和取值
// String path4 = "goOrder.skuInfos[0]";
// System.out.println(ReflectUtil.reflectGetPathVal(orderInfoDTO, path4));
// String expValTrue="{\"customerItemNO\":\"no-01\",\"goCargoId\":\"go01\"}";
// boolean resTrue = ReflectUtil.reflectCheckCondition(orderInfoDTO, path4, expValTrue);
//
// SkuInfoDTO skuInfoDTO1 = new SkuInfoDTO();
// skuInfoDTO1.setGoCargoId("goSku01");
// skuInfoDTO1.setCustomerItemNO("noSKU01");
// ReflectUtil.reflectAssignEvaluate(orderInfoDTO, path4, skuInfoDTO1);
// System.out.println(ReflectUtil.reflectGetPathVal(orderInfoDTO, path4));
/********************************************************************************/
// 第五次测试 此次测试的赋值无意义,几乎不会有吧所有数组元素赋值成完全一样内容的需求
// String path5 = "wmPlans.skuInfos";
// System.out.println(ReflectUtil.reflectGetPathVal(orderInfoDTO, path5));
// 这样取值会将上层数组平铺开然后展示出来 [{"customerItemNO":"no-01","goCargoId":"go01"},{"customerItemNO":"no-02","goCargoId":"go02"},{"customerItemNO":"no-03","goCargoId":"go03"},{"customerItemNO":"no-04","goCargoId":"go04","extensionParameter":{"bbb":"ccc"}}]
/********************************************************************************/
// 第六次测试 第一层是数组,第二层指定索引
// String path6 = "wmPlans.skuInfos[0].customerItemNO";
// System.out.println(ReflectUtil.reflectGetPathVal(orderInfoDTO, path6));
// ReflectUtil.reflectAssignEvaluate(orderInfoDTO, path6, "shishi");
// System.out.println(JSONUtil.toJsonStr(orderInfoDTO));
/********************************************************************************/
// 第七次测试 第一层是数组指定索引,第二层是数组
// String path7 = "wmPlans[0].skuInfos.customerItemNO";
// System.out.println(ReflectUtil.reflectGetPathVal(orderInfoDTO, path7));
// ReflectUtil.reflectAssignEvaluate(orderInfoDTO, path7, "shishi");
// System.out.println(ReflectUtil.reflectGetPathVal(orderInfoDTO, path7));
// System.out.println(JSONUtil.toJsonStr(orderInfoDTO));
/********************************************************************************/
// 第八次测试 第一层是数组,然后跟一个普通的属性
// String path8 = "wmPlans.warehouseType";
// System.out.println(ReflectUtil.reflectGetPathVal(orderInfoDTO, path8));
// ReflectUtil.reflectAssignEvaluate(orderInfoDTO, path8, "warehouseTypeTh8");
// System.out.println(ReflectUtil.reflectGetPathVal(orderInfoDTO, path8));
/********************************************************************************/
// String path9 = "goOrder.skuInfos.goCargoId";
// System.out.println(ReflectUtil.reflectGetPathVal(orderInfoDTO, path9));
// ReflectUtil.reflectAssignEvaluate(orderInfoDTO,path9, "123456");
// System.out.println(JSONUtil.toJsonStr(orderInfoDTO));
/********************************************************************************/
// 第十次测试 给 所有的wmPlans 里面所有的sku 添加扩展属性 aaa = wmsplanPath10keyaaaValue
// String path10 = "wmPlans.skuInfos.extensionParameter[aaa]";
// String val3="wmsplanPath10keyaaaValue";
// ReflectUtil.reflectAssignEvaluate(orderInfoDTO,path10, val3);
// System.out.println(JSONUtil.toJsonStr(orderInfoDTO));
/********************************************************************************/
// 第十一次测试给 wmPlans[0] 的 所有sku 添加扩展属性 aaa = wmsplanPath10keyaaaValue
// String path11 = "wmPlans[0].skuInfos.extensionParameter[aaa]";
// String val11="wmsplanPath11keyaaaValue";
// ReflectUtil.reflectAssignEvaluate(orderInfoDTO,path11, val11);
// System.out.println(JSONUtil.toJsonStr(orderInfoDTO));
/********************************************************************************/
// 第十一次测试 给 所有的wmPlans 的 skuInfos[1] 添加扩展属性 aaa = wmsplanPath12keyaaaValue
// String path12 = "wmPlans.skuInfos[1].extensionParameter[aaa]";
// String val12="wmsplanPath12keyaaaValue";
// ReflectUtil.reflectAssignEvaluate(orderInfoDTO,path12, val12);
// System.out.println(JSONUtil.toJsonStr(orderInfoDTO));
/********************************************************************************/
}
public static OrderInfoDTO initData() {
SkuInfoDTO skuInfoDTO1 = new SkuInfoDTO();
skuInfoDTO1.setGoCargoId("go01");
skuInfoDTO1.setCustomerItemNO("no-01");
SkuInfoDTO skuInfoDTO2 = new SkuInfoDTO();
skuInfoDTO2.setGoCargoId("go02");
skuInfoDTO2.setCustomerItemNO("no-02");
Map<String, String> goOrderEXTMap1 = new HashMap<>();
goOrderEXTMap1.put("bbb", "ccc");
List<SkuInfoDTO> goOrderSKUInfos = new ArrayList<>();
goOrderSKUInfos.add(skuInfoDTO1);
goOrderSKUInfos.add(skuInfoDTO2);
GoOrderDTO goOrderDTO = new GoOrderDTO();
goOrderDTO.setConsignorCode("ConsignorCode01");
goOrderDTO.setMark3("mark3");
goOrderDTO.setI3(20);
goOrderDTO.setSkuInfos(goOrderSKUInfos);
goOrderDTO.setExtionParam(goOrderEXTMap1);
// goOrder赋值完成
SkuInfoDTO wmsSku0101 = new SkuInfoDTO();
wmsSku0101.setGoCargoId("go03");
wmsSku0101.setCustomerItemNO("no-03");
SkuInfoDTO wmsSku0102 = new SkuInfoDTO();
wmsSku0102.setGoCargoId("go04");
wmsSku0102.setCustomerItemNO("no-04");
Map<String, String> wmsSku0102ExtionParam = new HashMap<>();
wmsSku0102ExtionParam.put("invNo", "abcd01");
wmsSku0102ExtionParam.put("bu", "BM");
wmsSku0102.setExtensionParameter(wmsSku0102ExtionParam);
WmsPlanDTO wmsPlanDTO1 = new WmsPlanDTO();
wmsPlanDTO1.setWarehouseType("wmsHouse1");
wmsPlanDTO1.setWarehousedocumentType("wmsDocument1");
wmsPlanDTO1.setSkuInfos(Arrays.asList(wmsSku0101, wmsSku0102));
// wmsPlanDTO1赋值完成
SkuInfoDTO wmsSku0201 = new SkuInfoDTO();
wmsSku0201.setGoCargoId("go03");
wmsSku0201.setCustomerItemNO("no-03");
SkuInfoDTO wmsSku0202 = new SkuInfoDTO();
wmsSku0202.setGoCargoId("go04");
wmsSku0202.setCustomerItemNO("no-04");
Map<String, String> wmsSku0202ExtionParam = new HashMap<>();
wmsSku0202ExtionParam.put("invNo", "abcd01");
wmsSku0202ExtionParam.put("bu", "BM");
wmsSku0102.setExtensionParameter(wmsSku0202ExtionParam);
WmsPlanDTO wmsPlanDTO2 = new WmsPlanDTO();
wmsPlanDTO2.setWarehouseType("wmsHouse2");
wmsPlanDTO2.setWarehousedocumentType("wmsDocument2");
wmsPlanDTO2.setSkuInfos(Arrays.asList(wmsSku0201, wmsSku0202));
// wmsPlanDTO2赋值完成
OrderInfoDTO orderInfoDTO = new OrderInfoDTO();
orderInfoDTO.setGoOrder(goOrderDTO);
orderInfoDTO.setWmPlans(Arrays.asList(wmsPlanDTO1, wmsPlanDTO2));
return orderInfoDTO;
}
}
22 - 基于BigDecimal的高精度数字运算
package cn.anzhongwei.lean.demo.bigdecimal;
import java.math.BigDecimal;
import java.math.RoundingMode;
/**
* 用于高精确处理常用的数学运算
*/
public class ArithmeticUtils {
public static void main(String[] args) {
System.out.println("2.5 to "+toCeiling(new BigDecimal(2.5)));
System.out.println("4 to "+toCeiling(new BigDecimal(4)));
System.out.println("4.023 to "+toCeiling(new BigDecimal(4.023)));
System.out.println("0.5 to "+toCeiling(new BigDecimal(0.5)));
System.out.println("-0.5 to "+toCeiling(new BigDecimal(-0.5)));
System.out.println("-1.5 to "+toCeiling(new BigDecimal(-1.5)));
System.out.println(new BigDecimal("1.000").abs());
System.out.println(new BigDecimal("1").abs());
BigDecimal b1 = new BigDecimal("5");
BigDecimal b2 = new BigDecimal("4.000");
System.out.println(b1.compareTo(b2));
}
//默认除法运算精度
private static final int DEF_DIV_SCALE = 10;
/**
* 提供精确的加法运算
*
* @param v1 被加数
* @param v2 加数
* @return 两个参数的和
*/
public static double add(double v1, double v2) {
BigDecimal b1 = new BigDecimal(Double.toString(v1));
BigDecimal b2 = new BigDecimal(Double.toString(v2));
return b1.add(b2).doubleValue();
}
/**
* 提供精确的加法运算
*
* @param v1 被加数
* @param v2 加数
* @return 两个参数的和
*/
public static BigDecimal add(String v1, String v2) {
BigDecimal b1 = new BigDecimal(v1);
BigDecimal b2 = new BigDecimal(v2);
return b1.add(b2);
}
/**
* 提供精确的加法运算
*
* @param v1 被加数
* @param v2 加数
* @param scale 保留scale 位小数
* @return 两个参数的和
*/
public static String add(String v1, String v2, int scale) {
if (scale < 0) {
throw new IllegalArgumentException(
"The scale must be a positive integer or zero");
}
BigDecimal b1 = new BigDecimal(v1);
BigDecimal b2 = new BigDecimal(v2);
return b1.add(b2).setScale(scale, BigDecimal.ROUND_HALF_UP).toString();
}
/**
* 提供精确的减法运算
*
* @param v1 被减数
* @param v2 减数
* @return 两个参数的差
*/
public static double sub(double v1, double v2) {
BigDecimal b1 = new BigDecimal(Double.toString(v1));
BigDecimal b2 = new BigDecimal(Double.toString(v2));
return b1.subtract(b2).doubleValue();
}
/**
* 提供精确的减法运算。
*
* @param v1 被减数
* @param v2 减数
* @return 两个参数的差
*/
public static BigDecimal sub(String v1, String v2) {
BigDecimal b1 = new BigDecimal(v1);
BigDecimal b2 = new BigDecimal(v2);
return b1.subtract(b2);
}
/**
* 提供精确的减法运算
*
* @param v1 被减数
* @param v2 减数
* @param scale 保留scale 位小数
* @return 两个参数的差
*/
public static String sub(String v1, String v2, int scale) {
if (scale < 0) {
throw new IllegalArgumentException(
"The scale must be a positive integer or zero");
}
BigDecimal b1 = new BigDecimal(v1);
BigDecimal b2 = new BigDecimal(v2);
return b1.subtract(b2).setScale(scale, BigDecimal.ROUND_HALF_UP).toString();
}
/**
* 提供精确的乘法运算
*
* @param v1 被乘数
* @param v2 乘数
* @return 两个参数的积
*/
public static double mul(double v1, double v2) {
BigDecimal b1 = new BigDecimal(Double.toString(v1));
BigDecimal b2 = new BigDecimal(Double.toString(v2));
return b1.multiply(b2).doubleValue();
}
/**
* 提供精确的乘法运算
*
* @param v1 被乘数
* @param v2 乘数
* @return 两个参数的积
*/
public static BigDecimal mul(String v1, String v2) {
BigDecimal b1 = new BigDecimal(v1);
BigDecimal b2 = new BigDecimal(v2);
return b1.multiply(b2);
}
/**
* 提供精确的乘法运算
*
* @param v1 被乘数
* @param v2 乘数
* @param scale 保留scale 位小数
* @return 两个参数的积
*/
public static double mul(double v1, double v2, int scale) {
BigDecimal b1 = new BigDecimal(Double.toString(v1));
BigDecimal b2 = new BigDecimal(Double.toString(v2));
return round(b1.multiply(b2).doubleValue(), scale);
}
/**
* 提供精确的乘法运算
*
* @param v1 被乘数
* @param v2 乘数
* @param scale 保留scale 位小数
* @return 两个参数的积
*/
public static String mul(String v1, String v2, int scale) {
if (scale < 0) {
throw new IllegalArgumentException(
"The scale must be a positive integer or zero");
}
BigDecimal b1 = new BigDecimal(v1);
BigDecimal b2 = new BigDecimal(v2);
return b1.multiply(b2).setScale(scale, BigDecimal.ROUND_HALF_UP).toString();
}
/**
* 提供(相对)精确的除法运算,当发生除不尽的情况时,精确到
* 小数点以后10位,以后的数字四舍五入
*
* @param v1 被除数
* @param v2 除数
* @return 两个参数的商
*/
public static double div(double v1, double v2) {
return div(v1, v2, DEF_DIV_SCALE);
}
/**
* 提供(相对)精确的除法运算。当发生除不尽的情况时,由scale参数指
* 定精度,以后的数字四舍五入
*
* @param v1 被除数
* @param v2 除数
* @param scale 表示表示需要精确到小数点以后几位。
* @return 两个参数的商
*/
public static double div(double v1, double v2, int scale) {
if (scale < 0) {
throw new IllegalArgumentException("The scale must be a positive integer or zero");
}
BigDecimal b1 = new BigDecimal(Double.toString(v1));
BigDecimal b2 = new BigDecimal(Double.toString(v2));
return b1.divide(b2, scale, BigDecimal.ROUND_HALF_UP).doubleValue();
}
/**
* 提供(相对)精确的除法运算。当发生除不尽的情况时,由scale参数指
* 定精度,以后的数字四舍五入
*
* @param v1 被除数
* @param v2 除数
* @param scale 表示需要精确到小数点以后几位
* @return 两个参数的商
*/
public static String div(String v1, String v2, int scale) {
if (scale < 0) {
throw new IllegalArgumentException("The scale must be a positive integer or zero");
}
BigDecimal b1 = new BigDecimal(v1);
BigDecimal b2 = new BigDecimal(v1);
return b1.divide(b2, scale, BigDecimal.ROUND_HALF_UP).toString();
}
/**
* 提供精确的小数位四舍五入处理
*
* @param v 需要四舍五入的数字
* @param scale 小数点后保留几位
* @return 四舍五入后的结果
*/
public static double round(double v, int scale) {
if (scale < 0) {
throw new IllegalArgumentException("The scale must be a positive integer or zero");
}
BigDecimal b = new BigDecimal(Double.toString(v));
return b.setScale(scale, BigDecimal.ROUND_HALF_UP).doubleValue();
}
/**
* 提供精确的小数位四舍五入处理
*
* @param v 需要四舍五入的数字
* @param scale 小数点后保留几位
* @return 四舍五入后的结果
*/
public static String round(String v, int scale) {
if (scale < 0) {
throw new IllegalArgumentException(
"The scale must be a positive integer or zero");
}
BigDecimal b = new BigDecimal(v);
return b.setScale(scale, BigDecimal.ROUND_HALF_UP).toString();
}
/**
* 取余数
*
* @param v1 被除数
* @param v2 除数
* @param scale 小数点后保留几位
* @return 余数
*/
public static String remainder(String v1, String v2, int scale) {
if (scale < 0) {
throw new IllegalArgumentException(
"The scale must be a positive integer or zero");
}
BigDecimal b1 = new BigDecimal(v1);
BigDecimal b2 = new BigDecimal(v2);
return b1.remainder(b2).setScale(scale, BigDecimal.ROUND_HALF_UP).toString();
}
/**
* 取余数 BigDecimal
*
* @param v1 被除数
* @param v2 除数
* @param scale 小数点后保留几位
* @return 余数
*/
public static BigDecimal remainder(BigDecimal v1, BigDecimal v2, int scale) {
if (scale < 0) {
throw new IllegalArgumentException(
"The scale must be a positive integer or zero");
}
return v1.remainder(v2).setScale(scale, BigDecimal.ROUND_HALF_UP);
}
/**
* 比较大小
*
* @param v1 被比较数
* @param v2 比较数
* @return 如果v1 大于v2 则 返回true 否则false
*/
public static boolean compare(String v1, String v2) {
BigDecimal b1 = new BigDecimal(v1);
BigDecimal b2 = new BigDecimal(v2);
int bj = b1.compareTo(b2);
boolean res;
if (bj > 0)
res = true;
else
res = false;
return res;
}
public static BigDecimal toCeiling(BigDecimal val){
// if (val.compareTo(new BigDecimal(1)) >= 0) {
// return val.round(new MathContext(1, RoundingMode.CEILING));
// } else {
return val.setScale(0, RoundingMode.CEILING);
// }
}
}
运行结果
23 - 基于序列化的深拷贝
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.ToString;
import java.io.Serializable;
@AllArgsConstructor
@Data
@ToString
class Car implements Serializable {
private static final long serialVersionUID = -5713945027627603702L;
private String brand; // 品牌
private int maxSpeed; // 最高时速
}
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.ToString;
import java.io.Serializable;
@AllArgsConstructor
@Data
@ToString
class Person implements Serializable {
private static final long serialVersionUID = -9102017020286042305L;
private String name; // 姓名
private int age; // 年龄
private Car car; // 座驾
}
import java.io.*;
/**
* 浅拷贝只是复制了对象的引用地址,两个对象指向同一个内存地址,所以修改其中任意的值,另一个值都会随之变化,这就是浅拷贝(例:assign())
*
* 深拷贝是将对象及值复制过来,两个对象修改其中任意的值另一个值不会改变,这就是深拷贝(例:JSON.parse()和JSON.stringify(),但是此方法无法复制函数类型)
*/
public class CloneUtil {
public static void main(String[] args) {
try {
Person p1 = new Person("郭靖", 33, new Car("Benz", 300));
Person p2 = CloneUtil.clone(p1); // 深度克隆
p2.getCar().setBrand("BYD");
// 修改克隆的Person对象p2关联的汽车对象的品牌属性
// 原来的Person对象p1关联的汽车不会受到任何影响
// 因为在克隆Person对象时其关联的汽车对象也被克隆了
System.out.println(p1);
} catch (Exception e) {
e.printStackTrace();
}
}
// 私有构造方法
private CloneUtil() {
throw new AssertionError();
}
/*
注意:基于序列化和反序列化实现的克隆不仅仅是深度克隆,更重要的是通过泛型限定,
可以检查出要克隆的对象是否支持序列化,这项检查是编译器完成的,不是在运行时抛出异常,
这种是方案明显优于使用Object类的clone方法克隆对象。
让问题在编译的时候暴露出来总是好过把问题留到运行时。
*/
@SuppressWarnings("unchecked")
public static <T extends Serializable> T clone(T obj) throws Exception {
ByteArrayOutputStream bout = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bout);
oos.writeObject(obj);
ByteArrayInputStream bin = new ByteArrayInputStream(bout.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bin);
return (T) ois.readObject();
// 说明:调用ByteArrayInputStream或ByteArrayOutputStream对象的close方法没有任何意义
// 这两个基于内存的流只要垃圾回收器清理对象就能够释放资源,这一点不同于对外部资源(如文件流)的释放
}
}
24 - 时间日期
package cn.anzhongwei.lean.demo.date;
import java.time.*;
import java.time.format.DateTimeFormatter;
import java.time.temporal.ChronoUnit;
import java.util.Date;
public class DateUtils {
private static final DateTimeFormatter YEAH_MONTH_DAY = DateTimeFormatter.ofPattern("yyyy-MM-dd");
private static final DateTimeFormatter YEAH_MONTH_DAY_HOUR_MINUTE_SECOND = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
public DateUtils() {
}
public static void main(String[] args) {
System.out.println("1. 获取当前日期字符串: " + getNowDateTime());
//
System.out.println("2.1 获取UTC时间: " + getUTCTime());
System.out.println("2.2 获取时间戳: " + getTimestamp());
//
System.out.println("3. 根据字符串日期转LocalDate: " + getString2LocalDate("2019-01-01"));
System.out.println("4. 根据字符串日期时间转 LocalDateTime: " + getString2FullDate("2019-01-01 00:00:00"));
System.out.println("5. 根据自定义格式获取当前日期: " + getNowDateByFormatter("yyyy++MM++dd"));
System.out.println("6. 根据自定义格式获取当前日期时间: " + getNowDateTimeByFormatter("yyyy++MM++dd HH:mm:ss"));
System.out.printf(
"""
7. 根据节点获取当前日期时间信息
%s 年 %s 月 %s 日 星期 %s 是今年的第%s天
%s时 %s分 %s秒
%n""", getNowDateNode(DateEnum.YEAR),
getNowDateNode(DateEnum.MONTH),
getNowDateNode(DateEnum.DAY),
getNowDateNode(DateEnum.WEEK),
getNowDateNode(DateEnum.DAYOFYEAR),
getNowDateNode(DateEnum.HOUR),
getNowDateNode(DateEnum.MINUTE),
getNowDateNode(DateEnum.SECOND)
);
System.out.println("8. 一年后的日期时间: " + getPreOrAfterDateTime(DateEnum.YEAR, 1));
System.out.println("8. 一年前的日期时间: " + getPreOrAfterDateTime(DateEnum.YEAR, -1));
System.out.println("8. 一小时后的日期时间: " + getPreOrAfterDateTime(DateEnum.HOUR, 1));
System.out.println("8. 一小时前的日期时间: " + getPreOrAfterDateTime(DateEnum.HOUR, -1));
System.out.println("9. 比较两个日期时间大小 2019-01-01 00:00:00 & 2019-01-01 00:00:01: " + compareDateTime("2019-01-01 00:00:00", "2019-01-01 00:00:01"));
System.out.println("10. 判断是否闰年: " + isLeapYear("2024-01-01"));
System.out.println("11. 两个日期间隔天数: " + getPeridNum("2019-01-01", "2019-01-01", DateEnum.DAY));
System.out.println("12. 获取当前时区:" + getTimeZone());
System.out.println("13. 获取America/Los_Angeles时区时间 ZoneId.SHORT_IDS:" + getTimeZoneDateTime("America/Los_Angeles"));
}
// 1. 获取当前日期时间字符串
// 改成LocalDate.now().toString() 获取当前日期字符串
// 改成LocalTime.now().toString() 获取当前时间字符串
public static String getNowDateTime() {
return LocalDateTime.now().format(YEAH_MONTH_DAY_HOUR_MINUTE_SECOND);
}
// 2.1 获得utd时间
public static String getUTCTime() {
//Clock类
return Clock.systemUTC().instant().toString();
}
// 2.2 获得时间戳
public static Long getTimestamp() {
//Clock类
return Clock.systemUTC().millis();
}
// 3. 根据字符串日期转LocalDate
public static LocalDate getString2LocalDate(String date) {
return LocalDate.parse(date, YEAH_MONTH_DAY);
}
// 4. 根据字符串日期时间转 LocalDateTime
public static LocalDateTime getString2FullDate(String date) {
return LocalDateTime.parse(date, YEAH_MONTH_DAY_HOUR_MINUTE_SECOND);
}
// 5. 根据自定义格式获取当前日期
public static String getNowDateByFormatter(String formatterPattern) {
return LocalDate.now().format(DateTimeFormatter.ofPattern(formatterPattern));
}
// 6. 根据自定义格式获取当前日期时间
public static String getNowDateTimeByFormatter(String formatterPattern) {
return LocalDateTime.now().format(DateTimeFormatter.ofPattern(formatterPattern));
}
// 7. 获取当前 节点
public static Integer getNowDateNode(DateEnum dateEnum) {
LocalDateTime nowDate = LocalDateTime.now();
Integer nowNode = null;
switch (dateEnum) {
case YEAR:
nowNode = nowDate.getYear();
break;
case MONTH:
nowNode = nowDate.getMonthValue();
break;
case WEEK:
nowNode = conversionWeek2Num(DateUtils.WeekEnum.valueOf(nowDate.getDayOfWeek().toString()));
break;
case DAY:
nowNode = nowDate.getDayOfMonth();
break;
case DAYOFYEAR:
nowNode = nowDate.getDayOfYear();
break;
case HOUR:
nowNode = nowDate.getHour();
break;
case MINUTE:
nowNode = nowDate.getMinute();
break;
case SECOND:
nowNode = nowDate.getSecond();
}
return nowNode;
}
// 8. 从当前日期加或减一个 年 月 日 周 时 分 秒 的时间 加就是正数 减就是负数
// LocalDateTime nowDate = LocalDateTime.now();
// 改成 LocalDate nowDate = LocalDate.now(); 就是只能获取 年月日周
// 改成 LocalTime nowDate = LocalTime.now(); 就是只能获取 时 分 秒
public static String getPreOrAfterDateTime(DateEnum dateEnum, long time) {
LocalDateTime nowDate = LocalDateTime.now();
switch (dateEnum) {
case YEAR:
// 从api 的角度也可以用 这个api提供的时间单位更多
// nowDate.plus(time, ChronoUnit.YEARS);
// nowDate.minus(time, ChronoUnit.YEARS)
return nowDate.plusYears(time).format(YEAH_MONTH_DAY_HOUR_MINUTE_SECOND);
case MONTH:
return nowDate.plusMonths(time).format(YEAH_MONTH_DAY_HOUR_MINUTE_SECOND);
case WEEK:
return nowDate.plusWeeks(time).format(YEAH_MONTH_DAY_HOUR_MINUTE_SECOND);
case DAY:
return nowDate.plusDays(time).format(YEAH_MONTH_DAY_HOUR_MINUTE_SECOND);
case DAYOFYEAR:
throw new RuntimeException("不支持的选项");
case HOUR:
return nowDate.plusHours(time).format(YEAH_MONTH_DAY_HOUR_MINUTE_SECOND);
case MINUTE:
return nowDate.plusMinutes(time).format(YEAH_MONTH_DAY_HOUR_MINUTE_SECOND);
case SECOND:
return nowDate.plusSeconds(time).format(YEAH_MONTH_DAY_HOUR_MINUTE_SECOND);
default:
throw new RuntimeException("不支持的选项");
}
}
// 9. 比较日期时间 begin 比 end 早 返回true
// 同理LocalDate比较日期 LocalTime 比较时间
public static boolean compareDateTime(String begin, String end) {
LocalDateTime beginDate = LocalDateTime.parse(begin, YEAH_MONTH_DAY_HOUR_MINUTE_SECOND);
LocalDateTime endDate = LocalDateTime.parse(end, YEAH_MONTH_DAY_HOUR_MINUTE_SECOND);
return beginDate.isBefore(endDate);
}
// 10. 判断是否闰年
public static boolean isLeapYear(String date) {
return LocalDate.parse(date.trim()).isLeapYear();
}
// 11. 获取两个日期之间的间隔 这个函数只能用LocalDate
public static Integer getPeridNum(String begin, String end, DateEnum dateEnum) {
switch (dateEnum) {
case YEAR:
return Period.between(LocalDate.parse(begin), LocalDate.parse(end)).getYears();
case MONTH:
return Period.between(LocalDate.parse(begin), LocalDate.parse(end)).getMonths();
case WEEK:
throw new RuntimeException("不支持的参数");
case DAY:
return Period.between(LocalDate.parse(begin), LocalDate.parse(end)).getDays();
default:
throw new RuntimeException("不支持的参数");
}
}
// 12. 获取当前时区
public static String getTimeZone() {
return ZoneId.systemDefault().toString();
}
// 13. 获取指定时区时间 ZoneId.SHORT_IDS
public static String getTimeZoneDateTime(String timeZone) {
return LocalDateTime.now(ZoneId.of(timeZone)).format(YEAH_MONTH_DAY_HOUR_MINUTE_SECOND);
}
public static int conversionWeek2Num(WeekEnum weekEnum) {
switch (weekEnum) {
case MONDAY:
return 1;
case TUESDAY:
return 2;
case WEDNESDAY:
return 3;
case THURSDAY:
return 4;
case FRIDAY:
return 5;
case SATURDAY:
return 6;
case SUNDAY:
return 7;
default:
return -1;
}
}
public static enum WeekEnum {
MONDAY,
TUESDAY,
WEDNESDAY,
THURSDAY,
FRIDAY,
SATURDAY,
SUNDAY;
private WeekEnum() {
}
}
public static enum DateEnum {
YEAR,
MONTH,
WEEK,
DAY,
HOUR,
MINUTE,
SECOND,
DAYOFYEAR;
private DateEnum() {
}
}
}
25 - 使用抽象类来实现接口
- 创建接口类
package cn.anzhongwei.lean.demo.abstractImplementInterface;
import java.math.BigDecimal;
/**
* 接口中定义3个方法
*/
public interface InterfaceA {
int a();
String b();
BigDecimal c();
}
- 创建抽象类
package cn.anzhongwei.lean.demo.abstractImplementInterface;
/**
* 抽象中去实现两个方法,只有c()没有在抽象类中实现,留给最后类去实现c
*
*/
public abstract class AbstractB implements InterfaceA{
@Override
public int a() {
return 1;
}
@Override
public String b() {
return "Hello World!";
}
}
- 创建实现类
package cn.anzhongwei.lean.demo.abstractImplementInterface;
import java.math.BigDecimal;
/**
* 由类来实现抽象中未实现得方法, 这样得好处是可以将抽象当成通用得实现,每个最终得实现只去实现差异即可,
* 既有通用得又有灵活的方法
*/
public class ClassC extends AbstractB{
@Override
public BigDecimal c() {
return new BigDecimal(20);
}
}
- 测试类
package cn.anzhongwei.lean.demo.abstractImplementInterface;
public class TestMain {
public static void main(String[] args) {
InterfaceA interfaceA1 = new ClassC();
System.out.println(interfaceA1.a()); // 1
System.out.println(interfaceA1.b()); // Hello World!
System.out.println(interfaceA1.c()); // 20
AbstractB abstractB2 = new ClassC();
System.out.println(abstractB2.a()); // 1
System.out.println(abstractB2.b()); // Hello World!
System.out.println(abstractB2.c()); // 20
ClassC classc3 = new ClassC();
System.out.println(classc3.a()); // 1
System.out.println(classc3.b()); // Hello World!
System.out.println(classc3.c()); // 20
}
}
26 - 文件拷贝
import java.io.*;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.channels.FileChannel;
import java.nio.charset.Charset;
import java.nio.charset.CharsetDecoder;
public class NIOFileReadAndWrite {
private String file;
public String getFile() {
return file;
}
public void setFile(String file) {
this.file = file;
}
public NIOFileReadAndWrite(String file) throws IOException {
super();
this.file = file;
}
/**
* NIO读取文件
* @param allocate
* @throws IOException
*/
public void read(int allocate) throws IOException {
RandomAccessFile access = new RandomAccessFile(this.file, "r");
//FileInputStream inputStream = new FileInputStream(this.file);
FileChannel channel = access.getChannel();
ByteBuffer byteBuffer = ByteBuffer.allocate(allocate);
CharBuffer charBuffer = CharBuffer.allocate(allocate);
Charset charset = Charset.forName("GBK");
CharsetDecoder decoder = charset.newDecoder();
int length = channel.read(byteBuffer);
while (length != -1) {
byteBuffer.flip();
decoder.decode(byteBuffer, charBuffer, true);
charBuffer.flip();
System.out.println(charBuffer.toString());
// 清空缓存
byteBuffer.clear();
charBuffer.clear();
// 再次读取文本内容
length = channel.read(byteBuffer);
}
channel.close();
if (access != null) {
access.close();
}
}
/**
* NIO写文件
* @param context
* @param allocate
* @param chartName
* @throws IOException
*/
public void write(String context, int allocate, String chartName) throws IOException{
// FileOutputStream outputStream = new FileOutputStream(this.file); //文件内容覆盖模式 --不推荐
FileOutputStream outputStream = new FileOutputStream(this.file, true); //文件内容追加模式--推荐
FileChannel channel = outputStream.getChannel();
ByteBuffer byteBuffer = ByteBuffer.allocate(allocate);
byteBuffer.put(context.getBytes(chartName));
byteBuffer.flip();//读取模式转换为写入模式
channel.write(byteBuffer);
channel.close();
if(outputStream != null){
outputStream.close();
}
}
/**
* nio事实现文件拷贝
* @param source
* @param target
* @param allocate
* @throws IOException
*/
public static void nioCpoy(String source, String target, int allocate) throws IOException{
ByteBuffer byteBuffer = ByteBuffer.allocate(allocate);
FileInputStream inputStream = new FileInputStream(source);
FileChannel inChannel = inputStream.getChannel();
FileOutputStream outputStream = new FileOutputStream(target);
FileChannel outChannel = outputStream.getChannel();
int length = inChannel.read(byteBuffer);
while(length != -1){
byteBuffer.flip();//读取模式转换写入模式
outChannel.write(byteBuffer);
byteBuffer.clear(); //清空缓存,等待下次写入
// 再次读取文本内容
length = inChannel.read(byteBuffer);
}
outputStream.close();
outChannel.close();
inputStream.close();
inChannel.close();
}
public static void fileChannelCopy(String sfPath, String tfPath) {
File sf = new File(sfPath);
File tf = new File(tfPath);
FileInputStream fi = null;
FileOutputStream fo = null;
FileChannel in = null;
FileChannel out = null;
try{
fi = new FileInputStream(sf);
fo = new FileOutputStream(tf);
in = fi.getChannel();//得到对应的文件通道
out = fo.getChannel();//得到对应的文件通道
in.transferTo(0, in.size(), out);//连接两个通道,并且从in通道读取,然后写入out通道
}catch (Exception e){
e.printStackTrace();
}finally {
try{
fi.close();
in.close();
fo.close();
out.close();
}catch (Exception e){
e.printStackTrace();
}
}
}
//IO方法实现文件k拷贝
private static void traditionalCopy(String sourcePath, String destPath) throws Exception {
File source = new File(sourcePath);
File dest = new File(destPath);
if (!dest.exists()) {
dest.createNewFile();
}
FileInputStream fis = new FileInputStream(source);
FileOutputStream fos = new FileOutputStream(dest);
byte[] buf = new byte[1024];
int len = 0;
while ((len = fis.read(buf)) != -1) {
fos.write(buf, 0, len);
}
fis.close();
fos.close();
}
public static void main(String[] args) throws Exception{
long start = System.currentTimeMillis();
nioCpoy("D:\\迅雷下载\\jdk-21_windows-x64_bin.zip", "D:\\qwe\\jdk-21_windows-x64_bin.zip",10240);
long end = System.currentTimeMillis();
System.out.println("用时为:" + (end-start));
long start2 = System.currentTimeMillis();
fileChannelCopy("D:\\迅雷下载\\jdk-21_windows-x64_bin.zip", "D:\\qwe\\jdk-21_windows-x64_bin.zip");
long end2 = System.currentTimeMillis();
System.out.println("用时为:" + (end2-start2));
long start3 = System.currentTimeMillis();
traditionalCopy("D:\\迅雷下载\\jdk-21_windows-x64_bin.zip", "D:\\qwe\\jdk-21_windows-x64_bin.zip");
long end3 = System.currentTimeMillis();
System.out.println("用时为:" + (end3-start3));
}
}
27 - 下载图片
package cn.anzhongwei.lean.demo.downfileforweb;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletResponse;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.nio.charset.StandardCharsets;
@RestController
@ResponseBody
public class DownImgController {
//http://localhost:8080/ttpng?seriesUniqueCode=1
@GetMapping(value = "/writeImg1")
public void writeImg1(HttpServletResponse response) throws IOException {
//设置响应头信息需要在输出流之前, 就可以触发浏览器下载机制, 注释下列3行或将其写在输出流之后, 都只会在浏览器显示不会触发下载
// response.setHeader("Content-disposition", "attachment;filename=cargoExport.jpg");
// response.setContentType("multipart/form-data");
// response.setCharacterEncoding(StandardCharsets.UTF_8.name());
URL url = null;
InputStream is = null;
ByteArrayOutputStream outStream = null;
HttpURLConnection httpUrl = null;
//随便找一张图片
url = new URL("http://img.ay1.cc/uploads/20221114/9f3e884272b562cf8b27a048c3634621.jpg");
httpUrl = (HttpURLConnection) url.openConnection();
httpUrl.connect();
httpUrl.getInputStream();
is = httpUrl.getInputStream();
outStream = new ByteArrayOutputStream();
//创建一个Buffer字符串
byte[] buffer = new byte[1024];
//每次读取的字符串长度,如果为-1,代表全部读取完毕
int len = 0;
//使用一个输入流从buffer里把数据读取出来
while ((len = is.read(buffer)) != -1) {
//用输出流往buffer里写入数据,中间参数代表从哪个位置开始读,len代表读取的长度
response.getOutputStream().write(buffer, 0, len);
}
response.flushBuffer();
}
@GetMapping(value = "/writeImg2", produces = MediaType.IMAGE_JPEG_VALUE)
public void writeImg2(HttpServletResponse response) throws IOException {
URL url = null;
InputStream is = null;
ByteArrayOutputStream outStream = null;
HttpURLConnection httpUrl = null;
url = new URL("http://img.ay1.cc/uploads/20221114/9f3e884272b562cf8b27a048c3634621.jpg");
httpUrl = (HttpURLConnection) url.openConnection();
httpUrl.connect();
httpUrl.getInputStream();
is = httpUrl.getInputStream();
outStream = new ByteArrayOutputStream();
//创建一个Buffer字符串
byte[] buffer = new byte[1024];
//每次读取的字符串长度,如果为-1,代表全部读取完毕
int len = 0;
//使用一个输入流从buffer里把数据读取出来
while ((len = is.read(buffer)) != -1) {
//用输出流往buffer里写入数据,中间参数代表从哪个位置开始读,len代表读取的长度
outStream.write(buffer, 0, len);
}
byte[] temp = outStream.toByteArray();
response.setHeader("Content-disposition", "attachment;filename=cargoExport.jpg");
response.setContentType("multipart/form-data");
response.setCharacterEncoding(StandardCharsets.UTF_8.name());
response.getOutputStream().write(temp);
response.flushBuffer();
}
public void imgToBase64() {
URL downUrl = new URL("http://localhost/test.png");
HttpURLConnection conn = (HttpURLConnection) downUrl.openConnection();
conn.setRequestMethod("GET");
conn.setConnectTimeout(5000);
InputStream inputStream = conn.getInputStream();
ByteArrayOutputStream data = new ByteArrayOutputStream();
byte[] by = new byte[1024];
int len = -1;
while((len = inputStream.read(by)) != -1){
data.write(by,0,len);
}
inputStream.close();
Base64.getEncoder().encode(data.toByteArray());
String imgStr = Base64.getEncoder().encodeToString(data.toByteArray());
System.out.println(imgStr);
}
}
28 - 原生Java发送Post请求
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStreamWriter;
import java.net.HttpURLConnection;
import java.net.URL;
import java.io.BufferedReader;
import java.io.InputStreamReader;
public class JavaPostJson2 {
final static String url = "http://localhost:1111/sendData";
// final static String params = "{\"token\":\"12345\","
// + "\"appId\":\"20180628173051169c85d965d164ed9ab1281d22ff350ec19\""
// + "\"appName\":\"test33\""
// + "\"classTopic\":\"112\""
// + "\"eventTag\":\"22\""
// + "\"msg\":{}"
// + "}";
final static String params = "{\"token\":\"12345\","
+ "\"appId\":\"20180628173051169c85d965d164ed9ab1281d22ff350ec19\","
+ "\"appName\":\"test33\","
+ "\"classTopic\":\"112\","
+ "\"eventTag\":\"22\""
+ "}";
public static void main(String[] args) {
String res = post(url, params);
System.out.println(res);
}
/**
* 发送HttpPost请求
*
* @param strURL
* 服务地址
* @param params
* json字符串,例如: "{ \"id\":\"12345\" }" ;其中属性名必须带双引号<br/>
* @return 成功:返回json字符串<br/>
*/
public static String post(String strURL, String params) {
System.out.println(strURL);
System.out.println(params);
BufferedReader reader = null;
try {
URL url = new URL(strURL);// 创建连接
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
connection.setDoOutput(true);
connection.setDoInput(true);
connection.setUseCaches(false);
connection.setInstanceFollowRedirects(true);
connection.setRequestMethod("POST"); // 设置请求方式
// connection.setRequestProperty("Accept", "application/json"); // 设置接收数据的格式
connection.setRequestProperty("Content-Type", "application/json"); // 设置发送数据的格式
connection.connect();
OutputStreamWriter out = new OutputStreamWriter(connection.getOutputStream(), "UTF-8"); // utf-8编码
out.append(params);
out.flush();
out.close();
// 读取响应
reader = new BufferedReader(new InputStreamReader(connection.getInputStream(), "UTF-8"));
String line;
String res = "";
while ((line = reader.readLine()) != null) {
res += line;
}
reader.close();
return res;
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return "error"; // 自定义错误信息
}
}
