Java 反射

它提供了动态性和灵活性,使得程序可以在运行时动态地加载类、调用方法、访问字段等,而不需要在编译时确定这些操作。

1. 动态加载类

在运行时根据类名动态加载类,而不是在编译时硬编码类名。 – 插件化架构:根据配置文件或用户输入动态加载类。

2. 创建对象

通过反射可以在运行时动态创建对象,即使类的构造函数是私有的。

  • 工厂模式:根据配置动态创建对象。
  • 依赖注入框架:Spring 通过反射创建 Bean 实例。

3. 调用方法

通过反射可以在运行时动态调用对象的方法,即使方法是私有的。

  • 测试框架:JUnit 通过反射调用测试方法。

4. 访问字段

通过反射可以在运行时动态访问对象的字段,即使字段是私有的

  • 序列化和反序列化:通过反射访问对象的字段。
  • 对象关系映射(ORM):Hibernate 通过反射访问实体类的字段。

7. 注解处理

通过反射可以获取类、方法、字段上的注解,并根据注解执行相应的逻辑。

24/4/14

1️⃣ 反射的作用

  • 获取类中的所有信息(e.g.持久化、IDE的代码提示)
 1Field[] fields = cls.getDeclaredFields();
 2for (int i = 0; i < fields.length; i++) {
 3    fields[i].setAccessible(true);
 4    // String name = fields[i].getName();  // 属性名 第一次先生成列名
 5    Object value = fields[i].get(obj);
 6    bw.append(value.toString());
 7    if (i < fields.length - 1) {
 8        bw.append(", ");
 9    }
10}
11bw.newLine();
  • 结合配置文件动态创建对象
 1Properties prop = new Properties();
 2
 3String filename = "prop.properties";
 4FileInputStream is = new FileInputStream(filename);
 5
 6prop.load(is);
 7
 8String classname = prop.getProperty("classname");
 9String method = prop.getProperty("method");
10String name = prop.getProperty("name");
11
12Class cls = Class.forName(classname);
13Constructor con = cls.getDeclaredConstructor();
14Object o = con.newInstance();
15
16Method setName = cls.getMethod("setName", String.class);
17setName.invoke(o, name);
18
19Method met = cls.getDeclaredMethod(method);
20met.invoke(o);

2️⃣ 获取Class的三种方式

  • Class.forname(“全类名”)
  • 类名.class
  • 对象.getClass()

3️⃣ 常用信息

  • Constructor(构造器) Parameter(方法参数) Field(成员变量) Modifiers(权限修饰符) Method(成员方法) Declared(用于获取私有信息)

Java 动态代理

无侵入的给代码添加额外的功能

流程:

  • 准备:具体行为对象 + 抽象行为接口 + 代理类
  • 执行:数据 => 代理类(抽象行为接口) => InvocationHandler.invoke

钩子之类的,插入代码

 1public interface Star                 (抽象接口定义行为)
 2public class BigStar implements Star  (具体类名实现行为)
 3
 4
 5/*
 6* ClassLoader: 用于加载代理类的类加载器 (具体)
 7* Interface  : 代理类需要实现的接口列表 (抽象)
 8* InvocationHandler:  execute proxy task
 9*/
10Star star = (Star) Proxy.newProxyInstance(
11    bigStar.getClass().getClassLoader(),
12    new Class[]{Star.class},
13    new InvocationHandler() {
14        @Override
15        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
16            if (method.getName().equals("sing")) {
17                System.out.println("准备话筒,收钱");
18            }else if (method.getName().equals("dance")) {
19                System.out.println("准备舞台,收钱");
20            }
21
22            return method.invoke(bigStar, args);
23        }
24    }
25);
26
27// 使用代理
28BigStar kun = new BigStar("KunKun");
29Star proxy = ProxyUtil.createProxy(kun);
30String result = proxy.sing("只因你太美");
31System.out.println(result);

Java 泛型

类型参数化

1️⃣ 编译时检查

不符合的编译通过不了

2️⃣ 代码复用

1public static <T extends Number> Double add(T a, T b) {
2    return a.doubleValue() + b.doubleValue();
3}
4
5// 函数签名 <T extends Number> 用于限定参数类型
6// int float double 都OK
  • 泛型类
 1// Class指定的T会传递给成员变量和成员方法
 2public class GenericClass <T>{
 3    private T good;
 4
 5    public T getGood(T var) {
 6        System.out.println(var);
 7        return this.good;
 8    }
 9
10    public void setGood(T good) {
11        this.good = good;
12    }
13}
14
15// 指定多个泛型类型
16public class MultiGenericClass<K, V> {
17    private K key;
18    private V value;
19
20    public K getKey() {
21        return key;
22    }
23
24    public void setKey(K key) {
25        this.key = key;
26    }
27
28    public V getValue(K key) {
29        return value;
30    }
31
32    public void setValue(V value) {
33        this.value = value;
34    }
35}
  • 泛型接口
 1// 就是实例化对象时 再指定参数类型
 2public interface GenericInterface <K, V>{
 3    public abstract V getValue(K key);
 4}
 5
 6class myMap<K, V> implements GenericInterface<K, V> {
 7
 8    private HashMap<K, V> map = new HashMap<>();
 9
10    @Override
11    public V getValue(K key) {
12        return map.get(key);
13    }
14
15    public void put(K key, V value) {
16        map.put(key, value);
17    }
18}
  • 泛型方法

函数签名表示此方法是泛型方法,并且这个与Class的无关,限定在方法部分 作用范围!!!

 1// 泛型结合反射,动态创建对象,可以进行初始化
 2public <T> T getObject(Class<T> c) throws NoSuchMethodException {
 3    Constructor<T> constructor = c.getConstructor();
 4    T t = constructor.newInstance();
 5    return t;
 6}
 7
 8public static <T extends Number> Double add(T a, T b) {
 9    return a.doubleValue() + b.doubleValue();
10}
  • 泛型边界
1// ? 是 父类  - 下界
2public static void funcAAA(List<? super Student> sList){
3    System.out.println(sList);
4}
5
6// ? 是 子类  - 上界
7public static void funcAA(List<? extends Person> pList){
8    System.out.println(pList);
9}

泛型擦除:

​ 为了兼容java没有泛型特性的老版本。在编译阶段会进行所谓的“类型擦除”。将所有的泛型表示(尖括号中的内容)都替换为具体的类型(其对应的原生态类型)、

  • 消除类型参数声明,即删除<>及其包围的部分。
  • 据类型参数的上下界推断并替换所有的类型参数为原生态类型(往父类转,更包容)
  • 自动产生“桥接方法”以保证擦除类型后的代码仍然具有泛型的“多态性”

无限制类型擦除 - <T><?>的类型参数都被替换为Object

有限制类型擦除 - <T extends Number><? extends Number>的类型参数被替换为Number

<? super Number>被替换为Object

桥接 - 编译阶段类型擦除时执行

保证多态特性 - 因为可以多个子类继承父类,父类的类型又是泛型,不能被某个子类的泛型限制住!

 1// 父类定义
 2class Pair<T> {  
 3    private T value;  
 4    public T getValue() {  
 5        return value;  
 6    }  
 7    public void setValue(T  value) {  
 8        this.value = value;  
 9    }  
10} 
11
12// 子类定义
13class DateInter extends Pair<Date> {  
14
15    @Override  
16    public void setValue(Date value) {  
17        super.setValue(value);  
18    }  
19
20    @Override  
21    public Date getValue() {  
22        return super.getValue();  
23    }  
24}
25
26// 编译时 类型擦除后
27class Pair {  
28    private Object value;  
29    public Object getValue() {  
30        return value;  
31    }  
32    public void setValue(Object  value) {  
33        this.value = value;  
34    }  
35}

image-20240415172031269

生成中间的桥接方法,进行连接,维护多态性。

image-20241122125640349


Java 注解

 1@Target({ElementType.METHOD, ElementType.PARAMETER})	// 放置在哪
 2@Retention(RetentionPolicy.RUNTIME)						// 存活时间
 3public @interface Log {
 4    // 模块
 5    String title() default "";
 6
 7    // 功能
 8    BusinessType businessType() default BusinessType.OTHER;
 9
10    // 操作人员
11    OperatorType operatorType() default OperatorType.MANGE;
12
13    // 是否保存请求参数
14    boolean isSaveRequestData() default true;
15}

与AOP结合,实现切入

 1@Aspect				// 给Spring托管,并且此注解标识这个类实现切入功能
 2@Component
 3public class LogAspect {
 4
 5    @Pointcut("@annotation(log_aop.Log)")	// 检测带有@Log注解的部分
 6    public void logPointCut(){}
 7
 8
 9    @Before("logPointCut()")				// 在切入点前执行操作
10    public void logPointCutBefore(JoinPoint joinPoint) {
11        System.out.println("在切入点前先执行");
12        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
13        Method method = signature.getMethod();
14
15        System.out.println(method.getName());
16        Log annotation = method.getAnnotation(Log.class);
17        if (annotation != null) {
18            System.out.println(annotation.title());
19            System.out.println(annotation.isSaveRequestData());
20            System.out.println(annotation.businessType());
21            System.out.println(annotation.operatorType());
22        } else {
23            System.err.println("没有获取到注解信息");
24        }
25    }
26}

Spring Boot 简单实现

 1@RestController
 2public class Controller {
 3
 4    @Log(title = "模拟查询信息", businessType = BusinessType.SELECT, operatorType = OperatorType.PERSON)
 5    @GetMapping("/test")
 6    public Map<String, Object> test() {
 7        System.out.println("执行处理请求操作");
 8        HashMap<String, Object> map = new HashMap<>();
 9        map.put("code", 200);
10        map.put("msg", "success");
11
12        return map;
13    }
14
15}

Java 多线程

创建线程的三种方式

- 单继承多实现

 1public class ThreadFirstWay extends Thread{
 2
 3    @Override
 4    public void run() {
 5        for (int i = 0; i < 20; i++) {
 6            System.out.println(this.getName() + " print : " + i);
 7        }
 8    }
 9}
10
11new ThreadFirstWay().start()
12
13// 更灵活
14-------------------------------------------------------
15public class ThreadSecondWay implements Runnable{
16
17    @Override
18    public void run() {
19        for (int i = 0; i < 20; i++) {
20            Thread thread = Thread.currentThread();
21            System.out.println(thread.getName() + " print : " + i);
22        }
23    }
24}
25
26new Thread(new ThreadSecondWay()).start()
27    
28// 返回结果
29-------------------------------------------------------
30class MyCallable implements Callable<Integer> {
31
32    @Override
33    public Integer call() throws Exception {
34        int sum = 0;
35        for (int i = 0; i < 20; i++) {
36            sum += i;
37        }
38        return sum;
39    }
40}
41
42MyCallable callable = new MyCallable();
43FutureTask<Integer> task = new FutureTask<>(callable);
44
45Thread thread1 = new Thread(task);
46thread1.start();
47
48Integer result = task.get();
49System.out.println(result);

image-20250109195552849

线程池

 1// newCachedThreadPool: 会复用之前的线程(空闲, 超出时间会关闭)
 2// newFixedThreadPool: 固定线程数,任务在阻塞队列中排队
 3
 4ExecutorService threadPool = Executors.newFixedThreadPool(2);
 5threadPool.submit(new MyRunnable("@Run 1"));
 6
 7// 更详细
 8-------------------------------------------------------
 9ThreadPoolExecutor threadPool = new ThreadPoolExecutor(
10    corePoolSize=2,	// 核心线程
11    maximumPoolSize=3,  // 临时线程
12    KeepAliveTime=60, // 临时线程存活时间
13    TimeUnit.SECONDS,
14    new ArrayBlockingQueue<>(3),  // 阻塞队列 
15    new ThreadPoolExecutor.AbortPolicy() // 默认抛出异常
16);
17
18threadPool.execute(new TestRunnable("@Runnable 1"));

单生产单消费者

使用 synchronized(唯一对象) 实现临界区

 1// 单生产单消费的同步
 2class Desk {
 3    // 简化版 1有,0无
 4    public static int foodFlag = 0;
 5
 6    // 总个数
 7    public final static int total = 10;
 8
 9    // 剩余数
10    public static int count = 10;
11
12    // 锁
13    public static Object lock = new Object();
14}
15
16// 消费者
17class Consumer extends Thread {
18
19    @SneakyThrows
20    @Override
21    public void run() {
22        while (true) {
23            synchronized (Desk.lock) {
24                // 美食家是否吃饱
25                if (Desk.count == 0) {
26                    break;
27                } else {
28                    // 是否做好食物
29                    if(Desk.foodFlag == 0) {
30                        // 等待生产者制作食物
31                        Desk.lock.wait();
32                    }else {
33                        // 消耗掉一份食物
34                        Desk.count--;
35
36                        // 吃食物
37                        System.out.println("消费者正在吃第" + (Desk.total-Desk.count) + "碗食物");
38
39                        // 通知生产者制作食物
40                        Desk.lock.notifyAll();
41
42                        // 是否存在食物
43                        Desk.foodFlag = 0;
44                    }
45                }
46            }
47        }
48    }
49
50}
51
52
53class Producer extends Thread {
54    @SneakyThrows
55    @Override
56    public void run() {
57        while (true) {
58            // 占用桌子
59            synchronized (Desk.lock) {
60                // 美食家是否吃饱
61                if(Desk.count == 0) {
62                    break;
63                }else {
64                    // 上份食物是否存在
65                    if(Desk.foodFlag == 1) {
66                        // 等食物被吃
67                        Desk.lock.wait();
68                    } else { // 制作食物
69                        System.out.println("生产者制作第" + (Desk.total - Desk.count + 1) + "份食物");
70
71                        Desk.foodFlag = 1;
72
73                        Desk.lock.notifyAll();
74                    }
75                }
76            }
77        }
78    }
79}

简单抽奖

 1// 简单考虑 100%中奖
 2class LotteryBox extends Thread {
 3    public final static int[] Pond = {2, 5, 10, 20, 50, 100, 200, 300, 500, 1000};
 4
 5    public static int[] count = {10, 9, 8, 7, 6, 5, 4, 3, 2, 1};
 6
 7    public static int totalNumber = 100;
 8    public static Set<Integer>[] winningNumber = new Set[count.length];
 9    public static List<Integer> number = new ArrayList<>();
10
11    public static int numberIdx = 0;
12
13    public static ReentrantLock lock = new ReentrantLock();
14
15    public static void initData() {
16        // 初始化数组中的每个 Set
17        for (int i = 0; i < winningNumber.length; i++) {
18            winningNumber[i] = new HashSet<>();
19        }
20
21        for (int i = 1; i <= totalNumber; i++) {
22            number.add(i);
23        }
24
25        Collections.shuffle(number);
26
27        int idx = 0;
28        // 指定中奖号码
29        for (int i = 0; i < count.length; i++) {
30            for (int j = 0; j < count[i]; j++) {
31                winningNumber[i].add(number.get(idx));
32                idx += 1;
33            }
34        }
35
36        Collections.shuffle(number);
37    }
38
39
40    @Override
41    public void run() {
42        while (true) {
43            LotteryBox.lock.lock();
44            if (LotteryBox.numberIdx < LotteryBox.totalNumber) {
45
46                int n = number.get(LotteryBox.numberIdx);
47                LotteryBox.numberIdx += 1;
48                LotteryBox.lock.unlock();
49
50                int rank = -1;
51                for (int j = 0; j < winningNumber.length; j++) {
52                    if (winningNumber[j].contains(n)) {
53                        rank = j;
54                    }
55                }
56
57                if (rank == -1) {
58                    System.out.println(getName() + "抽到的" + n + "号码没有中奖!");
59                } else {
60                    System.out.println(getName() + "抽到的" + n + "号码中了" + Pond[rank] + "元");
61                }
62            } else {
63                LotteryBox.lock.unlock();
64                System.out.println(getName() + ": 奖池已经抽取完毕!");
65                break;
66            }
67        }
68    }
69}

邮件服务

ArrayBlockingQueue 资源不够,会自动阻塞和唤醒线程

  1package thread;
  2
  3import lombok.AllArgsConstructor;
  4import lombok.Data;
  5import lombok.NoArgsConstructor;
  6import lombok.SneakyThrows;
  7
  8import java.io.Serializable;
  9import java.util.concurrent.ArrayBlockingQueue;
 10import java.util.concurrent.ExecutorService;
 11import java.util.concurrent.Executors;
 12
 13/**
 14 * Date: 2024/4/19
 15 */
 16public class EmailServer {
 17    public static void main(String[] args) throws InterruptedException {
 18        MailService service = new MailService();
 19
 20        ExecutorService threadPool = Executors.newFixedThreadPool(2);
 21
 22        ConsumeEmailQueue runnable1 = new ConsumeEmailQueue("@服务1", service);
 23        ConsumeEmailQueue runnable2 = new ConsumeEmailQueue("$服务2", service);
 24        threadPool.submit(runnable1);
 25        threadPool.submit(runnable2);
 26
 27        for (int i = 0; i < 5; i++) {
 28            Email email = new Email("title"+i, "content"+i);
 29            MailQueue.getMailQueue().produce(email);
 30            Thread.sleep(3000);
 31        }
 32    }
 33}
 34
 35
 36class MailService {
 37    public void sendEmail(Email email) {
 38        System.out.println("-------------------------");
 39        System.out.println("来新邮件了!");
 40        System.out.println(email);
 41        System.out.println("-------------------------");
 42    }
 43}
 44
 45class ConsumeEmailQueue implements Runnable {
 46    private MailService mailService;
 47    private String name;
 48
 49    public ConsumeEmailQueue(String name, MailService mailService) {
 50        this.name = name;
 51        this.mailService = mailService;
 52    }
 53
 54    @SneakyThrows
 55    @Override
 56    public void run() {
 57        while (true) {
 58            Email email = MailQueue.getMailQueue().consume();
 59            if (email != null) {
 60                System.out.println(name + "取走了一封邮件");
 61                System.out.println("剩余邮件: " + MailQueue.getMailQueue().size());
 62                mailService.sendEmail(email);
 63            }
 64            System.out.println("消费者检查了一轮\n");
 65        }
 66    }
 67}
 68
 69@Data
 70@NoArgsConstructor
 71@AllArgsConstructor
 72class Email implements Serializable {
 73    private String title;
 74    private String content;
 75}
 76
 77class MailQueue {
 78    // 队列大小
 79    public static int QUEUE_MAX_SIZE = 1000;
 80
 81    // 阻塞队列
 82    public static ArrayBlockingQueue<Email> blockingQueue = new ArrayBlockingQueue<>(100);
 83
 84    // 私有化构造器
 85    private MailQueue() {
 86    }
 87    // 实现单例模式
 88    private static class SingletonHolder {
 89        private static MailQueue mailQueue = new MailQueue();
 90    }
 91
 92    public static MailQueue getMailQueue() {
 93        return SingletonHolder.mailQueue;
 94    }
 95
 96    // 生产
 97    public void produce(Email email) throws InterruptedException {
 98        blockingQueue.put(email);
 99    }
100
101    // 消费
102    public Email consume() throws InterruptedException {
103        return blockingQueue.take();
104    }
105
106    public int size() {
107        return blockingQueue.size();
108    }
109}

Java Websocket

简易共享聊天室

服务端
1// 自定义终端配置类,目的就是在建立连接时,保存一些数据以供访问  - httpSession 保存了请求的信息
2public class GetHttpSessionConfigurator extends ServerEndpointConfig.Configurator {
3    @Override
4    public void modifyHandshake(ServerEndpointConfig sec, HandshakeRequest request, HandshakeResponse response) {
5        HttpSession httpSession = (HttpSession) request.getHttpSession();
6        sec.getUserProperties().put(HttpSession.class.getName(), httpSession);
7    }
8}
 1// 一个Endpoint == 一个客户端
 2// 服务终端对象, configurator关联自定义的配置器类,目的是让 WebSocket 会话能够访问存储在 HTTP 会话中的数据
 3@ServerEndpoint(value = "/chat", configurator = GetHttpSessionConfigurator.class)
 4@Component
 5public class ChatEndpoint {
 6    // 维护一份共享的连接终端在线信息。  多个线程频繁地访问和修改共享的键值对集合时,ConcurrentHashMap 是一个合适的选择
 7    private static Map<String, ChatEndpoint> onlineUsers= new ConcurrentHashMap<>(); // user => endpoint
 8
 9    // 用来发送信息给对应终端
10    private Session session;
11
12    // 用来获取请求的信息,标识发送对象
13    private HttpSession httpSession;
14
15    // 首次连接, 在HTTP握手连接时已经把<username, HttpSession>放入config中
16    @OnOpen
17    public void onOpen(Session session, EndpointConfig config) {
18        // 初始化数据
19        this.session = session;
20        HttpSession httpSession = (HttpSession) config.getUserProperties().get(HttpSession.class.getName());
21        this.httpSession = httpSession;
22
23        String user = (String) httpSession.getAttribute("user");
24        onlineUsers.put(user, this);
25
26        // 广播通知 更新在线用户,让别人知道你上线了
27        String message = MessageUtils.getMessage(true, null, getAllOnlineUsers());
28        broadcastAllUsers(message);
29    }
30
31    // 广播信息给所有在线用户
32    private void broadcastAllUsers(String message) {
33        try {
34            Set<String> names = onlineUsers.keySet();
35            for (String name : names) {
36                ChatEndpoint chatEndpoint = onlineUsers.get(name);
37                chatEndpoint.session.getBasicRemote().sendText(message); // @@@ 核心发送信息的代码
38            }
39        } catch (IOException e) {
40            e.printStackTrace();
41        }
42    }
43
44    // 获取所有在线用户
45    private Set<String> getAllOnlineUsers() {
46        return onlineUsers.keySet();
47    }
48
49    // @@@ 核心 => 转发信息
50    @OnMessage
51    public void onMessage(String message, Session session) {
52        try {
53            ObjectMapper mapper = new ObjectMapper();
54            Message msg = mapper.readValue(message, Message.class);
55            String toName = msg.getToName();
56            String data = msg.getMessage();
57            String user = (String) httpSession.getAttribute("user");
58            String resultMsg = MessageUtils.getMessage(false, user, data);
59
60            onlineUsers.get(toName).session.getBasicRemote().sendText(resultMsg);
61        } catch (IOException e) {
62            e.printStackTrace();
63        }
64    }
65
66    @OnClose
67    public void onClose(Session session) {
68        String user = (String) httpSession.getAttribute("user");
69        onlineUsers.remove(user);
70
71        String message = MessageUtils.getMessage(true, null, getAllOnlineUsers());
72        broadcastAllUsers(message);
73    }
74}
客户端
 1var ws = new WebSocket("ws://localhost:8080/chat");
 2        ws.onopen = function (e) {
 3            $("#username").html("用户:"+ username +"<span>在线</span>");
 4        }
 5        //接受消息
 6        ws.onmessage = function (ev) {
 7            var datastr = ev.data;
 8            var res = JSON.parse(datastr);
 9            //判断是否是系统消息
10            if(res.system){
11                //好友列表
12                //系统广播
13                var names = res.message;
14                var userlistStr = "";
15                var broadcastListStr = "";
16                for (var name of names){
17                    if (name != username){
18                        userlistStr += "<a onclick='showChat(\""+name+"\")'>"+ name +"</a></br>";
19                        broadcastListStr += "<p>"+ name +"上线了</p>";
20                    }
21                };
22                $("#hylist").html(userlistStr);
23                $("#xtlist").html(broadcastListStr);
24
25            }else {
26                //不是系统消息
27                var str = "<span id='mes_left'>"+ res.message +"</span></br>";
28                if (toName == res.fromName)
29                    $("#content").append(str);
30
31                var chatdata = sessionStorage.getItem(res.fromName);
32                if (chatdata != null){
33                    str = chatdata + str;
34                }
35                sessionStorage.setItem(res.fromName, str);
36
37            };
38        }
39
40        ws.onclose = function (ev) {
41            $("#username").html("用户:"+ username +"<span>离线</span>");
42        }
43
44        showChat = function(name){
45            // alert("dsaad");
46            toName = name;
47            //清空聊天区
48            $("#content").html("");
49            $("#new").html("当前正与"+toName+"聊天");
50            var chatdata = sessionStorage.getItem(toName);
51            if (chatdata != null){
52                $("#content").html(chatdata);
53            }
54        };
55        //发送消息
56        $("#submit").click(function () {
57            //获取输入的内容
58            var data = $("#input_text").val();
59            $("#input_text").val("");
60            var json = {"toName": toName ,"message": data};
61            //将数据展示在聊天区
62            var str = "<span id='mes_right'>"+ data +"</span></br>";
63            $("#content").append(str);
64
65            var chatdata = sessionStorage.getItem(toName);
66            if (chatdata != null){
67                str = chatdata + str;
68            }
69            sessionStorage.setItem(toName,str);
70            //发送数据
71            ws.send(JSON.stringify(json));
72        })
伪代码框架

服务器端

 1public class GetHttpSessionConfigurator extends ServerEndpointConfig.Configurator {
 2    @Override
 3    public void modifyHandshake() {
 4		// 保存请求的信息,后续访问
 5    }
 6}
 7
 8// 一个Endpoint == 一个客户端, value==url
 9// 服务终端 configurator关联自定义的配置器类,目的是让 WebSocket 会话能够访问存储在 HTTP 会话中的数据
10@ServerEndpoint(value = "/chat", configurator = GetHttpSessionConfigurator.class)
11@Component
12public class ChatEndpoint {
13
14    // 首次连接, 在HTTP握手连接时已经把<username, HttpSession>放入config中
15    @OnOpen
16    public void onOpen(Session session, EndpointConfig config) {
17        // 会话建立逻辑
18    }
19
20    // @@@ 核心 => 转发信息  chatEndpoint.session.getBasicRemote().sendText(message); @@@ 核心发送信息的代码
21    @OnMessage
22    public void onMessage(String message, Session session) {
23        // 接受终端消息逻辑
24    }
25
26    @OnClose
27    public void onClose(Session session) {
28		// 会话关闭逻辑
29    }
30}

客户端

 1var ws = new WebSocket("ws://localhost:8080/chat");
 2
 3ws.onopen = function (e) {
 4    // 会话建立逻辑
 5}
 6
 7ws.onmessage = function (ev) {
 8    // 消息接收逻辑
 9}
10
11ws.onclose = function (ev) {
12    // 会话关闭逻辑
13}
14
15// 主动发送数据
16// ws.send(JSON.stringify(data));

Nginx

 1# 正向代理
 2Client            Server  
 3X1 \\
 4X2 == =>  T(VPN) => Y
 5X3 //
 6
 7# 反向代理
 8                      //  Y1
 9X == =>  T(Nginx) =>  ==  Y2
10                      \\  Y3

nginx启停

注意linux权限,若无权限,则加sudo

1nginx -s ${signal}
2quit: 停止
3reload: 重新加载配置文件

nginx 配置

 1# 全局配置信息
 2worker_processes auto; # main进程 { 多个worker进程 }
 3
 4events {
 5	worker_connections 1024;   # 网络连接数
 6}
 7
 8http {
 9	...
10	server{ ... }
11	server{ ... }
12}
 1server {
 2        listen 80 default_server;	# 监听ipv4-端口(80)  
 3        listen [::]:80 default_server;	# 监听ipv6-端口(80)
 4
 5		root /var/www/html;		# 为请求提供文件的根目录
 6		index index.html index.htm index.nginx-debian.html;  # 定义了当请求为目录时,默认尝试返回的文件列表
 7
 8		server_name _;
 9		location / {	# 对于根URL(即/)的请求的处理规则
10                # 尝试按顺序找到对应的文件或目录,如果都找不到,则返回404错误(未找到)
11                try_files $uri $uri/ =404;
12        }   

反向代理

 1# nginx.config 中
 2
 3http {
 4	...
 5	upstream backend {	# default weight=1, 默认进行轮询
 6		ip_hash; // 终端ip和服务器ip绑定到一起
 7		server ?.?.?.?:? weight=?;
 8		server ?.?.?.?:?;
 9		server ?.?.?.?:?;
10	}
11	
12	server {
13		...
14		location /app {
15			proxy_pass http://backend/;
16		}
17	}
18	...
19}

解释:⭐upstream ? { … } 定义了上游服务器信息,ip_hash的作用就是让终端和某个服务器绑定(解决session问题),weight参数是分发数量的权重,默认1,越大往这个服务器分发的任务越多。 ⭐server中添加映射 location /app 将访问nginx.ip:80/app的请求转发给 http ://backend/ 的服务,进行均衡负载。

练习

1️⃣ 0.0.0.0 表示本机127.0.0.1和ip-v4

2️⃣ 创建多个Flask服务,模拟多个后端服务器,实验均衡负载

 1from flask import Flask
 2
 3app = Flask(__name__)
 4
 5@app.route('/')
 6def hello_world():
 7    return 'Hello, 800?'
 8
 9if __name__ == '__main__':
10    app.run(host='0.0.0.0', port=800?)
11    
12# 终端运行 python .\main8000.py --port=8000

MybatisPlus

springboot application.yaml 配置

1mybatis-plus:
2  type-aliases-package: com/yoo/entity
3  mapper-locations: classpath:/mapper/**.xml
4  configuration:
5    map-underscore-to-camel-case: true

Mapper模板增强:SQL语句:查询字段、筛选条件 - SQL片段分离

 1@TableName("user")  // 类-绑定数据库-表
 2Class User{...}
 3
 4// BaseMapper 内置大量基本的SQL模板
 5@Mapper
 6public interface UserMapper extends BaseMapper<User> {}
 7
 8// example
 9QueryWrapper wrapper = new QueryWrapper();	// 条件包装器
10wrapper.select("name, age, email");			// 显示指定查询字段
11wrapper.ge("age", 23);						// 条件 greater than
12List list = userMapper.selectList(wrapper);

混合自定义SQL.xml 配置和模板

 1@Mapper
 2public interface UserMapper extends BaseMapper<User> {
 3    Type methodID(...params);
 4    int UPDATEID(@Param(Constants.WRAPPER) LambdaQueryWrapper<User> wrapper, ...params);
 5}
 6
 7// Associated XML file
 8<? id="methodID" resultType="Type">
 9    SQL Segment
10</?>
11    
12// use Wrapper to build condition
13<update id="UPDATEID">
14    UPDATE `table_name`
15    SET ...
16	${ew.customSqlSegment}
17</update>

Service模板增强

 1// interface extends template abstract method
 2@Service
 3public interface IUserService extends IService<User> {}
 4
 5// class extends template implement method
 6@Service
 7public class UserPOServiceImpl extends ServiceImpl<UserPOMapper, UserPO> implements IUserPOService {
 8    @Override
 9    public List<UserPO> queryUserByBalance(int minBalance, int maxBalance) {
10        // lambdaQuery is method in ServiceImpl super class IService
11        return lambdaQuery()
12                .between(UserPO::getBalance, (double)minBalance, (double)maxBalance)
13                .list();
14    }
15}

knife4j 接口说明文档

 1@Api()
 2@RestController
 3@RequestMapping()
 4class Controller {
 5    
 6    @ApiOperation()
 7    @RequestMapping()
 8    public void method() {}
 9    
10}
11
12@ApiModel()
13class Bean{
14    @ApiModelProperty()
15    private Type Field;
16}

批量操作

1MySQL connection URL: add parameter rewriteBatchedStatements=true
2    
3userSerivice.saveBatch()
4    
5// MySQL开启批量操作,MybatisPlus再执行batch级的SQL

⭐ 静态的SQL,避免Service中相互依赖

1Db.lambdaQuery(AddressPO.class)
2                .eq(AddressPO::getUserId, user.getId())
3                .list();
4
5// => 最终调用AddressPOMapper执行基本SQL操作
6
7// AddressPO @TableName("address") => 关联数据库表,下划线转驼峰
8
9// iService 和 ServiceImpl 封装了基本的SQL操作

枚举

1// application.yaml
2mybatis-plus:
3  configuration:
4    default-enum-type-handler: com.baomidou.mybatisplus.core.handlers.MybatisEnumTypeHandler
 1// 定义状态
 2@Getter
 3public enum UserStatus {
 4    NORMAL(1, "正常"),
 5    FREEZE(2, "冻结");
 6
 7    @EnumValue	// 与数据库的转换
 8    private final int value;
 9    @JsonValue  // 响应结果的转换
10    private final String desc;
11
12    UserStatus(int value, String desc) {
13        this.value = value;
14        this.desc = desc;
15    }
16}
17
18// e.g.  Freeze user
19lambdaUpdate()
20    .eq(UserPO::getId, id)
21    .set(UserPO::getStatus, UserStatus.FREEZE)
22    .update();

分页

 1// 注入分页配置,添加分页拦截器
 2@Configuration
 3public class MyBatisConfig {
 4
 5    @Bean
 6    public MybatisPlusInterceptor mybatisPlusInterceptor() {
 7        MybatisPlusInterceptor mybatisPlusInterceptor = new MybatisPlusInterceptor();
 8
 9        PaginationInnerInterceptor paginationInnerInterceptor = new PaginationInnerInterceptor();
10        paginationInnerInterceptor.setDbType(DbType.MYSQL);
11        paginationInnerInterceptor.setMaxLimit(1000L);
12
13        mybatisPlusInterceptor.addInnerInterceptor(paginationInnerInterceptor);
14
15        return mybatisPlusInterceptor;
16    }
17
18}
1// 准备Page信息条件
2Page<AddressPO> page = Page.of(pageNum, pageSize);
3page.addOrder(OrderItem.desc("id"));  // 根据xxx降序/升序
4// 执行查询
5Page<AddressPO> addressPOPage = lambdaQuery().page(page);
6
7return addressPOPage.getRecords();

封装PageQuery和PageDTO

 1@Data
 2class PageQuery {
 3    // properity:
 4    pageSize, pageNum, sortBy, isAsc ...
 5    // method:
 6    public <PO> Page<PO> toMpPage(){
 7        // 1. 分页条件
 8        Page<PO> page = Page.of(pageNum, pageSize);
 9        // 2. 排序条件
10        if (StrUtil.isNotBlank(sortBy)) {
11            page.addOrder(new OrderItem().setColumn(sortBy).setAsc(isAsc));
12        }
13        return page;
14    }
15    
16    ... // 多态性质
17    public <PO> Page<PO> toMpPage(String sortBy, Boolean isAsc) {
18        return toMpPage(new OrderItem().setColumn(sortBy).setAsc(isAsc));
19    }
20    ... // 默认排序...
21}
22
23@Data
24class UserQuery extends PageQuery {
25    // properity: 查询条件
26    name, id, xxx
27}
28
29// use example
30构建UserQuery, 初始化好查询参数 => 调用UserService.method(userQuery)
31method(userQuery)  => userQuery.toMpPage() => Page<PO> p => lambdaQuery.condition().page(p) => getRecords()
32分步构建页面配置(复用) + 查询条件(继承额外加)        
 1@Data
 2public class PageDTO<T> {
 3
 4    private Long total;
 5
 6    private Long pages;
 7
 8    private List<T> list;
 9    
10    // 泛型不指定参数类型, PO=>VO转换器传入 
11	public static <PO, VO> PageDTO<VO> of (Page<PO> p, Class<VO> clazz, Function<PO, VO> converter) {
12        PageDTO<VO> voPageDTO = new PageDTO<>();
13        // 基本信息
14        voPageDTO.setTotal(p.getTotal());
15        voPageDTO.setPages(p.getPages());
16
17        List<PO> poList = p.getRecords();
18        if (CollectionUtils.isEmpty(poList)) {
19            voPageDTO.setList(Collections.emptyList());
20            return voPageDTO;
21        }
22
23        // 拷贝数据
24        List<VO> list = poList.stream().map(converter).collect(Collectors.toList());
25        voPageDTO.setList(list);
26
27        return voPageDTO;
28    }

Spring Cloud

父 pom

 1<properties>
 2    <maven.compiler.source>11</maven.compiler.source>
 3    <maven.compiler.target>11</maven.compiler.target>
 4    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
 5    <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
 6    <org.projectlombok.version>1.18.20</org.projectlombok.version>
 7    <spring-cloud.version>2021.0.3</spring-cloud.version>
 8    <spring-cloud-alibaba.version>2021.0.4.0</spring-cloud-alibaba.version>
 9    <mybatis-plus.version>3.4.3</mybatis-plus.version>
10    <hutool.version>5.8.25</hutool.version>
11    <mysql.version>8.0.23</mysql.version>
12    <knife4j.version>4.3.0</knife4j.version>
13</properties>
14    
15<!-- 对依赖包进行管理 -->
16<dependencyManagement>
17    <dependencies>
18    	<dependency>
19    	...
20        </dependency>
21    </dependencies>
22</dependencyManagement>

子项目 pom

1<parent>
2    <artifactId>super artifactId</artifactId>
3    <groupId>com.yoo</groupId>
4    <version>1.0-SNAPSHOT</version>
5</parent>

统一dependency version

垂直划分项目

User-Service

nacos托管微服务IP地址

 1java:    
 2	com:
 3        yoo:
 4            controller: SpringMVC
 5            service: IService + ServiceImpl   
 6            mapper: BaseMapper
 7            client: @Openfeign, remote call
 8            domain:
 9                   po: properity object reflex database table
10                   vo: value object, only show required properity
11
12----------------------------------                       
13                       
14resource:
15	application.yaml:
16		server:
17  			port: 
18
19        spring:
20          application:
21            name: user-service # service-name
22          profiles:
23            active: dev
24          datasource:
25            url: 
26            driver-class-name: 
27            username: 
28            password: 
29		  #  cloud service ip address
30          cloud:
31            nacos:
32              server-addr: 127.0.0.1
33
34    mybatis-plus:
35      configuration:
36		# 枚举字段类型映射到数据库表指定类型 下划线转驼峰
37        default-enum-type-handler: com.baomidou.mybatisplus.core.handlers.MybatisEnumTypeHandler
38      global-config:
39        db-config:
40          update-strategy: not_null
41          id-type: auto
42
43    knife4j:
44      enable: true
45      openapi:
46        title: 用户服务接口文档
47        description: "信息"
48        email: 1410124534@qq.com
49        concat: longwei
50        group:
51          default:
52            group-name: default
53            api-rule: package
54            api-rule-resources:
55              - com.yoo.controller
OpenFeign 远程调用微服务

Step 1. pom添加对应依赖

 1<!-- openfeign -->
 2<dependency>
 3    <groupId>org.springframework.cloud</groupId>
 4    <artifactId>spring-cloud-starter-openfeign</artifactId>
 5</dependency>
 6    
 7<!-- loadbalancer -->
 8<dependency>
 9    <groupId>org.springframework.cloud</groupId>
10    <artifactId>spring-cloud-starter-loadbalancer</artifactId>
11</dependency>

Step 2. 开启Openfeign功能

1@EnableFeignClients
2...
3public class xxxApplicaiotn {
4    ...
5}

Step 3. 编写Openfeign调用接口

 1@FeignClient(value = "order-service")
 2public interface OrderClient {
 3
 4    @GetMapping("/order/{id}")
 5    List<OrderVO> getOrderByUserId(@PathVariable("id") Long userId);  
 6
 7}
 8
 9// @Interface : @XXXMapping("/{param}")     <===
10//                                             ||
11// Method     : (@PathVariable("param") Type name)

Gateway setup

前端统一请求 =>8080网关 => 分发调度给微服务 类似nginx

dependencies

 1<!--网关-->
 2<dependency>
 3    <groupId>org.springframework.cloud</groupId>
 4    <artifactId>spring-cloud-starter-gateway</artifactId>
 5</dependency>
 6<!--nacos discovery-->
 7<dependency>
 8    <groupId>com.alibaba.cloud</groupId>
 9    <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
10</dependency>
11<!--负载均衡-->
12<dependency>
13    <groupId>org.springframework.cloud</groupId>
14    <artifactId>spring-cloud-starter-loadbalancer</artifactId>
15</dependency>

⭐⭐⭐application.yaml 配置转发接口映射

 1server:
 2  port: 8080
 3
 4spring:
 5  application:
 6    name: gateway-service
 7  cloud:
 8    nacos:
 9      server-addr: 127.0.0.1
10    gateway:
11      routes:  ### 路由
12        - id: item
13          uri: lb://user-service
14          predicates:
15            - Path=/user/**
16
17        - id: order
18          uri: lb://order-service
19          predicates:
20            - Path=/order/**

StarterClass

1@SpringBootApplication
2public class GatewayApplication {
3    public static void main(String[] args) {
4        SpringApplication.run(GatewayApplication.class, args);
5    }
6}

网关登录统一拦截

好比于守卫,统一校验登录Token,进行放行或者拦截

pom 依赖 支持yaml读取配置

1<dependency>
2    <groupId>org.springframework.boot</groupId>
3    <artifactId>spring-boot-configuration-processor</artifactId>
4    <optional>true</optional>
5</dependency>

application.yaml

1hm:
2  auth:
3    excludePaths: # 无需登录校验的路径
4      - /user/login
5      - /item/search
6      - /test/**

属性类

1@Data
2@Component
3@ConfigurationProperties(prefix = "hm.auth")
4public class AuthProperties {
5    private List<String> includePaths;
6    private List<String> excludePaths;
7}

GlobalFilter 全局拦截

先根据配置文件,决定是否拦截,拦=>检查Token=>存入信息在Header => 微服务(从Header中拿取信息)

 1@Component
 2@RequiredArgsConstructor
 3public class AuthGlobalFilter implements GlobalFilter, Ordered {
 4    // 已加入Spring容器
 5    private final AuthProperties authProperties;
 6    // 未加入,手动new
 7    private final AntPathMatcher antPathMatcher = new AntPathMatcher();
 8
 9    @Override
10    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
11        // 获取request对象
12        ServerHttpRequest request = exchange.getRequest();
13        // 判断路径是否需要拦截
14        if(isExclude(request.getPath().toString())) {
15            // 放行
16            return chain.filter(exchange);
17        }
18        // 获取Header中的Token
19        String token = null;
20        List<String> headers = request.getHeaders().get("authorization");
21        if(!CollUtil.isEmpty(headers)) {
22            token = headers.get(0);
23        }
24        Long userId = null;
25        // 简单模拟JwtToken校验
26        if(token != null && token.equals("hello")) {
27            // 校验成功
28            userId = 1L;
29        } else {
30            // 校验失败 => 直接返回UNAUTHORIZED状态码
31            ServerHttpResponse response = exchange.getResponse();
32            response.setStatusCode(HttpStatus.UNAUTHORIZED);
33            return response.setComplete();
34        }
35
36        String userInfo = userId.toString();
37
38        // 将用户信息存储在转发请求头中
39        ServerWebExchange ex = exchange.mutate()
40                .request(builder -> {
41                    builder.header("user-info", userInfo);
42                })
43                .build();
44
45        return chain.filter(ex);
46    }
47
48    private boolean isExclude(String path) {
49        for (String pathPattern : authProperties.getExcludePaths()) {
50            if (antPathMatcher.match(pathPattern, path)) {
51                return true;
52            }
53        }
54        return false;
55    }
56
57    @Override
58    public int getOrder() {
59        return 0;
60    }
61}

Service Interceptor

ThreadLocal,线程自己的独享空间,用来存储请求中携带的信息

 1public class UserContext {
 2
 3    private static final  ThreadLocal<Long> tl = new ThreadLocal<>();
 4
 5    public static void setUserId(Long userId) {
 6        tl.set(userId);
 7    }
 8
 9    public static Long getUserId() {
10        return tl.get();
11    }
12
13    public static void removeUserId(){
14        tl.remove();
15    }
16
17}

执行服务前,将request header中携带的信息取出来

 1public class UserInfoInterceptor implements HandlerInterceptor {
 2
 3    @Override
 4    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
 5        String userId = request.getHeader("user-info");
 6        if (StrUtil.isNotBlank(userId)) {
 7            UserContext.setUserId(Long.parseLong(userId));
 8        }
 9        return true;
10    }
11
12    @Override
13    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
14        UserContext.removeUserId();
15    }
16}

抽取公共部分

common-service

1com.yoo.common.xxx

其他module只要在pom内引用就行

公共部分如何区别不同的服务 - 有些子模块需要但另一些子模块不需要的Class(这个类会被Spring托管,但不想注入Spring容器中)

1@ConditionalOnClass(DispatherServlet.class)

在其他微服务加上,显示指定配置文件位置

spring.factories

1org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.yoo.common.config.MvcConfig

RabbitMQ

1publisher => exchanger => queue => consumer

exchanger:

  • fanout exchanger: 广播
  • direct exchanger:根据key发送给对应queue
  • topic exchanger: 根据key进行匹配一组符合的queue

消息订阅

1@RabbitListener(queues = "???")
2public T method(...) {
3    // 
4}

消息发布

1# 直接发送给Queue
2rabbitTemplate.convertAndSend(queueName, message)
3    
4# 发送给交换机
5rabbitTemplate.convertAndSend(ExchangeName, "RoutingKey", MessageObject)    

消息格式转换

Object => Binary 将发送的对象序列化为JSON格式字符串?

1@Bean
2...
3return new Jackson2JSONMessageConverter()

队列交换机和绑定

 1@Bean
 2public Queue orderQueue() {
 3    return new Queue("orderQueue", true);
 4}
 5
 6// Spring 容器会确保同一个 @Bean 方法只会被调用一次,返回的对象会被缓存并重复使用
 7@Bean
 8public DirectExchange orderExchange() {
 9    return new DirectExchange("orderExchange", true, false);
10}
11
12@Bean
13public Binding binding() {
14    return BindingBuilder.bind(orderQueue()).to(orderExchange()).with("orderRoutingKey");
15}

可靠传输

 1# 发送确认
 2publisher-confirm-type: none|correlated|simple
 3
 4none: 无确认机制,性能最好,不可靠
 5correlated: 异步关联确认,不阻塞线程,高可靠性,性能中、异步回调场景
 6simple: 同步阻塞确认,阻塞线程,性能底,可靠。
 7
 8# 接收确认
 9ackknowledge-mode: none|manual|auto
10none: 无确认机制
11manual: 手工ack
12auto: 自动ack,根据抛出的异常类型,决定重发还是reject
13
14
15# 重连配置
16retry:
17	enabled: true  # 启用重试机制
18	initial-interval: 1000ms # 第一次失败等待的时长
19	multiplier: 1 # 每次失败等待时间成x倍增长
20	max-attempts: 3 # 最大重试次数
21	stateless: false # 
22	
23# 如果接口幂等(调用多次都不会重复扣款) => stateless:true 每次重试都是独立的,不保留状态 ⭐性能高
24# 非幂等操作(多次执行可能有副作用)
25stateless:false # 带状态

死信交换机

image-20250113195557165

定义交换机和队列

 1@Bean
 2public DirectExchange createDLExchanger() {
 3    return ExchangeBuilder.directExchange("dl.direct").build();
 4}
 5
 6@Bean
 7public Queue createDLQueue() {
 8    // 队列信息是否持久化到磁盘? durable(持久化?)
 9    return QueueBuilder.nonDurable("dl.queue").build();
10}
11
12@Bean
13public Binding toBindingDL() {
14    return BindingBuilder.bind(createDLQueue()).to(createDLExchanger()).with("dlRoutingKey");
15}
16
17// -----------------------
18
19@Bean
20public FanoutExchange createTTLExchanger() {
21    return ExchangeBuilder.fanoutExchange("ttl.fanout").build();
22}
23
24@Bean
25public Queue createTTLQueue() {
26    // 队列信息是否持久化到磁盘? durable(持久化?)
27    return QueueBuilder.nonDurable("ttl.queue").deadLetterExchange("dl.direct").deadLetterRoutingKey("dlRoutingKey").build();
28}
29
30@Bean
31public Binding toBinding() {
32    return BindingBuilder.bind(createTTLQueue()).to(createTTLExchanger());
33}

步骤1:发送信息并设置过期时间

 1// 1:未付款; 2.已付款 ...
 2// 模拟用户下单
 3redisTemplate.opsForValue().set("order:userId:status", "1");
 4// ... 扣货物库存  -1
 5
 6MessageProperties properties = new MessageProperties();
 7properties.setExpiration("20000"); // 20秒过期,过期后进入死信队列
 8
 9Map<String, String> map = new HashMap<>();
10map.put("code", "200");
11map.put("msg", "Hi, RabbitMQ. This is a dead letter.");
12
13ObjectMapper mapper = new ObjectMapper();
14
15String msgContent = mapper.writeValueAsString(map);
16Message message = new Message(msgContent.getBytes(), properties);
17rabbitTemplate.convertAndSend("ttl.fanout", "", message);

步骤2:用户调用接口进行付款

1// 数据库
2// ....
3
4// 模拟付款成功
5redisTemplate.opsForValue().set("order:userId:status", "2");

步骤3:死信队列 处理逻辑

 1@RabbitListener(queues = "dl.queue")
 2public void deadLetterQueue(Message message) throws IOException {
 3    ObjectMapper mapper = new ObjectMapper();
 4    Map<String, String> msg = mapper.readValue(message.getBody(), Map.class);
 5
 6    System.out.println("receive Msg ... @@@");
 7    System.out.println(msg.get("code"));
 8    System.out.println(msg.get("msg"));
 9
10    String status = redisTemplate.opsForValue().get("order:userId:status");
11    if (status.equals("1")) {
12        // ... 还原货物库存  +1
13        return;
14    }else if (status.equals("2")){
15        // 订单支付完成,实现收尾工作
16        System.out.println("订单已支付,生成后续...");
17    }
18}

解耦各个部分。


苍穹外卖

AOP使用-自动补全参数共同信息

1// 自定义注解
2@Target(ElementType.METHOD)
3@Retention(RetentionPolicy.RUNTIME)
4public @interface AutoFill {
5    OperationType value();
6}
 1@Aspect  // !!! 交给Spring托管
 2@Component
 3public class AutoFillAspect {
 4
 5    // 任何 returnType package.?.class.method(params)  && with annotation
 6    @Pointcut("execution(* com.hmwm.service.*.*(..)) && @annotation(com.hmwm.common.AutoFill)")
 7    public void autoFillPointCut(){}
 8
 9    @Before("autoFillPointCut()")
10    public void autoFill(JoinPoint joinPoint) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
11        System.err.println("Start fill common field.");
12
13        // get method annotation value
14        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
15        AutoFill annotation = signature.getMethod().getAnnotation(AutoFill.class);
16        OperationType operationType = annotation.value();
17
18        // 拦截的method的参数列表
19        Object[] args = joinPoint.getArgs();
20        if (args==null && args.length==0) {
21            System.out.println("current method not params");
22            return;
23        }
24        // 第一个⭐参数⭐ 需要填充的对象. 提前交流指定:这里为User Obj
25        Object arg = args[0];
26
27        LocalDateTime now = LocalDateTime.now();
28        // Long currentId = BaseContext.getCurrentId();
29
30        // 给这个arg填充值
31        if (operationType == OperationType.INSERT) {
32            // getDeclaredMethod(method_name, params_type)
33            Method setCreateTime = arg.getClass().getDeclaredMethod(AutoFillConstant.SET_CREATE_TIME.getMethodName(), Date.class);
34            Method setUpdateTime = arg.getClass().getDeclaredMethod(AutoFillConstant.SET_UPDATE_TIME.getMethodName(), Date.class);
35
36            // method-obj: invoke(object, params) 方法(对象, 参数)
37            setCreateTime.invoke(arg, Date.from(now.atZone(ZoneId.systemDefault()).toInstant()));
38            setUpdateTime.invoke(arg, Date.from(now.atZone(ZoneId.systemDefault()).toInstant()));
39        }
40
41    }
1@AutoFill(OperationType.INSERT)
2public String addUser(User user) {
3    return user.toString();
4}

枚举类

 1// 枚举类在 Java 中是通过 enum 关键字定义的,但它本质上是一个类。每个枚举常量都是该枚举类的一个实例。
 2public enum AutoFillConstant {
 3    SET_CREATE_TIME("setCreateTime"),
 4    SET_UPDATE_TIME("setUpdateTime");
 5
 6    private String methodName; // 存储方法名的字段
 7
 8    // 构造函数
 9    AutoFillConstant(String methodName) {
10        this.methodName = methodName;
11        System.out.println("AutoFillConstant: " + methodName);
12    }
13
14    // 获取方法名
15    public String getMethodName() {
16        return methodName;
17    }
18}

定时任务

利用@Scheduled注解

1@Component
2public class ScheduledTask {
3    // 秒 分 时 天 周 月 年                     年/月/日  时/分/秒
4    @Scheduled(cron = "0 * * * * ? ") // 这里 xxxx/xx/xx xx:xx:x0 执行
5    public void processTimeOutOrder() {
6        System.err.println("process Timeout task ...");
7    }
8
9}

ThreadLocal-线程专享资源

可以请求来的时候存储一些 环境变量 ThreadLocal<Object>

 1public class BaseContext {
 2    public static ThreadLocal<Long> threadLocal = new ThreadLocal<>();
 3
 4    public static void setCurrentId(Long id) {
 5        threadLocal.set(id);
 6    }
 7
 8    public static Long getCurrentId() {
 9        return threadLocal.get();
10    }
11
12    public static void removeCurrentId() {
13        threadLocal.remove();
14    }
15}

Websocket

 1@Component
 2@ServerEndpoint("/websocket")
 3public class MyWebSocketEndpoint {
 4
 5    // 会话对象  server:client == 1:n
 6    private static Map<String, Session> sessionMap = new HashMap<>();
 7
 8
 9    @OnOpen
10    public void onOpen(Session session) {
11        System.out.println("onOpen: " + session.getId());
12        sessionMap.put(session.getId(), session);
13        sendMsg("Hi, can I help you?", session.getId());
14    }
15
16    @OnMessage
17    public void onMessage(String message, Session session) {
18        System.out.println("receive Message: " + message);
19        sendMsg("Yse! I am thinking ... this question!", session.getId());
20    }
21
22    @OnClose
23    public void OnClose(Session session) {
24        sendMsg("Bye!", session.getId());
25        System.out.println("OnClose: " + session.getId());
26        sessionMap.remove(session.getId());
27    }
28
29    public void sendMsg(String msg, String sid) {
30        try {
31            sessionMap.get(sid).getBasicRemote().sendText(msg);  // 向session对象sendTest
32        } catch (IOException e) {
33            e.printStackTrace();
34        }
35    }
36
37}

文件下载功能

很简单,利用Response进行。 需要设置response内容格式和response-header

 1ClassPathResource resource = new ClassPathResource("static/file_name.zip");
 2if (!resource.exists()) {
 3    System.err.println("file not exists!");
 4    return ResponseEntity.notFound().build();
 5}
 6
 7HttpHeaders headers = new HttpHeaders();
 8headers.add(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"file_name.zip\""); // attachment 附件
 9
10return ResponseEntity.ok()
11    .headers(headers)  // <= header
12    .contentType(MediaType.APPLICATION_OCTET_STREAM) // <= contentType stream流式
13    .body(resource); // <= file

Excel处理

1org.apache.poi // 利用这个Apache POI package
 1XSSFWorkbook excel = new XSSFWorkbook(); // 类似excel文件
 2XSSFSheet sheet = excel.createSheet("sheet1"); // sheet
 3
 4XSSFRow row1 = sheet.createRow(0);
 5row1.createCell(1).setCellValue("姓名");
 6row1.createCell(2).setCellValue("年纪");
 7row1.createCell(3).setCellValue("城市");
 8
 9XSSFRow row2 = sheet.createRow(1);
10row2.createCell(1).setCellValue("韦龙");
11row2.createCell(2).setCellValue(23);
12row2.createCell(3).setCellValue("桂林");
13
14XSSFRow row3 = sheet.createRow(2);
15row3.createCell(1).setCellValue("张三");
16row3.createCell(2).setCellValue(23);
17row3.createCell(3).setCellValue("杭州");
18
19File file = new File("static/staff_info.xlsx");
20FileOutputStream stream = new FileOutputStream(file);
21excel.write(stream);
22
23stream.close();
24excel.close();