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}
- 泛型方法
函数签名
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}
生成中间的桥接方法,进行连接,维护多态性。
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);
线程池
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 # 带状态
死信交换机
定义交换机和队列
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();