Flink
发表于|更新于|Java
|总字数:2.4k|阅读时长:14分钟|浏览量:
Flink
尝试flink
本地安装
步骤一:下载
为了能够运行Flink,唯一的要求是安装有效的Java 8或11。您可以通过发出以下命令来检查Java的正确安装:
1 | # 要安装java环境 |
步骤二:启动本地集群
1 | $ ./bin/start-cluster.sh |
步骤三:提交一个job
1 | $ ./bin/flink run examples/streaming/WordCount.jar |
步骤四:停止集群
1 | $ ./bin/stop-cluster.sh |
使用DataStream API进行欺诈检测
Java环境
1 | <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" |
输入
File输入
1 | public static void main(String[] args) throws Exception { |
Kafka
需要引入包
1 | <dependency> |
1 | public static void main(String[] args) throws Exception { |
集合获取
1 | public static void main(String[] args) throws Exception { |
输出
自定义数据源
模拟从kafka中获取
1 | public static void main(String[] args) throws Exception { |
自定义数据源
1 | public class MySensorce implements SourceFunction<SensorReading> { |
算子
基本算子
map
1 | final SingleOutputStreamOperator<Integer> map = inputStream.map(new MapFunction<String, Integer>() { |
flatmap
1 | final SingleOutputStreamOperator<String> stringSingleOutputStreamOperator = inputStream.flatMap(new FlatMapFunction<String, String>() { |
filter
1 | final SingleOutputStreamOperator<String> filter = inputStream.filter(new FilterFunction<String>() { |
分流,合流
1 | public static void main(String[] args) throws Exception { |
富函数
1 | public static void main(String[] args) throws Exception { |
分组,聚合
1 | public static void main(String[] args) throws Exception { |
相关推荐

2022-11-19
Java
基础知识点循环标签 如果不在嵌套循环中使用,用了跳转,也就相当于continue一样,没有区别 在嵌套循环中使用才有效果,用了innerLoop之后,可以看到 j=2 的时候是不打印 j=3,程序不会到j=3的时候,因为j=2的时候就已经跳出去了 12345678910111213141516171819202122232425262728293031323334/** * 0====0 * 0====1 * 1====0 * 1====1 * 2====0 * 2====1 * ------------------- * 0====0 * 0====1 * 0====2 * 0====3 * 1====0 * 1====1 * 1====2 * 1====3 * 2====0 * 2====1 * 2====2 * 2====3 */public static void main(String[] args) { innerLoop: for (int i = 0; i < 3; i++) { for (int j = 0; j < 4; j++) { if (j == 2) { continue innerLoop; } System.out.println(i+"===="+j); } }} Java常用的API方法集合/列表/数组/Map数组拼接1234567891011121314// 把数组风格,再循环添加逗号String[] split = result.split(" ");StringJoiner stringJoiner = new StringJoiner(",");Arrays.stream(split).forEach(stringJoiner::add);// 字符串数组数字,转成int类型,再转成数组Arrays.stream(split).mapToInt(Integer::parseInt).toArray()// BigDecimal想加,然后如果为空,就给个0BigDecimal winAmount = list.stream() .map(UserBetOrderRecord::getWinAmount) .reduce(BigDecimal::add) .map(bigDecimal -> bigDecimal.multiply(new BigDecimal("-1"))) .orElse(BigDecimal.ZERO); 数组转集合12345678910111213141516171819public static void test() { int[] arr = new int[]{4, 3, 2, 1, 12, 21, 11}; // 这样转是有问题的,不是想要的 // java.util.array List<int[]> list = Arrays.asList(arr); System.out.println(list); // 使用Hutool的方法,也不死想要的 // cn.hutool.core.collection List<int[]> ints = CollectionUtil.newArrayList(arr); System.out.println(ints); // 使用Spring的工具包,需要强转,强迫症受不了这种方法 // org.springframework.util.CollectionUtils List<Integer> objects = (List<Integer>) CollectionUtils.arrayToList(arr); System.out.println(objects);} 打印数组/集合/Map12345678910111213141516171819private static void test() { int[] arr = {1, 2, 3, 4, 5}; System.out.println(arr); // [I@17211155 System.out.println(Arrays.toString(arr)); // [1, 2, 3, 4, 5] List<Integer> integers = new ArrayList<>(); integers.add(1); integers.add(2); integers.add(3); System.out.println(integers); // [1, 2, 3] HashMap<Integer, String> integerStringHashMap = new HashMap<>(); integerStringHashMap.put(1, "a"); integerStringHashMap.put(2, "b"); integerStringHashMap.put(3, "c"); System.out.println(integerStringHashMap); // {1=a, 2=b, 3=c}} 数组截取1234567// java.util.Arrays// 从0开始,取出6个数private static void test() { int[] arr = {1, 2, 3, 4, 5, 6}; int[] ints = Arrays.copyOfRange(arr, 0, 6); System.out.println(Arrays.toString(ints));} 格式化数值格式化1234567private static void test() { String format = String.format("%02d", 2); System.out.println(format); // 02 String format2 = String.format("%02d", 20); System.out.println(format2); // 20} 数学余数/取模1234567891011121314private static void test() { int a = 5; int b = 3; // 使用 % 运算符取余数 int remainder = a % b; System.out.println(remainder); // 2 // 使用 Math.floorMod() 方法取余数 int floorMod = Math.floorMod(a, b); System.out.println(floorMod); // 2} BigDecimal比较1234567891011121314151617181920212223242526272829public static void main(String[] args) { BigDecimal a = new BigDecimal(10); BigDecimal b = new BigDecimal(5); int i = a.compareTo(b); switch (i) { case -1: // 小于 System.out.println("a < b"); break; case 0: // 等于 System.out.println("a = b"); break; case 1: // 大于 System.out.println("a > b"); break; } if (a.compareTo(b) != 0) { System.out.println("a != b"); } if (a.compareTo(b) != -1){ System.out.println("a >= b"); } if (a.compareTo(b) != 1) { System.out.println("a <= b"); } } 时间类型比较123456789101112131415161718192021222324252627public static void main(String[] args) throws ParseException { SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); Date begin = sdf.parse("2020-01-01 00:01:00"); Date end = sdf.parse("2020-01-01 00:02:00"); // 使用compareTo方法 int i = begin.compareTo(end); switch (i) { case -1: System.out.println("开始时间大于结束时间"); break; case 0: System.out.println("开始时间等于结束时间"); break; case 1: System.out.println("开始时间小于结束时间"); break; } // 使用before和after方法 boolean b = begin.before(end); System.out.println(b); boolean c = begin.after(end); System.out.println(c); } 值传递和引用传递123456789101112131415161718192021222324252627public class Demo { public static void main(String[] args) { HashMap<Integer, User> ingUserHashMap = new HashMap<Integer, User>(); User user = new User(); user.setId(1); ingUserHashMap.put(1, user); // {1=User(id=1, bigDecimal=10)} System.out.println(ingUserHashMap); // 从map里取值,然后再修改 User user1 = ingUserHashMap.get(1); user1.setBigDecimal(BigDecimal.ONE); // 打印map是修改了的 // {1=User(id=1, bigDecimal=1)} System.out.println(ingUserHashMap); }}@Data@ToStringclass User{ int id; BigDecimal bigDecimal = BigDecimal.TEN;} Lambda给集合分组 1234567891011121314151617181920212223242526public class Demo { public static void main(String[] args) { ArrayList<User> list = new ArrayList<>(); list.add(new User(1, "anthony")); list.add(new User(2, "anthony")); list.add(new User(1, "didid")); // 按照用户Id分组 Map<Integer, List<User>> collect = list.stream().collect(Collectors.groupingBy(User::getId)); // {1=[User(id=1, name=anthony), User(id=1, name=didid)], 2=[User(id=2, name=anthony)]} System.out.println(collect); // 1==[User(id=1, name=anthony), User(id=1, name=didid)] // 2==[User(id=2, name=anthony)] collect.forEach((id,userList)->{ System.out.println(id+"=="+userList); }); }}@AllArgsConstructor@Dataclass User{ Integer id; String name;} ThreadLocal掘金 HashMap面试八股文…. HashMap和HashTable的区别 HashMap HashTable null值 允许key,value为null,但最多允许一条记录的key为null 不允许有null 安全性 不安全,Collections.synchronizedMap方法,使HashMap具有线程安全的能力 线程安全的 遍历 Iterator 进行遍历 使用 Enumeration 进行遍历 负载因子 负载因子和扩容机制有关,意思是如果当前容器的容量,达到我们设置的定最大值,就要开始扩容 比如说当前的容器容量是16,负载因子是0.75,16*0.75=12,也就是说,当容量达到了12的时候就会进行扩容操作。 负载因子是1的时候: 只有当数组的16个一直都填满了,才回扩容,因此在填满的过程中,Hash冲突是避免不了的,也就意味着会出现大量的Hash冲突,底层的红黑树就变得复杂,这种就牺牲了时间,保证空间的利用率 负载因为是0.5的时候: 当到达数组一半的就开始扩容,既然填充的元素少了,Hash冲突就变少,那么底层的链表长度和红黑数也就变少,查询效率就高, 原本存储1M的数据,现在需要2M,时间效率提升了,但是空间利用率降低了 不同JDK版本HashMap的区别 组成 数组+链表 数组+链表+红黑树 hash 值我们能够快速定位到数组的具体下标,但是之后的话, 需要顺着链表一个个比较下去才能找到我们需要的,时间复杂度取决于链表的长度 当满足一定条件时,会将链表转换为红黑树 **链表转红黑树要满足的条件 ** 链表中的元素个数>=8,为什么? 和hashcode碰撞次数的泊松分布有关,主要是为了寻找一种时间和空间的平衡 红黑数的TreeNode 是 链表中的Node所占空间的2倍,虽然红黑树的查询效率要高于链表,但是,当链表长度比较小的时候,即使全部遍历,时间复杂度也不会太高 为什么是8,JDK源码也没有说,只是说做了很多时间,经验的问题 红黑树转链表的阀值是6,因为:避免红黑树和链表频繁转换 当前数组的长度>=64,为什么? 链表转为红黑树的目的是为了解决链表过长,导致查询和插入的效率慢的问题 如果要解决这个问题,也可以通过数组扩容,把链接缩短也可以解决问题 所以在数组长度还不太长的情况,可以先通过数组扩容来解决链表过长的问题 满足这两个条件才会变成红黑树 HashMap的数组的大小是2的幂 TODO 还是没有搞明白 只有数组长度是2的幂次方倍才能够确保数组中的每一个位置发生hash冲突的概率是相同的,数组长度减一的二进制码必须全部是1,否则会出现部分位置永远不会发生hash冲突而造成资源浪费 HashMap put流程 对key进行hash算法,得到一个hashcode 如果数组长度为0或者为null,对数组进行扩容,得到数组长度n 通过 key的hashcode&数组长度n 计算出数组下标 如果数组下标位置中没有数据,将put进来的数据封装Node对象并存到给下标位置 如果数组下标位置有数据p,即发生hash碰撞,如果key存在,覆盖数据 如果数据p属于红黑树,会把新数据插入到红黑树中 如果以上都不是就遍历链表,遍历链表过程中统计链表长度,当链表长度超过8 进行treeifyBin 红黑树化链表 treeifyBin树化中如果数组长度小于64或数组为null则进行扩容,否则数组长度大于等于64 链表转红黑树 HashMap 是如何扩容的? HashMap的扩容指的就是数组的扩容, 因为数组占用的是连续内存空间,所以数组的扩容其实只能新开一个新的数组,然后把老数组上的元素转移到新数组上来,这样才是数组的扩容 先新建一个2被数组大小的数组 然后遍历老数组上的每一个位置,如果这个位置上是一个链表,就把这个链表上的元素转移到新数组上去 在jdk8中,因为涉及到红黑树,这个其实比较复杂,jdk8中其实还会用到一个双向链表来维护红黑树中的元素,所以jdk8中在转移某个位置上的元素时,会去判断如果这个位置是一个红黑树,那么会遍历该位置的双向链表,遍历双向链表统计哪些元素在扩容完之后还是原位置,哪些元素在扩容之后在新位置,这样遍历完双向链表后,就会得到两个子链表,一个放在原下标位置,一个放在新下标位置,如果原下标位置或新下标位置没有元素,则红黑树不用拆分,否则判断这两个子链表的长度,如果超过8,则转成红黑树放到对应的位置,否则把单向链表放到对应的位置。 元素转移完了之后,在把新数组对象赋值给HashMap的table属性,老数组会被回收到。 多线程线程池手写一个线程池1:编写任务类(MyTask),实现Runnable接口; 12345678910111213141516171819202122232425public class MyTask implements Runnable { private int id; public MyTask(int id) { this.id = id; } @Override public void run() { String name = Thread.currentThread().getName(); System.out.println("线程:"+name + "即将开始任务:" + id); try { Thread.sleep(200); } catch (InterruptedException e) { throw new RuntimeException(e); } System.out.println("线程:"+name + "完成了任务:" + id); } @Override public String toString() { return "MyTask{" + "id=" + id + '}'; }} 2:编写线程类(MyWorker),用于执行任务,需要持有所有任务; 123456789101112131415161718public class MyWorker extends Thread{ private List<Runnable> tasks; public MyWorker(String name, List<Runnable> tasks) { super.setName(name); this.tasks = tasks; } @Override public void run() { // 判断集合中是否有任务,如果有就一直运行 while (!tasks.isEmpty()) { Runnable remove = tasks.remove(0); remove.run(); } }} 3:编写线程池类(MyThreadPool),包含提交任务,执行任务的能力; 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647public class MyThreadPool { // 任务队列 private List<Runnable> tasks = Collections.synchronizedList(new LinkedList<>()); // 当前线程数量 private int num; // 核心线程数量 private int corePoolSize; // 最大线程数量 private int maxSize; // 任务队列的长度 private int workSize; public MyThreadPool(int corePoolSize, int maxSize, int workSize) { this.corePoolSize = corePoolSize; this.maxSize = maxSize; this.workSize = workSize; } // 提交方法-将任务添加到队列中 public void submit(Runnable runnable){ // 判断当前队列的数量,是否超出了最大任务数量 if (tasks.size() >= workSize) { System.out.println("任务"+runnable+"被丢弃了"); }else { tasks.add(runnable); // 执行方法-判断当前线程的数量,决定创建核心线程数量还是非线程数量 execTask(runnable); } } private void execTask(Runnable runnable) { // 判断当前线程中的线程总数量,是否超出了核心线程数 if (num < corePoolSize) { // 创建核心线程 new MyWorker("核心线程:"+num, tasks).start(); num++; } else if (num < maxSize) { // 创建非核心线程 new MyWorker("非核心线程:"+num, tasks).start(); num++; }else { System.out.println("任务:"+runnable+"被缓存了...."); } }} 4:编写测试类(MyTest),创建线程池对象,提交多个任务测试; 123456789public class MyTest { public static void main(String[] args) { MyThreadPool myThreadPool = new MyThreadPool(2, 4, 20); for (int i = 0; i < 10; i++) { MyTask myTask = new MyTask(i); myThreadPool.submit(myTask); } }} 内置线程池ThreadPoolExecutor的使用 12345678public ThreadPoolExecutor(int corePoolSize, //核心线程数量 int maximumPoolSize,// 最大线程数 long keepAliveTime, // 最大空闲时间 TimeUnit unit, // 时间单位 BlockingQueue<Runnable> workQueue, // 任务队列 ThreadFactory threadFactory, // 线程工厂 RejectedExecutionHandler handler // 饱和处理机制) corePoolSize - 线程池核心线程的数量,即使线程是空闲的,核心线程也不会被销毁,除非设置了 allowCoreThreadTimeOut 为 true。 maximumPoolSize - 线程池中允许的最大线程数。 keepAliveTime - 非核心线程空闲时的超时时间,超过这个时间就会被销毁。 unit - keepAliveTime 参数的时间单位。 workQueue - 用于保存等待执行任务的阻塞队列。 SynchronousQueue: 一个不存储元素的阻塞队列。每个插入操作必须等待另一个线程的对应移除操作,反之亦然。适合于传递性的请求。 LinkedBlockingQueue: 一个无界队列,使用链表实现。如果没有指定容量,默认将是 Integer.MAX_VALUE。当任务数量超过核心线程数量时,多余的任务将被放置在队列中等待执行。 ArrayBlockingQueue: 一个有界的阻塞队列,使用数组实现。在创建时需要指定容量大小。适合于有界任务队列的场景,如数据库连接池和消息队列等。 threadFactory - 用于创建新线程的工厂。 handler - 当线程池中的线程数达到最大值且阻塞队列已满时,用来处理新提交的任务的饱和策略。 AbortPolicy: 默认的策略,将抛出RejectedExecutionException。 CallerRunsPolicy: 当任务被拒绝添加时,会使用当前线程执行被拒绝的任务。 DiscardPolicy: 直接丢弃被拒绝的任务,不提供任何反馈。 DiscardOldestPolicy: 丢弃等待时间最长的任务,然后尝试提交新的任务。 ExecutorService接口的常用方法: 接口名 作用 void shutdown() 启动一次顺序关闭,执行以前提交的任务,但不接受新任务。 List shutdownNow() 停止所有正在执行的任务,暂停处理正在等待的任务,并返回等待执行的任务列表。 Future submit(Callable task) 执行带返回值的任务,返回一个Future对象。 Future<?> submit(Runnable task) 执行 Runnable 任务,并返回一个表示该任务的 Future。 Future submit(Runnable task, T result) 执行 Runnable 任务,并返回一个表示该任务的 Future。 ExecutorService接口的的实现类 方法 static ExecutorService newCachedThreadPool() 创建一个默认的线程池对象,里面的线程可重用,且在第一次使用时才创建 static ExecutorService newCachedThreadPool(ThreadFactory threadFactory) 线程池中的所有线程都使用ThreadFactory来创建,这样的线程无需手动启动,自动执行; static ExecutorService newFixedThreadPool(int nThreads) 创建一个可重用固定线程数的线程池 static ExecutorService newFixedThreadPool(int nThreads, ThreadFactory threadFactory) 创建一个可重用固定线程数的线程池且线程池中的所有线程都使用ThreadFactory来创建。 static ExecutorService newSingleThreadExecutor() 创建一个使用单个 worker 线程的 Executor,以无界队列方式来运行该线程。 static ExecutorService newSingleThreadExecutor(ThreadFactory threadFactory) 创建一个使用单个 worker 线程的 Executor,且线程池中的所有线程都使用ThreadFactory来创建。 ScheduledExecutorService任务调度ScheduledExecutorService是ExecutorService的子接口,具备了延迟运行或定期执行任务的能力 方法() static ScheduledExecutorService newScheduledThreadPool(int corePoolSize)创建一个可重用固定线程数的线程池且允许延迟运行或定期执行任务; static ScheduledExecutorService newScheduledThreadPool(int corePoolSize, ThreadFactory threadFactory)创建一个可重用固定线程数的线程池且线程池中的所有线程都使用ThreadFactory来创建,且允许延迟运行或定期执行任务; static ScheduledExecutorService newSingleThreadScheduledExecutor() 创建一个单线程执行程序,它允许在给定延迟后运行命令或者定期地执行。 static ScheduledExecutorService newSingleThreadScheduledExecutor(ThreadFactory threadFactory) 创建一个单线程执行程序,它可安排在给定延迟后运行命令或者定期地执行。 ScheduledExecutorService常用方法如下: 方法 ScheduledFuture schedule(Callable callable, long delay, TimeUnit unit) 延迟时间单位是unit,数量是delay的时间后执行callable。 ScheduledFuture<?> schedule(Runnable command, long delay, TimeUnit unit) 延迟时间单位是unit,数量是delay的时间后执行command。 ScheduledFuture<?> scheduleAtFixedRate(Runnable command, long initialDelay, long period, TimeUnit unit) 延迟时间单位是unit,数量是initialDelay的时间后,每间隔period时间重复执行一次command。 ScheduledFuture<?> scheduleWithFixedDelay(Runnable command, long initialDelay, long delay, TimeUnit unit) 创建并执行一个在给定初始延迟后首次启用的定期操作,随后,在每一次执行终止和下一次执行开始之间都存在给定的延迟。 Future异步计算结果 boolean cancel(boolean mayInterruptIfRunning) 试图取消对此任务的执行。 V get()如有必要,等待计算完成,然后获取其结果。 V get(long timeout, TimeUnit unit) 如有必要,最多等待为使计算完成所给定的时间之后,获取其结果(如果结果可用)。 boolean isCancelled() 如果在任务正常完成前将其取消,则返回 true。 boolean isDone() 如果任务已完成,则返回 true。 代码演示ExecutorService12345678910111213141516171819202122232425262728293031323334353637383940414243// 演示 newCachedThreadPoolpublic class Demo { public static void main(String[] args) {// test1();// test2(); } private static void test1() { ExecutorService executorService = Executors.newCachedThreadPool(); for (int i = 0; i < 10; i++) { executorService.submit(new MyRunable(i)); } } private static void test2() { ExecutorService executorService = Executors.newCachedThreadPool(new ThreadFactory() { int i=1; @Override public Thread newThread(Runnable r) { return new Thread(r, "自定义线程名字:" + i++); } }); for (int i = 0; i < 10; i++) { executorService.submit(new MyRunable(i)); } }}class MyRunable implements Runnable { int i = 0; public MyRunable(int i) { this.i = i; } @Override public void run() { String name = Thread.currentThread().getName(); System.out.println("线程:"+name + "执行了任务:"+i); }} 123456789101112131415161718192021222324252627282930313233343536373839404142434445// 演示newFixedThreadPoolpublic class Demo2 { public static void main(String[] args) {// test1(); test2(); } private static void test1() { ExecutorService executorService = Executors.newFixedThreadPool(3); for (int i = 0; i < 10; i++) { executorService.submit(new MyRunable2(i)); } } private static void test2() { ExecutorService executorService = Executors.newFixedThreadPool(3,new ThreadFactory() { int i=1; @Override public Thread newThread(Runnable r) { return new Thread(r, "自定义线程名字:" + i++); } }); for (int i = 0; i < 10; i++) { executorService.submit(new MyRunable2(i)); } }}class MyRunable2 implements Runnable { int i = 0; public MyRunable2(int i) { this.i = i; } @Override public void run() { String name = Thread.currentThread().getName(); System.out.println("线程:"+name + "执行了任务:"+i); }} 12345678910111213141516171819202122232425262728293031323334353637383940414243444546// 演示newSingleThreadExecutorpublic class Demo3 { public static void main(String[] args) {// test1(); test2(); } private static void test1() { ExecutorService executorService = Executors.newSingleThreadExecutor(); for (int i = 0; i < 10; i++) { executorService.submit(new MyRunable3(i)); } } private static void test2() { ExecutorService executorService = Executors.newSingleThreadExecutor(new ThreadFactory() { int i=1; @Override public Thread newThread(Runnable r) { return new Thread(r, "自定义线程名字:" + i++); } }); for (int i = 0; i < 10; i++) { executorService.submit(new MyRunable3(i)); } }}class MyRunable3 implements Runnable { int i = 0; public MyRunable3(int i) { this.i = i; } @Override public void run() { String name = Thread.currentThread().getName(); System.out.println("线程:"+name + "执行了任务:"+i); }} 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465// ExecutorService接口的shutdown(),shutdownNow(),submit()public class Demo4 { public static void main(String[] args) {// test1(); test2(); } private static void test2() { ExecutorService executorService = Executors.newSingleThreadExecutor(new ThreadFactory() { int i=1; @Override public Thread newThread(Runnable r) { return new Thread(r, "自定义线程名字:" + i++); } }); for (int i = 0; i < 10; i++) { executorService.submit(new MyRunable4(i)); } // 立刻关闭线程池,如果线程池中的还有缓存任务,没有执行,则取消执行,并返回这些任务 List<Runnable> runnables = executorService.shutdownNow(); System.out.println(runnables); // 不能再接收新的方法了,会报错 executorService.submit(new MyRunable4(888)); } private static void test1() { ExecutorService executorService = Executors.newSingleThreadExecutor(); for (int i = 0; i < 10; i++) { executorService.submit(new MyRunable4(i)); } // 关闭线程池,仅仅是不再接收新的任务,以前的任务还会记录执行 executorService.shutdown(); // 不能再接收新的方法了,会报错 executorService.submit(new MyRunable4(888)); }}class MyRunable4 implements Runnable { int i = 0; public MyRunable4(int i) { this.i = i; } @Override public void run() { String name = Thread.currentThread().getName(); System.out.println("线程:"+name + "执行了任务:"+i); } @Override public String toString() { return "MyRunable4{" + "i=" + i + '}'; }} 代码演示ScheduledExecutorService12345678910111213141516171819202122232425262728293031323334/** * 演示newScheduledThreadPool,测试延迟执行和重复执行的功能 */public class Demo { public static void main(String[] args) { // 获取具备延迟执行任务的线程池对象 ScheduledExecutorService es = Executors.newScheduledThreadPool(3); for (int i = 0; i < 10; i++) { // 创建多个任务,并且提交任务,每个任务延迟2s执行 es.schedule(new MyRunable(i), 2, TimeUnit.SECONDS); } System.out.println("结束"); }}class MyRunable implements Runnable { int i = 0; public MyRunable(int i) { this.i = i; } @Override public void run() { String name = Thread.currentThread().getName(); System.out.println("线程:"+name + "执行了任务:"+i); }} 12345678910111213141516171819202122232425262728293031323334353637383940414243// 演示newScheduledThreadPoolpublic class Demo2 { public static void main(String[] args) { // 获取具备延迟执行任务的线程池对象 ScheduledExecutorService es = Executors.newScheduledThreadPool(3, new ThreadFactory() { int n = 1; @Override public Thread newThread(Runnable r) { return new Thread("自定义线程:" + n++); } }); // 创建多个任务,并且提交任务,每个任务延迟2s执行 es.scheduleAtFixedRate(new MyRunable2(1), 1,2, TimeUnit.SECONDS); System.out.println("结束"); }}class MyRunable2 implements Runnable { int i = 0; public MyRunable2(int i) { this.i = i; } @Override public void run() { String name = Thread.currentThread().getName(); try { Thread.sleep(1500); } catch (InterruptedException e) { throw new RuntimeException(e); } System.out.println("线程:"+name + "执行了任务:"+i); }} 12345678910111213141516171819202122232425262728293031323334353637383940414243// 演示newSingleThreadScheduledExecutorpublic class Demo3 { public static void main(String[] args) { // 获取具备延迟执行任务的线程池对象 ScheduledExecutorService es = Executors.newSingleThreadScheduledExecutor( new ThreadFactory() { int n = 1; @Override public Thread newThread(Runnable r) { return new Thread("自定义线程:" + n++); } }); // 创建多个任务,并且提交任务,每个任务延迟2s执行 es.scheduleWithFixedDelay(new MyRunable2(1), 1,2, TimeUnit.SECONDS); System.out.println("结束"); }}class MyRunable3 implements Runnable { int i = 0; public MyRunable3(int i) { this.i = i; } @Override public void run() { String name = Thread.currentThread().getName(); try { Thread.sleep(2000); } catch (InterruptedException e) { throw new RuntimeException(e); } System.out.println("线程:"+name + "执行了任务:"+i); }} 代码演示Future1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556// public class Demo5 { public static void main(String[] args) throws ExecutionException, InterruptedException, TimeoutException { // 创建线程池对象 ExecutorService executorService = Executors.newCachedThreadPool(); Future<Integer> submit = executorService.submit(new MyCall(1, 2));// test1(submit);// test2(submit); } private static void test2(Future<Integer> submit) throws InterruptedException, ExecutionException, TimeoutException { Thread.sleep(1000); System.out.println("取消任务执行的结果" + submit.cancel(true)); // 主线程等待超时 Integer i = submit.get(1, TimeUnit.SECONDS); System.out.println("任务执行的结果:" + i); } private static void test1(Future<Integer> submit) throws InterruptedException, ExecutionException { // 判断任务是否完成 boolean done = submit.isDone(); System.out.println("第一次判断任务是否完成:" + done); boolean cancelled = submit.isCancelled(); System.out.println("第一次判断任务是否取消:" + cancelled); // 无限期等待 Integer i = submit.get(); System.out.println("任务执行的结果:" + i); System.out.println("第二 次判断任务是否完成:" + submit.isDone()); System.out.println("第一次判断任务是否取消:" + submit.isCancelled()); }}class MyCall implements Callable<Integer> { int a; int b; public MyCall(int a, int b) { this.a = a; this.b = b; } @Override public Integer call() throws Exception { String name = Thread.currentThread().getName(); System.out.println("线程:"+name + "准备开始计算:"); Thread.sleep(2000); System.out.println("线程:"+name + "计算完成:"); return a+b; }} JVMJVM内存结构 程序计数器虚拟机指令的执行流程:CPU不是不是直接读二进制字节码 , 解释器读二进制字节码 转换成 机器码,交给CPU执行 程序计数器的在作用:记住下一跳JVM指令的执行地址 特点: 线程私有的, 不会存在内存溢出 虚拟机栈每个栈由多个栈桢组成,对应这每次方法调用的时所占用的内存 每个线程只能有一个活动栈桢,对应着当前正在执行的那个方法 数据结构,像子弹夹,先进后出,所以不需要GC -Xss 设置每个线程栈内存大小,默认1M,内存越大,能使用的线程越少 演示栈桢 演示线程安全方法内的局部变量线程是安全的 12345678910public class Demo3 { public void test() { // 每个线程,有各自的x int x = 0; for (int i = 0; i < 1000; i++) { x++; } System.out.println(x); }} 演示线程不安全如果是静态变量,就不是安全的了 123456789101112public class Demo3 { static int x = 0; // 多线程运行 public void demo() { for (int i = 0; i < 1000; i++) { x++; } System.out.println(x); }} 演示栈内存溢出栈桢过大或者过多会溢出 12345678910111213141516171819202122232425public class Demo2 { private static int count = 0; public static void main(String[] args) { try { method(); } catch (Throwable e) { e.printStackTrace(); // 19994 System.out.println(count); } } private static void method(){ count++; method(); }}// java.lang.StackOverflowError// at 栈.Demo2.method(Demo2.java:22)// at 栈.Demo2.method(Demo2.java:22) 线程诊断1.线程CPU占用高 12345678# 使用top命令看哪个进程占用CPU过高,只能看到进程,不能看到线程# 查看哪个线程占用过高ps H -eo pid,tid,%cpu | grep 进程id# jstack 线程id在具体查找的时候,十进制的线程ID转成16进制 2.运行很长时间,得不到结果 有可能发生死锁了 本地方法栈native 方法,比如object里的clone方法 堆使用-Xmx 设置堆内存空间大小 特点: 线程共享的,堆中对象需要考虑线程安全的问题 有垃圾回收机制 演示堆内存溢出1234567891011121314151617public class Demo { public static void main(String[] args) { int i = 0; List<String> List = new ArrayList<>(); String a = "hello"; try { while (true) { List.add(a); a = a + a; i++; } } catch (Exception e) { e.printStackTrace(); System.out.println(i); } }} 演示jmap12345678# 查看堆内存使用空间# 16279是pidjmap -heap 16279Error: -heap option usedCannot connect to core dump or remote debug server. Use jhsdb jmap instead# 对于jdk8之后的版本,不能再使用jmap -heap pid的命令了jhsdb jmap --heap --pid 16279 演示jconsole12345678910111213public class Demo2 { public static void main(String[] args) throws InterruptedException { System.out.println("1....."); Thread.sleep(30000); byte[] bytes = new byte[1028 * 1024 * 10]; System.out.println("2....."); Thread.sleep(30000); bytes = null; System.gc(); System.out.println("3....."); Thread.sleep(1000000); }} 打印1的时候,是刚开始运行,默认的内存占用 打印2的时候,代码创建了一个10MB数组 打印3的时候,数据被垃圾回收了,还回收了一些默认的创建的空间,所以内存占用就降下来了 演示jvisualvm点击gc之后,内存占用不下降的情况 1234567891011121314151617181920/** * 演示jvisualvm */public class Demo3 { public static void main(String[] args) throws InterruptedException { ArrayList<Persion> persions = new ArrayList<>(); for (int i = 0; i < 200; i++) { persions.add(new Persion()); } Thread.sleep(99999999999999999L); }}class Persion{ private byte[] big = new byte[1024 * 1024];} 点击Heap Dump 仔细找找,就能看到那些数据占用的内存比较多 方法区 12345678演示永久代内存溢出:# 1.8以前会导致永久代内存溢出java.lang.OutOfMemoreryError: PermGen space-XX:MaxPermSize=8m# 1.8以前会导致元空间内存溢出java.lang.OutOfMemoreryError: Metaspace-XX:MaxMetaspaceSize=8m 123456789101112131415161718192021222324252627282930313233343536// 没有演示出来import jdk.internal.org.objectweb.asm.ClassWriter;import jdk.internal.org.objectweb.asm.Opcodes;/** * 演示元空间内存溢出 * 方法区内存溢出,1.8才有这个版本 * -XX:MaxMetaspaceSize=8m * * ClassLoader:类加载器,可以用来加载类的二进制字节码 */public class Demo extends ClassLoader{ public static void main(String[] args) { int j = 0; try { Demo test = new Demo(); for (int i = 0; i < 10000; i++, j++) { // 生成类的二进制字节码 ClassWriter cw = new ClassWriter(0); // 版本号 // 访问修饰符 // 类名 // 包名 // 父类 // 接口名称,这里没有实现接口 cw.visit(Opcodes.V1_8, Opcodes.ACC_PUBLIC, "Class" + i, null, "java/lang/Object", null); // 返回byte数组 byte[] code = cw.toByteArray(); // 加载类,生成Class对象 test.defineClass("Class" + i, code, 0, code.length); } }finally { System.out.println(j); } }} 运用的场景: Spring和Mybatis都使用到了CGLIB技术,CGLIB也会像上面演示的使用ClassWriter创建对象,创建的对象多了,就会导致方法区异常,所以就移动到直接内存,虽然没啥本质上的作用,只是会影响OOM的时间 常量池123456789101112/** * 常量池 * 二进制字节码(类基本信息,常量池,类方法定义,包含了虚拟机指令) */public class Demo3 { public static void main(String[] args) { System.out.println("Hello world"); }}// 使用命令编译javap -v Demo3.class 编译的结果 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283// 以下是类的基本信息Classfile /Users/anthony/Documents/GitHub/study-java/java-base/target/classes/方法区/Demo3.class Last modified 2023年8月23日; size 538 bytes MD5 checksum 1aca08c8871c6946b2737975bbf15625 Compiled from "Demo3.java"public class 方法区.Demo3 minor version: 0 major version: 55 flags: (0x0021) ACC_PUBLIC, ACC_SUPER this_class: #5 // 方法区/Demo3 super_class: #6 // java/lang/Object interfaces: 0, fields: 0, methods: 2, attributes: 1 // 常量池Constant pool: #1 = Methodref #6.#20 // java/lang/Object."<init>":()V #2 = Fieldref #21.#22 // java/lang/System.out:Ljava/io/PrintStream; #3 = String #23 // Hello world #4 = Methodref #24.#25 // java/io/PrintStream.println:(Ljava/lang/String;)V #5 = Class #26 // 方法区/Demo3 #6 = Class #27 // java/lang/Object #7 = Utf8 <init> #8 = Utf8 ()V #9 = Utf8 Code #10 = Utf8 LineNumberTable #11 = Utf8 LocalVariableTable #12 = Utf8 this #13 = Utf8 L方法区/Demo3; #14 = Utf8 main #15 = Utf8 ([Ljava/lang/String;)V #16 = Utf8 args #17 = Utf8 [Ljava/lang/String; #18 = Utf8 SourceFile #19 = Utf8 Demo3.java #20 = NameAndType #7:#8 // "<init>":()V #21 = Class #28 // java/lang/System #22 = NameAndType #29:#30 // out:Ljava/io/PrintStream; #23 = Utf8 Hello world #24 = Class #31 // java/io/PrintStream #25 = NameAndType #32:#33 // println:(Ljava/lang/String;)V #26 = Utf8 方法区/Demo3 #27 = Utf8 java/lang/Object #28 = Utf8 java/lang/System #29 = Utf8 out #30 = Utf8 Ljava/io/PrintStream; #31 = Utf8 java/io/PrintStream #32 = Utf8 println #33 = Utf8 (Ljava/lang/String;)V // 类的方法定义{ public 方法区.Demo3(); descriptor: ()V flags: (0x0001) ACC_PUBLIC Code: stack=1, locals=1, args_size=1 0: aload_0 1: invokespecial #1 // Method java/lang/Object."<init>":()V 4: return LineNumberTable: line 6: 0 LocalVariableTable: Start Length Slot Name Signature 0 5 0 this L方法区/Demo3; public static void main(java.lang.String[]); descriptor: ([Ljava/lang/String;)V flags: (0x0009) ACC_PUBLIC, ACC_STATIC Code: stack=2, locals=1, args_size=1 // 虚拟机指令 0: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream; 3: ldc #3 // String Hello world 5: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 8: return LineNumberTable: line 8: 0 line 9: 8 LocalVariableTable: Start Length Slot Name Signature 0 9 0 args [Ljava/lang/String;}SourceFile: "Demo3.java" 串池(StringTable)的特性常量池中的字符串仅是符号,第一次用到的时候才回变成对象 利用串池的机制,来避免重复创建字符串对象 字符串变量拼接的原理是StringBuilder(1.8) 字符串常量拼接的原理是编译期优化 可以使用intern方法,主动将串池中的还没有的字符串对象放入串池 12345678910111213public class Demo2 { public static void main(String[] args) { String s = new String("a") + new String("b"); // 这是1.8的 讲字符串对象尝试放入到串池中,如果有不会放入,如果没有则会放入串池,会把串池中的对象返回 // 1.8和1.6 不一样 String intern = s.intern(); // true System.out.println(intern=="ab"); // true System.out.println(s=="ab"); }} 常量池和串池的区别1234567891011121314151617181920212223// StringTable["a","b","ab"]public class Demo { // 反编译之后,常量池中的信息,都会被加载到运行时常量池中,这时都是常量池中的符号,还没有变成Java字符串对象 // 运行到s1这行代码的时候,就会把a符号变成a字符串对象,存入到stringtable[]中 // 用到才会创建字符串对象放到串池中,也就是懒加载 public static void main(String[] args) { String s1 = "a"; // 懒加载 String s2 = "b"; String s3 = "ab"; // 字符串相加的原理:new StringBuilder().append(s1).append(s2).toString() // 上面s3的位置是在串池中,s4 是在堆内存中 String s4 = s1 + s2; // false System.out.println(s3 == s4); // javac在编译期间就确定为ab了,运行的时候直接去串池中查找 String s5 = "a" + "b"; // true System.out.println(s3 == s5); }} 证明字符串加载的延迟特性 StringTable的位置位置的移动,在1.6的时候要使用full gc才能被回收,1.8移动到堆内存了,minco gc 就可以回收 StringTable的调优1.调整-XX:StringTableSize=桶的个数 2.考虑将字符串对象是否入池 比如有很多人的地址,都包含北京市这样的字符串,就应该考虑入池 直接内存(Direct Memory)常见于NIO(ByteBuffer)操作时,用户数据缓冲区 分配回收成本较高,读写性能高 不受JVM内存回收管理 演示直接内存溢出12345678910111213141516171819202122232425262728public class Demo { static int _100MB = 1024 * 1024 * 100; public static void main(String[] args) { List<ByteBuffer> objects = new ArrayList<>(); int i = 0; try { while (true) { ByteBuffer allocate = ByteBuffer.allocateDirect(_100MB); objects.add(allocate); i++; } } finally { System.out.println(i); } }}Exception in thread "main" java.lang.OutOfMemoryError: Direct buffer memory at java.base/java.nio.Bits.reserveMemory(Bits.java:175) at java.base/java.nio.DirectByteBuffer.<init>(DirectByteBuffer.java:118) at java.base/java.nio.ByteBuffer.allocateDirect(ByteBuffer.java:317) at 直接内存.Demo.main(Demo.java:18) 释放原理使用UnSafe对象完成了直接内存的分配回收,而且回收需要主动调用freeMemory方法 ByteBuffer的实现类内部,使用了Cleaner(虚引用)来检测ByteBuffer对象,一旦ByteBuffer对象被垃圾回收,就会由ReferenceHandler线程通过Cleaner方法调用freeMemory来释放直接内存 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123public class Demo2 { static int _1GB = 1024 * 1024 * 1024; public static void main(String[] args) throws IOException { ByteBuffer byteBuffer = ByteBuffer.allocateDirect(_1GB); System.out.println("分配完毕"); // 看任务管理器,可以看到这个程序占用1G+ System.in.read(); System.out.println("开始释放"); byteBuffer = null; System.gc(); // 可以看到被会回收了,为什么会被回收呢 System.in.read(); }}// 释放原理public class Demo3 { static int _1GB = 1024 * 1024 * 1024; public static void main(String[] args) throws IOException, NoSuchFieldException, IllegalAccessException { Unsafe unsafe = getUnsafe(); // 分配内存 // base 表示内存地址 long base = unsafe.allocateMemory(_1GB); unsafe.setMemory(base,_1GB,(byte)0); System.in.read(); // 释放内存 unsafe.freeMemory(base); System.in.read(); } public static Unsafe getUnsafe() throws NoSuchFieldException, IllegalAccessException { Field declaredField = Unsafe.class.getDeclaredField("theUnsafe"); declaredField.setAccessible(true); return (Unsafe) declaredField.get(null); }}// 源码ByteBuffer.allocateDirect(_1GB);public static ByteBuffer allocateDirect(int capacity) { return new DirectByteBuffer(capacity);}DirectByteBuffer(int cap) { // package-private super(-1, 0, cap, cap); boolean pa = VM.isDirectMemoryPageAligned(); int ps = Bits.pageSize(); long size = Math.max(1L, (long)cap + (pa ? ps : 0)); Bits.reserveMemory(size, cap); long base = 0; try { // 分配内存 base = UNSAFE.allocateMemory(size); } catch (OutOfMemoryError x) { Bits.unreserveMemory(size, cap); throw x; } UNSAFE.setMemory(base, size, (byte) 0); if (pa && (base % ps != 0)) { // Round up to page boundary address = base + ps - (base & (ps - 1)); } else { address = base; } // 当this被回收时,就会调用回调函数Deallocator // 就是Java对象被回收,触发直接内存回收 cleaner = Cleaner.create(this, new Deallocator(base, size, cap)); att = null;}// 继承了虚应用对象public class Cleaner extends PhantomReference<Object>{ public void clean() { if (!remove(this)) return; try { thunk.run(); } catch (final Throwable x) { AccessController.doPrivileged(new PrivilegedAction<>() { public Void run() { if (System.err != null) new Error("Cleaner terminated abnormally", x) .printStackTrace(); System.exit(1); return null; }}); } }}// 回调函数private static class Deallocator implements Runnable{ private long address; private long size; private int capacity; private Deallocator(long address, long size, int capacity) { assert (address != 0); this.address = address; this.size = size; this.capacity = capacity; } public void run() { if (address == 0) { // Paranoia return; } // 释放直接内存 UNSAFE.freeMemory(address); address = 0; Bits.unreserveMemory(size, capacity); }} 垃圾回收机制可达性分析法如何判断对象是否可以回收 引用计数法 可达性分析法,使用Eclipse Memory Analyzer ,沿着GC Root查找,找到就不能回收 Eclipse Memory Analyzer的使用方法 123456789101112131415161718192021/** * 使用mat 演示GC root */public class Demo { public static void main(String[] args) throws IOException { ArrayList<Object> list1 = new ArrayList<>(); list1.add("a"); list1.add("b"); System.out.println(1); // jsp 获取到进程id // jmap -dump:format=b,live,file=1.bin 进程id System.in.read(); list1 = null; System.out.println(2); System.in.read(); System.out.println("end...."); }} live 代表的意思是,执行dump文件的时候,先执行一次GC 大概有四类: System Class Native Stack,调用native方法要使用到的 Thread,活动线程,使用到的对象 Busy Monitor,各种锁 四种引用 强引用 强引用 软引用 有软引用引用该对象,只有在内存不足时才会被垃圾回收 可以配合引用队列来释放引用自身 弱引用 在GC时,不管内存是否充足都会被回收 可以配合引用队列来释放引用自身 需引用 主要分配ByteBuffer使用,被引用对象回收时,被引用对象回收时,会将虚引用入队,由Reference Handler线程调用虚引用相关方法释放直接内存 必须配合引用队列 终结器引用 无需手动编码,GC时,终结器引用入队,再由Finalizer线程通过终结器引用并调用finalize方法,第二次GC时,才能被回收 垃圾回收算法 标记清除 标记整理 复制 标记清除原本的内存使用情况 标记 把要清楚的内存的开始地址放在列表你,等下次要分配内存的时候,直接在列表里找 优点:就是速度快 缺点:产生内存碎片 标记整理标记 整理 优点:不会产生碎片 缺点:速度慢 复制算法原来的内存使用 标记 复制 清理 交换from 和 to的位置 分代回收12345新生代: Minor GC 伊甸园 幸存区 from 幸存区 to老年代: Full GC 也会清理新生代 对象首先分配在伊甸园区域 新生代空间不足时,触发 minor ge,伊甸园和trom 存活的对象使用copy 复制到10中,存活的对象年龄加1并且交换 from to minor se 会引1发 stop the wvorid,暂停其它用户的线程,等垃圾回收结束,用户线程才恢复运行 当对象寿命超过國值时,会晋升至老年代,最大寿命是15 (4bit) 当老年代空间不足,会先尝试触发 mninor ge,如果之后空间仍不足,那么触发 foll ge, sTw的时间更长 含义 参数 堆初始大小 Xms 堆最大大小 xmx 或 -Xx:MaxFeapSize=size 新生代大小 Xmn 或 (-XX:NewSize=size + -XX:MaxNewSize=size) 幸存区比例(动态) Xx:InitialSurvivorRatio=ratio 和-XX:+UseAdaptiveSizePolicy 幸存区比例 XX:SurvivorRatio=ratio 晋升阈值 XX:MaxTenuringThreshold=threshold (threshold和具体的回收器有关) 晋升详情 XX: +PrintTenuringDistribution GC详情 XX:+PrintGCDetails - verbose:go FullGC 前 MinorGC XX:+ScavengeBeforeFullGC Servlet搭建环境(XML版本)下载,解压tomcat,运行 1234567891011121314151617181920212223# 进入目录cd apache-tomcat-10.0.27/bin# 运行sh startup.sh# 报错信息The file is absent or does not have execute permissionThis file is needed to run this program# 授权chmod 777 apache-tomcat-10.0.27# 再次运行sh startup.sh# 打印结果Using CATALINA_BASE: /Users/anthony/Downloads/apache-tomcat-10.0.27Using CATALINA_HOME: /Users/anthony/Downloads/apache-tomcat-10.0.27Using CATALINA_TMPDIR: /Users/anthony/Downloads/apache-tomcat-10.0.27/tempUsing JRE_HOME: /Library/Java/JavaVirtualMachines/jdk-18.0.1.jdk/Contents/HomeUsing CLASSPATH: /Users/anthony/Downloads/apache-tomcat-10.0.27/bin/bootstrap.jar:/Users/anthony/Downloads/apache-tomcat-10.0.27/bin/tomcat-juli.jarUsing CATALINA_OPTS:Tomcat started.# 访问http://localhost:8080 文件目录结构 导包 1234567891011121314151617181920212223<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>org.example</groupId> <artifactId>servlet-xml</artifactId> <!-- 打war包 --> <packaging>war</packaging> <version>1.0-SNAPSHOT</version> <name>servlet-xml Maven Webapp</name> <dependencies> <dependency> <groupId>org.apache.tomcat</groupId> <artifactId>tomcat-servlet-api</artifactId> <version>10.0.4</version> </dependency> </dependencies> <build> <finalName>servlet-xml</finalName> </build></project> 因为是使用的tomcat10,所有导入的包不一样,如果是tomcat10之前的版本,导入的包好像是java-serverlt-api 参考:https://taurusxin.com/tomcat10-issue/ 代码 HelloWorld.java 1234567891011121314151617181920212223242526272829303132333435363738package com.mmzcg.servlet;// 导入必需的 java 库import jakarta.servlet.ServletException;import jakarta.servlet.http.HttpServlet;import jakarta.servlet.http.HttpServletRequest;import jakarta.servlet.http.HttpServletResponse;import java.io.IOException;import java.io.PrintWriter;// 扩展 HttpServlet 类public class HelloWorld extends HttpServlet { private String message; public void init() throws ServletException { // 执行必需的初始化 message = "执行必需的初始化"; } public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // 设置响应内容类型 response.setContentType("text/html"); // 实际的逻辑是在这里 PrintWriter out = response.getWriter(); out.println("<h1>" + message + "</h1>"); } public void destroy() { // 什么也不做 System.out.printf("我就没见过销毁过"); }} Version.java 1234567891011121314151617181920212223242526272829303132333435363738package com.mmzcg.servlet;// 导入必需的 java 库import jakarta.servlet.ServletException;import jakarta.servlet.http.HttpServlet;import jakarta.servlet.http.HttpServletRequest;import jakarta.servlet.http.HttpServletResponse;import java.io.IOException;import java.io.PrintWriter;// 扩展 HttpServlet 类public class Version extends HttpServlet { private String message; public void init() throws ServletException { // 执行必需的初始化 message = "执行必需的初始化"; } public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // 设置响应内容类型 response.setContentType("text/html"); // 实际的逻辑是在这里 PrintWriter out = response.getWriter(); out.println("<h1>" + "返回的是版本号" + "</h1>"); } public void destroy() { // 什么也不做 System.out.printf("我就没见过销毁过"); }} index.jsp 12345678910111213// 解决中文乱码<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%><html><body><h2>这是首页</h2>有两个url<a href="/hello">hello</a><a href="/version">version</a></body></html> web.xml 1234567891011121314151617181920212223242526<!DOCTYPE web-app PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN" "http://java.sun.com/dtd/web-app_2_3.dtd" ><web-app> <display-name>Archetype Created Web Application</display-name> <servlet> <servlet-name>HelloWorld</servlet-name> <servlet-class>com.mmzcg.servlet.HelloWorld</servlet-class> </servlet> <servlet> <servlet-name>Version</servlet-name> <servlet-class>com.mmzcg.servlet.Version</servlet-class> </servlet> <servlet-mapping> <servlet-name>HelloWorld</servlet-name> <url-pattern>/hello</url-pattern> </servlet-mapping> <servlet-mapping> <servlet-name>Version</servlet-name> <url-pattern>/version</url-pattern> </servlet-mapping></web-app> 12345678910111213141516171819202122<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>org.example</groupId> <artifactId>servlet-xml</artifactId> <packaging>war</packaging> <version>1.0-SNAPSHOT</version> <name>servlet-xml Maven Webapp</name> <dependencies> <dependency> <groupId>org.apache.tomcat</groupId> <artifactId>tomcat-servlet-api</artifactId> <version>10.0.4</version> </dependency> </dependencies> <build> <finalName>servlet-xml</finalName> </build></project> 运行环境一:在IDEA上运行tomcat 添加tomcat模板 配置tomcat_home 配置包和路径 运行环境一:在命令行运行tomcat 先停掉tomcat,复制war包到webapps目录里 再启动tomcat,默认的访问地址就是:http://localhost:8080/servlet-xml/ 搭建环境(注解版本)前面的都不变,只是改下代码 web.xml 1234567<!DOCTYPE web-app PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN" "http://java.sun.com/dtd/web-app_2_3.dtd" ><web-app> <display-name>Archetype Created Web Application</display-name></web-app> index.jsp 12345<html><body><h2>Hello World!</h2></body></html> 运行环境一:在命令行运行tomcat HelloWorld.java 12345678910111213141516171819202122232425262728293031323334353637383940package com.mmzcg.annotation;// 导入必需的 java 库import jakarta.servlet.ServletException;import jakarta.servlet.annotation.WebServlet;import jakarta.servlet.http.HttpServlet;import jakarta.servlet.http.HttpServletRequest;import jakarta.servlet.http.HttpServletResponse;import java.io.IOException;import java.io.PrintWriter;// 扩展 HttpServlet 类@WebServlet("/hello")public class HelloWorld extends HttpServlet { private String message; public void init() throws ServletException { // 执行必需的初始化 message = "注解执行必需的初始化"; } public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // 设置响应内容类型 response.setContentType("text/html"); // 实际的逻辑是在这里 PrintWriter out = response.getWriter(); out.println("<h1>" + message + "</h1>"); } public void destroy() { // 什么也不做 System.out.printf("注解我就没见过销毁过"); }} Version.java 12345678910111213141516171819202122232425262728293031323334353637383940package com.mmzcg.annotation;// 导入必需的 java 库import jakarta.servlet.ServletException;import jakarta.servlet.annotation.WebServlet;import jakarta.servlet.http.HttpServlet;import jakarta.servlet.http.HttpServletRequest;import jakarta.servlet.http.HttpServletResponse;import java.io.IOException;import java.io.PrintWriter;// 扩展 HttpServlet 类@WebServlet("/version")public class Version extends HttpServlet { private String message; public void init() throws ServletException { // 执行必需的初始化 message = "执行必需的初始化"; } public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // 设置响应内容类型 response.setContentType("text/html"); // 实际的逻辑是在这里 PrintWriter out = response.getWriter(); out.println("<h1>" + "注解返回的是版本号" + "</h1>"); } public void destroy() { // 什么也不做 System.out.printf("我就没见过销毁过"); }} 中文乱码Tomcat配置文件apache-tomcat-8.5.69\conf\server.xml 添加URIEncoding=”UTF-8” 属性 1234<Connector port="8080" protocol="HTTP/1.1" connectionTimeout="20000" redirectPort="8443" URIEncoding="UTF-8"/> Idea指定编码 Jsp指定编码在jsp的html最先添加 1<%@ **page** language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> httpResponse指定编码12345678// 备注:该方法只对POST方式提交的数据有效,对GET方式提交的数据无效!request.setCharacterEncoding("UTF-8");//在响应中主动告诉浏览器使用UTF-8编码格式来接收数据response.setHeader("Content-Type", "text/html;charset=UTF-8");//可以使用封装类简写Content-Type,使用该方法则无需使用setCharacterEncodingresponse.setContentType("text/html;charset=UTF-8");

2022-11-19
Mybatis
教程 参考: 官方文档 源码 Provider的使用1.一定要注意type的类名.class 和 method方法名,还要注意形参也得是一样的 2.Provider的方法,大概就三个方法sql.SELECT,sql.WHERE,sql.FROM 3.SQL 对象里的方法名跟别的不一样,小写的不行,idea也识别不到,要用大写,比如SLELECT 4.Provider里返回的是String 注解 一对多查询(多个参数) 注解 一对多查询(多个参数)123456789101112131415161718192021222324252627282930313233343536373839404142@SelectProvider(method="queryPageFloors",type=BarterGroupProvider.class)@Results({ @Result(property = "userId",column="user_id"), @Result(property = "floorNum",column="floor_num"), @Result(property = "floorSecond",column="id"), @Result(property = "say",column="note"), @Result(property = "joinOrPublish",column="join_or_publish"), @Result(property="categorys",javaType = List.class,column="id",many=@Many(select="com.ecloud.hobay.marketing.service.infrastructure.mapper.bartergroup.BarterGroupMapper.queryCategory")), @Result(property="productIds",javaType = List.class,column="id",many=@Many(select="com.ecloud.hobay.marketing.service.infrastructure.mapper.bartergroup.BarterGroupMapper.queryProducts")), @Result(property="beforeResult",javaType = List.class,column="id",many=@Many(select="com.ecloud.hobay.marketing.service.infrastructure.mapper.bartergroup.BarterGroupEvaluationMapper.queryAll")), @Result(property="barterGroupUser",javaType = BarterGroupUser.class,column="{user_id=user_id,id = barter_group_id }",many=@Many(select="com.ecloud.hobay.marketing.service.infrastructure.mapper.bartergroup.BarterGroupMapper.isHead")), @Result(property="futureResultNum",column="id",many=@Many(select="com.ecloud.hobay.marketing.service.infrastructure.mapper.bartergroup.BarterGroupMapper.futureResultNum"))})List<QueryFloors> queryFloors3(Page<QueryFloors> page);/** * 分类 * @param id * @return */@Select("SELECT * FROM ecloud_marketing.barter_group_category WHERE barter_group_details_id = #{id} limit 0,7 ")List<BarterGroupCategory> queryCategory(@Param("id") Long id);/** * 产品 * @param id * @return */@Select("SELECT product_id from ecloud_marketing.barter_group_product_category WHERE barter_group_details_id = #{id} limit 0,3 ")List<Long> queryProducts(@Param("id") Long id);/** * 团员 * @param userId * @param id * @return */@Select("SELECT * FROM ecloud_marketing.barter_group_user WHERE user_id =#{user_id} and barter_group_id = #{id} and status = 1")BarterGroupUser isHead(Map<String,Object> map);@Select("SELECT count(*) from ecloud_marketing.barter_group_evaluation WHERE barter_group_details_id = #{id}")Integer futureResultNum(@Param("id") Long id); 1234567891011121314151617181920212223242526272829303132333435363738public class QueryFloors implements Serializable { @ApiModelProperty("我有的产品的id") List<ProductBarter> list; @ApiModelProperty("封装前的评论数据") private List<BarterGroupEvaluation> beforeResult; @ApiModelProperty("封装后的评论数据有分页给功能") private Page<BarterGroupEvaluation> beforeResultPage; @ApiModelProperty("封装后的评论数据") private List<BarterGroupEvaluationResult> futureResult; @ApiModelProperty("剩余评论条数") private Integer futureResultNum; @ApiModelProperty("分类") List<BarterGroupCategory> categorys; @ApiModelProperty(value="楼层数") private Integer floorNum; @ApiModelProperty(value="楼层id") private Long floorSecond; @ApiModelProperty(value="要说的") private String say; @ApiModelProperty(value="加入或者是发布") private Integer joinOrPublish; @ApiModelProperty("会员表") private BarterGroupUser barterGroupUser; @ApiModelProperty(value="加入时间") private Long joinData;} 注解 一对多查询(一个参数) 标签mybatis的xml的常用标签: include和sql标签 12345678<sql id="query_column"> id,user_no</sql><select id="test"> select <include refid="query_column"></include> from user_info</select> where标签 12345678910<select id="test"> select * from user_info where <if test="userName != null and userName != ''"> user_name = #{userName} </if> <if test="password != null and password != ''"> and password = #{password} </if></select> 如果userName= null,则sql语句就变成 1select * from user_info where and password = #{password} where标签可以去除where语句中的第一个and 或 or。 1234567891011<select id="test"> select * from user_info <where> <if test="userName != null and userName != ''"> and user_name = #{userName} </if> <if test="password != null and password != ''"> and password = #{password} </if> </where></select> set标签 123456789101112<update id="myupdate"> update user_info <set> <if test="userName != null"> user_name = #{userName}, </if> <if test="password != null"> password = #{password,jdbcType=VARCHAR}, </if> </set> where id = #{id,jdbcType=INTEGER} </update> trim标签 where标签只能去除第一个and或or,不够灵活,trim标签功能更加强大。 trim标签的属性如下: prefix 在trim标签内容前面加上前缀 suffix 在trim标签内容后面加上后缀 prefixOverrides 移除前缀内容。即 属性会忽略通过管道分隔的文本序列,多个忽略序列用 “|” 隔开。它带来的结果就是所有在 prefixOverrides 属性中指定的内容将被移除。 suffixOverrides 移除前缀内容。 12345678<trim prefix="where" prefixOverrides="and"> <if test="userId != null"> user_id=#{userId} </if> <if test="pwd != null and pwd !=''"> user_id=#{userId} </if></trim> foreach标签 foreach元素的属性主要有item,index,collection,open,separator,close. collection 要做foreach的对象,作为入参时,List对象默认用”list”代替作为键,数组对象有”array”代替作为键,Map对象没有默认的键。当然在作为入参时可以使用@Param(“keyName”)来设置键,设置keyName后,list,array将会失效。 除了入参这种情况外,还有一种作为参数对象的某个字段的时候。举个例子:如果User有属性List ids。入参是User对象,那么这个collection = “ids”.如果User有属性Ids ids;其中Ids是个对象,Ids有个属性List id;入参是User对象,那么collection = “ids.id“,必填 item 合中元素迭代时的别名,必选 index 在list和数组中,index是元素的序号,在map中,index是元素的key,可选 open foreach代码的开始符号,一般是(和close=”)”合用。常用在in(),values()时。可选 separator 元素之间的分隔符,例如在in()的时候,separator=”,”会自动在元素中间用“,“隔开,避免手动输入逗号导致sql错误,如in(1,2,)这样,可选。 close foreach代码的关闭符号,一般是)和open=”(“合用。常用在in(),values()时。可选 choose,when,otherwise标签 功能有点类似于Java中的 swicth - case - default 12345678910111213141516<select id="getUser" resultType="com.cat.pojo.User"> SELECT * FROM user <where> <choose> <when test="id != null and test.trim() != '' "> id = #{id} </when> <when test="name != null and name.trim() != '' "> name = #{name} </when> <otherwise> age = 17 </otherwise> </choose></where></select> if标签 123456<!-- 判断字符串--><if test="item != null and item != ''"></if><!-- 如果是Integer类型的需要把and后面去掉或是加上or--><if test="item != null"></if><if test="item != null and item != '' or item == 0"></if> 存过/函数存过 12345678<select id="pNextSupperUsers" parameterType="map" statementType="CALLABLE" resultType="vo.UserAgentVO"> { call p_next_supper_users( #{type,mode=IN,jdbcType=INTEGER}, #{userId,mode=IN,jdbcType=BIGINT} ) }</select> 123456List<UserAgentVO> pNextSupperUsers(Map<String, Object> param);Map<String, Object> param = new HashMap<>();param.put("type", 1);param.put("userId", userId);List<UserAgentVO> list = userInfoMapper.pNextSupperUsers(param); 函数 1234SELECT fn_next_user_count ( 1, u.id ) AS teamCount,FROMuser_info 源码参考 b站博学谷 架构设计 启动测试方法123456789101112131415161718192021# 第一种调用方法public static void test(){ String resource = "com/analyze/mybatis/mybatis-config.xml"; InputStream inputStream = Resources.getResourceAsStream(resource); SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); SqlSession sqlSession = sqlSessionFactory.openSession(); Map map = sqlSession.selectOne("com.analyze.mybatis.mapper.UserMapper.getUA"); sqlSession.close();}# 第二种调用方法public static void test2(){ String resource = "com/analyze/mybatis/mybatis-config.xml"; InputStream inputStream = Resources.getResourceAsStream(resource); SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); SqlSession sqlSession = sqlSessionFactory.openSession(); UserMapper userMapper = sqlSession.getMapper(UserMapper.class); Map map = userMapper.getUA(); sqlSession.close();} 下面的代码,大概意思就是能加载的配置文件的信息,解释 InputStream inputStream = Resources.getResourceAsStream(resource);这行代码的作用 读取mybatis的配置文件12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849// org.apache.ibatis.io.Resources#getResourceAsStream(java.lang.String)public static InputStream getResourceAsStream(String resource) throws IOException { return getResourceAsStream(null, resource);}// org.apache.ibatis.io.Resources#getResourceAsStream(java.lang.ClassLoader, java.lang.String)public static InputStream getResourceAsStream(ClassLoader loader, String resource) throws IOException { InputStream in = classLoaderWrapper.getResourceAsStream(resource, loader); if (in == null) { throw new IOException("Could not find resource " + resource); } return in;}// org.apache.ibatis.io.ClassLoaderWrapper#getResourceAsStream(java.lang.String, java.lang.ClassLoader)public InputStream getResourceAsStream(String resource, ClassLoader classLoader) { return getResourceAsStream(resource, getClassLoaders(classLoader));}// org.apache.ibatis.io.ClassLoaderWrapper#getClassLoadersClassLoader[] getClassLoaders(ClassLoader classLoader) { return new ClassLoader[]{ classLoader, defaultClassLoader, Thread.currentThread().getContextClassLoader(), getClass().getClassLoader(), systemClassLoader};}// org.apache.ibatis.io.ClassLoaderWrapper#getResourceAsStream(java.lang.String, java.lang.ClassLoader[])// 找到一个可以用的ClassloaderInputStream getResourceAsStream(String resource, ClassLoader[] classLoader) { for (ClassLoader cl : classLoader) { if (null != cl) { // try to find the resource as passed InputStream returnValue = cl.getResourceAsStream(resource); if (null == returnValue) { returnValue = cl.getResourceAsStream("/" + resource); } if (null != returnValue) { return returnValue; } } } return null;} 下面的代码,解释SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);,用来解析全局配置文件和解析mapper文件 下面是解析全局配置文件 解析全局配置文件123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188// org.apache.ibatis.session.SqlSessionFactoryBuilder#build(java.io.InputStream)public SqlSessionFactory build(InputStream inputStream) { return build(inputStream, null, null);}// org.apache.ibatis.session.SqlSessionFactoryBuilder#build(java.io.InputStream, java.lang.String, java.util.Properties)public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) { try { // 1.创建XpathParse解析器对象,根据inputStream解析成Document对象 // 2.创建全局配置Configuration对象 // 使用构建者模式,好处降低偶尔,分离复杂对象的创建 // 构建XMLConfig XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties); // 根据Xpath解析xml配置文件,将配置文件封装到Configuration对象 // 返回DefaultSqlSessionFactory对象 // parse() 就是配置文件解析完成了 return build(parser.parse()); } catch (Exception e) { throw ExceptionFactory.wrapException("Error building SqlSession.", e); } finally { ErrorContext.instance().reset(); try { inputStream.close(); } catch (IOException e) { // Intentionally ignore. Prefer previous error. } }}// org.apache.ibatis.session.SqlSessionFactoryBuilder#build(org.apache.ibatis.session.Configuration)// 最终返回public SqlSessionFactory build(Configuration config) { return new DefaultSqlSessionFactory(config);}// org.apache.ibatis.builder.xml.XMLConfigBuilder#XMLConfigBuilder(java.io.InputStream, java.lang.String, java.util.Properties)public XMLConfigBuilder(InputStream inputStream, String environment, Properties props) { // 点this,查看代码 this(new XPathParser(inputStream, true, props, new XMLMapperEntityResolver()), environment, props);}// org.apache.ibatis.builder.xml.XMLConfigBuilder#XMLConfigBuilder(org.apache.ibatis.parsing.XPathParser, java.lang.String, java.util.Properties)private XMLConfigBuilder(XPathParser parser, String environment, Properties props) { // 创建Configuration对象,并通过TypeAliasRegistry注册一些Mybatis内部相关类的别名 super(new Configuration()); ErrorContext.instance().resource("SQL Mapper Configuration"); this.configuration.setVariables(props); this.parsed = false; this.environment = environment; this.parser = parser;}// org.apache.ibatis.parsing.XPathParser#XPathParser(java.io.InputStream, boolean, java.util.Properties, org.xml.sax.EntityResolver)public XPathParser(InputStream inputStream, boolean validation, Properties variables, EntityResolver entityResolver) { commonConstructor(validation, variables, entityResolver); // 创建解析器 this.document = createDocument(new InputSource(inputStream));}// org.apache.ibatis.parsing.XPathParser#createDocument// 不用太关系,只是创建一个解析器的对象,顺便检查xml文档有没有写错private Document createDocument(InputSource inputSource) { // important: this must only be called AFTER common constructor try { DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); factory.setValidating(validation); factory.setNamespaceAware(false); factory.setIgnoringComments(true); factory.setIgnoringElementContentWhitespace(false); factory.setCoalescing(false); factory.setExpandEntityReferences(true); DocumentBuilder builder = factory.newDocumentBuilder(); builder.setEntityResolver(entityResolver); builder.setErrorHandler(new ErrorHandler() { @Override public void error(SAXParseException exception) throws SAXException { throw exception; } @Override public void fatalError(SAXParseException exception) throws SAXException { throw exception; } @Override public void warning(SAXParseException exception) throws SAXException { } }); // 解析器的源码了,跟mybatis没有关系了 return builder.parse(inputSource); } catch (Exception e) { throw new BuilderException("Error creating document instance. Cause: " + e, e); } }// org.apache.ibatis.builder.xml.XMLConfigBuilder#parsepublic Configuration parse() { if (parsed) { // 每一个配置文件,只能解析一次 throw new BuilderException("Each XMLConfigBuilder can only be used once."); } parsed = true; // 从根节点开始解析,最终封装到Configuration对象 parseConfiguration(parser.evalNode("/configuration")); return configuration;}// org.apache.ibatis.parsing.XPathParser#evalNode(java.lang.String)public XNode evalNode(String expression) { return evalNode(document, expression);}// org.apache.ibatis.parsing.XPathParser#evalNode(java.lang.Object, java.lang.String)public XNode evalNode(Object root, String expression) { Node node = (Node) evaluate(expression, root, XPathConstants.NODE); if (node == null) { return null; } return new XNode(this, node, variables);}// org.apache.ibatis.builder.xml.XMLConfigBuilder#parseConfiguration// 可以配置这些信息:https://mybatis.org/mybatis-3/zh/configuration.htmlprivate void parseConfiguration(XNode root) { try { //issue #117 read properties first propertiesElement(root.evalNode("properties")); Properties settings = settingsAsProperties(root.evalNode("settings")); loadCustomVfs(settings); loadCustomLogImpl(settings); typeAliasesElement(root.evalNode("typeAliases")); // 插件 pluginElement(root.evalNode("plugins")); objectFactoryElement(root.evalNode("objectFactory")); objectWrapperFactoryElement(root.evalNode("objectWrapperFactory")); reflectorFactoryElement(root.evalNode("reflectorFactory")); settingsElement(settings); // read it after objectFactory and objectWrapperFactory issue #631 // 重点 environmentsElement(root.evalNode("environments")); databaseIdProviderElement(root.evalNode("databaseIdProvider")); typeHandlerElement(root.evalNode("typeHandlers")); // 重点 mapperElement(root.evalNode("mappers")); } catch (Exception e) { throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e); }}// org.apache.ibatis.builder.xml.XMLConfigBuilder#mapperElementprivate void mapperElement(XNode parent) throws Exception { if (parent != null) { for (XNode child : parent.getChildren()) { if ("package".equals(child.getName())) { String mapperPackage = child.getStringAttribute("name"); configuration.addMappers(mapperPackage); } else { String resource = child.getStringAttribute("resource"); String url = child.getStringAttribute("url"); String mapperClass = child.getStringAttribute("class"); // 三个值是互斥的 if (resource != null && url == null && mapperClass == null) { ErrorContext.instance().resource(resource); InputStream inputStream = Resources.getResourceAsStream(resource); XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments()); mapperParser.parse(); } else if (resource == null && url != null && mapperClass == null) { ErrorContext.instance().resource(url); // 专门用来解析mapper映射文件 InputStream inputStream = Resources.getUrlAsStream(url); // 重点 XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments()); mapperParser.parse(); } else if (resource == null && url == null && mapperClass != null) { Class<?> mapperInterface = Resources.classForName(mapperClass); configuration.addMapper(mapperInterface); } else { throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one."); } } } }} 解释下environment作用,就是<environment id="development">这里的,不同的环境不同的配置 1234567891011121314151617181920212223<configuration> <environments default="development"> <environment id="development"> <transactionManager type="JDBC"/> <dataSource type="POOLED"> <property name="driver" value="com.mysql.cj.jdbc.Driver"/> <property name="url" value="jdbc:mysql://192.168.3.12:3306/orangedb"/> <property name="username" value="root"/> <property name="password" value="abcd2022"/> </dataSource> </environment> <environment id="test"> <transactionManager type="JDBC"/> <dataSource type="POOLED"> <property name="driver" value="com.mysql.cj.jdbc.Driver"/> <property name="url" value="jdbc:mysql://192.168.3.12:3306/orangedb"/> <property name="username" value="root"/> <property name="password" value="abcd2022"/> </dataSource> </environment> </environments></configuration> 用法:如下代码 1SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream,"development"); 下面是解析mapper文件,配置的属性,参考:https://mybatis.org/mybatis-3/zh/sqlmap-xml.html 解析mapper配置文件123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223// org.apache.ibatis.builder.xml.XMLMapperBuilder#XMLMapperBuilder(java.io.InputStream, org.apache.ibatis.session.Configuration, java.lang.String, java.util.Map<java.lang.String,org.apache.ibatis.parsing.XNode>)// 这个方法,前面已经用过了 public XMLMapperBuilder(InputStream inputStream, Configuration configuration, String resource, Map<String, XNode> sqlFragments) { this(new XPathParser(inputStream, true, configuration.getVariables(), new XMLMapperEntityResolver()), configuration, resource, sqlFragments); }// org.apache.ibatis.builder.xml.XMLMapperBuilder#parsepublic void parse() { // mapper映射文件是否已经加载过 if (!configuration.isResourceLoaded(resource)) { // 从根节点解析 configurationElement(parser.evalNode("/mapper")); // 标记已经解析 configuration.addLoadedResource(resource); // 为命名空间绑定映射 bindMapperForNamespace(); } // parsePendingResultMaps(); parsePendingCacheRefs(); parsePendingStatements();}// org.apache.ibatis.builder.xml.XMLMapperBuilder#configurationElementprivate void configurationElement(XNode context) { try { // 获取命名空间 String namespace = context.getStringAttribute("namespace"); // 命名空间不能为空 if (namespace == null || namespace.equals("")) { throw new BuilderException("Mapper's namespace cannot be empty"); } // 设置当前的命名空间的值 // 构建mappedStatement对象 builderAssistant.setCurrentNamespace(namespace); cacheRefElement(context.evalNode("cache-ref")); cacheElement(context.evalNode("cache")); parameterMapElement(context.evalNodes("/mapper/parameterMap")); resultMapElements(context.evalNodes("/mapper/resultMap")); sqlElement(context.evalNodes("/mapper/sql")); // 重点 buildStatementFromContext(context.evalNodes("select|insert|update|delete")); } catch (Exception e) { throw new BuilderException("Error parsing Mapper XML. The XML location is '" + resource + "'. Cause: " + e, e); }}// org.apache.ibatis.builder.xml.XMLMapperBuilder#buildStatementFromContext(java.util.List<org.apache.ibatis.parsing.XNode>)private void buildStatementFromContext(List<XNode> list) { if (configuration.getDatabaseId() != null) { buildStatementFromContext(list, configuration.getDatabaseId()); } // 没有配置过getDatabaseId,所以走这里 buildStatementFromContext(list, null);}// org.apache.ibatis.builder.xml.XMLMapperBuilder#buildStatementFromContext(java.util.List<org.apache.ibatis.parsing.XNode>, java.lang.String)private void buildStatementFromContext(List<XNode> list, String requiredDatabaseId) { for (XNode context : list) { final XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant, context, requiredDatabaseId); try { statementParser.parseStatementNode(); } catch (IncompleteElementException e) { configuration.addIncompleteStatement(statementParser); } }}// org.apache.ibatis.builder.xml.XMLStatementBuilder#parseStatementNodepublic void parseStatementNode() { String id = context.getStringAttribute("id"); String databaseId = context.getStringAttribute("databaseId"); // 没有设置过databaseId if (!databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) { return; } String nodeName = context.getNode().getNodeName(); // 解析Sql命令类型是什么,确实是 Select,update,insert,delete 类型 SqlCommandType sqlCommandType = SqlCommandType.valueOf(nodeName.toUpperCase(Locale.ENGLISH)); boolean isSelect = sqlCommandType == SqlCommandType.SELECT; boolean flushCache = context.getBooleanAttribute("flushCache", !isSelect); boolean useCache = context.getBooleanAttribute("useCache", isSelect); boolean resultOrdered = context.getBooleanAttribute("resultOrdered", false); // Include Fragments before parsing XMLIncludeTransformer includeParser = new XMLIncludeTransformer(configuration, builderAssistant); includeParser.applyIncludes(context.getNode()); String parameterType = context.getStringAttribute("parameterType"); Class<?> parameterTypeClass = resolveClass(parameterType); // 配置语言驱动 String lang = context.getStringAttribute("lang"); LanguageDriver langDriver = getLanguageDriver(lang); // Parse selectKey after includes and remove them. processSelectKeyNodes(id, parameterTypeClass, langDriver); // Parse the SQL (pre: <selectKey> and <include> were parsed and removed) KeyGenerator keyGenerator; String keyStatementId = id + SelectKeyGenerator.SELECT_KEY_SUFFIX; keyStatementId = builderAssistant.applyCurrentNamespace(keyStatementId, true); if (configuration.hasKeyGenerator(keyStatementId)) { keyGenerator = configuration.getKeyGenerator(keyStatementId); } else { keyGenerator = context.getBooleanAttribute("useGeneratedKeys", configuration.isUseGeneratedKeys() && SqlCommandType.INSERT.equals(sqlCommandType)) ? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE; } // 替换占位符?,保存#{}里面的内容 SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass); StatementType statementType = StatementType.valueOf(context.getStringAttribute("statementType", StatementType.PREPARED.toString())); Integer fetchSize = context.getIntAttribute("fetchSize"); Integer timeout = context.getIntAttribute("timeout"); String parameterMap = context.getStringAttribute("parameterMap"); String resultType = context.getStringAttribute("resultType"); Class<?> resultTypeClass = resolveClass(resultType); String resultMap = context.getStringAttribute("resultMap"); String resultSetType = context.getStringAttribute("resultSetType"); ResultSetType resultSetTypeEnum = resolveResultSetType(resultSetType); if (resultSetTypeEnum == null) { resultSetTypeEnum = configuration.getDefaultResultSetType(); } String keyProperty = context.getStringAttribute("keyProperty"); String keyColumn = context.getStringAttribute("keyColumn"); String resultSets = context.getStringAttribute("resultSets"); // 通过构建者助手,创建mappedstatement对象 builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType, fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass, resultSetTypeEnum, flushCache, useCache, resultOrdered, keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets); }// org.apache.ibatis.builder.MapperBuilderAssistant#addMappedStatement(java.lang.String, org.apache.ibatis.mapping.SqlSource, org.apache.ibatis.mapping.StatementType, org.apache.ibatis.mapping.SqlCommandType, java.lang.Integer, java.lang.Integer, java.lang.String, java.lang.Class<?>, java.lang.String, java.lang.Class<?>, org.apache.ibatis.mapping.ResultSetType, boolean, boolean, boolean, org.apache.ibatis.executor.keygen.KeyGenerator, java.lang.String, java.lang.String, java.lang.String, org.apache.ibatis.scripting.LanguageDriver, java.lang.String)public MappedStatement addMappedStatement( String id, SqlSource sqlSource, StatementType statementType, SqlCommandType sqlCommandType, Integer fetchSize, Integer timeout, String parameterMap, Class<?> parameterType, String resultMap, Class<?> resultType, ResultSetType resultSetType, boolean flushCache, boolean useCache, boolean resultOrdered, KeyGenerator keyGenerator, String keyProperty, String keyColumn, String databaseId, LanguageDriver lang, String resultSets) { if (unresolvedCacheRef) { throw new IncompleteElementException("Cache-ref not yet resolved"); } id = applyCurrentNamespace(id, false); boolean isSelect = sqlCommandType == SqlCommandType.SELECT; // 创建MappedStatement对象 MappedStatement.Builder statementBuilder = new MappedStatement.Builder(configuration, id, sqlSource, sqlCommandType) .resource(resource) .fetchSize(fetchSize) .timeout(timeout) .statementType(statementType) .keyGenerator(keyGenerator) .keyProperty(keyProperty) .keyColumn(keyColumn) .databaseId(databaseId) .lang(lang) .resultOrdered(resultOrdered) .resultSets(resultSets) .resultMaps(getStatementResultMaps(resultMap, resultType, id)) .resultSetType(resultSetType) .flushCacheRequired(valueOrDefault(flushCache, !isSelect)) .useCache(valueOrDefault(useCache, isSelect)) .cache(currentCache); ParameterMap statementParameterMap = getStatementParameterMap(parameterMap, parameterType, id); if (statementParameterMap != null) { statementBuilder.parameterMap(statementParameterMap); } // 封装好MappedStatement,并返回 MappedStatement statement = statementBuilder.build(); configuration.addMappedStatement(statement); return statement; }// org.apache.ibatis.builder.MapperBuilderAssistant#applyCurrentNamespacepublic String applyCurrentNamespace(String base, boolean isReference) { if (base == null) { return null; } if (isReference) { // is it qualified with any namespace yet? if (base.contains(".")) { return base; } } else { // is it qualified with this namespace yet? if (base.startsWith(currentNamespace + ".")) { return base; } if (base.contains(".")) { throw new BuilderException("Dots are not allowed in element names, please remove it from " + base); } } // namespacename+点+方法名 return currentNamespace + "." + base; } 到这里,配置文件就解析完成了,下面是创建SqlSessionFactory对象 SqlSession1234 // org.apache.ibatis.session.SqlSessionFactoryBuilder#build(org.apache.ibatis.session.Configuration)public SqlSessionFactory build(Configuration config) { return new DefaultSqlSessionFactory(config);} 下面是mybatis创建sql的流程 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475// /Users/anthony/.m2/repository/org/mybatis/mybatis/3.5.2/mybatis-3.5.2-sources.jar!/org/apache/ibatis/builder/xml/XMLStatementBuilder.java:96// 占位符是如果进行替换的?动态sql如果进行的解析String lang = context.getStringAttribute("lang");LanguageDriver langDriver = getLanguageDriver(lang);SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);// org.apache.ibatis.builder.xml.XMLStatementBuilder#getLanguageDriverprivate LanguageDriver getLanguageDriver(String lang) { Class<? extends LanguageDriver> langClass = null; if (lang != null) { langClass = resolveClass(lang); } return configuration.getLanguageDriver(langClass);}// org.apache.ibatis.session.Configuration#getLanguageDriverpublic LanguageDriver getLanguageDriver(Class<? extends LanguageDriver> langClass) { if (langClass == null) { return languageRegistry.getDefaultDriver(); } languageRegistry.register(langClass); return languageRegistry.getDriver(langClass);}// org.apache.ibatis.scripting.LanguageDriverRegistry#getDefaultDriver// 打断点可以看到是:XMLLanguageDriverpublic LanguageDriver getDefaultDriver() { return getDriver(getDefaultDriverClass());}public Class<? extends LanguageDriver> getDefaultDriverClass() { return defaultDriverClass;}// org.apache.ibatis.scripting.xmltags.XMLLanguageDriver#createSqlSource(org.apache.ibatis.session.Configuration, org.apache.ibatis.parsing.XNode, java.lang.Class<?>)public SqlSource createSqlSource(Configuration configuration, XNode script, Class<?> parameterType) { XMLScriptBuilder builder = new XMLScriptBuilder(configuration, script, parameterType); return builder.parseScriptNode();}// org.apache.ibatis.scripting.xmltags.XMLScriptBuilder#XMLScriptBuilder(org.apache.ibatis.session.Configuration, org.apache.ibatis.parsing.XNode, java.lang.Class<?>)public XMLScriptBuilder(Configuration configuration, XNode context, Class<?> parameterType) { super(configuration); this.context = context; this.parameterType = parameterType; // 初始化动态sql的节点处理器结合 initNodeHandlerMap();}// org.apache.ibatis.scripting.xmltags.XMLScriptBuilder#initNodeHandlerMap private void initNodeHandlerMap() { nodeHandlerMap.put("trim", new TrimHandler()); nodeHandlerMap.put("where", new WhereHandler()); nodeHandlerMap.put("set", new SetHandler()); nodeHandlerMap.put("foreach", new ForEachHandler()); nodeHandlerMap.put("if", new IfHandler()); nodeHandlerMap.put("choose", new ChooseHandler()); nodeHandlerMap.put("when", new IfHandler()); nodeHandlerMap.put("otherwise", new OtherwiseHandler()); nodeHandlerMap.put("bind", new BindHandler()); }// org.apache.ibatis.scripting.xmltags.XMLScriptBuilder#parseScriptNode// 解析sql,还有参数类型和结果集类型public SqlSource parseScriptNode() { MixedSqlNode rootSqlNode = parseDynamicTags(context); SqlSource sqlSource; if (isDynamic) { sqlSource = new DynamicSqlSource(configuration, rootSqlNode); } else { sqlSource = new RawSqlSource(configuration, rootSqlNode, parameterType); } return sqlSource;} 全部就解析完成了,接下来 看看session = sqlSessionFactory.openSession(); 创建事务对象 创建了执行器对象CasheingExecutor 创建DefaultSqlSession对象 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768// org.apache.ibatis.session.defaults.DefaultSqlSessionFactory#openSession()@Overridepublic SqlSession openSession() { return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false);}// org.apache.ibatis.session.Configuration#getDefaultExecutorType// protected ExecutorType defaultExecutorType = ExecutorType.SIMPLE;public ExecutorType getDefaultExecutorType() { return defaultExecutorType;}// org.apache.ibatis.session.defaults.DefaultSqlSessionFactory#openSessionFromDataSource// 参数一:执行器,参数二:隔离级别private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) { Transaction tx = null; try { // 获取数据源环境信息 final Environment environment = configuration.getEnvironment(); // 获取事务工厂 final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment); // 获取JdbcTransaction或者ManagedTransaction // ManagedTransaction 就相当于没有事务 tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit); // 创建Executor执行器 final Executor executor = configuration.newExecutor(tx, execType); // 创建DefaultSqlSession return new DefaultSqlSession(configuration, executor, autoCommit); } catch (Exception e) { closeTransaction(tx); // may have fetched a connection so lets call close() throw ExceptionFactory.wrapException("Error opening session. Cause: " + e, e); } finally { ErrorContext.instance().reset(); }}// org.apache.ibatis.transaction.jdbc.JdbcTransactionFactory#newTransaction(java.sql.Connection)@Overridepublic Transaction newTransaction(DataSource ds, TransactionIsolationLevel level, boolean autoCommit) { return new JdbcTransaction(ds, level, autoCommit);}// org.apache.ibatis.session.Configuration#newExecutor(org.apache.ibatis.transaction.Transaction, org.apache.ibatis.session.ExecutorType) public Executor newExecutor(Transaction transaction, ExecutorType executorType) { executorType = executorType == null ? defaultExecutorType : executorType; executorType = executorType == null ? ExecutorType.SIMPLE : executorType; Executor executor; if (ExecutorType.BATCH == executorType) { executor = new BatchExecutor(this, transaction); } else if (ExecutorType.REUSE == executorType) { executor = new ReuseExecutor(this, transaction); } else { executor = new SimpleExecutor(this, transaction); } // 如果允许缓存,会通过CachingExecutor去代理一层 if (cacheEnabled) { executor = new CachingExecutor(executor); } // 拦截器插件 executor = (Executor) interceptorChain.pluginAll(executor); return executor; }// org.apache.ibatis.executor.CachingExecutor#CachingExecutorpublic CachingExecutor(Executor delegate) { this.delegate = delegate; delegate.setExecutorWrapper(this);} 调用具体的API进行查询用启动类中的方法,对比可以发现两段代码不同之处为: 1234Map map = sqlSession.selectOne("com.analyze.mybatis.mapper.UserMapper.getUA");UserMapper userMapper = sqlSession.getMapper(UserMapper.class);Map map = userMapper.getUA(); 在查看DefaultSqlSession中的selectOne方法,会执行以下的调用链 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205// org.apache.ibatis.session.defaults.DefaultSqlSession#selectOne(java.lang.String, java.lang.Object)@Overridepublic <T> T selectOne(String statement, Object parameter) { // Popular vote was to return null on 0 results and throw exception on too many. List<T> list = this.selectList(statement, parameter); if (list.size() == 1) { return list.get(0); } else if (list.size() > 1) { throw new TooManyResultsException("Expected one result (or null) to be returned by selectOne(), but found: " + list.size()); } else { return null; }}// org.apache.ibatis.session.defaults.DefaultSqlSession#selectList(java.lang.String, java.lang.Object)// RowBounds 分页对象public <E> List<E> selectList(String statement, Object parameter) { return this.selectList(statement, parameter, RowBounds.DEFAULT);}// org.apache.ibatis.session.defaults.DefaultSqlSession#selectList(java.lang.String, java.lang.Object, org.apache.ibatis.session.RowBounds)@Overridepublic <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) { try { // 根据传入的statementId,获取mapperStatement对象 MappedStatement ms = configuration.getMappedStatement(statement); // 调用执行器的方法 return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER); } catch (Exception e) { throw ExceptionFactory.wrapException("Error querying database. Cause: " + e, e); } finally { ErrorContext.instance().reset(); }}// org.apache.ibatis.executor.CachingExecutor#query(org.apache.ibatis.mapping.MappedStatement, java.lang.Object, org.apache.ibatis.session.RowBounds, org.apache.ibatis.session.ResultHandler)// 注意这里是CachingExecutor,默认的@Overridepublic <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException { // 获取带问号的sql语句,比如 select * from user_info where id = ? BoundSql boundSql = ms.getBoundSql(parameterObject); // 生成缓存key CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql); return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);}// org.apache.ibatis.executor.CachingExecutor#query(org.apache.ibatis.mapping.MappedStatement, java.lang.Object, org.apache.ibatis.session.RowBounds, org.apache.ibatis.session.ResultHandler, org.apache.ibatis.cache.CacheKey, org.apache.ibatis.mapping.BoundSql)@Overridepublic <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException { // 二级缓存 // usermapper的标签里可以设置<Cache>缓存标签 Cache cache = ms.getCache(); if (cache != null) { // 刷新二级缓存,在<select> 标签里可以配置flushcache flushCacheIfRequired(ms); // 在<select> 标签里可以配置usecache if (ms.isUseCache() && resultHandler == null) { // 判断是不是存过 ensureNoOutParams(ms, boundSql); @SuppressWarnings("unchecked") // 从二级缓存中查询数据 List<E> list = (List<E>) tcm.getObject(cache, key); // 如果从二级缓存没有查询到数据 if (list == null) { // 委托给BaseExecutor执行 list = delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql); // 将查询结果保存到二级缓存中,这里只是存到map集合中,没有真正存到二级缓存中 tcm.putObject(cache, key, list); // issue #578 and #116 } return list; } } return delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);}// org.apache.ibatis.executor.BaseExecutor#query(org.apache.ibatis.mapping.MappedStatement, java.lang.Object, org.apache.ibatis.session.RowBounds, org.apache.ibatis.session.ResultHandler, org.apache.ibatis.cache.CacheKey, org.apache.ibatis.mapping.BoundSql)@Overridepublic <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException { ErrorContext.instance().resource(ms.getResource()).activity("executing a query").object(ms.getId()); if (closed) { throw new ExecutorException("Executor was closed."); } // 如果配置了FlushCacheRequired为true,则会在执行器执行之前就清空本地一级缓存 if (queryStack == 0 && ms.isFlushCacheRequired()) { // 清空缓存 clearLocalCache(); } List<E> list; try { queryStack++; // 从一级缓存中获取数据 list = resultHandler == null ? (List<E>) localCache.getObject(key) : null; if (list != null) { // 如果有数据,则处理本地缓存结果给输出参数 // 还是处理存过 handleLocallyCachedOutputParameters(ms, key, parameter, boundSql); } else { // 没有缓存结果,则从数据库查询结果 list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql); } } finally { queryStack--; } if (queryStack == 0) { for (DeferredLoad deferredLoad : deferredLoads) { deferredLoad.load(); } // issue #601 deferredLoads.clear(); if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) { // issue #482 clearLocalCache(); } } return list;}// org.apache.ibatis.executor.BaseExecutor#queryFromDatabaseprivate <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException { List<E> list; // 向本地缓存中存入一个ExecutionPlaceholder的枚举类占位 localCache.putObject(key, EXECUTION_PLACEHOLDER); try { // 执行查询 list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql); } finally { // 执行玩移除这个key localCache.removeObject(key); } localCache.putObject(key, list); if (ms.getStatementType() == StatementType.CALLABLE) { localOutputParameterCache.putObject(key, parameter); } return list;}// org.apache.ibatis.executor.SimpleExecutor#doQuery@Overridepublic <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException { Statement stmt = null; try { // 获取全局配置文件实例 Configuration configuration = ms.getConfiguration(); // new一个statementHandler实例 StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql); // 准备处理器,主要包括创建statement以及动态参数的设置 stmt = prepareStatement(handler, ms.getStatementLog()); // 执行真正的数据库操作调用 return handler.query(stmt, resultHandler); } finally { closeStatement(stmt); }}// org.apache.ibatis.session.Configuration#newStatementHandlerpublic StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) { // 创建路由功能的StatementHanlder,根据MappedStatement中的StetementType StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql); // 插件机制:对核心对象进行拦截 statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler); return statementHandler;}// org.apache.ibatis.executor.statement.RoutingStatementHandler#RoutingStatementHandlerpublic RoutingStatementHandler(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) { switch (ms.getStatementType()) { case STATEMENT: delegate = new SimpleStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql); break; case PREPARED: delegate = new PreparedStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql); break; case CALLABLE: delegate = new CallableStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql); break; default: throw new ExecutorException("Unknown statement type: " + ms.getStatementType()); }}// org.apache.ibatis.executor.SimpleExecutor#prepareStatementprivate Statement prepareStatement(StatementHandler handler, Log statementLog){ Statement stmt; // 获取代理后,增加日志功能的Connection对象 Connection connection = getConnection(statementLog); // 创建Statement对象 stmt = handler.prepare(connection, transaction.getTimeout()); // 参数化处理 handler.parameterize(stmt); return stmt;}// org.apache.ibatis.executor.statement.PreparedStatementHandler#query @Overridepublic <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException { PreparedStatement ps = (PreparedStatement) statement; ps.execute(); return resultSetHandler.handleResultSets(ps);} 解析结果1234567891011121314151617181920212223242526272829303132333435363738// org.apache.ibatis.executor.resultset.DefaultResultSetHandler#handleResultSets@Overridepublic List<Object> handleResultSets(Statement stmt) throws SQLException { ErrorContext.instance().activity("handling results").object(mappedStatement.getId()); final List<Object> multipleResults = new ArrayList<>(); int resultSetCount = 0; ResultSetWrapper rsw = getFirstResultSet(stmt); List<ResultMap> resultMaps = mappedStatement.getResultMaps(); int resultMapCount = resultMaps.size(); validateResultMapsCount(rsw, resultMapCount); while (rsw != null && resultMapCount > resultSetCount) { ResultMap resultMap = resultMaps.get(resultSetCount); handleResultSet(rsw, resultMap, multipleResults, null); rsw = getNextResultSet(stmt); cleanUpAfterHandlingResultSet(); resultSetCount++; } String[] resultSets = mappedStatement.getResultSets(); if (resultSets != null) { while (rsw != null && resultSetCount < resultSets.length) { ResultMapping parentMapping = nextResultMaps.get(resultSets[resultSetCount]); if (parentMapping != null) { String nestedResultMapId = parentMapping.getNestedResultMapId(); ResultMap resultMap = configuration.getResultMap(nestedResultMapId); handleResultSet(rsw, resultMap, null, parentMapping); } rsw = getNextResultSet(stmt); cleanUpAfterHandlingResultSet(); resultSetCount++; } } return collapseSingleResultList(multipleResults);} 缓存1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253// org.apache.ibatis.executor.CachingExecutor#createCacheKey// CacheKey重新了 hacode和equals方法@Overridepublic CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql) { // delegate 是具体类型的执行器的应用 // 默认是SimpleExecutor return delegate.createCacheKey(ms, parameterObject, rowBounds, boundSql);}// org.apache.ibatis.executor.BaseExecutor#createCacheKey@Overridepublic CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql) { if (closed) { throw new ExecutorException("Executor was closed."); } // 通过构造器创建 CacheKey cacheKey = new CacheKey(); // id cacheKey.update(ms.getId()); // 分页参数 cacheKey.update(rowBounds.getOffset()); cacheKey.update(rowBounds.getLimit()); // sql cacheKey.update(boundSql.getSql()); List<ParameterMapping> parameterMappings = boundSql.getParameterMappings(); TypeHandlerRegistry typeHandlerRegistry = ms.getConfiguration().getTypeHandlerRegistry(); // mimic DefaultParameterHandler logic for (ParameterMapping parameterMapping : parameterMappings) { if (parameterMapping.getMode() != ParameterMode.OUT) { Object value; String propertyName = parameterMapping.getProperty(); if (boundSql.hasAdditionalParameter(propertyName)) { value = boundSql.getAdditionalParameter(propertyName); } else if (parameterObject == null) { value = null; } else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) { value = parameterObject; } else { MetaObject metaObject = configuration.newMetaObject(parameterObject); value = metaObject.getValue(propertyName); } // 参数的值 cacheKey.update(value); } } if (configuration.getEnvironment() != null) { // issue #176 // 当前环境的值 cacheKey.update(configuration.getEnvironment().getId()); } return cacheKey;} 面试题

2022-11-19
Mycat
主从复制启动1 .下载jre包 2 下载mycat包 3 打包镜像 Dockerfile 1234567891011121314FROM centos# 解压这个包到home下面ADD server-jre-8u251-linux-x64.tar.gz /home/ADD ./Mycat-server-1.6.7.4-release-20200105164103-linux.tar.gz /homeENV WORKPATH /home/mycat/WORKDIR $WORKPATHENV JAVA_HOME /home/jdk1.8.0_251ENV CLASSPATH $JAVA_HOME/dt.jar:$JAVA_HOME/lib/tools.jarENV PATH $PATH:$JAVA_HOME/BIN:$CATALINA_HOME/lib:$CATALINA_HOME/bin# 暴露8066端口EXPOSE 8066CMD /home/mycat/bin/mycat console 把mycat里的conf 和 logs 从容器中拷贝出来 再挂在两个目录,并启动 123456789101112131415docker run -it \ --name mycat \ -p 8066:8066 \ -v /home/anthony/mycat/conf:/home/mycat/conf/ \ -v /home/anthony/mycat/logs:/home/mycat/logs/ \ java1234/mycat:1.0Running Mycat-server...Removed stale pid file: /home/mycat/logs/mycat.pidwrapper | --> Wrapper Started as Consolewrapper | Launching a JVM...jvm 1 | Wrapper (Version 3.2.3) http://wrapper.tanukisoftware.orgjvm 1 | Copyright 1999-2006 Tanuki Software, Inc. All Rights Reserved.jvm 1 |jvm 1 | MyCAT Server startup successfully. see logs in logs/mycat.log 配置 schems.xml 定义逻辑库,表,分片节点等内容 rule.xml 定义分片规则 server.xml 定义用户以及系统相关变量 安装mysql5.7123# 从容器中拷贝这些文件docker cp xxx:/etc/mysql/mysql.conf.d/ /home/anthony/mysqldocker cp xxx:/var/log/ /home/anthony/mysql 启动的时候映射这些 创建网络12345678910# 可以查看容器的虚拟ipdocker inspect ac7# 查看网络docker network ls# 自定义一个网络模式# extnetwork 是网络模式的名字# 172.20.0.1 是网关docker network create --subnet=172.20.0.0/16 extnetwork mysql,mycat指定网路,重建容器12345678910111213141516171819202122232425262728# 指定网络模式和ip地址--net extnetwork --ip 172.20.0.2docker run -itd \ --name mysql-master \ -p 3306:3306 \ -e MYSQL_ROOT_PASSWORD=123456 \ -v /home/anthony/mysql/mysql.conf.d/:/etc/mysql/conf.d/ \ -v /home/anthony/mysql/log/:/var/log/ \ --net extnetwork --ip 172.20.0.2 \ mysql:5.7docker run -itd \ --name mysql-slave \ -p 3307:3306 \ -e MYSQL_ROOT_PASSWORD=123456 \ -v /home/anthony/mysql2/mysql.conf.d/:/etc/mysql/conf.d/ \ -v /home/anthony/mysql2/log/:/var/log/ \ --net extnetwork --ip 172.20.0.3 \ mysql:5.7docker run -it \ --name mycat \ -p 8066:8066 \ -v /home/anthony/mycat/conf:/home/mycat/conf/ \ -v /home/anthony/mycat/logs:/home/mycat/logs/ \ --net extnetwork --ip 172.20.0.4 \ java1234/mycat:1.0 主从配置主,在[mysqld] 12345678910111213# 主服务器ID,必须唯一server-id=2# 开启和设置二进制日志文件名称log_bin=mysql-bin# 要同步的数据库binlog-do-db=db_java1234# 不需要同步的数据库binlog-ignore_db=mysqlbinlog-ignore_db=information_schemabinlog-ignore_db=performation_schemabinlog_ignore_db=sys# 设置logbin格式,mysql默认采用statement,建议使用mixedbinlog_format=MIXED 从,在[mysqld] 12345678910111213141516# 主服务器ID,必须唯一server-id=3# 这行是从库需要添加的relay-log=mysql-relay# 开启和设置二进制日志文件名称log_bin=mysql-bin# 要同步的数据库binlog-do-db=db_java1234# 不需要同步的数据库binlog-ignore_db=mysqlbinlog-ignore_db=information_schemabinlog-ignore_db=performation_schemabinlog_ignore_db=sys# 设置logbin格式,mysql默认采用statement,建议使用mixedbinlog_format=MIXED 测试主从还没有生效 12345678910mysql> show master status;+------------------+----------+--------------+--------------------------------------------------+-------------------+| File | Position | Binlog_Do_DB | Binlog_Ignore_DB | Executed_Gtid_Set |+------------------+----------+--------------+--------------------------------------------------+-------------------+| mysql-bin.000003 | 357 | db_java1234 | mysql,information_schema,performation_schema,sys | |+------------------+----------+--------------+--------------------------------------------------+-------------------+1 row in set (0.01 sec)mysql> show slave status;Empty set 设置用户名在master执行 12345678910111213# 创建用户名为slave1# 172.20.0.3从库ip# 123456 密码CREATE USER 'slave1'@'172.20.0.3' IDENTIFIED BY '123456';# 语法:GRANT privileges ON databasename.tablename TO 'username'@'host'# 172.20.0.3 从库# 两个权限:REPLICATION和SLAVE# *.* 所有的库和表GRANT REPLICATION SLAVE ON *.* TO 'slave1'@'172.20.0.3';# 刷新权限FLUSH PRIVILEGES; 在slave执行 1234CHANGE MASTER TO MASTER_HOST='172.20.0.2',MASTER_USER='slave1',MASTER_PASSWORD='123456',MASTER_LOG_FILE='mysql-bin.000003',MASTER_LOG_POS=964;# 开启主从start slave; 在从库执行 1show slave status; 主要看Slave_io_running =yes 主要看Slave_sql_running =yes 读写分离scheml.xmlschema标签1234567<schema name="TESTDB" checkSQLschema="true" sqlMaxLimit="100" randomDataNode="dn1"> <!-- auto sharding by id (long) --> <!--splitTableNames 启用<table name 属性使用逗号分割配置多个表,即多个表使用这个配置--> <table name="travelrecord,address" dataNode="dn1,dn2,dn3" rule="auto-sharding-long" splitTableNames ="true"/> <!-- <table name="oc_call" primaryKey="ID" dataNode="dn1$0-743" rule="latest-month-calldate" /> --></schema> name: 配置逻辑库的名称 checkSQLschema: 当执行select * from T_Account.user 为true时,mycat会把sql转成select * from user false 会报错 sqlMaxLimit:最大查询每页条数,如果sql指定了limit,就以sql为准,没有,就以配置为准 randomDataNode:这个先不用,先改成datanote='dn1' datanote标签123<dataNode name="dn1" dataHost="localhost1" database="db1" /><dataNode name="dn2" dataHost="localhost1" database="db2" /><dataNode name="dn3" dataHost="localhost1" database="db3" /> name:数据分片节点名称 dataHost:定义该数据分片节点属于哪台数据库主机 database:指定哪个数据库,db_java1234 datahost标签123456789<dataHost name="localhost1" maxCon="1000" minCon="10" balance="3" writeType="0" dbType="mysql" dbDriver="native" switchType="1" slaveThreshold="100"> <heartbeat>select user()</heartbeat> <!-- can have multi write hosts --> <writeHost host="hostM1" url="localhost:3306" user="root" password="123456"> </writeHost> <!-- <writeHost host="hostM2" url="localhost:3316" user="root" password="123456"/> --></dataHost> name:跟datanote标签的datahost对应 maxCon 指定每个读写实例连接池的的最大链接,write合readhost标签都会使用这个属性的值来实例化出连接池的最大连接数 min 最小的连接 balance: 0 不开启读写分离,所有读操作都发送到当前可用的writehost上 1 2 所有读操作都随机在writehost合readehost上分发 3 所有读请求随机分发到writehost合readhos 双组双从第二套主从1.在mysql添加中添加 123log-slave-update=1auto-increment-increment=2auto-increment-offset=1 2.把mysql文件复制一份mysql3 3.把mysql3的配置文件修修改这些配置 123# 主服务器ID,必须唯一server-id=5auto-increment-offset=2 4.再创建两个容器,m2 和 s2 master 和 master同步就是相互slave 测试读写分离插入语句 1insert into test valus(@@hostname)

2023-12-10
Netty
Server 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162// 把 NettyServer 的创建交给 Spring 管理@Componentpublic class NettyServer { private Logger logger = LoggerFactory.getLogger(getClass()); @Value("${netty.port}") private Integer port; @Resource private NettyServerHandlerInitializer nettyServerHandlerInitializer; // boss 线程组,用于服务端接受客户端的连接 private EventLoopGroup bossGroup = new NioEventLoopGroup(); // worker 线程组,用于服务端接受客户端的数据读写 private EventLoopGroup workerGroup = new NioEventLoopGroup(); // Netty Server Channel private Channel channel; // 启动 Netty Server @PostConstruct public void start() throws InterruptedException { // 创建 ServerBootstrap 对象,用于 Netty Server 启动 ServerBootstrap bootstrap = new ServerBootstrap(); // 设置 ServerBootstrap 的各种属性 // 设置两个 EventLoopGroup 对象 bootstrap.group(bossGroup, workerGroup) // 指定 Channel 为服务端 NioServerSocketChannel .channel(NioServerSocketChannel.class) // 设置 Netty Server 的端口 .localAddress(new InetSocketAddress(port)) // 服务端 accept 队列的大小 .option(ChannelOption.SO_BACKLOG, 1024) // TCP Keepalive 机制,实现 TCP 层级的心跳保活功能 .childOption(ChannelOption.SO_KEEPALIVE, true) // 允许较小的数据包的发送,降低延迟 .childOption(ChannelOption.TCP_NODELAY, true) // 处理器 .childHandler(nettyServerHandlerInitializer); // 绑定端口,并同步等待成功,即启动服务端 ChannelFuture future = bootstrap.bind().sync(); if (future.isSuccess()) { channel = future.channel(); logger.info("[start][Netty Server 启动在 {} 端口]", port); } } // 关闭 Netty Server @PreDestroy public void shutdown() { // 关闭 Netty Server if (channel != null) { channel.close(); } // <3.2> 优雅关闭两个 EventLoopGroup 对象 bossGroup.shutdownGracefully(); workerGroup.shutdownGracefully(); }} NettyServerHandlerInitializer 12345678910111213141516171819202122232425262728293031@Componentpublic class NettyServerHandlerInitializer extends ChannelInitializer<Channel> { /** * 心跳超时时间 */ private static final Integer READ_TIMEOUT_SECONDS = 3 * 60; @Resource private NettyServerHandler nettyServerHandler; @Override protected void initChannel(Channel ch) { // 获得 Channel 对应的 ChannelPipeline ChannelPipeline channelPipeline = ch.pipeline(); // 添加一堆 NettyServerHandler 到 ChannelPipeline 中 channelPipeline // 空闲检测 .addLast(new ReadTimeoutHandler(READ_TIMEOUT_SECONDS, TimeUnit.SECONDS)) // 编码器 .addLast(new InvocationEncoder()) // 解码器 .addLast(new InvocationDecoder()) // 消息分发器 .addLast(messageDispatcher) // 服务端处理器 .addLast(nettyServerHandler) ; }}

2022-11-19
Spring Security
Spring Security1234<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId></dependency> 登录业务 1234567891011121314151617181920212223242526272829303132333435363738@Servicepublic class LoginService { public static Map<String, RedisUser> map = new HashMap<>(); @Resource AuthenticationManager authenticationManager; public String login(SysUser sysUser) { // AuthenticationManager 进行用户认证 UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = new UsernamePasswordAuthenticationToken(sysUser.getUsername(), sysUser.getPassword()); Authentication authenticate = authenticationManager.authenticate(usernamePasswordAuthenticationToken); // 如果认证不通过,给出对应的提示 if (Objects.isNull(authenticate)) { throw new RuntimeException("登录失败"); } // 如果认证通过,使用username给jwt生成一个 RedisUser principal = (RedisUser) authenticate.getPrincipal(); map.put(principal.getUsername(), principal); return createJWT("1234567", 9990000, principal.getUsername()); } public void logout() { UsernamePasswordAuthenticationToken authentication = (UsernamePasswordAuthenticationToken) SecurityContextHolder.getContext().getAuthentication(); RedisUser principal = (RedisUser) authentication.getPrincipal(); String username = principal.getUsername(); // 从redis里删除 map.remove(username); }} 123456789101112131415161718192021222324@RestController@RequestMapping("/")public class LoginController { @Resource LoginService loginService; @PostMapping("/user/login") public ResponseEntity<HashMap> login(@RequestParam("username") String username,@RequestParam("password") String password){ SysUser sysUser = new SysUser(); sysUser.setUsername(username); sysUser.setPassword(password); HashMap<String, Object> stringObjectHashMap = new HashMap<>(); stringObjectHashMap.put("token", loginService.login(sysUser)); return ResponseEntity.status(200).body(stringObjectHashMap); } @PostMapping("/user/logout") public ResponseEntity<String> logout(){ loginService.logout(); return ResponseEntity.ok("success"); }} 验证账户 12345678910111213141516171819202122232425262728293031323334353637383940414243import com.example.spirngsecutirylearn.pojo.RedisUser;import com.example.spirngsecutirylearn.pojo.SysUser;import org.springframework.security.core.userdetails.UserDetails;import org.springframework.security.core.userdetails.UserDetailsService;import org.springframework.security.core.userdetails.UsernameNotFoundException;import org.springframework.stereotype.Service;import java.util.ArrayList;@Servicepublic class TestService implements UserDetailsService { static String user = "anthony"; static String password = "$2a$10$yyR9WuT9JY/bpe1VPU0yguqlv0lWpgzTD9NEetf2.n8y7NXIa1rfm"; /** * DaoAuthenticationProvier 会调用这个方法查询用户,并且返回UserDetails对象 * @param username * @return * @throws UsernameNotFoundException */ @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { // 从数据库查询用户信息 SysUser sysUser = new SysUser(); if (username.equals(user)) { sysUser.setUsername(user); sysUser.setPassword(password); }else { // 没有查到用户 throw new RuntimeException("没有查到用户信息"); } // 查询对应的权限信息 ArrayList<String> strings = new ArrayList<>();// strings.add("test"); strings.add("admin"); // 返回封装的信息 return new RedisUser(sysUser,strings); }} 缓存的用户对象 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263package com.example.spirngsecutirylearn.pojo;import lombok.AllArgsConstructor;import lombok.Data;import org.springframework.security.core.GrantedAuthority;import org.springframework.security.core.authority.SimpleGrantedAuthority;import org.springframework.security.core.userdetails.UserDetails;import java.util.Collection;import java.util.List;import java.util.stream.Collectors;@Datapublic class RedisUser implements UserDetails { SysUser sysUser; List<String> list; public RedisUser(SysUser sysUser, List<String> list) { this.sysUser = sysUser; this.list = list; } /** * 权限 * @return */ @Override public Collection<? extends GrantedAuthority> getAuthorities() { return list.stream().map(SimpleGrantedAuthority::new).collect(Collectors.toList()); } @Override public String getPassword() { return sysUser.getPassword(); } @Override public String getUsername() { return sysUser.getUsername(); } @Override public boolean isAccountNonExpired() { return true; } @Override public boolean isAccountNonLocked() { return true; } @Override public boolean isCredentialsNonExpired() { return true; } @Override public boolean isEnabled() { return true; }} 认证失败的回调 123456789101112131415161718192021222324252627282930313233package com.example.spirngsecutirylearn.handler;import cn.hutool.json.JSONUtil;import org.springframework.http.ResponseEntity;import org.springframework.security.core.AuthenticationException;import org.springframework.security.web.AuthenticationEntryPoint;import org.springframework.stereotype.Component;import javax.servlet.ServletException;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;import java.io.IOException;@Componentpublic class AuthException implements AuthenticationEntryPoint { @Override public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException { ResponseEntity<String> responseEntity = ResponseEntity.ok().body("认证失败,请重新登录"); String str = JSONUtil.toJsonStr(responseEntity); /// 处理异常 extracted(response,str); } private static void extracted(HttpServletResponse response,String string) throws IOException { response.setStatus(200); response.setContentType("application/json"); response.setCharacterEncoding("utf-8"); response.getWriter().println(string); }} 授权失败的回调 123456789101112131415161718192021222324252627282930313233package com.example.spirngsecutirylearn.handler;import cn.hutool.json.JSONUtil;import org.springframework.http.ResponseEntity;import org.springframework.security.access.AccessDeniedException;import org.springframework.security.core.AuthenticationException;import org.springframework.security.web.AuthenticationEntryPoint;import org.springframework.security.web.access.AccessDeniedHandler;import org.springframework.stereotype.Component;import javax.servlet.ServletException;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;import java.io.IOException;@Componentpublic class AccessException implements AccessDeniedHandler { @Override public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException { ResponseEntity<String> responseEntity = ResponseEntity.ok().body("权限不租"); String str = JSONUtil.toJsonStr(responseEntity); /// 处理异常 extracted(response,str); } private static void extracted(HttpServletResponse response,String string) throws IOException { response.setStatus(200); response.setContentType("application/json"); response.setCharacterEncoding("utf-8"); response.getWriter().println(string); }} 开启注解权限控制和自定义注解 1234567891011121314151617181920@RestController@RequestMapping("/test")public class TestController { @GetMapping("/hello") @PreAuthorize("hasAnyAuthority('test')") public ResponseEntity<String> hello(){ return ResponseEntity.ok("hello server"); } /** * 自定义校验权限 * @return */ @GetMapping("/hello2") @PreAuthorize("@anthony.hasAnyAuthority('test')") public ResponseEntity<String> hello2(){ return ResponseEntity.ok("hello server2"); }} 配置 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100package com.example.spirngsecutirylearn.cofig;import com.example.spirngsecutirylearn.filter.JwtFilter;import com.example.spirngsecutirylearn.handler.AccessException;import com.example.spirngsecutirylearn.handler.AuthException;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import org.springframework.http.HttpMethod;import org.springframework.security.authentication.AuthenticationManager;import org.springframework.security.config.annotation.authentication.configuration.EnableGlobalAuthentication;import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;import org.springframework.security.config.annotation.web.builders.HttpSecurity;import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;import org.springframework.security.config.http.SessionCreationPolicy;import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;import org.springframework.security.crypto.password.PasswordEncoder;import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;import org.springframework.security.web.authentication.logout.LogoutFilter;import javax.annotation.Resource;@Configuration@EnableGlobalMethodSecurity(prePostEnabled = true)public class SecurityConfig extends WebSecurityConfigurerAdapter { @Resource JwtFilter jwtFilter; @Resource AuthException authException; @Resource AccessException accessException; @Override @Bean public AuthenticationManager authenticationManagerBean() throws Exception { return super.authenticationManagerBean(); } @Override protected void configure(HttpSecurity httpSecurity) throws Exception { httpSecurity // CSRF禁用,因为不使用session .csrf().disable() // 基于token,所以不需要session .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS) .and() // 过滤请求 .authorizeRequests() // 对于登录login 注册register 验证码captchaImage 允许匿名访问 .antMatchers("/user/login").anonymous() // 静态资源,permitAll 有没有登录都访问// .antMatchers(HttpMethod.GET, "/", "/*.html", "/**/*.html", "/**/*.css", "/**/*.js", "/profile/**").permitAll()// .antMatchers("/swagger-ui.html", "/swagger-resources/**", "/webjars/**", "/*/api-docs", "/druid/**").permitAll() // 除上面外的所有请求全部需要鉴权认证 .anyRequest().authenticated();// .and()// .headers().frameOptions().disable(); // 添加Logout filter// httpSecurity.logout().logoutUrl("/logout").logoutSuccessHandler(logoutSuccessHandler);// // 添加JWT filter httpSecurity.addFilterBefore(jwtFilter, UsernamePasswordAuthenticationFilter.class);// // 添加CORS filter// httpSecurity.addFilterBefore(corsFilter, JwtAuthenticationTokenFilter.class);// httpSecurity.addFilterBefore(corsFilter, LogoutFilter.class); // 添加自定义的认证和授权的自定义失败处理 httpSecurity.exceptionHandling().authenticationEntryPoint(authException).accessDeniedHandler(accessException); httpSecurity.cors(); } /** * 密码加密的规则 * @return */ @Bean public PasswordEncoder passwordEncoder(){ return new BCryptPasswordEncoder(); } /** * BCryptPasswordEncoder 的测试方法 * @param args */ public static void main(String[] args) { BCryptPasswordEncoder bCryptPasswordEncoder = new BCryptPasswordEncoder(); // 加密 String encode = bCryptPasswordEncoder.encode("123456"); System.out.println(encode); // 解密 boolean matches = bCryptPasswordEncoder.matches("123456", encode); System.out.println(matches); }} 自定义注解 123456789101112131415161718@Component("anthony")public class TestExpress { public boolean hasAnyAuthority(String authority){ Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); RedisUser principal = (RedisUser) authentication.getPrincipal();// List<String> list = (List<String>)principal.getAuthorities(); List<SimpleGrantedAuthority> authorities = (List<SimpleGrantedAuthority>) principal.getAuthorities(); for (SimpleGrantedAuthority simpleGrantedAuthority : authorities) { if (simpleGrantedAuthority.getAuthority().equals(authority)) { return true; } } return false; }} 找Java包的路径 12# mac/usr/libexec/java_home -V

2023-08-25
Spring
SpringMVC 总结: 首先请求进入DispatcherServlet 由DispatcherServlet 从HandlerMappings中提取对应的Handler。 2.此时只是获取到了对应的Handle,然后得去寻找对应的适配器,即:HandlerAdapter。 拿到对应HandlerAdapter时,这时候开始调用对应的Handler处理业务逻辑了。 (这时候实际上已经执行完了我们的Controller) 执行完成之后返回一个ModeAndView 这时候交给我们的ViewResolver通过视图名称查找出对应的视图然后返回。 最后 渲染视图 返回渲染后的视图 –>响应请求。 初始化过程version 5.3.8 1234567// org.springframework.web.servlet.HttpServletBean#init@Overridepublic final void init() throws ServletException { // 子类实现,初始化web环境 // Let subclasses do whatever initialization they like. initServletBean();} 12345678// org.springframework.web.servlet.FrameworkServlet#initServletBean@Overrideprotected final void initServletBean() throws ServletException { // 初始化spring上下文 this.webApplicationContext = initWebApplicationContext(); // 子类实现 initFrameworkServlet();} 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657// org.springframework.web.servlet.FrameworkServlet#initWebApplicationContextprotected WebApplicationContext initWebApplicationContext() { WebApplicationContext rootContext = WebApplicationContextUtils.getWebApplicationContext(getServletContext()); WebApplicationContext wac = null; if (this.webApplicationContext != null) { // A context instance was injected at construction time -> use it wac = this.webApplicationContext; if (wac instanceof ConfigurableWebApplicationContext) { ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) wac; if (!cwac.isActive()) { // The context has not yet been refreshed -> provide services such as // setting the parent context, setting the application context id, etc if (cwac.getParent() == null) { // The context instance was injected without an explicit parent -> set // the root application context (if any; may be null) as the parent cwac.setParent(rootContext); } //配置和刷新spring容器(重要) //这个无非就是初始化spring ioc的环境,创建bean和实例化bean等操作 //这个方法最终也是调用refresh()方法,已在spring源码解析中解析过了 configureAndRefreshWebApplicationContext(cwac); } } } if (wac == null) { // No context instance was injected at construction time -> see if one // has been registered in the servlet context. If one exists, it is assumed // that the parent context (if any) has already been set and that the // user has performed any initialization such as setting the context id wac = findWebApplicationContext(); } if (wac == null) { // No context instance is defined for this servlet -> create a local one wac = createWebApplicationContext(rootContext); } if (!this.refreshEventReceived) { // Either the context is not a ConfigurableApplicationContext with refresh // support or the context injected at construction time had already been // refreshed -> trigger initial onRefresh manually here. synchronized (this.onRefreshMonitor) { // 初始化DispatcherServlet的配置initStrategies() onRefresh(wac); } } if (this.publishContext) { // Publish the context as a servlet context attribute. String attrName = getServletContextAttributeName(); getServletContext().setAttribute(attrName, wac); } return wac; } 12345// org.springframework.web.servlet.DispatcherServlet#onRefresh protected void onRefresh(ApplicationContext context) { // //初始化springmvc的配置 initStrategies(context); } 总体流程: 执行DispatcherServlet的init()方法, 会执行父类的HttpServletBean的init()方法 然后调用了FrameworkServlet的initServletBean()方法 没看懂,执行initWebApplicationContext()方法,就是对spring ioc环境的初始化。那么这里就衍生出了一个面试题:spring容器和spring mvc的容器的区别?通过源码的分析,spring和spring mvc底层,都是调用了同一个refresh()方法,所以spring容器和spring mvc容器是没有区别的,都是指的是同一个容器。 (3)执行到onRefresh()方法,就是开始初始化DispatcherServlet了,也就是开始初始化spring mvc。 12345678910111213141516171819// org.springframework.web.servlet.DispatcherServlet#initStrategies protected void initStrategies(ApplicationContext context) { //上传文件 initMultipartResolver(context); //国际化 initLocaleResolver(context); //前段的主题样式 initThemeResolver(context); //初始化HandlerMappings(请求映射器)重点 initHandlerMappings(context); // 初始化HandlerAdapters(处理适配器) initHandlerAdapters(context); initHandlerExceptionResolvers(context); initRequestToViewNameTranslator(context); //视图转换器 initViewResolvers(context); //重定向数据管理器 initFlashMapManager(context); } 1234567891011121314// org.springframework.web.servlet.DispatcherServlet#initHandlerMappingsprivate void initHandlerMappings(ApplicationContext context) { // Ensure we have at least one HandlerMapping, by registering // a default HandlerMapping if no other mappings are found. // 通过配置文件中的配置信息,得到handlerMappings if (this.handlerMappings == null) { this.handlerMappings = getDefaultStrategies(context, HandlerMapping.class); if (logger.isTraceEnabled()) { logger.trace("No HandlerMappings declared for servlet '" + getServletName() + "': using default strategies from DispatcherServlet.properties"); } }} 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657// org.springframework.web.servlet.DispatcherServlet#getDefaultStrategiesprivate static final String DEFAULT_STRATEGIES_PATH = "DispatcherServlet.properties";protected <T> List<T> getDefaultStrategies(ApplicationContext context, Class<T> strategyInterface) { if (defaultStrategies == null) { try { // Load default strategy implementations from properties file. // This is currently strictly internal and not meant to be customized // by application developers. /** * 从属性文件加载默认策略实现 * 说白了这里的意思就是从DEFAULT_STRATEGIES_PATH这个文件当中拿出所有的配置 * 可以去数一下一共有8个: DispatcherServlet.properties == DEFAULT_STRATEGIES_PATH */ ClassPathResource resource = new ClassPathResource(DEFAULT_STRATEGIES_PATH, DispatcherServlet.class); defaultStrategies = PropertiesLoaderUtils.loadProperties(resource); } catch (IOException ex) { throw new IllegalStateException("Could not load '" + DEFAULT_STRATEGIES_PATH + "': " + ex.getMessage()); } } String key = strategyInterface.getName(); // defaultStrategies 是DispatcherServlet.properties 配置文件,在static静态代码块初始化 // 版本变了,不是从静态方法中获取到的 String value = defaultStrategies.getProperty(key); if (value != null) { String[] classNames = StringUtils.commaDelimitedListToStringArray(value); List<T> strategies = new ArrayList<>(classNames.length); for (String className : classNames) { try { // 获取class字节码文件 Class<?> clazz = ClassUtils.forName(className, DispatcherServlet.class.getClassLoader()); // 底层是通过调用spring的getBean的方式创建该对象(可以进行bean的属性装配) // 请求映射就是在这个方法实现装配的 Object strategy = createDefaultStrategy(context, clazz); strategies.add((T) strategy); } catch (ClassNotFoundException ex) { throw new BeanInitializationException( "Could not find DispatcherServlet's default strategy class [" + className + "] for interface [" + key + "]", ex); } catch (LinkageError err) { throw new BeanInitializationException( "Unresolvable class definition for DispatcherServlet's default strategy class [" + className + "] for interface [" + key + "]", err); } } return strategies; } else { return Collections.emptyList(); } } DispatcherServlet.properties 从DispatcherServlet.properties配置文件,可以看出handlerMapping默认是有两个: 1.BeanNameUrlHandlerMapping (主要处理object) 2.RequestMappingHandlerMapping(主要处理method) 123456789101112131415161718192021222324252627282930# Default implementation classes for DispatcherServlet's strategy interfaces.# Used as fallback when no matching beans are found in the DispatcherServlet context.# Not meant to be customized by application developers.org.springframework.web.servlet.LocaleResolver=org.springframework.web.servlet.i18n.AcceptHeaderLocaleResolverorg.springframework.web.servlet.ThemeResolver=org.springframework.web.servlet.theme.FixedThemeResolver// HandlerMappingorg.springframework.web.servlet.HandlerMapping=org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping,\ org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping,\ org.springframework.web.servlet.function.support.RouterFunctionMapping// HandlerAdapterorg.springframework.web.servlet.HandlerAdapter=org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter,\ org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter,\ org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter,\ org.springframework.web.servlet.function.support.HandlerFunctionAdapterorg.springframework.web.servlet.HandlerExceptionResolver=org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver,\ org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolver,\ org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolverorg.springframework.web.servlet.RequestToViewNameTranslator=org.springframework.web.servlet.view.DefaultRequestToViewNameTranslatororg.springframework.web.servlet.ViewResolver=org.springframework.web.servlet.view.InternalResourceViewResolverorg.springframework.web.servlet.FlashMapManager=org.springframework.web.servlet.support.SessionFlashMapManager (1) initHandlerMappings方法,就是初始化我们的handlerMapping(请求映射器)。 (2) handlerMapping的主要作用是,找到请求路径对应的controller的方法。 例如:请求的路径 “/index”,然后这个handlerMapping,在初始化的时候,已经将所有controller的请求路径映射保存在一个map集合,当请求过来的时候,就将”/index”作为一个key,从map集合中找到对应的controller的index方法。 (3) 这里初始化handlerMappings ,默认是有两个handlerMappings ,是直接在defaultStrategies配置文件中获取。 (4) 那么defaultStrategies的值是什么时候初始化的呢? 通过查看源码,defaultStrategies这个值,是DispatcherServlet类的静态代码块初始化的。 全世界都知道,当一个类被初始化的时候,会执行该类的static静态代码块的。 请求阶段分析用户的一个请求过来,会由servlet接收到,然后一步一步调用到DispatcherServlet的doService方法。 123456// org.springframework.web.servlet.DispatcherServlet#doService@Overrideprotected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception { // 核心方法(重点) doDispatch(request, response);} 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879// org.springframework.web.servlet.DispatcherServlet#doDispatchprotected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception { HttpServletRequest processedRequest = request; HandlerExecutionChain mappedHandler = null; boolean multipartRequestParsed = false; // 异步编程 WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request); try { // 定义变量,哈哈哈,好熟悉呀 ModelAndView mv = null; Exception dispatchException = null; try { //检查请求中是否有文件上传操作 processedRequest = checkMultipart(request); multipartRequestParsed = (processedRequest != request); // Determine handler for the current request. // 确定当前请求的处理程序(重点),推断controller和handler的类型, // 进到这里的getHandler方法 mappedHandler = getHandler(processedRequest); if (mappedHandler == null) { noHandlerFound(processedRequest, response); return; } // Determine handler adapter for the current request. //推断适配器,不同的controller类型,交给不同的适配器去处理 //如果是一个bean,mappedHandler.getHandler()返回的是一个对象 //如果是一个method,mappedHandler.getHandler()返回的是一个方法 HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler()); //到这里,spring才确定我要怎么反射调用 // Process last-modified header, if supported by the handler. String method = request.getMethod(); boolean isGet = HttpMethod.GET.matches(method); if (isGet || HttpMethod.HEAD.matches(method)) { long lastModified = ha.getLastModified(request, mappedHandler.getHandler()); if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) { return; } } // 前置拦截器处理 if (!mappedHandler.applyPreHandle(processedRequest, response)) { return; } //通过适配器,处理请求(可以理解为,反射调用方法)(重点) // Actually invoke the handler. mv = ha.handle(processedRequest, response, mappedHandler.getHandler()); if (asyncManager.isConcurrentHandlingStarted()) { return; } applyDefaultViewName(processedRequest, mv); mappedHandler.applyPostHandle(processedRequest, response, mv); } catch (Exception ex) { dispatchException = ex; } catch (Throwable err) { // As of 4.3, we're processing Errors thrown from handler methods as well, // making them available for @ExceptionHandler methods and other scenarios. dispatchException = new NestedServletException("Handler dispatch failed", err); } processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException); } catch (Exception ex) { triggerAfterCompletion(processedRequest, response, mappedHandler, ex); } catch (Throwable err) { triggerAfterCompletion(processedRequest, response, mappedHandler, new NestedServletException("Handler processing failed", err)); } } 通过对DispatcherServlet的分析,得到请求的核心处理方法是doDispatch(), 主要是分了几步: (1) 检查请求中是否有文件上传操作 (2) 确定当前请求的处理的handler(重点) (3) 推断适配器,不同的controller类型,交给不同的适配器去处理 (4) 执行前置拦截器处理interceptor (5) 通过找到的HandlerAdapter ,反射执行相关的业务代码controller的方法。 (6) 返回结果。 123456789101112131415161718// org.springframework.web.servlet.DispatcherServlet#getHandler@Nullableprotected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception { if (this.handlerMappings != null) { //循环所有的HandlerMappings //this.handlerMappings这个是什么时候初始化的?(重点) //在handlerMappings初始化的时候 for (HandlerMapping mapping : this.handlerMappings) { //把请求传过去看能不能得到一个handler //注意:怎么得到handler和handlerMapping自己实现的逻辑有关系 HandlerExecutionChain handler = mapping.getHandler(request); if (handler != null) { return handler; } } } return null;} 12345678910111213141516171819202122232425262728293031323334353637383940414243444546// org.springframework.web.servlet.handler.AbstractHandlerMapping#getHandler@Override@Nullablepublic final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception { //获取handler(重点) Object handler = getHandlerInternal(request); if (handler == null) { handler = getDefaultHandler(); } if (handler == null) { return null; } // Bean name or resolved handler? if (handler instanceof String) { String handlerName = (String) handler; handler = obtainApplicationContext().getBean(handlerName); } // Ensure presence of cached lookupPath for interceptors and others if (!ServletRequestPathUtils.hasCachedPath(request)) { initLookupPath(request); } HandlerExecutionChain executionChain = getHandlerExecutionChain(handler, request); if (logger.isTraceEnabled()) { logger.trace("Mapped to " + handler); } else if (logger.isDebugEnabled() && !DispatcherType.ASYNC.equals(request.getDispatcherType())) { logger.debug("Mapped to " + executionChain.getHandler()); } if (hasCorsConfigurationSource(handler) || CorsUtils.isPreFlightRequest(request)) { CorsConfiguration config = getCorsConfiguration(handler, request); if (getCorsConfigurationSource() != null) { CorsConfiguration globalConfig = getCorsConfigurationSource().getCorsConfiguration(request); config = (globalConfig != null ? globalConfig.combine(config) : config); } if (config != null) { config.validateAllowCredentials(); } executionChain = getCorsHandlerExecutionChain(request, executionChain, config); } return executionChain;} (1) getHandler()方法,主要是遍历在DispatcherServlet初始化是,初始化的handlerMappings。 (2) 这个方法的主要思想是,通过request的路径,去匹配对应的controller去处理。 (3) SpringMVC自己自带了2个HandlerMapping 来供我们选择 至于 为什么要有2个呢? 两种注册Controller的方式我们用2种方式来注册Controller 分别是: (1) 作为Bean的形式:实现Controller接口,重写handleRequest方法,请求路径为”/test” 123456789@Component("/test")public class TesrController implements org.springframework.web.servlet.mvc.Controller{ @Override public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception { System.out.println("1"); return null; }} (2) 以Annotation形式: 12345678@Controllerpublic class AnnotationController { @RequestMapping("/test2") public Object test(){ System.out.println("test"); return null; }} 经过测试: (1)可以得到以Bean方式的controller,是通过BeanNameUrlHandlerMapping去匹配 (2)以注解方法的controller,是通过RequestMappingHandlerMapping去匹配 BeanNameUrlHandlerMappingBeanNameUrlHandlerMapping处理bean方式的源码分析: 12345678910111213141516171819202122232425262728293031323334353637// org.springframework.web.servlet.handler.AbstractUrlHandlerMapping#getHandlerInternal @Override @Nullable protected Object getHandlerInternal(HttpServletRequest request) throws Exception { // 获取请求的路径 String lookupPath = initLookupPath(request); // 到对应的handler(重点)调用 lookupHandler() Object handler; if (usesPathPatterns()) { RequestPath path = ServletRequestPathUtils.getParsedRequestPath(request); handler = lookupHandler(path, lookupPath, request); } else { handler = lookupHandler(lookupPath, request); } if (handler == null) { // We need to care for the default handler directly, since we need to // expose the PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE for it as well. Object rawHandler = null; if (StringUtils.matchesCharacter(lookupPath, '/')) { rawHandler = getRootHandler(); } if (rawHandler == null) { rawHandler = getDefaultHandler(); } if (rawHandler != null) { // Bean name or resolved handler? if (rawHandler instanceof String) { String handlerName = (String) rawHandler; rawHandler = obtainApplicationContext().getBean(handlerName); } validateHandler(rawHandler, request); handler = buildPathExposingHandler(rawHandler, lookupPath, lookupPath, null); } } return handler; } 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869// org.springframework.web.servlet.handler.AbstractUrlHandlerMapping#lookupHandler(java.lang.String, javax.servlet.http.HttpServletRequest)@Nullable protected Object lookupHandler(String lookupPath, HttpServletRequest request) throws Exception { // 查看这里的方法 Object handler = getDirectMatch(lookupPath, request); if (handler != null) { return handler; } // Pattern match? List<String> matchingPatterns = new ArrayList<>(); for (String registeredPattern : this.handlerMap.keySet()) { if (getPathMatcher().match(registeredPattern, lookupPath)) { matchingPatterns.add(registeredPattern); } else if (useTrailingSlashMatch()) { if (!registeredPattern.endsWith("/") && getPathMatcher().match(registeredPattern + "/", lookupPath)) { matchingPatterns.add(registeredPattern + "/"); } } } String bestMatch = null; Comparator<String> patternComparator = getPathMatcher().getPatternComparator(lookupPath); if (!matchingPatterns.isEmpty()) { matchingPatterns.sort(patternComparator); if (logger.isTraceEnabled() && matchingPatterns.size() > 1) { logger.trace("Matching patterns " + matchingPatterns); } bestMatch = matchingPatterns.get(0); } if (bestMatch != null) { handler = this.handlerMap.get(bestMatch); if (handler == null) { if (bestMatch.endsWith("/")) { handler = this.handlerMap.get(bestMatch.substring(0, bestMatch.length() - 1)); } if (handler == null) { throw new IllegalStateException( "Could not find handler for best pattern match [" + bestMatch + "]"); } } // Bean name or resolved handler? if (handler instanceof String) { String handlerName = (String) handler; handler = obtainApplicationContext().getBean(handlerName); } validateHandler(handler, request); String pathWithinMapping = getPathMatcher().extractPathWithinPattern(bestMatch, lookupPath); // There might be multiple 'best patterns', let's make sure we have the correct URI template variables // for all of them Map<String, String> uriTemplateVariables = new LinkedHashMap<>(); for (String matchingPattern : matchingPatterns) { if (patternComparator.compare(bestMatch, matchingPattern) == 0) { Map<String, String> vars = getPathMatcher().extractUriTemplateVariables(matchingPattern, lookupPath); Map<String, String> decodedVars = getUrlPathHelper().decodePathVariables(request, vars); uriTemplateVariables.putAll(decodedVars); } } if (logger.isTraceEnabled() && uriTemplateVariables.size() > 0) { logger.trace("URI variables " + uriTemplateVariables); } return buildPathExposingHandler(handler, bestMatch, pathWithinMapping, uriTemplateVariables); } // No handler found... return null; } 1234567891011121314151617// org.springframework.web.servlet.handler.AbstractUrlHandlerMapping#getDirectMatch@Nullable private Object getDirectMatch(String urlPath, HttpServletRequest request) throws Exception { // 通过请求的路径,在handlerMap中去匹配。 // handlerMap这个值,什么时候填充值?在init初始化的时候,就已经存放在这个handlerMap种 Object handler = this.handlerMap.get(urlPath); if (handler != null) { // Bean name or resolved handler? if (handler instanceof String) { String handlerName = (String) handler; handler = obtainApplicationContext().getBean(handlerName); } validateHandler(handler, request); return buildPathExposingHandler(handler, urlPath, urlPath, null); } return null; } (1) 以Bean方式的controller,匹配请求的路径,是通过一个handlerMap去匹配,比较简单。 (2) 这里的问题是,这个handlerMap的值,是什么时候放进去的? 通过源码分析,BeanNameUrlHandlerMapping是实现了ApplicationContextAware接口。 如果你精通spring的源码,就知道spring的实例bean的时候,会回调这些类的setApplicationContext()方法。 12345678910111213141516171819202122232425262728// org.springframework.context.support.ApplicationObjectSupport#setApplicationContext@Override public final void setApplicationContext(@Nullable ApplicationContext context) throws BeansException { if (context == null && !isContextRequired()) { // Reset internal context state. this.applicationContext = null; this.messageSourceAccessor = null; } else if (this.applicationContext == null) { // Initialize with passed-in context. if (!requiredContextClass().isInstance(context)) { throw new ApplicationContextException( "Invalid application context: needs to be of type [" + requiredContextClass().getName() + "]"); } this.applicationContext = context; this.messageSourceAccessor = new MessageSourceAccessor(context); // 初始化ApplicationContext,就会执行到子类的方法(重点) initApplicationContext(context); } else { // Ignore reinitialization if same context passed in. if (this.applicationContext != context) { throw new ApplicationContextException( "Cannot reinitialize with different application context: current one is [" + this.applicationContext + "], passed-in one is [" + context + "]"); } } } 123456789// 没看懂怎么走到这里来呢// org.springframework.web.servlet.handler.AbstractDetectingUrlHandlerMapping#initApplicationContext @Override public void initApplicationContext() throws ApplicationContextException { super.initApplicationContext(); // 检测出handler detectHandlers(); } 12345678910111213141516171819// org.springframework.web.servlet.handler.AbstractDetectingUrlHandlerMapping#detectHandlers protected void detectHandlers() throws BeansException { // 获取spring ioc所有的beanName,然后判断beanName,那些是以 "/" 开头 ApplicationContext applicationContext = obtainApplicationContext(); String[] beanNames = (this.detectHandlersInAncestorContexts ? BeanFactoryUtils.beanNamesForTypeIncludingAncestors(applicationContext, Object.class) : applicationContext.getBeanNamesForType(Object.class)); // Take any bean name that we can determine URLs for. for (String beanName : beanNames) { // 然后判断beanName,那些是以 "/" 开头 String[] urls = determineUrlsForHandler(beanName); if (!ObjectUtils.isEmpty(urls)) { // 注册handler(重点) // URL paths found: Let's consider it a handler. registerHandler(urls, beanName); } } } 1234567// org.springframework.web.servlet.handler.AbstractUrlHandlerMapping#registerHandler(java.lang.String[], java.lang.String) protected void registerHandler(String[] urlPaths, String beanName) throws BeansException, IllegalStateException { Assert.notNull(urlPaths, "URL path array must not be null"); for (String urlPath : urlPaths) { registerHandler(urlPath, beanName); } } 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748// org.springframework.web.servlet.handler.AbstractUrlHandlerMapping#registerHandler(java.lang.String, java.lang.Object)protected void registerHandler(String urlPath, Object handler) throws BeansException, IllegalStateException { Assert.notNull(urlPath, "URL path must not be null"); Assert.notNull(handler, "Handler object must not be null"); Object resolvedHandler = handler; // Eagerly resolve handler if referencing singleton via name. if (!this.lazyInitHandlers && handler instanceof String) { String handlerName = (String) handler; ApplicationContext applicationContext = obtainApplicationContext(); if (applicationContext.isSingleton(handlerName)) { resolvedHandler = applicationContext.getBean(handlerName); } } Object mappedHandler = this.handlerMap.get(urlPath); if (mappedHandler != null) { if (mappedHandler != resolvedHandler) { throw new IllegalStateException( "Cannot map " + getHandlerDescription(handler) + " to URL path [" + urlPath + "]: There is already " + getHandlerDescription(mappedHandler) + " mapped."); } } else { if (urlPath.equals("/")) { if (logger.isTraceEnabled()) { logger.trace("Root mapping to " + getHandlerDescription(handler)); } setRootHandler(resolvedHandler); } else if (urlPath.equals("/*")) { if (logger.isTraceEnabled()) { logger.trace("Default mapping to " + getHandlerDescription(handler)); } setDefaultHandler(resolvedHandler); } else { // 最终put到map集合中(省略其他无关代码) this.handlerMap.put(urlPath, resolvedHandler); if (getPatternParser() != null) { this.pathPatternHandlerMap.put(getPatternParser().parse(urlPath), resolvedHandler); } if (logger.isTraceEnabled()) { logger.trace("Mapped [" + urlPath + "] onto " + getHandlerDescription(handler)); } } } } BeanNameUrlHandlerMapping处理bean方式的源码分析,其实是很简单: (1) 在类初始化的时候,就已经将所有实现了Controller接口的controller类,拿到他们的@Componet(‘/test’) (2) 然后将’/test’这个作为key,controller类作为value,放入到一个map集合。 (3) 当一个请求过来的时候,拿到这个请求的uri,在map里面找,找到了即表示匹配上 RequestMappingHandlerMapping处理注解方式的源码分析: 1234567891011121314151617// org.springframework.web.servlet.handler.AbstractHandlerMethodMapping#getHandlerInternal// 对于RequestMappingHandlerMapping,indexController.index(),方法的请求路径映射 @Override @Nullable protected HandlerMethod getHandlerInternal(HttpServletRequest request) throws Exception { // 获取请求路径 String lookupPath = initLookupPath(request); this.mappingRegistry.acquireReadLock(); try { // 通过请求路径,获取handler HandlerMethod handlerMethod = lookupHandlerMethod(lookupPath, request); return (handlerMethod != null ? handlerMethod.createWithResolvedBean() : null); } finally { this.mappingRegistry.releaseReadLock(); } } 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748// org.springframework.web.servlet.handler.AbstractHandlerMethodMapping#lookupHandlerMethod@Nullable protected HandlerMethod lookupHandlerMethod(String lookupPath, HttpServletRequest request) throws Exception { List<Match> matches = new ArrayList<>(); // 从mappingRegistry的urlLookup,匹配请求路径 List<T> directPathMatches = this.mappingRegistry.getMappingsByDirectPath(lookupPath); if (directPathMatches != null) { addMatchingMappings(directPathMatches, matches, request); } if (matches.isEmpty()) { addMatchingMappings(this.mappingRegistry.getRegistrations().keySet(), matches, request); } if (!matches.isEmpty()) { Match bestMatch = matches.get(0); if (matches.size() > 1) { Comparator<Match> comparator = new MatchComparator(getMappingComparator(request)); matches.sort(comparator); bestMatch = matches.get(0); if (logger.isTraceEnabled()) { logger.trace(matches.size() + " matching mappings: " + matches); } if (CorsUtils.isPreFlightRequest(request)) { for (Match match : matches) { if (match.hasCorsConfig()) { return PREFLIGHT_AMBIGUOUS_MATCH; } } } else { Match secondBestMatch = matches.get(1); if (comparator.compare(bestMatch, secondBestMatch) == 0) { Method m1 = bestMatch.getHandlerMethod().getMethod(); Method m2 = secondBestMatch.getHandlerMethod().getMethod(); String uri = request.getRequestURI(); throw new IllegalStateException( "Ambiguous handler methods mapped for '" + uri + "': {" + m1 + ", " + m2 + "}"); } } } request.setAttribute(BEST_MATCHING_HANDLER_ATTRIBUTE, bestMatch.getHandlerMethod()); handleMatch(bestMatch.mapping, lookupPath, request); // 返回handler return bestMatch.getHandlerMethod(); } else { return handleNoMatch(this.mappingRegistry.getRegistrations().keySet(), lookupPath, request); } } 12345// 3.AbstractHandlerMethodMapping.MappingRegistry#getMappingsByDirectPath@Nullablepublic List<T> getMappingsByDirectPath(String urlPath) { return this.pathLookup.get(urlPath);} RequestMappingHandlerMapping处理注解方式的源码分析,比较复杂,用一个MappingRegistry维护所有的请求路径映射。 MappingRegistry的初始化,也是在该bean实例化的时候,就已经做好的了。 原理也是和上一个差不多,都是从一个map集合里面匹配。所以这里就不再做解析了 总结:getHandler() 找适配器 123456789101112// org.springframework.web.servlet.DispatcherServlet#getHandlerAdapter protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException { if (this.handlerAdapters != null) { for (HandlerAdapter adapter : this.handlerAdapters) { if (adapter.supports(handler)) { return adapter; } } } throw new ServletException("No adapter for handler [" + handler + "]: The DispatcherServlet configuration needs to include a HandlerAdapter that supports this handler"); } 其实能看见他是从一个handlerAdapters属性里面遍历了我们的适配器 这个handlerAdapters哪来的呢? 跟我们的HandlerMappings一样 在他的配置文件里面有写,就是我们刚刚所说的 。 至于什么是适配器,我们结合Handler来讲, 就如我们在最开始的总结时所说的, 一开始只是找到了Handler 现在要执行了,但是有个问题,Handler不止一个, 自然而然对应的执行方式就不同了, 这时候适配器的概念就出来了:对应不同的Handler的执行方案。当找到合适的适配器的时候, 基本上就已经收尾了,因为后面在做了一些判断之后(判断请求类型之类的),就开始执行了你的Handler了,上代码: mv = ha.handle(processedRequest, response, mappedHandler.getHandler()); 这个mv就是我们的ModlAndView 其实执行完这一行 我们的Controller的逻辑已经执行完了, 剩下的就是寻找视图 渲染图的事情了。 总结: 其实我们的SpringMVC关键的概念就在于Handler(处理器) 和Adapter(适配器) 通过一个关键的HandlerMappings 找到合适处理你的Controller的Handler 然后再通过HandlerAdapters找到一个合适的HandlerAdapter 来执行Handler即Controller里面的逻辑。 最后再返回ModlAndView… 参考:https://juejin.cn/post/6991290858880368676 Spring事务的传播 参考:https://segmentfault.com/a/1190000013341344 传播等级 描述 理解 REQUIRED 默认的事务传播级别表示如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务。 A有事务,B就跟着用A没有事务,B就开启自己的事务,只B方法用 SUPPORTS 如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续运行。 MANDATORY 如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常。 REQUIRES_NEW 表示创建一个新的事务如果当前存在事务,则把当前事务挂起。也就是说不管外部方法是否开启事务,Propagation.REQUIRES_NEW 修饰的内部方法会新开启自己的事务,且开启的事务相互独立,互不干扰。 NOT_SUPPORTED 以非事务方式运行,如果当前存在事务,则把当前事务挂起。 NEVER 以非事务方式运行,如果当前存在事务,则抛出异常。 NESTED 如果当前存在事务,则创建一个事务作为当前事务的嵌套事务来运行;如果当前没有事务,则该取值等价于REQUIRED。 默认数据是anthony和0 REQUIRED A方法有事务,A方法报错,有一个报错都会回滚,结果是:anthony和0 1234567891011121314151617@PostMapping("/test1")@Transactionalpublic void methodA(){ Test byId = testService.getById(1); byId.setUsername("anthony2"); byId.updateById(); transactionalController.methodB(); int i = 1 / 0;}@Transactional(propagation= Propagation.REQUIRED)public void methodB(){ Test byId = testService.getById(1); byId.setParentId(1); byId.updateById();} A方法没有事务,A方法有报错,结果是:anthony2和1 B方法自己开启事务,就不管A事务了,所以A方法,就算报错了,也成功写入数据库,B事务没有报错,也成功写入数据库 12345678910111213141516@PostMapping("/test1")public void methodA(){ Test byId = testService.getById(1); byId.setUsername("anthony2"); byId.updateById(); transactionalController.methodB(); int i = 1 / 0;}@Transactional(propagation= Propagation.REQUIRED)public void methodB(){ Test byId = testService.getById(1); byId.setParentId(1); byId.updateById();} A方法没有事务,A方法和B方法都报错,,结果是:anthony2和0 A方法没有事务,所以A方法插入数据库成功,就算报错,也没有回滚 B方法自己开始事务,B方法报错,所以回滚 1234567891011121314151617@PostMapping("/test1")public void methodA(){ Test byId = testService.getById(1); byId.setUsername("anthony2"); byId.updateById(); transactionalController.methodB(); int i = 1 / 0;}@Transactional(propagation= Propagation.REQUIRED)public void methodB(){ Test byId = testService.getById(1); byId.setParentId(1); byId.updateById(); int i = 1 / 0;} SUPPORTS A方法有事务,A方法报错,都回滚,,结果是:anthony和0 1234567891011121314151617@PostMapping("/test1")@Transactionalpublic void methodA(){ Test byId = testService.getById(1); byId.setUsername("anthony2"); byId.updateById(); transactionalController.methodB(); int i = 1 / 0;}@Transactional(propagation= Propagation.SUPPORTS)public void methodB(){ Test byId = testService.getById(1); byId.setParentId(1); byId.updateById();} A没有事务,,A方法报错,都没有回滚,结果是:anthony2和1 12345678910111213141516@PostMapping("/test1")public void methodA(){ Test byId = testService.getById(1); byId.setUsername("anthony2"); byId.updateById(); transactionalController.methodB(); int i = 1 / 0;}@Transactional(propagation= Propagation.SUPPORTS)public void methodB(){ Test byId = testService.getById(1); byId.setParentId(1); byId.updateById();} A没有事务,,A,B方法都报错,都没有回滚,结果是:anthony2和1 1234567891011121314151617@PostMapping("/test1")public void methodA(){ Test byId = testService.getById(1); byId.setUsername("anthony2"); byId.updateById(); transactionalController.methodB(); int i = 1 / 0;}@Transactional(propagation= Propagation.SUPPORTS)public void methodB(){ Test byId = testService.getById(1); byId.setParentId(1); byId.updateById(); int i = 1 / 0;} MANDATORY A有事务,A报错,都回滚,结果是:anthony和0 1234567891011121314151617@PostMapping("/test1")@Transactionalpublic void methodA(){ Test byId = testService.getById(1); byId.setUsername("anthony2"); byId.updateById(); transactionalController.methodB(); int i = 1 / 0;}@Transactional(propagation= Propagation.MANDATORY)public void methodB(){ Test byId = testService.getById(1); byId.setParentId(1); byId.updateById();} A没有事务,运行报错了 12345678910111213141516@PostMapping("/test1")public void methodA(){ Test byId = testService.getById(1); byId.setUsername("anthony2"); byId.updateById(); transactionalController.methodB(); int i = 1 / 0;}@Transactional(propagation= Propagation.MANDATORY)public void methodB(){ Test byId = testService.getById(1); byId.setParentId(1); byId.updateById();} REQUIRES_NEW 测试的时候,不要操作同一条数据,容易超时….. A开始事务,B也开始事务,B报错了,B回滚,A插入成功 1234567891011121314151617@PostMapping("/test1")@Transactionalpublic void methodA(){ Test byId = testService.getById(1); byId.setUsername("anthony2"); byId.updateById(); transactionalController.methodB(); int i = 1 / 0;}@Transactional(propagation= Propagation.REQUIRES_NEW)public void methodB(){ Test byId = testService.getById(2); byId.setParentId(1); byId.updateById();} 这样没有复现出问题 12345678910111213141516@PostMapping("/test1")@Transactionalpublic void methodA(){ Test byId = testService.getById(1); byId.setUsername("anthony2"); byId.updateById(); transactionalController.methodB();}@Transactional(propagation= Propagation.REQUIRES_NEW)public void methodB(){ Test byId = testService.getById(2); byId.setParentId(1); byId.updateById(); int i = 1 / 0;} A没有事务,B有事务 A报错,没有回滚,B插入数据成功 外围方法异常,不影响内部调用的方法 12345678910111213141516@PostMapping("/test1")public void methodA(){ Test byId = testService.getById(1); byId.setUsername("anthony2"); byId.updateById(); transactionalController.methodB(); int i = 1 / 0;}@Transactional(propagation= Propagation.REQUIRES_NEW)public void methodB(){ Test byId = testService.getById(2); byId.setParentId(1); byId.updateById();} A没有事务,B有事务 A插入数据成功,B回滚 内部调用的方法,不影响外围的方法成功插入 123456789101112131415@PostMapping("/test1")public void methodA(){ Test byId = testService.getById(1); byId.setUsername("anthony2"); byId.updateById(); transactionalController.methodB();}@Transactional(propagation= Propagation.REQUIRES_NEW)public void methodB(){ Test byId = testService.getById(2); byId.setParentId(1); byId.updateById(); int i = 1 / 0;} NOT_SUPPORTED A有事务,B也有事务,A回滚了,B报错了,没有回滚 12345678910111213141516171819@PostMapping("/test1")@Transactionalpublic void methodA(){ Test byId = testService.getById(1); byId.setUsername("anthony2"); byId.updateById(); transactionalController.methodB();}@Transactional(propagation= Propagation.NOT_SUPPORTED)public void methodB(){ Test byId = testService.getById(2); byId.setParentId(1); byId.updateById(); int i = 1 / 0;} A有事务,B也有事务,A回滚了,B没有回滚 1234567891011121314151617181920@PostMapping("/test1")@Transactionalpublic void methodA(){ Test byId = testService.getById(1); byId.setUsername("anthony2"); byId.updateById(); transactionalController.methodB(); int i = 1 / 0;}@Transactional(propagation= Propagation.NOT_SUPPORTED)public void methodB(){ Test byId = testService.getById(2); byId.setParentId(1); byId.updateById(); int i = 1 / 0;} NEVER 直接报错 123456789101112131415@PostMapping("/test1")@Transactionalpublic void methodA(){ Test byId = testService.getById(1); byId.setUsername("anthony2"); byId.updateById(); transactionalController.methodB();}@Transactional(propagation= Propagation.NEVER)public void methodB(){ Test byId = testService.getById(2); byId.setParentId(1); byId.updateById();} NESTED 全部提交成功 123456789101112131415@PostMapping("/test1")@Transactionalpublic void methodA(){ Test byId = testService.getById(1); byId.setUsername("anthony2"); byId.updateById(); transactionalController.methodB();}@Transactional(propagation= Propagation.NESTED)public void methodB(){ Test byId = testService.getById(2); byId.setParentId(1); byId.updateById();} 全部失败 12345678910111213141516@PostMapping("/test1")@Transactionalpublic void methodA(){ Test byId = testService.getById(1); byId.setUsername("anthony2"); byId.updateById(); transactionalController.methodB();}@Transactional(propagation= Propagation.NESTED)public void methodB(){ Test byId = testService.getById(2); byId.setParentId(1); byId.updateById(); int i = 1 / 0;} A没有事务,B有事务 A执行成功,B回滚成功 123456789101112131415@PostMapping("/test1")public void methodA(){ Test byId = testService.getById(1); byId.setUsername("anthony2"); byId.updateById(); transactionalController.methodB();}@Transactional(propagation= Propagation.NESTED)public void methodB(){ Test byId = testService.getById(2); byId.setParentId(1); byId.updateById(); int i = 1 / 0;} 拦截器和过滤器1、过滤器和拦截器触发时机不一样,先拦截器,后过滤器 2、拦截器可以获取IOC容器中的各个bean,而过滤器就不行,因为拦截器是spring提供并管理的,spring的功能可以被拦截器使用,在拦截器里注入一个service,可以调用业务逻辑。而过滤器是JavaEE标准,只需依赖servlet api ,不需要依赖spring。 3、过滤器的实现基于回调函数。而拦截器(代理模式)的实现基于反射 4、过滤器是依赖于Servlet容器,属于Servlet规范的一部分,而拦截器则是独立存在的,可以在任何情况下使用。 5、Filter的执行由Servlet容器回调完成,而拦截器通常通**过动态代理(反射)**的方式来执行。 6、Filter的生命周期由Servlet容器管理,而拦截器则可以通过IoC容器来管理,因此可以通过注入等方式来获取其他Bean的实例,因此使用会更方便。 7、过滤器只能在请求的前后使用,而拦截器可以详细到每个方法 Spring IOCSpring提供了两种容器:BeanFactory和ApplicationContext **BeanFactory:**基本的IoC容器,默认采用延迟初始化策略(lazy-load),即只有当客户端对象需要访问容器中某个bean对象的时候,才会对该bean对象进行初始化以及依赖注入操作。所以BeanFactory容器的特点是启动初期速度快,所需资源有限,适合于资源有限功能要求不严格的场景。 ApplicationContext: ApplicationContext在BeanFactory基础上构建,支持其他的高级特性,如国际化,事件发布等。相对于BeanFactory容器来说,ApplicationContext在启动的时候即完成资源的初始化,所以启动时间较长,适合于系统资源充足,需要更多功能的场景 Spring BeanJava 中Bean的定义: 类中所有的属性都必须封装,即:使用private声明;这个不太确定 封装的属性如果需要被外部所操作,则必须编写对应的setter、getter方法; 一个JavaBean中至少存在一个无参构造方法。 12345678910111213141516171819202122public class Staff{ private String name; private int age; public void setName(String name) { this.name = name; } public void setAge(int age) { this.age = age; } public String getName() { return name; } public int getAge() { return age; }} 而Spring IoC容器就是管理bean的工厂。Spring中bean 是一个被实例化,组装,并通过 Spring IoC 容器所管理的对象。这些 bean 是由用容器提供的配置元数据创建的。Spring可以采用XML配置文件的方式来管理和配置Bean信息,如下: 12345678<?xml version="1.0" encoding="UTF-8"?><beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"><bean id="user" class="com.wgs.spring.bean.User"></bean></beans> <beans>是XML配置文件中根节点,下面可包含多个<bean>子节点。Spring的XML配置文件中的配置与<bean>元素是一一对应的。 属性 描述 id 注册到容器的对象都有一个唯一的id值,如id=”user” name bean的别名,name可以使用逗号、空格或冒号等分割指定多个name,而id就不可以 scope 作用域 constructor-arg 用来注入依赖关系 properties 用来注入依赖关系 autowiring mode 用来注入依赖关系 lazy-initialization mode 是否延迟加载 initialization method bean被创建的时候,初始化的的方法 destruction method 销毁指定的方法 Spring Bean 生命周期2.低昂registerBeanFactoryPostProcessor 完成扫描,运行之前,不会有我们自己的类,除了@componentScan这个注解的这个类,等完成之后,就会有我们自己的类 1:实例化一个ApplicationContext的对象;2:调用bean工厂后置处理器完成扫描;3:循环解析扫描出来的类信息;4:实例化一个BeanDefinition对象来存储解析出来的信息;5:把实例化好的beanDefinition对象put到beanDefinitionMap当中缓存起来,以便后面实例化bean;6:再次调用bean工厂后置处理器;7:当然spring还会干很多事情,比如国际化,比如注册BeanPostProcessor等等,如果我们只关心如何实例化一个bean的话那么这一步就是spring调用finishBeanFactoryInitialization方法来实例化单例的bean,实例化之前spring要做验证,需要遍历所有扫描出来的类,依次判断这个bean是否Lazy,是否prototype,是否abstract等等;8:如果验证完成spring在实例化一个bean之前需要推断构造方法,因为spring实例化对象是通过构造方法反射,故而需要知道用哪个构造方法;9:推断完构造方法之后spring调用构造方法反射实例化一个对象;注意我这里说的是对象、对象、对象;这个时候对象已经实例化出来了,但是并不是一个完整的bean,最简单的体现是这个时候实例化出来的对象属性是没有注入,所以不是一个完整的bean;10:spring处理合并后的beanDefinition(合并?是spring当中非常重要的一块内容,后面的文章我会分析);11:判断是否支持循环依赖,如果支持则提前把一个工厂存入singletonFactories——map;12:判断是否需要完成属性注入13:如果需要完成属性注入,则开始注入属性14:判断bean的类型回调Aware接口15:调用生命周期回调方法16:如果需要代理则完成代理17:put到单例池——bean完成——存在spring容器当中 Spring Bean 循环依赖https://juejin.im/post/6844904166351978504#h5 AnnotationConfigApplicationContext#AnnotationConfigApplicationContext 123456public AnnotationConfigApplicationContext(Class<?>... componentClasses) { this(); register(componentClasses); // 关键方法 refresh();} org.springframework.context.support.AbstractApplicationContext#refresh 1234567891011121314151617181920212223242526272829303132333435363738394041424344@Overridepublic void refresh() throws BeansException, IllegalStateException { synchronized (this.startupShutdownMonitor) { // Prepare this context for refreshing. prepareRefresh(); // Tell the subclass to refresh the internal bean factory. ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory(); // Prepare the bean factory for use in this context. prepareBeanFactory(beanFactory); try { // Allows post-processing of the bean factory in context subclasses. postProcessBeanFactory(beanFactory); // Invoke factory processors registered as beans in the context. invokeBeanFactoryPostProcessors(beanFactory); // Register bean processors that intercept bean creation. registerBeanPostProcessors(beanFactory); // Initialize message source for this context. initMessageSource(); // Initialize event multicaster for this context. // 完成所有的扫描 initApplicationEventMulticaster(); // Initialize other special beans in specific context subclasses. onRefresh(); // Check for listener beans and register them. registerListeners(); // Instantiate all remaining (non-lazy-init) singletons. // 实例化所有没有延迟的单例类 finishBeanFactoryInitialization(beanFactory); // Last step: publish corresponding event. finishRefresh(); } }} org.springframework.context.support.AbstractApplicationContext#finishBeanFactoryInitialization 1234567protected void finishBeanFactoryInitialization(ConfigurableListableBeanFactory beanFactory) { // .... // Instantiate all remaining (non-lazy-init) singletons. // 实例化所有单例,非lazy beanFactory.preInstantiateSingletons();} org.springframework.beans.factory.support.DefaultListableBeanFactory#preInstantiateSingletons 12345678910111213141516171819202122232425262728293031323334353637383940414243@Overridepublic void preInstantiateSingletons() throws BeansException { if (logger.isTraceEnabled()) { logger.trace("Pre-instantiating singletons in " + this); } // Iterate over a copy to allow for init methods which in turn register new bean definitions. // While this may not be part of the regular factory bootstrap, it does otherwise work fine. List<String> beanNames = new ArrayList<>(this.beanDefinitionNames); // Trigger initialization of all non-lazy singleton beans... for (String beanName : beanNames) { RootBeanDefinition bd = getMergedLocalBeanDefinition(beanName); // 验证,判断这个是是不是抽象的和是不是单例的和是不是延迟加载的 if (!bd.isAbstract() && bd.isSingleton() && !bd.isLazyInit()) { if (isFactoryBean(beanName)) { Object bean = getBean(FACTORY_BEAN_PREFIX + beanName); if (bean instanceof FactoryBean) { FactoryBean<?> factory = (FactoryBean<?>) bean; boolean isEagerInit; if (System.getSecurityManager() != null && factory instanceof SmartFactoryBean) { isEagerInit = AccessController.doPrivileged( (PrivilegedAction<Boolean>) ((SmartFactoryBean<?>) factory)::isEagerInit, getAccessControlContext()); } else { isEagerInit = (factory instanceof SmartFactoryBean && ((SmartFactoryBean<?>) factory).isEagerInit()); } if (isEagerInit) { getBean(beanName); } } } else { // 验证一切都通过的类,开始实例化普通的bean,还不是spring bean getBean(beanName); } } } // ....} org.springframework.beans.factory.support.AbstractBeanFactory#getBean(java.lang.String) 1234@Overridepublic Object getBean(String name) throws BeansException { return doGetBean(name, null, null, false);} org.springframework.beans.factory.support.AbstractBeanFactory#doGetBean 这里面大部分都是验证,比如depenon,或者import 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161protected <T> T doGetBean( String name, @Nullable Class<T> requiredType, @Nullable Object[] args, boolean typeCheckOnly) throws BeansException { // 理解bean的名字是否非法 String beanName = transformedBeanName(name); Object bean; // Eagerly check singleton cache for manually registered singletons. // 这里的方法啊 Object sharedInstance = getSingleton(beanName); if (sharedInstance != null && args == null) { bean = getObjectForBeanInstance(sharedInstance, name, beanName, null); } else { // Fail if we're already creating this bean instance: // We're assumably within a circular reference. // 判断这个类是不是在创建过程中,循环依赖的时候要用 if (isPrototypeCurrentlyInCreation(beanName)) { throw new BeanCurrentlyInCreationException(beanName); } // Check if bean definition exists in this factory. BeanFactory parentBeanFactory = getParentBeanFactory(); if (parentBeanFactory != null && !containsBeanDefinition(beanName)) { // 方法注入 // Not found -> check parent. String nameToLookup = originalBeanName(name); if (parentBeanFactory instanceof AbstractBeanFactory) { return ((AbstractBeanFactory) parentBeanFactory).doGetBean( nameToLookup, requiredType, args, typeCheckOnly); } else if (args != null) { // Delegation to parent with explicit args. return (T) parentBeanFactory.getBean(nameToLookup, args); } else if (requiredType != null) { // No args -> delegate to standard getBean method. return parentBeanFactory.getBean(nameToLookup, requiredType); } else { return (T) parentBeanFactory.getBean(nameToLookup); } } if (!typeCheckOnly) { markBeanAsCreated(beanName); } try { RootBeanDefinition mbd = getMergedLocalBeanDefinition(beanName); checkMergedBeanDefinition(mbd, beanName, args); // Guarantee initialization of beans that the current bean depends on. String[] dependsOn = mbd.getDependsOn(); if (dependsOn != null) { for (String dep : dependsOn) { if (isDependent(beanName, dep)) { throw new BeanCreationException(mbd.getResourceDescription(), beanName, "Circular depends-on relationship between '" + beanName + "' and '" + dep + "'"); } registerDependentBean(dep, beanName); try { getBean(dep); } catch (NoSuchBeanDefinitionException ex) { throw new BeanCreationException(mbd.getResourceDescription(), beanName, "'" + beanName + "' depends on missing bean '" + dep + "'", ex); } } } // Create bean instance. // 判断类是不是单例 if (mbd.isSingleton()) { // getSingleton(String,facotory) 这个方法里有正在创建中的标识设置 sharedInstance = getSingleton(beanName, () -> { try { // 完成了目标对象的创建 // 如果需要代理,还创建了代理 return createBean(beanName, mbd, args); } catch (BeansException ex) { // Explicitly remove instance from singleton cache: It might have been put there // eagerly by the creation process, to allow for circular reference resolution. // Also remove any beans that received a temporary reference to the bean. destroySingleton(beanName); throw ex; } }); bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd); } else if (mbd.isPrototype()) { // It's a prototype -> create a new instance. Object prototypeInstance = null; try { beforePrototypeCreation(beanName); prototypeInstance = createBean(beanName, mbd, args); } finally { afterPrototypeCreation(beanName); } bean = getObjectForBeanInstance(prototypeInstance, name, beanName, mbd); } else { String scopeName = mbd.getScope(); if (!StringUtils.hasLength(scopeName)) { throw new IllegalStateException("No scope name defined for bean ´" + beanName + "'"); } Scope scope = this.scopes.get(scopeName); if (scope == null) { throw new IllegalStateException("No Scope registered for scope name '" + scopeName + "'"); } try { Object scopedInstance = scope.get(beanName, () -> { beforePrototypeCreation(beanName); try { return createBean(beanName, mbd, args); } finally { afterPrototypeCreation(beanName); } }); bean = getObjectForBeanInstance(scopedInstance, name, beanName, mbd); } catch (IllegalStateException ex) { throw new BeanCreationException(beanName, "Scope '" + scopeName + "' is not active for the current thread; consider " + "defining a scoped proxy for this bean if you intend to refer to it from a singleton", ex); } } } catch (BeansException ex) { cleanupAfterBeanCreationFailure(beanName); throw ex; } } // Check if required type matches the type of the actual bean instance. if (requiredType != null && !requiredType.isInstance(bean)) { try { T convertedBean = getTypeConverter().convertIfNecessary(bean, requiredType); if (convertedBean == null) { throw new BeanNotOfRequiredTypeException(name, requiredType, bean.getClass()); } return convertedBean; } catch (TypeMismatchException ex) { if (logger.isTraceEnabled()) { logger.trace("Failed to convert bean '" + name + "' to required type '" + ClassUtils.getQualifiedName(requiredType) + "'", ex); } throw new BeanNotOfRequiredTypeException(name, requiredType, bean.getClass()); } } return (T) bean;} org.springframework.beans.factory.support.DefaultSingletonBeanRegistry#getSingleton(java.lang.String) 12345678910111213141516171819202122232425262728293031323334// 上个代码块第七行调用的@Override@Nullablepublic Object getSingleton(String beanName) { return getSingleton(beanName, true);}/** Cache of singleton objects: bean name to bean instance. *//** 缓存单例对象: bean name to bean instance. */private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);@Nullableprotected Object getSingleton(String beanName, boolean allowEarlyReference) { // 初始化的时候这里肯定是null,但是在初始化完成之后,再调用getBean就肯定不是null // isSingletonCurrentlyInCreation 这个方法很重要,说明对象是不是正在创建 // singletonFactories 也很重要 Object singletonObject = this.singletonObjects.get(beanName); // 判断循环依赖的时候 if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) { synchronized (this.singletonObjects) { singletonObject = this.earlySingletonObjects.get(beanName); if (singletonObject == null && allowEarlyReference) { ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName); if (singletonFactory != null) { singletonObject = singletonFactory.getObject(); this.earlySingletonObjects.put(beanName, singletonObject); this.singletonFactories.remove(beanName); } } } } return singletonObject;} org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#createBean 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859@Override protected Object createBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args) throws BeanCreationException { if (logger.isTraceEnabled()) { logger.trace("Creating instance of bean '" + beanName + "'"); } RootBeanDefinition mbdToUse = mbd; // Make sure bean class is actually resolved at this point, and // clone the bean definition in case of a dynamically resolved Class // which cannot be stored in the shared merged bean definition. // 从beanDefinition对象中获取出来bean的类型 Class<?> resolvedClass = resolveBeanClass(mbd, beanName); if (resolvedClass != null && !mbd.hasBeanClass() && mbd.getBeanClassName() != null) { mbdToUse = new RootBeanDefinition(mbd); mbdToUse.setBeanClass(resolvedClass); } // Prepare method overrides. try { mbdToUse.prepareMethodOverrides(); } catch (BeanDefinitionValidationException ex) { throw new BeanDefinitionStoreException(mbdToUse.getResourceDescription(), beanName, "Validation of method overrides failed", ex); } try { // Give BeanPostProcessors a chance to return a proxy instead of the target bean instance. // 第一次调用个后置处理器 Object bean = resolveBeforeInstantiation(beanName, mbdToUse); if (bean != null) { return bean; } } catch (Throwable ex) { throw new BeanCreationException(mbdToUse.getResourceDescription(), beanName, "BeanPostProcessor before instantiation of bean failed", ex); } try { // 调用方法 Object beanInstance = doCreateBean(beanName, mbdToUse, args); if (logger.isTraceEnabled()) { logger.trace("Finished creating instance of bean '" + beanName + "'"); } return beanInstance; } catch (BeanCreationException | ImplicitlyAppearedSingletonException ex) { // A previously detected exception with proper bean creation context already, // or illegal singleton state to be communicated up to DefaultSingletonBeanRegistry. throw ex; } catch (Throwable ex) { throw new BeanCreationException( mbdToUse.getResourceDescription(), beanName, "Unexpected exception during bean creation", ex); } } org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#doCreateBean 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798protected Object doCreateBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args) throws BeanCreationException { // Instantiate the bean. BeanWrapper instanceWrapper = null; if (mbd.isSingleton()) { instanceWrapper = this.factoryBeanInstanceCache.remove(beanName); } if (instanceWrapper == null) { // 实例化对象,里面第二次调调用后置处理器 // 反射调用对象的构造方法 // 这里java对象就已经有了 instanceWrapper = createBeanInstance(beanName, mbd, args); } Object bean = instanceWrapper.getWrappedInstance(); Class<?> beanType = instanceWrapper.getWrappedClass(); if (beanType != NullBean.class) { mbd.resolvedTargetType = beanType; } // Allow post-processors to modify the merged bean definition. synchronized (mbd.postProcessingLock) { if (!mbd.postProcessed) { try { // 第三次调用后置处理器 applyMergedBeanDefinitionPostProcessors(mbd, beanType, beanName); } mbd.postProcessed = true; } } // Eagerly cache singletons to be able to resolve circular references // even when triggered by lifecycle interfaces like BeanFactoryAware. // 判断是否需要循环依赖 boolean earlySingletonExposure = // 到这里了,也肯定是true (mbd.isSingleton() && // 默认值是true this.allowCircularReferences && isSingletonCurrentlyInCreation(beanName)); if (earlySingletonExposure) { // 第四次调用后置处理器,判断是否需要AOP addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean)); } // Initialize the bean instance. Object exposedObject = bean; try { // 填充属性,也就是我们说的自动注入 // 里面会完成第五次和第六次后置处理器的调用 // 看这里 populateBean(beanName, mbd, instanceWrapper); // 初始化spring // 里面会进行第七次和第八次后置处理的调用个 exposedObject = initializeBean(beanName, exposedObject, mbd); } catch (Throwable ex) { if (ex instanceof BeanCreationException && beanName.equals(((BeanCreationException) ex).getBeanName())) { throw (BeanCreationException) ex; } else { throw new BeanCreationException( mbd.getResourceDescription(), beanName, "Initialization of bean failed", ex); } } if (earlySingletonExposure) { Object earlySingletonReference = getSingleton(beanName, false); if (earlySingletonReference != null) { if (exposedObject == bean) { exposedObject = earlySingletonReference; } else if (!this.allowRawInjectionDespiteWrapping && hasDependentBean(beanName)) { String[] dependentBeans = getDependentBeans(beanName); Set<String> actualDependentBeans = new LinkedHashSet<>(dependentBeans.length); for (String dependentBean : dependentBeans) { if (!removeSingletonIfCreatedForTypeCheckOnly(dependentBean)) { actualDependentBeans.add(dependentBean); } } // 省略代码 } } } // Register bean as disposable. try { registerDisposableBeanIfNecessary(beanName, bean, mbd); } catch (BeanDefinitionValidationException ex) { throw new BeanCreationException( mbd.getResourceDescription(), beanName, "Invalid destruction signature", ex); } return exposedObject;} org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#createBeanInstance 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455protected BeanWrapper createBeanInstance(String beanName, RootBeanDefinition mbd, @Nullable Object[] args) { // Make sure bean class is actually resolved at this point. Class<?> beanClass = resolveBeanClass(mbd, beanName); if (beanClass != null && !Modifier.isPublic(beanClass.getModifiers()) && !mbd.isNonPublicAccessAllowed()) { throw new BeanCreationException(mbd.getResourceDescription(), beanName, "Bean class isn't public, and non-public access not allowed: " + beanClass.getName()); } Supplier<?> instanceSupplier = mbd.getInstanceSupplier(); if (instanceSupplier != null) { return obtainFromSupplier(instanceSupplier, beanName); } if (mbd.getFactoryMethodName() != null) { return instantiateUsingFactoryMethod(beanName, mbd, args); } // Shortcut when re-creating the same bean... boolean resolved = false; boolean autowireNecessary = false; if (args == null) { synchronized (mbd.constructorArgumentLock) { if (mbd.resolvedConstructorOrFactoryMethod != null) { resolved = true; autowireNecessary = mbd.constructorArgumentsResolved; } } } if (resolved) { if (autowireNecessary) { return autowireConstructor(beanName, mbd, null, null); } else { return instantiateBean(beanName, mbd); } } // Candidate constructors for autowiring? // 第二次调用后置处理器构造方法,通过反射实例化对象,这时候构造方法里有打印,就会打印出日志 Constructor<?>[] ctors = determineConstructorsFromBeanPostProcessors(beanClass, beanName); if (ctors != null || mbd.getResolvedAutowireMode() == AUTOWIRE_CONSTRUCTOR || mbd.hasConstructorArgumentValues() || !ObjectUtils.isEmpty(args)) { return autowireConstructor(beanName, mbd, ctors, args); } // Preferred constructors for default construction? ctors = mbd.getPreferredConstructors(); if (ctors != null) { return autowireConstructor(beanName, mbd, ctors, null); } // No special handling: simply use no-arg constructor. return instantiateBean(beanName, mbd);} org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#populateBean 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283protected void populateBean(String beanName, RootBeanDefinition mbd, @Nullable BeanWrapper bw) { if (bw == null) { if (mbd.hasPropertyValues()) { throw new BeanCreationException( mbd.getResourceDescription(), beanName, "Cannot apply property values to null instance"); } else { // Skip property population phase for null instance. return; } } // Give any InstantiationAwareBeanPostProcessors the opportunity to modify the // state of the bean before properties are set. This can be used, for example, // to support styles of field injection. if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) { for (BeanPostProcessor bp : getBeanPostProcessors()) { if (bp instanceof InstantiationAwareBeanPostProcessor) { InstantiationAwareBeanPostProcessor ibp = (InstantiationAwareBeanPostProcessor) bp; if (!ibp.postProcessAfterInstantiation(bw.getWrappedInstance(), beanName)) { return; } } } } PropertyValues pvs = (mbd.hasPropertyValues() ? mbd.getPropertyValues() : null); int resolvedAutowireMode = mbd.getResolvedAutowireMode(); if (resolvedAutowireMode == AUTOWIRE_BY_NAME || resolvedAutowireMode == AUTOWIRE_BY_TYPE) { MutablePropertyValues newPvs = new MutablePropertyValues(pvs); // Add property values based on autowire by name if applicable. if (resolvedAutowireMode == AUTOWIRE_BY_NAME) { autowireByName(beanName, mbd, bw, newPvs); } // Add property values based on autowire by type if applicable. if (resolvedAutowireMode == AUTOWIRE_BY_TYPE) { autowireByType(beanName, mbd, bw, newPvs); } pvs = newPvs; } boolean hasInstAwareBpps = hasInstantiationAwareBeanPostProcessors(); boolean needsDepCheck = (mbd.getDependencyCheck() != AbstractBeanDefinition.DEPENDENCY_CHECK_NONE); PropertyDescriptor[] filteredPds = null; if (hasInstAwareBpps) { if (pvs == null) { pvs = mbd.getPropertyValues(); } for (BeanPostProcessor bp : getBeanPostProcessors()) { if (bp instanceof InstantiationAwareBeanPostProcessor) { // 这里的ibp常用的有两种类型 // 1.@Resouce 使用的是CommonAnnotationBeanPostProcessor // 2.@Autowire 使用的是AutoWireAnnotationBeanPostProcessor InstantiationAwareBeanPostProcessor ibp = (InstantiationAwareBeanPostProcessor) bp; // 这里会调用属性的注入,也就是在这里,碰到循环依赖的时候,就会调用个 // org.springframework.beans.factory.support.DefaultSingletonBeanRegistry#getSingleton PropertyValues pvsToUse = ibp.postProcessProperties(pvs, bw.getWrappedInstance(), beanName); if (pvsToUse == null) { if (filteredPds == null) { filteredPds = filterPropertyDescriptorsForDependencyCheck(bw, mbd.allowCaching); } pvsToUse = ibp.postProcessPropertyValues(pvs, filteredPds, bw.getWrappedInstance(), beanName); if (pvsToUse == null) { return; } } pvs = pvsToUse; } } } if (needsDepCheck) { if (filteredPds == null) { filteredPds = filterPropertyDescriptorsForDependencyCheck(bw, mbd.allowCaching); } checkDependencies(beanName, mbd, filteredPds, pvs); } if (pvs != null) { applyPropertyValues(beanName, mbd, bw, pvs); }} org.springframework.beans.factory.support.DefaultSingletonBeanRegistry#getSingleton(java.lang.String, org.springframework.beans.factory.ObjectFactory<?>) 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) { Assert.notNull(beanName, "Bean name must not be null"); synchronized (this.singletonObjects) { Object singletonObject = this.singletonObjects.get(beanName); if (singletonObject == null) { if (this.singletonsCurrentlyInDestruction) { throw new BeanCreationNotAllowedException(beanName, "Singleton bean creation not allowed while singletons of this factory are in destruction " + "(Do not request a bean from a BeanFactory in a destroy method implementation!)"); } if (logger.isDebugEnabled()) { logger.debug("Creating shared instance of singleton bean '" + beanName + "'"); } // 重点,如果没有获取到,就设置个标识,表示正在创建 beforeSingletonCreation(beanName); boolean newSingleton = false; boolean recordSuppressedExceptions = (this.suppressedExceptions == null); if (recordSuppressedExceptions) { this.suppressedExceptions = new LinkedHashSet<>(); } try { singletonObject = singletonFactory.getObject(); newSingleton = true; } catch (IllegalStateException ex) { // Has the singleton object implicitly appeared in the meantime -> // if yes, proceed with it since the exception indicates that state. singletonObject = this.singletonObjects.get(beanName); if (singletonObject == null) { throw ex; } } catch (BeanCreationException ex) { if (recordSuppressedExceptions) { for (Exception suppressedException : this.suppressedExceptions) { ex.addRelatedCause(suppressedException); } } throw ex; } finally { if (recordSuppressedExceptions) { this.suppressedExceptions = null; } afterSingletonCreation(beanName); } if (newSingleton) { addSingleton(beanName, singletonObject); } } return singletonObject; }} org.springframework.beans.factory.support.DefaultSingletonBeanRegistry#beforeSingletonCreation 12345678910/** Names of beans that are currently in creation. */// 添加到这里来了之后就标识当前这个bean正在创建private final Set<String> singletonsCurrentlyInCreation = Collections.newSetFromMap(new ConcurrentHashMap<>(16));protected void beforeSingletonCreation(String beanName) { if (!this.inCreationCheckExclusions.contains(beanName) && !this.singletonsCurrentlyInCreation.add(beanName)) { throw new BeanCurrentlyInCreationException(beanName); }} org.springframework.beans.factory.support.DefaultSingletonBeanRegistry#addSingletonFactory 二级缓存 12345678910protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) { Assert.notNull(singletonFactory, "Singleton factory must not be null"); synchronized (this.singletonObjects) { if (!this.singletonObjects.containsKey(beanName)) { this.singletonFactories.put(beanName, singletonFactory); this.earlySingletonObjects.remove(beanName); this.registeredSingletons.add(beanName); } }} 三个缓存12345678// singletonObjects:第一级缓存,里面存放的都是创建好的成品Bean。private final Map<String, Object> singletonObjects = new ConcurrentHashMap<String, Object(256);// earlySingletonObjects : 第二级缓存,里面存放的都是半成品的Beanprivate final Map<String, Object> earlySingletonObjects = new HashMap<String, Object(16);// singletonFactories :第三级缓存, 不同于前两个存的是 Bean对象引用,此缓存存的bean 工厂对象,也就存的是 专门创建Bean的一个工厂对象。此缓存用于解决循环依赖private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<String, ObjectFactory<?>>(16); 两个缓存能解决不A引用创建后,提前暴露到半成品缓存中 依赖B,创建B ,B填充属性时发现依赖A, 先从成品缓存查找,没有,再从半成品缓存查找 取到A的早期引用。 1B顺利走完创建过程`, 将`B的早期引用从半成品缓存移动到成品缓存 B创建完成,A获取到B的引用,继续创建。 A创建完成,将A的早期引用从半成品缓存移动到成品缓存 为啥需要三个缓存上面两个缓存的地方,我们只是没有考虑代理的情况。 Bean在创建的最后阶段,会检查是否需要创建代理,如果创建了代理,那么最终返回的就是代理实例的引用。我们通过beanname获取到最终是代理实例的引用 也就是说:假设A最终会创建代理,提前暴露A的引用, B填充属性时填充的是A的原始对象引用。A最终放入成品库里是代理的引用。那么B中依然是A的早期引用。这种结果最终会与我们的期望的大相径庭了。 完整的流程关键点: A绑定到ObjectFactory 注册到工厂缓存singletonFactory中, B在填充A时,先查成品缓存有没有,再查半成品缓存有没有,最后看工厂缓存有没有单例工厂类,有A的ObjectFactory。调用getObject ,执行扩展逻辑,可能返回的代理引用,也可能返回原始引用。 成功获取到A的早期引用,将A放入到半成品缓存中,B填充A引用完毕。 代理问题, 循环依赖问题都解决了 Spring Bean 二次开发在实例化Bean之前,Spring会调用扩展的类,实现BeanFactoryPostProcessor,并且机上@component注解,如果没有实现,spring就不会调用 Spring AOPAOP是什么AOP的全称是Aspect Orient Programming,即面向切面编程。是对OOP(Object Orient Programming)的一种补充,战门用于处理一些具有横切性质的服务。常常用于日志输出、安全控制等。 上面说到是对OOP的一种补充,具体补充的是什么呢?考虑一种情况,如果我们需要在所有方法执行前打印一句日志,按照OOP的处理思想,我们需要在每个业务方法开始时加入一些语句,但是我们辛辛苦苦加完之后,如果又要求在这句日志打印后再打印一句,那是不是又要加一遍?这时候你一定会想到,在某个类中编写一个日志打印方法,该方法执行这些日志打印操作,然后在每个业务方法之前加入这句方法调用,这就是面向对象编程思想。但是如果要求我们在业务方法结束时再打印一些日志呢,是不是还要去每个业务方法结束时加一遍?这样始终不是办法,而且我们总是在改业务方法,在业务方法里面掺杂了太多的其他操作,侵入性太高。 这时候AOP就起到作用了,我们可以编写一个切面类(Aspect),在其中的方法中来编写横切逻辑(如打印日志),然后通过配置或者注解的方式来声明该横切逻辑起作用的位置。 实现技术AOP(这里的AOP指的是面向切面编程思想,而不是Spring AOP)主要的的实现技术主要有Spring AOP和AspectJ。 1、AspectJ的底层技术。 AspectJ的底层技术是静态代理,即用一种AspectJ支持的特定语言编写切面,通过一个命令来编译,生成一个新的代理类,该代理类增强了业务类,这是在编译时增强,相对于下面说的运行时增强,编译时增强的性能更好。 2、Spring AOP Spring AOP采用的是动态代理,在运行期间对业务方法进行增强,所以不会生成新类,对于动态代理技术,Spring AOP提供了对JDK动态代理的支持以及CGLib的支持。 JDK动态代理只能为接口创建动态代理实例,而不能对类创建动态代理。需要获得被目标类的接口信息(应用Java的反射技术),生成一个实现了代理接口的动态代理类(字节码),再通过反射机制获得动态代理类的构造函数,利用构造函数生成动态代理类的实例对象,在调用具体方法前调用invokeHandler方法来处理。 CGLib动态代理需要依赖asm包,把被代理对象类的class文件加载进来,修改其字节码生成子类。 但是Spring AOP基于注解配置的情况下,需要依赖于AspectJ包的标准注解,但是不需要额外的编译以及AspectJ的织入器,而基于XML配置不需要。 知识点PointCut你想要去切某个东西之前总得先知道要在哪里切入是吧,切点格式如下:execution(* com.nuofankj.springdemo.aop.*Service.*(..))格式使用了正常表达式来定义那个范围内的类、那些接口会被当成切点 Advice通知,所谓的Advice其实就是定义了Aop何时被调用,确实有种通知的感觉 Before 在方法被调用之前调用 After 在方法完成之后调用 After-returning 在方法成功执行之后调用 After-throwing 在方法抛出异常之后调用 Around 在被通知的方法调用之前和调用之后调用 JoinPointJoinPoint连接点,其实很好理解,上面又有通知、又有切点,那和具体业务的连接点又是什么呢?没错,其实就是对应业务的方法对象,因为我们在横切代码中是有可能需要用到具体方法中的具体数据的,而连接点便可以做到这一点。 Aspect就是我们关注点的模块化。这个关注点可能会横切多个对象和模块,事务管理是横切关注点的很好的例子。它是一个抽象的概念,从软件的角度来说是指在应用程序不同模块中的某一个领域或方面。又pointcut 和advice组成。 Weaving把切面应用到目标对象来创建新的 advised 对象的过程。 原理简单说说 AOP 的设计 每个 Bean 都会被 JDK 或者 Cglib 代理。取决于是否有接口。 每个 Bean 会有多个“方法拦截器”。注意:拦截器分为两层,外层由 Spring 内核控制流程,内层拦截器是用户设置,也就是 AOP。 当代理方法被调用时,先经过外层拦截器,外层拦截器根据方法的各种信息判断该方法应该执行哪些“内层拦截器”。内层拦截器的设计就是职责连的设计。 流程代理的创建(按步骤): 首先,需要创建代理工厂,代理工厂需要 3 个重要的信息:拦截器数组,目标对象接口数组,目标对象。 创建代理工厂时,默认会在拦截器数组尾部再增加一个默认拦截器 —— 用于最终的调用目标方法。 当调用 getProxy 方法的时候,会根据接口数量大余 0 条件返回一个代理对象(JDK or Cglib)。 注意:创建代理对象时,同时会创建一个外层拦截器,这个拦截器就是 Spring 内核的拦截器。用于控制整个 AOP 的流程。 代理的调用 当对代理对象进行调用时,就会触发外层拦截器。 外层拦截器根据代理配置信息,创建内层拦截器链。创建的过程中,会根据表达式判断当前拦截是否匹配这个拦截器。而这个拦截器链设计模式就是职责链模式。 当整个链条执行到最后时,就会触发创建代理时那个尾部的默认拦截器,从而调用目标方法。最后返回。 SpringMCC临时用的https://zhuanlan.zhihu.com/p/62562499 设置属性123456789// 1. 设置属性// Make web application context availablerequest.setAttribute(WEB_APPLICATION_CONTEXT_ATTRIBUTE, getWebApplicationContext());// Make locale resolver availablerequest.setAttribute(LOCALE_RESOLVER_ATTRIBUTE, this.localeResolver);// Make theme resolver availablerequest.setAttribute(THEME_RESOLVER_ATTRIBUTE, this.themeResolver); 根据 Request 请求的 URL 得到对应的 handler 执行链,其实就是拦截器和 Controller 代理对象12// 2. 找 handler 返回执行链HandlerExecutionChain mappedHandler = getHandler(request); 得到 handler 的适配器123// This will throw an exception if no adapter is found// 3. 返回 handler 的适配器HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler()); 循环执行 handler 的 pre 拦截器12345678// 4. 循环执行 handler 的 pre 拦截器for (int i = 0; i < mappedHandler.getInterceptors().length; i++) { HandlerInterceptor interceptor = mappedHandler.getInterceptors()[i]; // pre 拦截器 if (!interceptor.preHandle(request, response, mappedHandler.getHandler())) { return; }} 执行真正的 handler,并返回 ModelAndView(Handler 是个代理对象,可能会执行 AOP )12// 5. 执行真正的 handler,并返回 ModelAndView(Handler 是个代理对象,可能会执行 AOP )ModelAndView mv = ha.handle(request, response, mappedHandler.getHandler()); 循环执行 handler 的 post 拦截器123456789101112131415161718// 6. 循环执行 handler 的 post 拦截器for (int i = mappedHandler.getInterceptors().length - 1; i >=0 ; i--) { HandlerInterceptor interceptor = mappedHandler.getInterceptors()[i]; // post 拦截器 interceptor.postHandle(request, response, mappedHandler.getHandler());}# 根据 ModelAndView 信息得到 View 实例 View view = null;if (mv.isReference()) { // We need to resolve this view name // 7. 根据 ModelAndView 信息得到 View 实例 view = this.viewResolver.resolveViewName(mv.getViewName(), locale);}# 渲染 View 返回// 8. 渲染 View 返回view.render(mv.getModel(), request, response); 其实理解这些才是最重要的。 用户发送请求至前端控制器DispatcherServlet DispatcherServlet收到请求调用HandlerMapping处理器映射器。 处理器映射器根据请求url找到具体的处理器,生成处理器对象及处理器拦截器(如果有则生成)一并返回给DispatcherServlet。 DispatcherServlet通过HandlerAdapter处理器适配器调用处理器 HandlerAdapter执行处理器(handler,也叫后端控制器)。 Controller执行完成返回ModelAndView HandlerAdapter将handler执行结果ModelAndView返回给DispatcherServlet DispatcherServlet将ModelAndView传给ViewReslover视图解析器 ViewReslover解析后返回具体View对象 DispatcherServlet对View进行渲染视图(即将模型数据填充至视图中)。 DispatcherServlet响应用户 Springboot 启动流程https://juejin.im/post/6844903669998026759 通过 SpringFactoriesLoader 加载 META-INF/spring.factories 文件,获取并创建 SpringApplicationRunListener 对象 然后由 SpringApplicationRunListener 来发出 starting 消息 创建参数,并配置当前 SpringBoot 应用将要使用的 Environment 完成之后,依然由 SpringApplicationRunListener 来发出 environmentPrepared 消息 创建 ApplicationContext 初始化 ApplicationContext,并设置 Environment,加载相关配置等 由 SpringApplicationRunListener 来发出 contextPrepared 消息,告知SpringBoot 应用使用的 ApplicationContext 已准备OK 将各种 beans 装载入 ApplicationContext,继续由 SpringApplicationRunListener 来发出 contextLoaded 消息,告知 SpringBoot 应用使用的 ApplicationContext 已装填OK refresh ApplicationContext,完成IoC容器可用的最后一步 由 SpringApplicationRunListener 来发出 started 消息 完成最终的程序的启动 由 SpringApplicationRunListener 来发出 running 消息,告知程序已运行起来了 静态变量注入1234application.properties中配置下面两个配置项ccb.ip.address=10.25.177.31ccb.ip.port=1600下面问题代码中读取不到application.properties配置文件中的配置 123456789101112131415161718@Componentpublic class BISFrontFileUtil { private static Logger logger = LoggerFactory.getLogger(BISFrontFileUtil.class); private static String CCBIPADDRESS; private static int CCBIPPORT; @Value("${ccb.ip.address}") public void setCCBIPADDRESS(String cCBIPADDRESS) { CCBIPADDRESS = cCBIPADDRESS; } @Value("${ccb.ip.port}") public void setCCBIPPORT(int cCBIPPORT) { CCBIPPORT = cCBIPPORT; }} 注意: 修正代码中的@Component不可丢掉了 set方法要是非静态的 SpringBoot的注解 @Configuration @Configuration配置并启动Spring容器@Configuration标注在类上,相当于把该类作为spring的xml配置文件中的,作用为:配置spring容器(应用上下文) 123456789import org.springframework.context.annotation.Configuration;@Configurationpublic class TestConfig { public TestConfig(){ System.out.println("testconfig collection init success"); }} 相当于 1234567891011121314<?xml version="1.0" encoding="UTF-8"?><beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xmlns:jdbc="http://www.springframework.org/schema/jdbc" xmlns:jee="http://www.springframework.org/schema/jee" xmlns:tx="http://www.springframework.org/schema/tx" xmlns:util="http://www.springframework.org/schema/util" xmlns:task="http://www.springframework.org/schema/task" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd http://www.springframework.org/schema/jdbc http://www.springframework.org/schema/jdbc/spring-jdbc-4.0.xsd http://www.springframework.org/schema/jee http://www.springframework.org/schema/jee/spring-jee-4.0.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.0.xsd http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-4.0.xsd http://www.springframework.org/schema/task http://www.springframework.org/schema/task/spring-task-4.0.xsd" default-lazy-init="false"></beans> 主方法进行测试: 1234567891011121314151617181920import org.springframework.context.ApplicationContext;import org.springframework.context.annotation.AnnotationConfigApplicationContext;public class Main { public static void main(String[] args) { // @Configuration注解的spring容器加载方式,用AnnotationConfigApplicationContext替换ClassPathXmlApplicationContext ApplicationContext context = new AnnotationConfigApplicationContext(TestConfig.class); // 如果加载spring-context.xml文件: // ApplicationContext context = new // ClassPathXmlApplicationContext("spring-context.xml"); }}// 结果WARNING: All illegal access operations will be denied in a future releasetestconfig collection init successProcess finished with exit code 0 @Configuration启动容器+@Bean注册Bean@Bean标注在方法上(返回某个实例的方法),等价于spring的xml配置文件中的,作用为:注册bean对象 1234567891011121314151617181920212223242526272829303132333435@Configurationpublic class TestConfig { public TestConfig(){ System.out.println("testconfig collection init success"); } // @Bean注解注册bean,同时可以指定初始化和销毁方法 // @Bean(name="testBean",initMethod="start",destroyMethod="cleanup") //name属性相当于<bean>标签的id @Bean @Scope("prototype") public TestBean testBean() { return new TestBean(); }}class TestBean { private String username; private String url; private String password; public void sayHello() { System.out.println("TestBean sayHello..."); } public void start() { System.out.println("TestBean init..."); } public void cleanup() { System.out.println("TestBean destroy..."); }} 测试类 123456789101112131415161718192021public class Main { public static void main(String[] args) { // @Configuration注解的spring容器加载方式,用AnnotationConfigApplicationContext替换ClassPathXmlApplicationContext ApplicationContext context = new AnnotationConfigApplicationContext(TestConfig.class); System.out.println(context); // 如果加载spring-context.xml文件: // ApplicationContext context = new // ClassPathXmlApplicationContext("spring-context.xml"); //获取bean TestBean testBean = (TestBean) context.getBean("testBean"); testBean.sayHello(); }}// 结果结果:testconfig collection init successTestBean sayHello... @Bean注解在返回实例的方法上,如果未通过@Bean指定bean的名称,则默认与标注的方法名相同(第一个单词转小写) @Bean注解默认作用域为单例singleton作用域,可通过@Scope(“prototype”)设置为原型作用域 既然@Bean的作用是注册bean对象,那么完全可以使用@Component、@Controller、@Service、@Ripository等注解注册bean,当然需要配置@ComponentScan注解进行自动扫描 scope属性1). singleton属性值(掌握):默认值,单例2). prototype属性值(掌握):多例(原型作用域)3). request属性值(了解):创建对象,把对象放到request域里4). session属性值(了解):创建对象,把对象放到session域里5). globalSession属性值(了解):创建对象,把对象放到globalSession域里 @Bean下管理bean的生命周期1234567// 用上面的例子//@Bean注解注册bean,同时可以指定初始化和销毁方法@Bean(name="testBean",initMethod="start",destroyMethod="cleanUp")@Scope("prototype")public TestBean testBean() { return new TestBean();} 测试类 12345// 结果testconfig collection init successorg.springframework.context.annotation.AnnotationConfigApplicationContext@41975e01, started on Mon Jul 19 09:51:42 PST 2021TestBean init...TestBean sayHello... @Configuration启动容器+@Component注册Beanbean类 1234567891011121314151617181920//添加注册bean的注解@Componentpublic class TestBean { private String username; private String url; private String password; public void sayHello() { System.out.println("TestBean sayHello..."); } public void start() { System.out.println("TestBean init..."); } public void cleanup() { System.out.println("TestBean destroy..."); }} 配置类: 1234567891011121314151617@Configuration//添加自动扫描注解,basePackages为TestBean包路径@ComponentScan(basePackages = "com.example.demo.spring2")public class TestConfig { public TestConfig(){ System.out.println("testconfig collection init success"); } // @Bean注解注册bean,同时可以指定初始化和销毁方法// @Bean(name="testBean",initMethod="start",destroyMethod="cleanup")//// @Bean// @Scope("prototype")// public TestBean testBean() {// return new TestBean();// }} 测试类: 123456789101112131415161718public class Main { public static void main(String[] args) { // @Configuration注解的spring容器加载方式,用AnnotationConfigApplicationContext替换ClassPathXmlApplicationContext ApplicationContext context = new AnnotationConfigApplicationContext(TestConfig.class); // 如果加载spring-context.xml文件: // ApplicationContext context = new ClassPathXmlApplicationContext("spring-context.xml"); //获取bean TestBean testBean1 = (TestBean) context.getBean("testBean"); testBean1.sayHello(); }}// 结果testconfig collection init successTestBean sayHello... AnnotationConfigApplicationContext 注册 AppContext 类的两种方法第一种: 123456789public static void main(String[] args) { // @Configuration注解的spring容器加载方式,用AnnotationConfigApplicationContext替换ClassPathXmlApplicationContext ApplicationContext context = new AnnotationConfigApplicationContext(TestConfig.class); //获取bean TestBean tb = (TestBean) context.getBean("testBean"); tb.sayHello();} 第二种: 1234567public class Main { public static void main(String[] args) { AnnotationConfigApplicationContext annotationConfigApplicationContext = new AnnotationConfigApplicationContext(); annotationConfigApplicationContext.register(TestConfig.class); annotationConfigApplicationContext.refresh(); }} @Configuration组合xml配置类 12345678@Configuration@ImportResource("classpath:configtest.xml")public class WebConfig { public WebConfig(){ System.out.println("WebConfig coolection init success"); }} 实体类 123456789101112131415161718192021222324252627282930public class TestBean2 { private String username; private String url; private String password; public void setUsername(String username) { this.username = username; } public void setUrl(String url) { this.url = url; } public void setPassword(String password) { this.password = password; } public void sayHello() { System.out.println("TestBean2 sayHello..."+username); } public void start() { System.out.println("TestBean2 init..."); } public void cleanUp() { System.out.println("TestBean2 destroy..."); }} spring的xml配置文件 123456789<?xml version="1.0" encoding="UTF-8"?><beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <bean id="testBean2" class="com.example.demo.spring3.TestBean2"> <property name="username" value="ranjun"/> </bean></beans> 测试类 123456789101112131415161718public class TestMain2 { public static void main(String[] args) { // @Configuration注解的spring容器加载方式,用AnnotationConfigApplicationContext替换ClassPathXmlApplicationContext ApplicationContext context = new AnnotationConfigApplicationContext(WebConfig.class); // 如果加载spring-context.xml文件: // ApplicationContext context = new ClassPathXmlApplicationContext("spring-context.xml"); // 获取bean TestBean2 tb = (TestBean2) context.getBean("testBean2"); tb.sayHello(); }}// 结果WebConfig coolection init successTestBean2 sayHello...ranjun @Configuration组合xml和其它注解实体类: 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849public class TestBean { private String username; private String url; private String password; public void sayHello() { System.out.println("TestBean sayHello..."); } public void start() { System.out.println("TestBean init..."); } public void cleanup() { System.out.println("TestBean destroy..."); }}public class TestBean2 { private String username; private String url; private String password; public void setUsername(String username) { this.username = username; } public void setUrl(String url) { this.url = url; } public void setPassword(String password) { this.password = password; } public void sayHello() { System.out.println("TestBean2 sayHello..."+username); } public void start() { System.out.println("TestBean2 init..."); } public void cleanUp() { System.out.println("TestBean2 destroy..."); }} 配置类 1234567891011121314151617181920212223@Configurationpublic class TestConfig { public TestConfig(){ System.out.println("testconfig collection init success"); } @Bean @Scope("prototype") public TestBean testBean() { return new TestBean(); }}@Configuration@ImportResource("classpath:configtest.xml")@Import(TestConfig.class)public class WebConfig { public WebConfig(){ System.out.println("WebConfig coolection init success"); }} 测试类: 1234567891011121314151617181920public class TestMain2 { public static void main(String[] args) { // @Configuration注解的spring容器加载方式,用AnnotationConfigApplicationContext替换ClassPathXmlApplicationContext ApplicationContext context = new AnnotationConfigApplicationContext(WebConfig.class); // 获取bean TestBean tb = (TestBean) context.getBean("testBean"); tb.sayHello(); TestBean2 tb2 = (TestBean2) context.getBean("testBean2"); tb2.sayHello(); }}// 结果WebConfig coolection init successtestconfig collection init successTestBean sayHello...TestBean2 sayHello...ranjun
评论