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

这里主要是Map和JSONObject的转换
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序列化

  1. 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;
}
  1. 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;
}
  1. 写序列化到文件
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();
        }
    }
}
  1. 从文件读序列化并实例化
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专栏

所有和Spring相关的内容。

9.1 - 多个网卡时指定注册ip段.md

添加配置项

  cloud:
    inetutils:
      preferred-networks: 192.168.*

9.2 - 如何在pom文件中管理多个环境

dev dev true prod prod test test

9.3 - Spring 上传文件的配置

spring:

  servlet:
    multipart:
      #配置文件传输
      enabled: true
      file-size-threshold: 0B
      #单个文件的最大上限
      max-file-size: 100MB
      #单个请求的文件总大小上限
      max-request-size: 1000MB

9.4 - SpringBoot单元测试

测试均通过 SpringBoot3.5.0、JDK版本21。

简介

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

  1. Service测试: no01servicetest

  2. Controller测试: no02ControllerTest

  3. Controller对Service打桩&Service对Mapper打桩的测试: no03MockitoService

  4. 对文件上传下载的测试: no04FileUploadDownload

  5. 使用H2数据库对数据写入进行实际测试: no05H2DBTest

在 no03MockitoService 其实已经包含了该测试内容

  1. 测试Controller 对 Cookie 、 Session 、 Header 的读写: no06CookieSessionHeaderTest

  2. 使用嵌入式redis服务器(embedded-redis)对Redis进行测试: no07RedisTest

这种场景只有使用完整的spring容器方式进行单元测试能进行测试, 其他两种(完全脱离spring和部分启动的方式只能对redisTemplate进行打桩测试)

  1. 测试SrpingBoot的Filter: no08SpringFilterTest

  2. 演示controller使用thymeleaf模板而不是返回json时的单元测试: no09ThymeleafTest

  3. 异步调用测试: no10AsyncTest

  4. Service超时测试: no11TimeoutTest

  5. 普通的类中(不是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

  1. 被校验的类 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;

}
  1. 校验结果实体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();
    }
}
  1. 校验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;
    }

}
  1. 分组校验的交验组定义
public interface CheckGroup1 {
}
  1. 测试客户端
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占位符

应该由bug,没详细测试
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动态代理

java17以后替换cglib方式的动态代理。他和cglib实现动态代理的优势是不用定义接口也不需要去实现接口,对现有的代码扩展更友好。
  • pom文件添加依赖
        <dependency>
            <groupId>net.bytebuddy</groupId>
            <artifactId>byte-buddy</artifactId>
            <!-- 我用的最新的, 一般1.14.0以上都没问题 -->
            <version>1.15.1</version>
        </dependency>
  1. 先创建要被代理的类
public class TargetClass {
    public void performAction() {
        System.out.println("原始方法: performAction");
    }

    public String sayHello(String name) {
        return "Hello, " + name;
    }
}
  1. 创建拦截器
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;
    }
}
  1. 代理工具
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;

    }
}
  1. 一个测试类用来测试动态代理
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动态代理

CGLIB基于ASM实现。提供比反射更为强大的动态特性。使用CGLIB可以非常方便的实现的动态代理。但是由于jdk17+由于java.base模块未向未命名模块开放java.lang包,所以CGLIB无法在jdk17+中运行。在java17+后 需要使用 Byte Buddy,这里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 - 动态代理

动态代理实现 MethodInterceptor 他可以在使用时再确定要代理什么并且由使用者决定要调用的方法。本质上是使用了反射来实现的
  1. 定义一个接口
public interface IRegisterService {
    void register(String name, String pwd);
}
  1. 一个接口的实现类
public class RegisterServiceImpl implements IRegisterService {
    @Override
    public void register(String name, String pwd) {
        System.out.println(String.format("【向数据库中插入数据】name:%s,pwd:%s", name, pwd));
    }
}
  1. 一个实现了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]一些后置处理");
    }

}
  1. 测试
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 - 静态代理

静态代理的代理类持有要代理的目标类的引用。代理关系是确定的。每一个需要代理的类都需要一个代理类来持有他的引用
  1. 定义一个接口 IRegisterService
public interface IRegisterService {
    void register(String name, String pwd);
}
  1. 创建被代理的类,并实现 接口
public class RegisterServiceImpl implements IRegisterService {
    @Override
    public void register(String name, String pwd) {
        System.out.println(String.format("【向数据库中插入数据】name:%s,pwd:%s", name, pwd));
    }
}
  1. 创建代理类,并实现接口
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]一些后置处理");

    }
}
  1. 测试
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 - 线程交替执行的实践

线程交替执行的实践 也就是 1a2b3c4d交替输出的问题
  1. 只能保证交替执行, 至于先输出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();
    }
}
  1. 先等待一直等到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();


    }
}
  1. 通过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();


    }
}
  1. 指定线程锁,相互唤醒,第一次也使用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();
    }
}
  1. 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();
    }
}
  1. 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

不仅仅是获取, 我还希望可以对这个深层次的对象进行赋值 所以有了这个小例子

先定义数据结构

  1. OrderInfoDTO
import lombok.Data;
import lombok.ToString;

import java.util.List;

@Data
@ToString
public class OrderInfoDTO {
    private GoOrderDTO goOrder;
    private List<WmsPlanDTO> wmPlans;
}
  1. 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;
}
  1. 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;
}
  1. 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;
}

定义反射方法这是我们的核心

  1. 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;
    }
}
  1. 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 - 使用抽象类来实现接口

  1. 创建接口类
package cn.anzhongwei.lean.demo.abstractImplementInterface;

import java.math.BigDecimal;

/**
 * 接口中定义3个方法
 */
public interface InterfaceA {
    int a();
    String b();
    BigDecimal c();
}
  1. 创建抽象类
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!";
    }

}
  1. 创建实现类
package cn.anzhongwei.lean.demo.abstractImplementInterface;

import java.math.BigDecimal;

/**
 * 由类来实现抽象中未实现得方法, 这样得好处是可以将抽象当成通用得实现,每个最终得实现只去实现差异即可,
 * 既有通用得又有灵活的方法
 */
public class ClassC extends AbstractB{
    @Override
    public BigDecimal c() {
        return new BigDecimal(20);
    }
}
  1. 测试类
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"; // 自定义错误信息
    }

}