重学设计模式-Java-单例模式

引文:

面试官:你在项目中使用过什么设计模式?

发哥讲java:我用过单例模式

面试官:那你简单说一下单例模式以及项目中如何使用的?

思考:当遇到一个问题,我们应该如果回答?如何做到言简意赅,直奔主题,还能让面试官觉得你会,懂得多,如何让自己装个杯~~~

首先:

要理解面试官问的问题,理解好面试官问的问题,比如: 你了解hashmap嘛?此时只需要回答hashmap是什么样的数据结构(k-v键值对),再说一下这种数据结构的好处(键自动去重,值保留最新的一个,取key对应的值速度快,效率高)以及缺点( 遍历速度慢).此时就ok了,其他的像默认初始容量了,项目中怎么用等就别说了!!!

记住:不要回答面试官问题的其他知识点这个很重要,不然很容易给自己带入坑里面,而且如果自己回答不清楚的话,很可能会拉低你在面试官前的技术形象.

而且大多数面试官,他们都有自己的一个面试路线,有自己引入方向.所以自己备面的时候也要多了解一些,也扩展一下自己的技术知识面,总之有备无患

其次:

讲清楚自己对问题的理解,以及项目中真正使用到的地方,讲自己由零到壹的实现以及遇到实际问题解决的方案.

做到这俩步,足以应付大多数的面试官了. 奥利给~~~~

如何回复单例模式以及项目中如何使用的?

单例模式是设计模式中创建型的一种,他常用于定义整个系统的唯一变量和对象等,实现方式有: 饿汉式,懒汉式,懒汉锁式,懒汉双重锁式,枚举等.在实际项目中像接口返回统一的响应码和响应内容枚举类,老项目或者小项目非springboot项目中的数据库jdbc连接池的使用和常用的全局静态变量( 像业务章redis中配置的key的值,请求头的header的key等).

正文

单例模式

是设计模式中最简单的一种,也是常用的设计模式之一.其定义是单例对象的实例只有一个. 许多时候,我们整个系统只需要拥有一个全局唯一对象,这样有利于协调和全局公用整体的行为.比如:系统的配置文件,数据库jdbc统一的获取DataSource等

实现的步骤: 1.类的构造方法私有化,保证外部不能通过构造方法实例化. 2.提供一个静态方法返回值为该类,用于对外开发获取实例的方法

public class XXX {
    // 私有化
    private XXX() {
    }
​
    // 开放获取实例的方法
    public static XXX getInstance() {
        return new XXX();
    }
}

实现方式: 1.饿汉式 我们知道,类加载的方式是按需加载,且加载一次。因此,在上述单例类被加载时,就会实例化一个对象并交给自己的引用,供系统使用; 而且,由于这个类在整个生命周期中只会被加载一次,因此只会创建一个实例,即能够充分保证单例。

/**
 * 饿汉式: 在定义的类加载之后,实例对象已经创建过了,直接使用即可
 *
 * @author <发哥讲Java-694204477@qq.com>
 * @version 1.0
 * @date 2021/6/5 9:55
 */
public class Hungry {
​
    //1. 构造方法私有
    private Hungry() {
    }
​
    //饿汉式,类加载之后实例对象已经创建完成
    private static final Hungry hungry = new Hungry();
​
    //2. 提供对外获取该实例对象的方法
    public static Hungry getInstance() {
        return hungry;
    }
}

优点:这种写法比较简单,就是在类装载的时候就完成实例化。避免了线程同步问题。

缺点:在类装载的时候就完成实例化,没有达到Lazy Loading的效果。如果从始至终从未使用过这个实例,则会造成内存的浪费。

2.懒汉式

package com.fagejiang.single;
​
import java.util.concurrent.TimeUnit;
​
/**
 * 懒汉式,在需要的时候创建实例对象
 *
 * @author <发哥讲Java-694204477@qq.com>
 * @version 1.0
 * @date 2021/6/5 10:01
 */
public class Lazy {
​
    //1.构造方法私有化
    private Lazy() {
        System.out.println("验证几个线程进入到创建lazy实例的构造方法中:" + Thread.currentThread().getName());
    }
​
    //饿汉式变量
    private static Lazy lazy = null;
​
    //2.提供开发方法
    public static Lazy getInstance() {
        if (lazy == null) {
            lazy = new Lazy();
        }
        return lazy;
    }
​
    // 单线程环境下使用,没有问题
    // 多线程环境下,如果多个线程同时进入到判断中,则多个线程都会实例化一个lazy对象,测试如下
    public static void main(String[] args) throws InterruptedException {
        // 开启10个线程去获取实例
        for (int i = 0; i < 10; i++) {
            new Thread(() -> Lazy.getInstance()).start();
        }
        // 休眠5秒,保证main方法的10个线程都执行完成
        TimeUnit.SECONDS.sleep(5);
    }
}
​

3.双重锁

package com.fagejiang.single;
​
import java.util.concurrent.TimeUnit;
​
/**
 * 懒汉式 加锁,双重判断模式, 解决多线程下多次创建问题
 *
 * @author <发哥讲Java-694204477@qq.com>
 * @version 1.0
 * @date 2021/6/5 10:49
 */
public class LazySynchronized {
    //1.构造方法私有化
    private LazySynchronized() {
        System.out.println("验证几个线程进入到创建lazy实例的构造方法中:" + Thread.currentThread().getName());
    }
​
    //饿汉式变量
    private static LazySynchronized lazy = null;
​
    //2.提供开发方法
    public static LazySynchronized getInstance() {
        if (lazy == null) {
            synchronized (LazySynchronized.class) {
                if (lazy == null) {
                    lazy = new LazySynchronized();
                }
            }
        }
        return lazy;
    }
​
    // 多线程环境下 测试是否会多次创建
    public static void main(String[] args) throws InterruptedException {
        // 开启10个线程去获取实例
        for (int i = 0; i < 10; i++) {
            new Thread(() -> LazySynchronized.getInstance()).start();
        }
        // 休眠5秒,保证main方法的10个线程都执行完成
        TimeUnit.SECONDS.sleep(1);
    }
}
​

4.防止指令重排

package com.fagejiang.single;
​
import java.util.concurrent.TimeUnit;
​
/**
 * 懒汉式 添加volatile关键字,房子指令重拍,保证原子性操作,加锁,双重判断模式, 解决多线程下多次创建问题
 *
 * @author <发哥讲Java-694204477@qq.com>
 * @version 1.0
 * @date 2021/6/5 10:49
 */
public class LazyVolatileSynchronized {
    //1.构造方法私有化
    private LazyVolatileSynchronized() {
        System.out.println("验证几个线程进入到创建lazy实例的构造方法中:" + Thread.currentThread().getName());
    }
​
    //饿汉式变量
    private volatile static LazyVolatileSynchronized lazy = null;
​
    //2.提供开发方法
    public static LazyVolatileSynchronized getInstance() {
        if (lazy == null) {
            synchronized (LazyVolatileSynchronized.class) {
                if (lazy == null) {
                    // lazy 添加 volatile 后 ,变成原子性操作,不会发生指令重排
                    lazy = new LazyVolatileSynchronized();
                }
            }
        }
        return lazy;
    }
​
    // 多线程环境下 测试是否会多次创建
    public static void main(String[] args) throws InterruptedException {
        // 开启10个线程去获取实例
        for (int i = 0; i < 10; i++) {
            new Thread(() -> LazyVolatileSynchronized.getInstance()).start();
        }
        // 休眠5秒,保证main方法的10个线程都执行完成
        TimeUnit.SECONDS.sleep(1);
    }
}
​

5.静态内部类

package com.fagejiang.single;
​
/**
 * 静态内部类的方式
 *
 * @author <发哥讲Java-694204477@qq.com>
 * @version 1.0
 * @date 2021/6/5 23:23
 */
public class StaticInnerClass {
​
    // 1. 定义私有构造参数
    private StaticInnerClass() {
    }
​
    // 2. 提供对外获取实例方法
    public static StaticInnerClass getInstance() {
        return InnerClass.STATIC_INNER_CLASS;
    }
​
    // 静态内部类
    private static class InnerClass {
        private static final StaticInnerClass STATIC_INNER_CLASS = new StaticInnerClass();
    }
​
}
​

问题: 使用反射打破单例模式

  1. 通过反射获取当构造器对象,然后调用 newInstance 方法创建

package com.fagejiang.single;
​
import java.lang.reflect.Constructor;
​
/**
 * 通过反射 打破单例模式
 *
 * @author <发哥讲Java-694204477@qq.com>
 * @version 1.0
 * @date 2021/6/5 23:22
 */
public class ReflectLazy {
    //1.构造方法私有化
    private ReflectLazy() {
        System.out.println("验证几个线程进入到创建lazy实例的构造方法中:" + Thread.currentThread().getName());
    }
​
    //饿汉式变量
    private volatile static ReflectLazy lazy = null;
​
    //2.提供开发方法
    public static ReflectLazy getInstance() {
        if (lazy == null) {
            synchronized (ReflectLazy.class) {
                if (lazy == null) {
                    // lazy 添加 volatile 后 ,变成原子性操作,不会发生指令重排
                    lazy = new ReflectLazy();
                }
            }
        }
        return lazy;
    }
​
    public static void main(String[] args) throws Exception {
        ReflectLazy instance1 = ReflectLazy.getInstance();
        ReflectLazy instance2 = ReflectLazy.getInstance();
        System.out.println(instance1 == instance2);
        System.out.println(instance1);
        System.out.println(instance2);
​
        //获取到反射对象
        Class<ReflectLazy> lazyClass = ReflectLazy.class;
        Constructor<ReflectLazy> constructor = lazyClass.getDeclaredConstructor(null);
//        constructor.setAccessible(true);
        // 可以发现 构造方法被重新调用了,则打破单例模式
        ReflectLazy lazy = constructor.newInstance();
        System.out.println(lazy);
    }
​
}
​
  1. 通过反射 打破单例模式, 构造方法跳转, 防止反射打破单例模式

package com.fagejiang.single;
​
import java.lang.reflect.Constructor;
​
/**
 * 通过反射 打破单例模式, 构造方法跳转, 防止反射打破单例模式
 *
 * @author <发哥讲Java-694204477@qq.com>
 * @version 1.0
 * @date 2021/6/5 23:22
 */
public class ReflectLazy2 {
    //1.构造方法私有化
    private ReflectLazy2() {
        synchronized (ReflectLazy2.class) {
            if (lazy != null) {
                throw new RuntimeException("不要试图打破单例模式");
            }
            System.out.println("验证几个线程进入到创建lazy实例的构造方法中:" + Thread.currentThread().getName());
        }
    }
​
    //饿汉式变量
    private volatile static ReflectLazy2 lazy = null;
​
    //2.提供开发方法
    public static ReflectLazy2 getInstance() {
        if (lazy == null) {
            synchronized (ReflectLazy2.class) {
                if (lazy == null) {
                    // lazy 添加 volatile 后 ,变成原子性操作,不会发生指令重排
                    lazy = new ReflectLazy2();
                }
            }
        }
        return lazy;
    }
​
    public static void main(String[] args) throws Exception {
//        ReflectLazy2 instance1 = ReflectLazy2.getInstance();
//        ReflectLazy2 instance2 = ReflectLazy2.getInstance();
//        System.out.println(instance1 == instance2);
//        System.out.println(instance1);
//        System.out.println(instance2);
​
        //获取到反射对象
//        Class<ReflectLazy2> lazyClass = ReflectLazy2.class;
//        Constructor<ReflectLazy2> constructor = lazyClass.getDeclaredConstructor(null);
//        // 可以发现 构造方法被重新调用了,则打破单例模式
//        ReflectLazy2 lazy = constructor.newInstance();
//        System.out.println(lazy);
​
        /**
         * 此时方式,当我们第一次调用过 getInstance 后, lazy就被赋予值了.之后再通过反射 newInstance 时则会出现  异常
         * ------------------------
         * 那么如果,我先通过反射 newInstance , 只要不调用 getInstance,那么反射就能一直创建出实例对象,
         */
        //获取到反射对象
        Class<ReflectLazy2> lazyClass = ReflectLazy2.class;
        Constructor<ReflectLazy2> constructor = lazyClass.getDeclaredConstructor(null);
        ReflectLazy2 lazy2 = constructor.newInstance(null);
        ReflectLazy2 lazy3 = constructor.newInstance(null);
        ReflectLazy2 lazy4 = constructor.newInstance(null);
        System.out.println("lazy2 = " + lazy2);
        System.out.println("lazy3 = " + lazy3);
        System.out.println("lazy4 = " + lazy4);
    }
​
}
​

3.通过反射 打破单例模式, 添加标志位,防止反射打破单例模式

package com.fagejiang.single;
​
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
​
/**
 * 通过反射 打破单例模式, 添加标志位,防止反射打破单例模式
 *
 * @author <发哥讲Java-694204477@qq.com>
 * @version 1.0
 * @date 2021/6/5 23:22
 */
public class ReflectLazy3 {
​
    // 定义标志位
    private static boolean flag = false;
​
    //1.构造方法私有化
    private ReflectLazy3() {
        synchronized (ReflectLazy3.class) {
            // 调用 getInstance 是 标志位 flag 是false
            if (flag == false) {
                flag = true;
            } else {
                throw new RuntimeException("不要试图打破单例模式");
            }
            System.out.println("验证几个线程进入到创建lazy实例的构造方法中:" + Thread.currentThread().getName());
        }
    }
​
    //饿汉式变量
    private volatile static ReflectLazy3 lazy = null;
​
    //2.提供开发方法
    public static ReflectLazy3 getInstance() {
        if (lazy == null) {
            synchronized (ReflectLazy3.class) {
                if (lazy == null) {
                    // lazy 添加 volatile 后 ,变成原子性操作,不会发生指令重排
                    lazy = new ReflectLazy3();
                }
            }
        }
        return lazy;
    }
​
    public static void main(String[] args) throws Exception {
        //获取到反射对象
//        Class<ReflectLazy3> lazyClass = ReflectLazy3.class;
//        Constructor<ReflectLazy3> constructor = lazyClass.getDeclaredConstructor(null);
//        ReflectLazy3 lazy1 = constructor.newInstance(null);
//        System.out.println("lazy1 = " + lazy1);
​
        /**
         * 此时方式,直接通过之前的方式进行 newInstance会发生异常,因为存在标志位flag.
         * 但是反射也可以对属性进行赋值.
         */
        ReflectLazy3 instance = ReflectLazy3.getInstance();
        System.out.println("instance = " + instance);
​
        Class<ReflectLazy3> lazy3Class = ReflectLazy3.class;
        Field flag = lazy3Class.getDeclaredField("flag");
        flag.setBoolean(lazy3Class, false);
        Constructor<ReflectLazy3> constructor = lazy3Class.getDeclaredConstructor(null);
        ReflectLazy3 lazy3 = constructor.newInstance(null);
        System.out.println("lazy3 = " + lazy3);
​
        // 可以发现当我们调用flag.setBoolean(lazy3Class, false)给flag标志位修改值之后,又打破了单利模式
​
        /**
         * 查看反射 newInstance 的源码
         *   if ((clazz.getModifiers() & Modifier.ENUM) != 0)
         *       throw new IllegalArgumentException("Cannot reflectively create enum objects");
         *  对于枚举类不能通过反射 进行实例化,则枚举类是最完美的单例模式
         */
    }
​
}
​

6.枚举类单利

package com.fagejiang.single;
​
import java.lang.reflect.Constructor;
​
/**
 * 最完美的单利模式: 枚举类
 *
 * @author <发哥讲Java-694204477@qq.com>
 * @version 1.0
 * @date 2021/6/6 0:12
 */
public enum SingleEnum {
    // 定义单例对象-实例
    INSTANCE;
​
    // 使用反射打破枚举类单例模式:
    public static void main(String[] args) throws Exception {
//        Class<SingleEnum> enumClass = SingleEnum.class;
//        Constructor<SingleEnum> constructor = enumClass.getDeclaredConstructor(null);
//        SingleEnum anEnum = constructor.newInstance(null);
//        System.out.println("anEnum = " + anEnum);
​
​
        // enumClass.getDeclaredConstructor(null); 获取到的构造器,进行newInstance时抛出 NoSuchMethodException 没有此方法异常
        // 奇了怪了, 为什么没有构造方法呢,
        // 此时就需要通过 反编译 去了解一下枚举类 在Java编译后真正的构造对象
​
        // 1.使用 jdk 命令: javap -p SingleEnum.class 反编译后:
        /**
         * Compiled from "SingleEnum.java"
         * public final class com.fagejiang.single.SingleEnum extends java.lang.Enum<com.fagejiang.single.SingleEnum> {
         *   public static final com.fagejiang.single.SingleEnum INSTANCE;
         *   private static final com.fagejiang.single.SingleEnum[] $VALUES;
         *   public static com.fagejiang.single.SingleEnum[] values();
         *   public static com.fagejiang.single.SingleEnum valueOf(java.lang.String);
         *   private com.fagejiang.single.SingleEnum();
         *   public static void main(java.lang.String[]) throws java.lang.Exception;
         *   static {};
         * }
         */
        // 发现构造函数是空的
        // 2.使用idea,查看target包下的SingleEnum.class,idea自动反编译,查看:
        /**
         * public enum SingleEnum {
         *     INSTANCE;
         *
         *     private SingleEnum() {
         *     }
         *
         *     public static void main(String[] args) throws Exception {
         *         System.out.println(INSTANCE);
         *         Class<SingleEnum> enumClass = SingleEnum.class;
         *         Constructor<SingleEnum> constructor = enumClass.getDeclaredConstructor((Class[])null);
         *         SingleEnum anEnum = (SingleEnum)constructor.newInstance((Object[])null);
         *         System.out.println("anEnum = " + anEnum);
         *     }
         * }
         */
        // 发现构造参数任然为空的
        //3.使用 jad 工具 反编译  https://www.cnblogs.com/dkblog/archive/2008/04/07/1980817.html
        // 命令:  jad -sjava SingleEnum.class
        /**
         * public final class SingleEnum extends Enum
         * {
         *
         *     public static SingleEnum[] values()
         *     {
         *         return (SingleEnum[])$VALUES.clone();
         *     }
         *
         *     public static SingleEnum valueOf(String name)
         *     {
         *         return (SingleEnum)Enum.valueOf(com/fagejiang/single/SingleEnum, name);
         *     }
         *
         *     private SingleEnum(String s, int i)
         *     {
         *         super(s, i);
         *     }
         *
         *     public static void main(String args[])
         *         throws Exception
         *     {
         *         System.out.println(INSTANCE);
         *         Class enumClass = com/fagejiang/single/SingleEnum;
         *         Constructor constructor = enumClass.getDeclaredConstructor(null);
         *         SingleEnum anEnum = (SingleEnum)constructor.newInstance(null);
         *         System.out.println((new StringBuilder()).append("anEnum = ").append(anEnum).toString());
         *     }
         *
         *     public static final SingleEnum INSTANCE;
         *     private static final SingleEnum $VALUES[];
         *
         *     static
         *     {
         *         INSTANCE = new SingleEnum("INSTANCE", 0);
         *         $VALUES = (new SingleEnum[] {
         *             INSTANCE
         *         });
         *     }
         * }
         */
        // 此时我们发现 构造方法里面多了俩个参数 private SingleEnum(String s, int i)
        // 重新测试
        Class<SingleEnum> aClass = SingleEnum.class;
        Constructor<SingleEnum> constructor = aClass.getDeclaredConstructor(String.class, int.class);
        SingleEnum anEnum = constructor.newInstance();
        System.out.println("anEnum = " + anEnum);
​
        // 此时出现我们期望看到的异常:  Cannot reflectively create enum objects
​
    }
}
​

7.cas[使用AtomicReference]

package com.fagejiang.single;
​
import java.util.concurrent.atomic.AtomicReference;
​
/**
 * CAS 借用 AtomicReference 保证现场安全
 *
 * @author <发哥讲Java-694204477@qq.com>
 * @version 1.0
 * @date 2021/6/6 13:04
 */
public class SingleAtomic {
​
    private static final AtomicReference<SingleAtomic> ATOMIC_REFERENCE = new AtomicReference<>();
​
    // 1. 定义私有的构造的方法
    public SingleAtomic() {
    }
​
    // 2. 提供对外暴露实例的方法
    public static SingleAtomic getInstance() {
        for (; ; ) {
            SingleAtomic atomic = ATOMIC_REFERENCE.get();
            if (null != atomic) {
                return atomic;
            }
            ATOMIC_REFERENCE.set(new SingleAtomic());
            return ATOMIC_REFERENCE.get();
        }
    }
​
    /**
     * java并发库提供了很多原⼦类来⽀持并发访问的数据安全性;
     * AtomicInteger 、 AtomicBoolean 、 AtomicLong 、 AtomicReference 。
     * AtomicReference 可以封装引⽤⼀个V实例,⽀持并发访问如上的单例⽅式就是使⽤了这样的⼀个特点。
     * <p>
     * 使⽤CAS的好处就是不需要使⽤传统的加锁⽅式保证线程安全,⽽是依赖于CAS的忙等算法,依赖
     * 于底层硬件的实现,来保证线程安全。相对于其他锁的实现没有线程的切换和阻塞也就没有了额外
     * 的开销,并且可以⽀持较⼤的并发性。
     * <p>
     * 当然CAS也有⼀个缺点就是忙等,如果⼀直没有获取到将会处于死循环中。
     */
    public static void main(String[] args) {
        SingleAtomic instance1 = SingleAtomic.getInstance();
        SingleAtomic instance2 = SingleAtomic.getInstance();
        SingleAtomic instance3 = SingleAtomic.getInstance();
        System.out.println("instance1 = " + instance1);
        System.out.println("instance2 = " + instance2);
        System.out.println("instance3 = " + instance3);
    }
}
​

实战: 1.web接口统一返回结果的状态码枚举

package com.fagejiang.single.properties;
​
/**
 * 我还没有写描述
 *
 * @author <发哥讲Java-694204477@qq.com>
 * @version 1.0
 * @date 2021/6/6 13:28
 */
public interface IResultCode {
    String getCode();
​
    String getMsg();
}
​
package com.fagejiang.single.properties;
​
import java.io.Serializable;
​
/**
 * web 接口统一返回结果的状态码枚举
 *
 * @author <发哥讲Java-694204477@qq.com>
 * @version 1.0
 * @date 2021/6/6 13:23
 */
public enum ResultCodeEnum implements IResultCode, Serializable {
    SUCCESS("00000", "一切ok"),
​
    USER_ERROR("A0001", "用户端错误"),
    USER_LOGIN_ERROR("A0200", "用户登录异常"),
​
    USER_NOT_EXIST("A0201", "用户不存在"),
    USER_ACCOUNT_LOCKED("A0202", "用户账户被冻结"),
    USER_ACCOUNT_INVALID("A0203", "用户账户已作废"),
​
    USERNAME_OR_PASSWORD_ERROR("A0210", "用户名或密码错误"),
    INPUT_PASSWORD_EXCEED_LIMIT("A0211", "用户输入密码次数超限"),
    CLIENT_AUTHENTICATION_FAILED("A0212", "客户端认证失败"), // *
    TOKEN_INVALID_OR_EXPIRED("A0230", "token无效或已过期"),
    TOKEN_ACCESS_FORBIDDEN("A0231", "token已被禁止访问"),
​
    AUTHORIZED_ERROR("A0300", "访问权限异常"),
    ACCESS_UNAUTHORIZED("A0301", "访问未授权"),
    FORBIDDEN_OPERATION("A0302", "演示环境禁止修改、删除重要数据,请本地部署后测试"),
​
​
    PARAM_ERROR("A0400", "用户请求参数错误"),
    PARAM_IS_NULL("A0410", "请求必填参数为空"),
    QUERY_MODE_IS_NULL("A0411", "查询模式为空"),
​
    USER_UPLOAD_FILE_ERROR("A0700", "用户上传文件异常"),
    USER_UPLOAD_FILE_TYPE_NOT_MATCH("A0701", "用户上传文件类型不匹配"),
    USER_UPLOAD_FILE_SIZE_EXCEEDS("A0702", "用户上传文件太大"),
    USER_UPLOAD_IMAGE_SIZE_EXCEEDS("A0703", "用户上传图片太大"),
​
    SYSTEM_EXECUTION_ERROR("B0001", "系统执行出错"),
    SYSTEM_EXECUTION_TIMEOUT("B0100", "系统执行超时"),
    SYSTEM_ORDER_PROCESSING_TIMEOUT("B0100", "系统订单处理超时"),
​
    SYSTEM_DISASTER_RECOVERY_TRIGGER("B0200", "系统容灾功能被出发"),
    FLOW_LIMITING("B0210", "系统限流"),
    DEGRADATION("B0220", "系统功能降级"),
​
    SYSTEM_RESOURCE_ERROR("B0300", "系统资源异常"),
    SYSTEM_RESOURCE_EXHAUSTION("B0310", "系统资源耗尽"),
    SYSTEM_RESOURCE_ACCESS_ERROR("B0320", "系统资源访问异常"),
    SYSTEM_READ_DISK_FILE_ERROR("B0321", "系统读取磁盘文件失败"),
​
    CALL_THIRD_PARTY_SERVICE_ERROR("C0001", "调用第三方服务出错"),
    MIDDLEWARE_SERVICE_ERROR("C0100", "中间件服务出错"),
    INTERFACE_NOT_EXIST("C0113", "接口不存在"),
​
    MESSAGE_SERVICE_ERROR("C0120", "消息服务出错"),
    MESSAGE_DELIVERY_ERROR("C0121", "消息投递出错"),
    MESSAGE_CONSUMPTION_ERROR("C0122", "消息消费出错"),
    MESSAGE_SUBSCRIPTION_ERROR("C0123", "消息订阅出错"),
    MESSAGE_GROUP_NOT_FOUND("C0124", "消息分组未查到"),
​
    DATABASE_ERROR("C0300", "数据库服务出错"),
    DATABASE_TABLE_NOT_EXIST("C0311", "表不存在"),
    DATABASE_COLUMN_NOT_EXIST("C0312", "列不存在"),
    DATABASE_DUPLICATE_COLUMN_NAME("C0321", "多表关联中存在多个相同名称的列"),
    DATABASE_DEADLOCK("C0331", "数据库死锁"),
    DATABASE_PRIMARY_KEY_CONFLICT("C0341", "主键冲突");
​
    private String code;
    private String msg;
​
    ResultCodeEnum(String code, String msg) {
        this.code = code;
        this.msg = msg;
    }
​
    @Override
    public String getCode() {
        return code;
    }
​
    @Override
    public String getMsg() {
        return msg;
    }
​
    public static ResultCodeEnum getValue(String code) {
        for (ResultCodeEnum value : values()) {
            if (value.getCode().equals(code)) {
                return value;
            }
        }
        return SYSTEM_EXECUTION_ERROR;
    }
}
​

2.常用静态变量key

package com.fagejiang.single.properties;
​
/**
 * 我还没有写描述
 *
 * @author <发哥讲Java-694204477@qq.com>
 * @version 1.0
 * @date 2021/6/6 13:29
 */
public interface AuthConstants {
​
    /**
     * 认证请求头key
     */
    String AUTHORIZATION_KEY = "Authorization";
​
    /**
     * JWT令牌前缀
     */
    String AUTHORIZATION_PREFIX = "bearer ";
​
​
    /**
     * Basic认证前缀
     */
    String BASIC_PREFIX = "Basic ";
​
    /**
     * JWT载体key
     */
    String JWT_PAYLOAD_KEY = "payload";
​
    /**
     * JWT ID 唯一标识
     */
    String JWT_JTI = "jti";
​
    /**
     * JWT ID 唯一标识
     */
    String JWT_EXP = "exp";
​
    /**
     * Redis缓存权限规则key
     */
    String PERMISSION_ROLES_KEY = "auth:permission:roles";
​
    /**
     * 黑名单token前缀
     */
    String TOKEN_BLACKLIST_PREFIX = "auth:token:blacklist:";
​
    String CLIENT_DETAILS_FIELDS = "client_id, CONCAT('{noop}',client_secret) as client_secret, resource_ids, scope, "
            + "authorized_grant_types, web_server_redirect_uri, authorities, access_token_validity, "
            + "refresh_token_validity, additional_information, autoapprove";
​
    String BASE_CLIENT_DETAILS_SQL = "select " + CLIENT_DETAILS_FIELDS + " from oauth_client_details";
​
    String FIND_CLIENT_DETAILS_SQL = BASE_CLIENT_DETAILS_SQL + " order by client_id";
​
    String SELECT_CLIENT_DETAILS_SQL = BASE_CLIENT_DETAILS_SQL + " where client_id = ?";
​
    /**
     * 密码加密方式
     */
    String BCRYPT = "{bcrypt}";
​
    String USER_ID_KEY = "user_id";
​
    String USER_NAME_KEY = "username";
​
    String CLIENT_ID_KEY = "client_id";
​
    /**
     * JWT存储权限前缀
     */
    String AUTHORITY_PREFIX = "ROLE_";
​
    /**
     * JWT存储权限属性
     */
    String JWT_AUTHORITIES_KEY = "authorities";
​
​
    /**
     * 有来商城后台管理客户端ID
     */
    String ADMIN_CLIENT_ID = "xxx-admin";
​
​
    /**
     * 有来商城微信小程序客户端ID
     */
    String WEAPP_CLIENT_ID = "xxx-weapp";
​
    /**
     * 后台管理接口路径匹配
     */
    String ADMIN_URL_PATTERN = "**/api.admin/**";
​
​
    String LOGOUT_PATH = "/xxx-auth/oauth/logout";
​
​
    String GRANT_TYPE_KEY = "grant_type";
​
    String REFRESH_TOKEN = "refresh_token";
}
​

3.jdbc数据库连接池

    <dependencies>
        <!-- https://mvnrepository.com/artifact/com.zaxxer/HikariCP -->
        <dependency>
            <groupId>com.zaxxer</groupId>
            <artifactId>HikariCP</artifactId>
            <version>4.0.3</version>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.17</version>
        </dependency>
        <!--可以使用自动setter和getter以及日志注解-->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.4</version>
        </dependency>
        <!--添加 slf4j 实现类-->
        <!-- https://mvnrepository.com/artifact/ch.qos.logback/logback-classic -->
        <dependency>
            <groupId>ch.qos.logback</groupId>
            <artifactId>logback-classic</artifactId>
            <version>1.2.3</version>
        </dependency>
    </dependencies>

配置文件 - hikaricp

driverClassName=com.mysql.cj.jdbc.Driver
jdbcUrl=jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=Asia/Shanghai
username=root
password=root

配置文件 - logback.xml

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
    <!-- 配置文件xml 参考:https://logback.qos.ch/manual/configuration.html -->
    <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
        <!-- encoders are assigned the type
             ch.qos.logback.classic.encoder.PatternLayoutEncoder by default -->
        <encoder>
            <pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
        </encoder>
    </appender>
    <!-- 设置 日志打印级别 -->
    <root level="info">
        <appender-ref ref="STDOUT"/>
    </root>
</configuration>
​

数据库连接池工具类

package com.fagejiang.single.datasource;
​
import com.zaxxer.hikari.HikariConfig;
import com.zaxxer.hikari.HikariDataSource;
import lombok.extern.slf4j.Slf4j;
​
import javax.sql.DataSource;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.sql.*;
import java.util.Objects;
import java.util.Properties;
​
/**
 * HikariCP jdbc 数据库连接池工具
 *
 * @author <发哥讲Java-694204477@qq.com>
 * @version 1.0
 * @date 2021/6/6 13:39
 */
@Slf4j
public class HikariCPUti {
​
    private final static HikariCPUti HIKARI_CP_UTI = new HikariCPUti();
​
    private static Properties properties = null;
    private static HikariDataSource dataSource = null;
​
    //1.单例模式中,创建私有构造方法
    private HikariCPUti() {
        // 私有构造
    }
​
    /**
     * 1.配置和获取数据库连接配置信息
     * 2.扩展HikariCP功能,进行配置
     * 3.获取数据库连接,提供对外获取数据库资源的方法
     */
​
    private void initConfig() throws IOException {
        String filePath = Objects.requireNonNull(HikariCPUti.class.getClassLoader().getResource("hikaricp.properties")).getFile();
        FileReader fileReader = new FileReader(filePath);
        properties = new Properties();
        properties.load(fileReader);
        properties.forEach((k, v) -> {
            log.debug(String.format("key:%s value:%S", k, v));
        });
        log.info("初始化配置文件成功.....");
    }
​
    private void registerHikariCP() {
        if (null != dataSource) {
            return;
        }
        HikariConfig config = new HikariConfig(properties);
        dataSource = new HikariDataSource(config);
    }
​
    //2.提供对外 获取 HikariCPDatasource 的方法
    public static DataSource getHikariCPDataSource() {
        if (null != dataSource) {
            return dataSource;
        }
        try {
            HIKARI_CP_UTI.initConfig();
            HIKARI_CP_UTI.registerHikariCP();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return dataSource;
    }
​
    public static void main(String[] args) {
        for (int i = 0; i < 10; i++) {
            new Thread(() -> {
                DataSource dataSource = HikariCPUti.getHikariCPDataSource();
                System.out.println(Thread.currentThread().getName() + " dataSource = " + dataSource);
                //Thread-5 dataSource = HikariDataSource (HikariPool-6)
                // 可以明显的看出来 是默认使用了数据库连接池 HikariPool....
            }).start();
        }
        /**
         * 测试和验证 datasource 的准确性
         */
        String sql = "SELECT NOW() nowDate, ROUND(( RAND()* 100 )) randVal;";
        // 获取数据库资源
        DataSource dataSource = HikariCPUti.getHikariCPDataSource();
​
        // 使用 try-resource-catch 方式,自动关闭资源
        try (
                //获取数据库连接
                Connection connection = dataSource.getConnection();
                //预编译
                PreparedStatement preparedStatement = connection.prepareStatement(sql);
                //获取结果
                ResultSet resultSet = preparedStatement.executeQuery();
        ) {
            ResultSetMetaData metaData = preparedStatement.getMetaData();
            while (resultSet.next()) {
                int columnCount = metaData.getColumnCount();
                for (int i = 1; i <= columnCount; i++) {
                    log.info(metaData.getColumnName(i) + " : " + resultSet.getObject(i));
                }
            }
        } catch (SQLException sqlException) {
            sqlException.printStackTrace();
        }
    }
​
}
​

喜欢小编的可以关注公众号 发哥讲Java, 点击联系我,可以获取到我的私人微信,拉你进群

 

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

航迹者

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值