清朝云资源网

作者: 清朝云

  • Java 中的各种类锁简单了解


    共计 4111 个字符,预计需要花费 11 分钟才能阅读完成。

    提到并发编程,就不得不了解锁,因为使用锁的类型不一样,结果也就不一样。

    1. 公平锁/非公平锁

    • 公平锁:多个线程按照申请锁的顺序来获取锁。
    • 非公平锁:不按照顺序获取锁,有可能后申请锁的线程先获取到锁。非公平锁有可能造成优先级反转或线程饥饿现象。

    对于 ReentrantLock 而言,线程在放入等待队列阻塞之前会多次尝试获取锁,如下图所示:

    Java 中的各种类锁简单了解

    />

    public ReentrantLock() {
        sync = new NonfairSync();
    }
    
    public ReentrantLock(boolean fair) {
        sync = fair ? new FairSync() : new NonfairSync();
    }

    ReentrantLock 默认使用的是非公平锁,需要指定有参构造才会使用公平锁。

    2. 可重入锁/不可重入锁

    • 可重入锁:可重复可递归调用的锁,在外层使用锁之后,在内层仍然可以使用,并且不发生死锁。(前提得是同一个对象或者class)
    • 不可重入锁:不可递归调用,递归调用就发生死锁。

    例如 synchronized 为可重入锁,所以调用 testA() 方法不会出现死锁的情况:

    public synchronized void testA(){
        System.out.println("A");
        testB();
    }
    
    public synchronized void testB(){
        System.out.println("B");
    }

    而不可重入锁容易造成死锁,如下案例:

    class UnreentrantLock {
        private AtomicReference<Thread> owner = new AtomicReference<>();
    
        public void lock() {
            Thread current = Thread.currentThread();
    
            // 自旋锁:不断尝试获得锁
            for (;;) {
                // 如果锁当前没有被任何线程持有 (owner为null),就将当前线程设置为持有者
                if (owner.compareAndSet(null, current)) {
                    return;
                }
            }
        }
    
        public void unlock() {
            Thread current = Thread.currentThread();
    
            // 释放锁:确保只有持有锁的线程才能释放锁
            owner.compareAndSet(current, null);
        }
    }
    
    class TestSynchronized {
    
        UnreentrantLock unreentrantLock = new UnreentrantLock();
    
        public void testA(){
            unreentrantLock.lock();
            System.out.println("A");
            testB();
            unreentrantLock.unlock();
        }
    
        public void testB(){
            unreentrantLock.lock();
            System.out.println("B");
            unreentrantLock.unlock();
        }
    }

    可以修改为可重入:

    public class ReentrantLock {
        private AtomicReference<Thread> owner = new AtomicReference<>();
        private int state = 0;  // 记录锁的重入次数
    
        // 获取锁
        public void lock() {
            Thread current = Thread.currentThread();
    
            // 如果当前线程已经持有锁,则增加锁的重入计数器
            if (current == owner.get()) {
                state++;
                return;
            }
    
            // 自旋锁,直到当前线程成功获得锁
            for (;;) {
                if (owner.compareAndSet(null, current)) {
                    state = 1;  // 成功获取锁时,初始化重入计数
                    return;
                }
            }
        }
    
        // 释放锁
        public void unlock() {
            Thread current = Thread.currentThread();
    
            // 确保只有持有锁的线程才能解锁
            if (current == owner.get()) {
                if (state > 1) {
                    // 如果锁重入次数大于1,说明还可以继续递归调用,不释放锁
                    state--;
                } else {
                    // 否则,将锁释放,设置状态为0
                    owner.compareAndSet(current, null);
                    state = 0;
                }
            }
        }
    }

    3. 独享锁/共享锁

    • 独享锁:该锁每一次只能被一个线程所持有。
    • 共享锁:该锁可被多个线程共有,典型的就是 ReentrantReadWriteLock 里的读锁,它的读锁是可以被共享的,但是它的写锁确每次只能被独占。读锁的共享可保证并发读是非常高效的,但是读写和写写,写读都是互斥的。

    独享锁与共享锁也是通过 AQS 来实现的,通过实现不同的方法,来实现独享或者共享。对于synchronized 而言,当然是独享锁。

    4. 乐观锁/悲观锁

    • 悲观锁:总是假设最坏的情况,每次访问共享资源都加锁,synchronizedReentrantLock 属于悲观锁。
    • 乐观锁:假设数据不会被修改,只有在更新数据时检查是否有其他线程修改过数据。java.util.concurrent.atomic 包下面的原子变量类就是使用了乐观锁的一种实现方式,使用 CAS 实现的。

    5. 互斥锁

    • 互斥锁:在访问共享资源之前对进行加锁操作,在访问完成之后进行解锁操作。加锁后,任何其他试图再次加锁的线程会被阻塞,直到当前进程解锁。

    其实,独享锁在许多情况下就是互斥锁的一种表现形式,但独享这一说法强调了锁的独占性质。

    6. 读写锁

    读写锁既是互斥锁,又是共享锁,读模式是共享,写模式是互斥(排它锁)的。

    读写锁有三种状态:读加锁状态、写加锁状态和不加锁状态。

    只有一个线程可以占有写状态的锁,但可以有多个线程同时占有读状态锁,这也是它可以实现高并发的原因。当其处于写状态锁下,任何想要尝试获得锁的线程都会被阻塞,直到写状态锁被释放;如果是处于读状态锁下,允许其它线程获得它的读状态锁,但是不允许获得它的写状态锁,直到所有线程的读状态锁被释放;为了避免想要尝试写操作的线程一直得不到写状态锁,当读写锁感知到有线程想要获得写状态锁时,便会阻塞其后所有想要获得读状态锁的线程。所以读写锁非常适合资源的读操作远多于写操作的情况。

    7. 分段锁

    分段锁其实是一种锁的设计,并不是具体的一种锁,对于ConcurrentHashMap而言,其并发的实现就是通过分段锁的形式来实现高效的并发操作。

    容器里有多把锁,每一把锁用于锁容器其中一部分数据,那么当多线程访问容器里不同数据段的数据时,线程间就不会存在锁竞争,从而可以有效的提高并发访问效率,这就是ConcurrentHashMap所使用的锁分段技术,首先将数据分成一段一段的存储,然后给每一段数据配一把锁,当一个线程占用锁访问其中一个段数据的时候,其他段的数据也能被其他线程访问。

    比如:在ConcurrentHashMap中使用了一个包含16个锁的数组,每个锁保护所有散列桶的1/16,其中第N个散列桶由第(N mod 16)个锁来保护。假设使用合理的散列算法使关键字能够均匀的分部,那么这大约能使对锁的请求减少到越来的1/16。也正是这项技术使得ConcurrentHashMap支持多达16个并发的写入线程。

    8. 偏向锁/轻量级锁/重量级锁

    • 偏向锁:指一段同步代码一直被一个线程所访问,那么该线程会自动获取锁。降低获取锁的代价。
    • 轻量级锁:当锁是偏向锁的时候,被另一个线程所访问,偏向锁就会升级为轻量级锁,其他线程会通过自旋的形式尝试获取锁,不会阻塞,提高性能。
    • 重量级锁:当锁为轻量级锁的时候,另一个线程虽然是自旋,但自旋不会一直持续下去,当自旋一定次数的时候,还没有获取到锁,就会进入阻塞,该锁膨胀为重量级锁。重量级锁会让其他申请的线程进入阻塞,性能降低。

    这四种状态都不是Java语言中的锁,而是 Jvm 为了提高锁的获取与释放效率而做的优化(使用synchronized时)。

    锁的状态:

    1.无锁状态

    2.偏向锁状态

    3.轻量级锁状态

    4.重量级锁状态

    锁的状态是通过对象监视器在对象头中的字段来表明的。四种状态会随着竞争的情况逐渐升级,而且是不可逆的过程,即不可降级。

    9. 自旋锁

    • 自旋锁(spinlock):是指当一个线程在获取锁的时候,如果锁已经被其它线程获取,那么该线程将循环等待,然后不断的判断锁是否能够被成功获取,直到获取到锁才会退出循环。

    优点:

    1. 自旋锁不会使线程状态发生切换,一直处于用户态,即线程一直都是active的;不会使线程进入阻塞状态,减少了不必要的上下文切换,执行速度快
    2. 非自旋锁在获取不到锁的时候会进入阻塞状态,从而进入内核态,当获取到锁的时候需要从内核态恢复,需要线程上下文切换。(线程被阻塞后便进入内核调度状态,这个会导致系统在用户态与内核态之间来回切换,严重影响锁的性能)

    缺点:

    1. 如果某个线程持有锁的时间过长,就会导致其它等待获取锁的线程进入循环等待,消耗CPU。使用不当会造成CPU使用率极高。
    2. 自旋锁不是公平的,即无法满足等待时间最长的线程优先获取锁。不公平的锁就会存在线程饥饿问题。

    其实,自旋锁与互斥锁比较类似,它们都是为了解决对某项资源的互斥使用。无论是互斥锁,还是自旋锁,在任何时刻,最多只能有一个保持者,也就说,在任何时刻最多只能有一个执行单元获得锁。但是两者在调度机制上略有不同。对于互斥锁,如果资源已经被占用,资源申请者只能进入睡眠状态。但是自旋锁不会引起调用者睡眠,如果自旋锁已经被别的执行单元保持,调用者就一直循环在那里看是否该自旋锁的保持者已经释放了锁,”自旋”一词就是因此而得名。

    自旋锁通过 CAS 的方式保证线程安全,CAS 是英文单词 Compare and Swap(比较并交换),是一种有名的无锁算法。无锁编程,即不使用锁的情况下实现多线程之间的变量同步,也就是在没有线程被阻塞的情况下实现变量的同步,所以也叫非阻塞同步(Non-blocking Synchronization)。

    参考文章:常见的Java锁总结:公平锁,独享锁,互斥锁,乐观锁,分段锁,偏向锁,自旋锁等等

    Tips:清朝云网络工作室

  • 12月20日,星期五, 每天60秒读懂全世界!

    百度热搜新闻

    新闻来源:百度热搜榜

    1. 美代表突然嘴瓢 耿爽“蚌埠住了” 当地时间12月18日,在联合国公开会议上,美国代表发言时却朝韩不分,称中国“是否一直在保护韩国”。耿爽直接“蚌埠住了”。

    2. 南方人最近不要天天洗澡 近日,气象领域科普博主发文称南方人最近不要天天洗澡。因为南方中东部几乎都没有降水,空气非常干燥,洗澡会加速破坏皮肤油脂层。

    3. 续写濠江华彩新篇章 回归祖国的25年,是澳门历史上发展最快、质量最高、居民获得感幸福感安全感最强的时期。具有澳门特色的“一国两制”实践必将谱写出新的华彩篇章。

    4. 妈妈:全红婵现在是国家的女儿了 19日,有媒体发布了一段全红婵家人的采访视频。全红婵妈妈透露,自己遭遇病痛不敢告诉女儿,她现在是国家的女儿了,听国家安排。

    5. 《唐探1900》定档大年初一 由陈思诚执导的电影《唐探1900》定档大年初一,引发网友广泛关注。

    6. 女子送出的结婚礼金被朋友退回 12月19日,网友称给出去的结婚礼金被朋友退还了。朋友说:“鉴于我领证已久且你目前没有结婚计划,特此返还结婚礼金。”

    7. “憨厚版”黑悟空冰雕火了 近日,黑龙江一“黑悟空”冰雕因自带八块腹肌和超大苹果肌而爆火出圈,被网友们称为“憨厚版”黑悟空。

    8. 微信能送礼物了 12月19日,据媒体消息,微信小店测试“送礼物”功能,除了珠宝首饰、教育培训类目外,其余商品将默认支持“送礼物”功能。

    9. 明年消费市场总体态势如何 在12月19日的新闻发布会上,商务部发言人表示,将推出多方面务实举措,持续释放消费潜力,预计明年消费市场总体将继续呈现平稳增长态势。

    10. 75岁官员被公诉 其市长儿子已离世 12月19日,据最高人民检察院消息,日前,陈世贵涉嫌受贿罪一案,已移送检察机关审查起诉。其子保山市长陈锐已于日前离世。

    —- 百度热搜新闻 End —-

    知乎新闻

    新闻来源:知乎日榜

    标题: 如何欣赏蒙德里安的画?
    链接: https://daily.zhihu.com/story/9777576
    ———————-
    标题: 为什么迫击炮这个问世快 100 年的武器一直没有被淘汰,甚至基本结构都没怎么改进过,真的这么好用吗?
    链接: https://daily.zhihu.com/story/9777740
    ———————-
    标题: 物质是如何从无生命到有生命的?
    链接: https://daily.zhihu.com/story/9777747
    ———————-
    标题: 瞎扯 · 如何正确地吐槽
    链接: https://daily.zhihu.com/story/9777470
    ———————-

    —- 知乎新闻 End —-

    IT之家新闻

    新闻来源:ITHome之家科技新闻

    标题: 本田严厉警告日产:若与鸿海合作,那么本田与日产的合作将终止
    发布时间: 2024-12-19T08:29:29.563
    新闻简介: 一位本田高管严厉警告日产:“如果日产与富士康合作,我们将取消我们的合作关系。”本田同样担心,其增长战略的核心 —— 这一伙伴关系,会被富士康的举动破坏。
    ———————-
    标题: 微信功能更新接连不断,腾讯张军称诸如“史诗级”“奇袭”之类词汇实在担不起
    发布时间: 2024-12-19T17:59:01.837
    新闻简介: 腾讯公关总监张军今日在朋友圈发文称,“最近我们确实推出来一些新功能,产品也在不断迭代,但是恳请大家别替我们定太高的调门,诸如‘史诗级’‘奇袭’之类的词汇,实在担不起,安心做好产品是要务。”
    ———————-
    标题: 电影《唐探 1900》定档海报发布,2025 年大年初一上映
    发布时间: 2024-12-19T19:45:54.24
    新闻简介: 导演、演员陈思诚今日发布电影《唐探 1900》海报,影片定档 2025 年 1 月 29 日(大年初一)。从电影《唐探 1900》的定档海报可以看到,影片由陈思诚、戴墨担任导演,王宝强、刘昊然主演。
    ———————-
    标题: 百万粉丝博主恶意差评,被判删帖道歉赔款手机厂商 5 万元
    发布时间: 2024-12-19T09:23:36.08
    新闻简介: 近期,深圳市南山区人民法院发布一起名誉权纠纷案,百万粉丝博主恶意差评,被判删帖道歉赔款手机厂商 5 万元。
    ———————-
    标题: 极氪 007 猎装版谍照曝光,空气悬架气罐现身车尾
    发布时间: 2024-12-19T10:55:17.7
    新闻简介: 昨天下午,博主“高转青年-画车魏sir”公布了极氪007猎装版的路试谍照。该车仍覆盖了伪装贴纸,车身姿态较为圆润修长,但车尾部件裸露在外。
    ———————-
    标题: 微信灰度测试“送礼物”功能 :商品限价 1 万元,不支持珠宝及教培小店
    发布时间: 2024-12-19T10:28:53.497
    新闻简介: 用户轻触“送给朋友”拉起“确认礼物”页面,选择款式、浏览金额、选择朋友并完成支付后,礼物即自动送出。收货地址由接收方填写,在收下礼物前,接收方还可以选择更换该商品下价格相同的其他款式。
    ———————-
    标题: 消息称极越员工获“N+1”赔偿,垫付资金由百度、吉利打入
    发布时间: 2024-12-19T15:29:51.747
    新闻简介: 其中,N 为司龄起算日至 2024 年 12 月 20 日的服务期限折算,“1”为上个月的基础月薪和津贴(餐补及话补)。截止到离职日尚未享有的年假及调休予以折算。(21 财经)
    ———————-
    标题: 华为余承东谈鸿蒙智行合作:一开始别人都不看好我们,只有赛力斯一拍即合
    发布时间: 2024-12-19T12:52:58.94
    新闻简介: 在今日午间的鸿蒙智行年度直播中,华为常务董事、终端 BG 董事长、智能汽车解决方案 BU 董事长余承东谈及与赛力斯合作的起源。
    ———————-
    标题: 鸿蒙智行“四界”老总首度同框合影,华为余承东称很多车厂想合作但没那么多资源
    发布时间: 2024-12-19T16:04:44.117
    新闻简介: 在今日的鸿蒙智行年度直播中,鸿蒙智行“四界”老总首度合体亮相,与华为常务董事、终端 BG 董事长、智能汽车解决方案 BU 董事长余承东一起圆桌会谈。
    ———————-
    标题: 鸿蒙智行智界 R7 增程版正式上市:24.98 万-28.98 万元,明年 1 月 5 日交付
    发布时间: 2024-12-19T13:55:16.347
    新闻简介: 鸿蒙智行智界R7今日正式上市,新车提供Pro及MAX两款配置(IT之家注:前者无激光雷达,后者有),售价分别为24.98万元和28.98万元,将于明年1月5日开启交付。
    ———————-
    标题: 华为余承东:问界 M9 累计大定超 19 万台,销量超 50 万元以上车型第 2-20 名总和
    发布时间: 2024-12-19T12:55:57.567
    新闻简介: 余承东表示,问界M9创造了50万元以上中国豪华车的纪录,销量已经超过了第2名到第20名销量的总和,销量接近2万台一个月。
    ———————-
    标题: 北汽董事长张建勇展示中国第一张私人新能源汽车号牌:京 N・253M0
    发布时间: 2024-12-19T13:41:14.81
    新闻简介: 在今日午间的直播活动中,北汽董事长张建勇带来了2014年取得的中国第一张私人新能源汽车牌照:蓝牌,牌号为京N253M0。
    ———————-

    —- IT之家新闻 End —-

  • Docker 搭建在线练习英语软件


    共计 302 个字符,预计需要花费 1 分钟才能阅读完成。

    最近时常想找点事干,感觉如果可以在线背英语单词是个不错的选择,于是找到了 qwerty-learner 这个开源项目

    于是拉下来自己构建了一个镜像,这样随时都可以练习英语和背单词,使用 compose 文件搭建如下:

    services:
      el:
        image: hausen1012/qwerty-learner:20241204
        container_name: el
        ports:
            - 5173:5173
        restart: always

    界面非常美观,看着非常舒适。

    Docker 搭建在线练习英语软件

    />

    不但可以选择英语还可以选择其他如日语、德语等,并且英语也有很多词库可以选择。

    Docker 搭建在线练习英语软件

    />

    还可以针对练习做一些配置。

    Docker 搭建在线练习英语软件

    />

    当练习完章节还可以进行默写,加深记忆。

    Docker 搭建在线练习英语软件

    />

    还支持导入导出数据,非常方便。

    Docker 搭建在线练习英语软件

    />

    Tips:清朝云网络工作室

  • 源码学习 Spring 容器初始化流程


    共计 15146 个字符,预计需要花费 38 分钟才能阅读完成。

    0. 简介

    本文以 ClassPathXmlApplicationContext 源码学习 Spring 初始化流程,那么首先要了解它实现了哪些接口,可以直观地了解到它具备了哪些能力。

    源码学习 Spring 容器初始化流程

    />

    最重要的一点是它实现了 BeanFactory 接口,BeanFactory 其实就是常说的 ioc 容器,因为它实现了该接口,所以 ClassPathXmlApplicationContext 也是 ioc 容器。重点来了ClassPathXmlApplicationContext 虽然实现了接口,但是它重写 BeanFactory 的方法都是委托给了成员变量 beanFactory 去实现。下面的代码都是其基类实现的。

    // AbstractApplicationContext 实现
    @Override
    public <T> T getBean(Class<T> requiredType) throws BeansException {
        assertBeanFactoryActive();
        return getBeanFactory().getBean(requiredType);
    }
    
    // AbstractRefreshableApplicationContext 实现
    @Override
    public final ConfigurableListableBeanFactory getBeanFactory() {
        DefaultListableBeanFactory beanFactory = this.beanFactory;
        if (beanFactory == null) {
            throw new IllegalStateException("BeanFactory not initialized or already closed - " +
                    "call 'refresh' before accessing beans via the ApplicationContext");
        }
        return beanFactory;
    }

    ClassPathXmlApplicationContext 不但实现了 BeanFactory 接口,并且实现了其他的接口为容器扩展了功能如:消息国际化资源解析器事件发布获取环境变量等功能。

    构造函数时创建 ioc 容器的入口,代码如下:

    public ClassPathXmlApplicationContext(
            String[] configLocations, boolean refresh, @Nullable ApplicationContext parent)
            throws BeansException {
    
        super(parent);
        setConfigLocations(configLocations);
        if (refresh) {
            refresh();
        }
    }

    执行构造方法,每个 ApplicationContext 都有自己的实现,但是都会初始化容器状态标识 activeclosed

    ClassPathXmlApplicationContext 在执行构造方法时,并不会初始化 beanFactory。而 AnnotationConfigApplicationContext 在初始化时并创建好 beanFactory,并将 internalConfigurationAnnotationProcessorinternalAutowiredAnnotationProcessor 等几个后 bean 后处理器注入,用于解析 @Configuration@Autowired等注解。如果全部以 xml 的形式给容器注入 bean,那么是不需要这几个后处理器的,但是如果需要配置文件的方式也能使用注解,就需要配置 <context:annotation-config/>

    不管怎么说,都会执行 refresh() 方法用于刷新容器。其中 AbstractApplicationContextrefresh() 方法,代码如下:

    @Override
    public void refresh() throws BeansException, IllegalStateException {
        synchronized (this.startupShutdownMonitor) {
            // 执行刷新容器前的一些必要方法
            prepareRefresh();
    
            // 调用子类获取到 bean 工厂
            ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
    
            // 配置 bean 工厂,以增强功能
            prepareBeanFactory(beanFactory);
    
            try {
                // 修改和调整 Bean 的定义(默认没有实现)
                postProcessBeanFactory(beanFactory);
    
                // 调用后置处理器
                invokeBeanFactoryPostProcessors(beanFactory);
    
                // 注册 BeanPostProcessor,用于修改和增强 bean 的功能
                registerBeanPostProcessors(beanFactory);
    
                // 为上下文初始化 Message 源,即对不同语言的消息体进行国际化处理
                initMessageSource();
    
                // 初始化容器事件传播器
                initApplicationEventMulticaster();
    
                // 调用子类的某些特殊 Bean 初始化方法
                onRefresh();
    
                // 为事件传播器注册事件监听器(这里用到了观察者模式)
                registerListeners();
    
                // 初始化 Bean,并对 lazy-init 属性进行处理
                finishBeanFactoryInitialization(beanFactory);
    
                // Last step: publish corresponding event.
                finishRefresh();
            }
    
            catch (BeansException ex) {
                if (logger.isWarnEnabled()) {
                    logger.warn("Exception encountered during context initialization - " +
                            "cancelling refresh attempt: " + ex);
                }
    
                // 销毁创建的单态 Bean
                destroyBeans();
    
                // 取消 refresh 操作,重置容器的同步标识.
                cancelRefresh(ex);
    
                // Propagate exception to caller.
                throw ex;
            }
    
            finally {
                // Reset common introspection caches in Spring's core, since we
                // might not ever need metadata for singleton beans anymore...
                resetCommonCaches();
            }
        }
    }

    1. prepareRefresh

    protected void prepareRefresh() {
    
        // 设置容器的启动时间,以便在刷新容器时进行时间跟踪
        this.startupDate = System.currentTimeMillis();
        // 容器在关闭时会设置该值为true,此时容器不能再进行一些初始化操作。
        this.closed.set(false);
        // 将容器的活动状态设置为true,表示容器当前处于活动状态。
        this.active.set(true);
    
        if (logger.isDebugEnabled()) {
            if (logger.isTraceEnabled()) {
                logger.trace("Refreshing " + this);
            }
            else {
                logger.debug("Refreshing " + getDisplayName());
            }
        }
    
        // 初始化容器中可能使用的占位符属性源,例如 ${some.property},确保这些占位符被正确解析和设置。(这里的属性包含 JVM 属性 systemProperties,和操作系统的环境变量 systemEnvironment)
        // 这是个空方法,是留给子类实现的。
        initPropertySources();
    
        // 验证所需要的属性都存在,否则抛出异常
        getEnvironment().validateRequiredProperties();
    
        // 设置监听器
        if (this.earlyApplicationListeners == null) {
            this.earlyApplicationListeners = new LinkedHashSet<>(this.applicationListeners);
        }
        else {
            // Reset local application listeners to pre-refresh state.
            this.applicationListeners.clear();
            this.applicationListeners.addAll(this.earlyApplicationListeners);
        }
    
        this.earlyApplicationEvents = new LinkedHashSet<>();
    }

    通过继承 ClassPathXmlApplicationContext 可以重写 initPropertySources() 方法,启动时校验必要参数。

    public class MyInitApplicationContext extends ClassPathXmlApplicationContext {
    
        public MyInitApplicationContext(String configLocation) throws BeansException {
            super(configLocation);
        }
    
        @Override
        protected void initPropertySources() {
            super.initPropertySources();
            System.out.println("test");
            getEnvironment().setRequiredProperties("aaa");
        }
    }

    2. obtainFreshBeanFactory

    protected ConfigurableListableBeanFactory obtainFreshBeanFactory() {
    
        // 自己定义了抽象的 refreshBeanFactory() 方法,具体实现交给了自己的子类
        refreshBeanFactory();
        return getBeanFactory();
    }
    
    protected abstract void refreshBeanFactory() throws BeansException, IllegalStateException;

    obtainFreshBeanFactory 方法用于创建 bean 工厂,调用 refreshBeanFactory 方法为一个抽象方法,需要子类自行实现。这里有一个好处,就是比如使用 xml 文件和注解方式,因为他们获取 bean 信息方式不同,可以通过自己重写该方法创建工厂。

    refreshBeanFactory() 方法交由子类 AbstractRefreshableApplicationContext 实现:

    @Override
    protected final void refreshBeanFactory() throws BeansException {
        // 如果已经建立了 IoC 容器,则销毁并关闭容器
        if (hasBeanFactory()) {
            destroyBeans();
            closeBeanFactory();
        }
        try {
            // 创建 beanFactory 容器,DefaultListableBeanFactory 类实现了 ConfigurableListableBeanFactory 接口
            DefaultListableBeanFactory beanFactory = createBeanFactory();
            beanFactory.setSerializationId(getId());
            // 添加一些自定义操作
            customizeBeanFactory(beanFactory);
            // 载入 BeanDefinition,在当前类中只定义了抽象的 loadBeanDefinitions() 方法,具体实现 调用子类容器
            loadBeanDefinitions(beanFactory);
            this.beanFactory = beanFactory;
        }
        catch (IOException ex) {
            throw new ApplicationContextException("I/O error parsing bean definition source for " + getDisplayName(), ex);
        }
    }

    loadBeanDefinitions() 方法交由子类 AbstractXmlApplicationContext 实现:

    @Override
    protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException {
        // 创建用于从 Xml 中读取 BeanDefinition 的读取器
        XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);
    
        // 配置 BeanDefinition 的读取器
        beanDefinitionReader.setEnvironment(this.getEnvironment());
        // 为 beanDefinition 读取器设置 资源加载器,由于本类的基类 AbstractApplicationContext
        // 继承了 DefaultResourceLoader,因此,本容器自身也是一个资源加载器
        beanDefinitionReader.setResourceLoader(this);
        // 设置 SAX 解析器,SAX(simple API for XML)是另一种 XML 解析方法。相比于 DOM,SAX 速度更快,占用内存更小。
        // 它逐行扫描文档,一边扫描一边解析。相比于先将整个 XML 文件扫描进内存,再进行解析的 DOM,SAX 可以在解析文档的
        // 任意时刻停止解析,但操作也比 DOM 复杂。
        beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this));
    
        // 初始化
        initBeanDefinitionReader(beanDefinitionReader);
        // Bean 读取器真正实现加载的方法
        loadBeanDefinitions(beanDefinitionReader);
    }

    读取 BeanDefinition 的真正方法:

    /**
     * ClassPathXmlApplicationContext 与 FileSystemXmlApplicationContext 在这里的调用出现分歧,
     * 各自按不同的方式加载解析 Resource 资源,最后在具体的解析和 BeanDefinition 定位上又会殊途同归。
     */
    protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws BeansException, IOException {
    
        Resource[] configResources = getConfigResources();
        if (configResources != null) {
            reader.loadBeanDefinitions(configResources);
        }
        String[] configLocations = getConfigLocations();
        if (configLocations != null) {
            reader.loadBeanDefinitions(configLocations);
        }
    }

    3. prepareBeanFactory

    protected void prepareBeanFactory(ConfigurableListableBeanFactory beanFactory) {
        // 设置类加载器和属性、资源解析器
        beanFactory.setBeanClassLoader(getClassLoader());
        beanFactory.setBeanExpressionResolver(new StandardBeanExpressionResolver(beanFactory.getBeanClassLoader()));
        beanFactory.addPropertyEditorRegistrar(new ResourceEditorRegistrar(this, getEnvironment()));
    
        // 注册 AwareProcessor,用来给实现了 xxxAware 接口的类扩展功能
        beanFactory.addBeanPostProcessor(new ApplicationContextAwareProcessor(this));
    
        // 是忽略指定接口类型的属性的自动注入,而通过其他方式实现注入(通过上面的 ApplicationContextAwareProcessor 进行属性注入)
        beanFactory.ignoreDependencyInterface(EnvironmentAware.class);
        beanFactory.ignoreDependencyInterface(EmbeddedValueResolverAware.class);
        beanFactory.ignoreDependencyInterface(ResourceLoaderAware.class);
        beanFactory.ignoreDependencyInterface(ApplicationEventPublisherAware.class);
        beanFactory.ignoreDependencyInterface(MessageSourceAware.class);
        beanFactory.ignoreDependencyInterface(ApplicationContextAware.class);
    
        // 用于动态注册某些类型的依赖,使其可以被 Spring 容器自动注入
        // 第一个参数为:希望注入的类型,第二个参数为当遇到这种希望注入的类型时,时间注入该对象
        beanFactory.registerResolvableDependency(BeanFactory.class, beanFactory);
        beanFactory.registerResolvableDependency(ResourceLoader.class, this);
        beanFactory.registerResolvableDependency(ApplicationEventPublisher.class, this);
        beanFactory.registerResolvableDependency(ApplicationContext.class, this);
    
        // 注册检测和解析实现 ApplicationListener 接口的后处理器
        beanFactory.addBeanPostProcessor(new ApplicationListenerDetector(this));
    
        // 检测是否包含特定 bean,有则加入对应的后处理器
        if (beanFactory.containsBean(LOAD_TIME_WEAVER_BEAN_NAME)) {
            beanFactory.addBeanPostProcessor(new LoadTimeWeaverAwareProcessor(beanFactory));
            // Set a temporary ClassLoader for type matching.
            beanFactory.setTempClassLoader(new ContextTypeMatchClassLoader(beanFactory.getBeanClassLoader()));
        }
    
        // 注册默认环境bean。environment、systemProperties、systemEnvironment
        if (!beanFactory.containsLocalBean(ENVIRONMENT_BEAN_NAME)) {
            beanFactory.registerSingleton(ENVIRONMENT_BEAN_NAME, getEnvironment());
        }
        if (!beanFactory.containsLocalBean(SYSTEM_PROPERTIES_BEAN_NAME)) {
            beanFactory.registerSingleton(SYSTEM_PROPERTIES_BEAN_NAME, getEnvironment().getSystemProperties());
        }
        if (!beanFactory.containsLocalBean(SYSTEM_ENVIRONMENT_BEAN_NAME)) {
            beanFactory.registerSingleton(SYSTEM_ENVIRONMENT_BEAN_NAME, getEnvironment().getSystemEnvironment());
        }
    }

    对 BeanFactory 进行各种功能填充。其实如果自己直接 new 一个 BeanFactory,再添加上某些组件,也能够实现一个简单的容器。

    这里主要是设置了一些类加载器和属性、资源解析器,还注册了一些后处理器用户处理实现特定接口的类,还注册了几个环境单例 bean。

    4. postProcessBeanFactory

    protected void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {
    }

    postProcessBeanFactory 方法主要用于在 Spring 容器实例化 bean 之前,修改和调整 bean 的定义。默认没有实现,子类可以重写。(ClassPathXmlApplicationContext 的基类 AbstractApplicationContext 对该方法并没有实现)

    该方法与下面的 invokeBeanFactoryPostProcessors 方法效果一样,都是可以直接操作 beanFactory,可以新增、修改 bean 的定义。

    5. invokeBeanFactoryPostProcessors

    protected void invokeBeanFactoryPostProcessors(ConfigurableListableBeanFactory beanFactory) {
        PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(beanFactory, getBeanFactoryPostProcessors());
    
        // Detect a LoadTimeWeaver and prepare for weaving, if found in the meantime
        // (e.g. through an @Bean method registered by ConfigurationClassPostProcessor)
        if (beanFactory.getTempClassLoader() == null && beanFactory.containsBean(LOAD_TIME_WEAVER_BEAN_NAME)) {
            beanFactory.addBeanPostProcessor(new LoadTimeWeaverAwareProcessor(beanFactory));
            beanFactory.setTempClassLoader(new ContextTypeMatchClassLoader(beanFactory.getBeanClassLoader()));
        }
    }

    调用各种 BeanFactoryPostProcessor,以实现对 bean 定义的新增和修改等,包括 spring 本身的和自己实现的 BeanFactoryPostProcessor 接口的类,通过委托 PostProcessorRegistrationDelegate 实现。

    6. registerBeanPostProcessors

    protected void registerBeanPostProcessors(ConfigurableListableBeanFactory beanFactory) {
        PostProcessorRegistrationDelegate.registerBeanPostProcessors(beanFactory, this);
    }

    注册 BeanPostProcessor,同样通过委托 PostProcessorRegistrationDelegate 实现。(注意这里是注册,而不是执行)

    特性 BeanFactoryPostProcessor BeanPostProcessor
    执行时机 在 Bean 定义加载后、Bean 实例化之前。 在 Bean 实例化后、初始化方法之前或之后。
    作用范围 主要用来修改或定制 Bean 定义(如修改属性、构造函数参数等)。 主要用来修改或增强 Bean 实例的行为。
    执行对象 操作的是 BeanFactoryBeanDefinition,改变 Bean 的元数据。 操作的是已经实例化的 Bean 对象。
    修改内容 可以修改 Bean 的定义,如属性、依赖等。 可以修改实例化后的 Bean,增加自定义逻辑或增强功能。
    常见用途 修改 Bean 配置、条件注册 Bean、动态改变 Bean 定义。 增强 Bean 功能、AOP、日志记录、延迟初始化等。

    7. initMessageSource

    protected void initMessageSource() {
        ConfigurableListableBeanFactory beanFactory = getBeanFactory();
        if (beanFactory.containsLocalBean(MESSAGE_SOURCE_BEAN_NAME)) {
            this.messageSource = beanFactory.getBean(MESSAGE_SOURCE_BEAN_NAME, MessageSource.class);
            // Make MessageSource aware of parent MessageSource.
            if (this.parent != null && this.messageSource instanceof HierarchicalMessageSource) {
                HierarchicalMessageSource hms = (HierarchicalMessageSource) this.messageSource;
                if (hms.getParentMessageSource() == null) {
                    // Only set parent context as parent MessageSource if no parent MessageSource
                    // registered already.
                    hms.setParentMessageSource(getInternalParentMessageSource());
                }
            }
            if (logger.isTraceEnabled()) {
                logger.trace("Using MessageSource [" + this.messageSource + "]");
            }
        }
        else {
            // Use empty MessageSource to be able to accept getMessage calls.
            DelegatingMessageSource dms = new DelegatingMessageSource();
            dms.setParentMessageSource(getInternalParentMessageSource());
            this.messageSource = dms;
            beanFactory.registerSingleton(MESSAGE_SOURCE_BEAN_NAME, this.messageSource);
            if (logger.isTraceEnabled()) {
                logger.trace("No '" + MESSAGE_SOURCE_BEAN_NAME + "' bean, using [" + this.messageSource + "]");
            }
        }
    }

    8. initApplicationEventMulticaster

    protected void initApplicationEventMulticaster() {
        ConfigurableListableBeanFactory beanFactory = getBeanFactory();
        if (beanFactory.containsLocalBean(APPLICATION_EVENT_MULTICASTER_BEAN_NAME)) {
            this.applicationEventMulticaster =
                    beanFactory.getBean(APPLICATION_EVENT_MULTICASTER_BEAN_NAME, ApplicationEventMulticaster.class);
            if (logger.isTraceEnabled()) {
                logger.trace("Using ApplicationEventMulticaster [" + this.applicationEventMulticaster + "]");
            }
        }
        else {
            this.applicationEventMulticaster = new SimpleApplicationEventMulticaster(beanFactory);
            beanFactory.registerSingleton(APPLICATION_EVENT_MULTICASTER_BEAN_NAME, this.applicationEventMulticaster);
            if (logger.isTraceEnabled()) {
                logger.trace("No '" + APPLICATION_EVENT_MULTICASTER_BEAN_NAME + "' bean, using " +
                        "[" + this.applicationEventMulticaster.getClass().getSimpleName() + "]");
            }
        }
    }

    事件广播器负责在应用程序中传递事件,确保事件可以从发布者传播到所有的监听者。这个方法的功能是确保容器中有一个有效的 ApplicationEventMulticaster,并根据容器中是否已经存在相应的 bean 来进行初始化。

    9. onRefresh

    protected void onRefresh() throws BeansException {
        // For subclasses: do nothing by default.
    }

    空方法,用于留给子类进行扩展。

    10. registerListeners

    protected void registerListeners() {
        // Register statically specified listeners first.
        for (ApplicationListener<?> listener : getApplicationListeners()) {
            getApplicationEventMulticaster().addApplicationListener(listener);
        }
    
        // Do not initialize FactoryBeans here: We need to leave all regular beans
        // uninitialized to let post-processors apply to them!
        String[] listenerBeanNames = getBeanNamesForType(ApplicationListener.class, true, false);
        for (String listenerBeanName : listenerBeanNames) {
            getApplicationEventMulticaster().addApplicationListenerBean(listenerBeanName);
        }
    
        // Publish early application events now that we finally have a multicaster...
        Set<ApplicationEvent> earlyEventsToProcess = this.earlyApplicationEvents;
        this.earlyApplicationEvents = null;
        if (!CollectionUtils.isEmpty(earlyEventsToProcess)) {
            for (ApplicationEvent earlyEvent : earlyEventsToProcess) {
                getApplicationEventMulticaster().multicastEvent(earlyEvent);
            }
        }
    }

    所有注册的 bean 中查找监听器,注册到应用事件广播器中。

    11. finishBeanFactoryInitialization

    protected void finishBeanFactoryInitialization(ConfigurableListableBeanFactory beanFactory) {
        // 如果有的话,初始化默认转换服务。
        if (beanFactory.containsBean(CONVERSION_SERVICE_BEAN_NAME) &&
                beanFactory.isTypeMatch(CONVERSION_SERVICE_BEAN_NAME, ConversionService.class)) {
            beanFactory.setConversionService(
                    beanFactory.getBean(CONVERSION_SERVICE_BEAN_NAME, ConversionService.class));
        }
    
        // 注册嵌入式值解析器,以支持占位符(placeholders)解析的功能。例如:${property.name}
        if (!beanFactory.hasEmbeddedValueResolver()) {
            beanFactory.addEmbeddedValueResolver(strVal -> getEnvironment().resolvePlaceholders(strVal));
        }
    
        // 初始化 LoadTimeWeaverAware 实例
        String[] weaverAwareNames = beanFactory.getBeanNamesForType(LoadTimeWeaverAware.class, false, false);
        for (String weaverAwareName : weaverAwareNames) {
            getBean(weaverAwareName);
        }
    
        // 停止使用临时类加载器,并将其清除。
        beanFactory.setTempClassLoader(null);
    
        // 锁定 beanFactory 配置。一旦进入这个阶段,Spring 假设所有的 Bean 定义已经准备就绪,锁定配置,以提升性能。
        // 1. 不再允许新增或修改 Bean 定义 2. 可以更高效地缓存元数据,例如 Bean 的依赖关系或类型信息。
        beanFactory.freezeConfiguration();
    
        // 实例化所有非延迟加载的 bean
        beanFactory.preInstantiateSingletons();
    }

    初始化剩下的非懒加载实例,程序员自己编写的大部分 bean 都是在这里进行初始化的。在这里调用了 getBean 方法,创建了非惰性的 bean 实例。getBean 方法其中会调用 createBean 方法,毕竟 getBean 所需要获取的 bean

    这里具体就涉及到了 bean 的生命周期,比较复杂,可以当作另外一个探究的点。

    12. finishRefresh

    protected void finishRefresh() {
        // 清楚资源缓存
        clearResourceCaches();
    
        // 初始化生命周期处理器
        initLifecycleProcessor();
    
        // 触发生命周期处理器的刷新事件
        getLifecycleProcessor().onRefresh();
    
        // 发布上下文刷新事件
        publishEvent(new ContextRefreshedEvent(this));
    
        // 注册到 LiveBeansView
        // LiveBeansView 是 Spring 提供的工具类,用于调试和监控应用中定义的 bean。
        LiveBeansView.registerApplicationContext(this);
    }

    Tips:清朝云网络工作室

  • 亚马逊无服务器计算 lambda 的初体验


    共计 4477 个字符,预计需要花费 12 分钟才能阅读完成。

    1. 简介

    Amazon Lambda亚马逊云科技提供的一种无服务器计算服务,它的特点如下:

    • 无服务器(Serverless)

      • 开发者无需管理底层服务器。Amazon Lambda 会自动为代码提供执行环境,包括计算资源、内存、存储等。
      • 计费基于实际执行时间和资源消耗,避免了闲置资源浪费。
    • 事件驱动
      代码的执行通常由某些事件触发。常见触发器包括:

      • 文件上传到 Amazon S3。
      • 数据库更新(如 DynamoDB)。
      • API Gateway 请求。
      • 定时任务(通过 Amazon EventBridge)。
      • 消息队列(如 Amazon SQS 或 Kafka)。
      • 函数即服务(Function as a Service, FaaS)

    此外,对于 Amazon Lambda 提供了非常高的永久免费额度,关于其他更多免费产品可以点击这里

    亚马逊无服务器计算 lambda 的初体验

    />

    2. 账号注册

    如果还没有注册需要先注册,当然注册也非常简单,但是需要一张信用卡。首先点击注册账户,与其他的注册方式不同,这里需要先输入邮箱和用户名。

    亚马逊无服务器计算 lambda 的初体验

    />

    点击验证电子邮箱会收到验证码,输入进行验证即可。

    亚马逊无服务器计算 lambda 的初体验

    />

    然后当然就是要创建密码了,这里需要输入复杂一点的密码,否则不能通过验证。

    亚马逊无服务器计算 lambda 的初体验

    />

    接下来需要填写联系人信息,这里按自己情况进行填写即可。需要注意的是 州/省/自治区/直辖市或地区 这里必须使用英文,所以就索性全部用英文了。

    亚马逊无服务器计算 lambda 的初体验

    />

    然后就是关键了,需要填写信用卡信息,因为申请亚马逊云科技必须持有一张信用,比如万事达、运通卡、VISA卡、银联信用卡支付方式

    亚马逊无服务器计算 lambda 的初体验

    />

    然后就是验证手机号码了,这个也简单,收个验证码就行。

    亚马逊无服务器计算 lambda 的初体验

    />

    然后就到了最后一步,选择支持计划,这里选择免费的基本支持就好了。

    亚马逊无服务器计算 lambda 的初体验

    />

    3. 简单使用

    登陆后就可以进入 lambda 控制台,然后点击创建函数。

    亚马逊无服务器计算 lambda 的初体验

    />

    这里有多种运行时环境可以选择,比如 Python、Node,甚至还支持 Java 环境。

    亚马逊无服务器计算 lambda 的初体验

    />

    这里选择 Python,因为待会用到的案例也是 Python 环境。填写好函数名称,点击创建即可。

    亚马逊无服务器计算 lambda 的初体验

    />

    创建后的页面如下。

    亚马逊无服务器计算 lambda 的初体验

    />

    提示我们已经创建好了函数,可以在该页面修改代码和进行测试。

    亚马逊无服务器计算 lambda 的初体验

    />

    这里的代码就是重头戏了,因为这里是处理事件关键代码。入口函数一般都命名为 lambda_handler,可以看见案例里面是直接返回一个 Hello from Lambda! 字符串。

    那么如何看写的代码是否有问题呢?总不能每次都发布再看结果有没有问题吧。这个也简单,只需要点击 Test 创建一个测试事件。

    亚马逊无服务器计算 lambda 的初体验

    />

    然后定义测试事件名称创建。

    亚马逊无服务器计算 lambda 的初体验

    />

    点击运行后可以看见返回没问题。

    亚马逊无服务器计算 lambda 的初体验

    />

    4. 自定义函数

    上面的官方案例仅仅只能作为学习,我们基本用不到,所以我们需要自定义函数内容来满足我们自己的需求。

    比如说我想定义一个函数,当触发这个函数时可以给特定邮箱发送邮件。这里只是一个简单的案例,还可以有其他的更多玩法,就像 cf 的 workers 那样,也是可以将 workers 的内容改造成 lambda 的格式的。

    import json
    import smtplib
    from email.mime.text import MIMEText
    from email.mime.multipart import MIMEMultipart
    from email.header import Header
    import logging
    import os
    
    # 设置日志
    logger = logging.getLogger()
    logger.setLevel(logging.INFO)
    
    def sendmail():
        # 从环境变量或 Secrets Manager 获取凭证
        from_addr = os.getenv('FROM_ADDR', 'xxx@qq.com')  # 使用环境变量存储邮箱地址
        password = os.getenv('EMAIL_PASSWORD', 'xxx')  # 使用环境变量存储邮箱密码
        to_addr = "xxx@qq.com"
        smtp_server = "smtp.qq.com"
        smtp_port = 465
    
        html_msg = """
            <h1>测试内容</h1>
        """
    
        # 创建邮件对象
        msg = MIMEMultipart()
        msg['From'] = from_addr  # 发件人
        msg['To'] = to_addr  # 收件人
        subject = '来自 Amazon lambda'
        msg['Subject'] = Header(subject, 'utf-8')  # 邮件主题
        msg.attach(MIMEText(html_msg, 'html', 'utf-8'))  # 邮件正文内容
    
        try:
            smtpobj = smtplib.SMTP_SSL(smtp_server, smtp_port)
            smtpobj.login(from_addr, password)  # 登录邮箱
            smtpobj.sendmail(from_addr, to_addr, msg.as_string())  # 发送邮件
            logger.info("邮件发送成功")
        except smtplib.SMTPException as e:
            logger.error("发送邮件时出错: %s", str(e))
        finally:
            smtpobj.quit()  # 退出 SMTP 会话
    
    def lambda_handler(event, context):
        try:
            sendmail()
            return {
                'statusCode': 200,
                'body': json.dumps('邮件发送成功')
            }
        except Exception as e:
            return {
                'statusCode': 500,
                'body': json.dumps(f"邮件发送失败: {str(e)}")
            }

    只需要将上面的内容替换掉之前 lambda_function.py 的内容,并填写好 smtp 相关账户密码。

    然后这里可能还需要在配置里面配置一下超时事件,比如将超时改为 10 秒。

    亚马逊无服务器计算 lambda 的初体验

    />

    再创建一个函数 url,目的是为了方便通过访问 url 触发。

    亚马逊无服务器计算 lambda 的初体验

    />

    这里为了简单选择不需要授权。

    亚马逊无服务器计算 lambda 的初体验

    />

    此时也可以通过测试事件查看是否能够发送邮件,由于上面讲过了这里就不提了。这里多了一种触发的方式,就是访问刚刚创建的 url。另外,可以看见下图还能添加其他很多类型的触发器。

    亚马逊无服务器计算 lambda 的初体验

    />

    效果如下:

    亚马逊无服务器计算 lambda 的初体验

    />

    5. 使用容器映像

    有些时候像之前那样创建,编写的函数代码需要的环境并不能满足需求。此时一个比较好的方式是使用容器,基于它提供的容器打造成自己需要的容器环境,再将函数代码放入容器内。构建好镜像以后,将镜像上传到 容器注册表 ECR,然后在创建函数时选择该镜像。

    5.1 创建仓库

    既然需要用到容器注册表,就需要先创建一个仓库,点击创建存储库,输入仓库名,点击创建。

    亚马逊无服务器计算 lambda 的初体验

    />

    创建好以后点击右上角可以查看推送命令。

    亚马逊无服务器计算 lambda 的初体验

    />

    5.2 安装命令行工具

    参考文档,安装命令如下:

    # 1. 下载
    curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "awscliv2.zip"
    
    # 2. 解压
    unzip awscliv2.zip
    
    # 3. 安装
    sudo ./aws/install

    查看版本:

    aws --version

    5.3 配置

    由于命令行需要配置凭据才能进行相关操作,所以先需要创建访问密钥,访问 安全凭证,点击创建访问密钥。

    亚马逊无服务器计算 lambda 的初体验

    />

    如果是根用户会提示建议使用 IAM 角色创建,但这里为了方便还是勾选根用户继续创建,如果有需要可以创建对应角色。

    亚马逊无服务器计算 lambda 的初体验

    />

    创建好密钥后记得保存,因为以后不能查看了。

    亚马逊无服务器计算 lambda 的初体验

    />

    创建好以后就可以在服务器创建 ~/.aws/credentials 文件,并填写好对应的内容。

    [default]
    aws_access_key_id=xxx
    aws_secret_access_key=xxx

    5.4 构建镜像并上传

    上面的准备工作做完以后,接下来就是创建镜像了。首先创建一个 app.py,内容还是和上面一样。

    import json
    import smtplib
    from email.mime.text import MIMEText
    from email.mime.multipart import MIMEMultipart
    from email.header import Header
    import logging
    import os
    
    # 设置日志
    logger = logging.getLogger()
    logger.setLevel(logging.INFO)
    
    def sendmail():
        # 从环境变量或 Secrets Manager 获取凭证
        from_addr = os.getenv('FROM_ADDR', 'xxx@qq.com')  # 使用环境变量存储邮箱地址
        password = os.getenv('EMAIL_PASSWORD', 'xxx')  # 使用环境变量存储邮箱密码
        to_addr = "xxx@qq.com"
        smtp_server = "smtp.qq.com"
        smtp_port = 465
    
        html_msg = """
            <h1>测试内容</h1>
        """
    
        # 创建邮件对象
        msg = MIMEMultipart()
        msg['From'] = from_addr  # 发件人
        msg['To'] = to_addr  # 收件人
        subject = '来自 Amazon lambda'
        msg['Subject'] = Header(subject, 'utf-8')  # 邮件主题
        msg.attach(MIMEText(html_msg, 'html', 'utf-8'))  # 邮件正文内容
    
        try:
            smtpobj = smtplib.SMTP_SSL(smtp_server, smtp_port)
            smtpobj.login(from_addr, password)  # 登录邮箱
            smtpobj.sendmail(from_addr, to_addr, msg.as_string())  # 发送邮件
            logger.info("邮件发送成功")
        except smtplib.SMTPException as e:
            logger.error("发送邮件时出错: %s", str(e))
        finally:
            smtpobj.quit()  # 退出 SMTP 会话
    
    def lambda_handler(event, context):
        try:
            sendmail()
            return {
                'statusCode': 200,
                'body': json.dumps('邮件发送成功')
            }
        except Exception as e:
            return {
                'statusCode': 500,
                'body': json.dumps(f"邮件发送失败: {str(e)}")
            }

    然后编写 dockerfile:

    FROM public.ecr.aws/lambda/python:3.8
    
    # Copy function code
    COPY app.py ${LAMBDA_TASK_ROOT}
    
    # Set the CMD to your handler (could also be done as a parameter override outside of the Dockerfile)
    CMD [ "app.lambda_handler" ]

    接下来推送命令就派上用场了,按提示操作即可。

    亚马逊无服务器计算 lambda 的初体验

    />

    5.5 创建函数

    相比较于之前的创建函数,这里需要选择容器映像,不出意外可以看见刚刚上传的镜像。

    亚马逊无服务器计算 lambda 的初体验

    />

    选择好以后点击创建。

    亚马逊无服务器计算 lambda 的初体验

    />

    由于是容器,不能直接编辑代码,但是可以在界面上进行测试,效果也是一样的。

    亚马逊无服务器计算 lambda 的初体验

    />

    Tips:清朝云网络工作室

  • 12月19日,星期四, 每天60秒读懂全世界!

    百度热搜新闻

    新闻来源:百度热搜榜

    1. 莫言坦言:为百万奖金决定领奖 日前,莫言获得2024“爱心奖”。当得知此奖可得百万元奖金时,便坦言“一定要去拿这个奖,来救治60名先天性心脏病儿童”。

    2. 俄罗斯高官专家接连被暗杀 到明年2月24日,俄乌冲突将满三年。近一周以来,俄罗斯军事将领、武器专家先后遇刺,寒冬中的俄乌局势再添变数。

    3. 我在澳门“养”国宝 2015年,大熊猫“开开”和“心心”来到澳门安家。次年,双胞胎宝宝“健健”和“康康”出生,四只大熊猫成为澳门一道靓丽的风景线。

    4. 费翔片场等4小时只为告别马东锡 近日,一则关于费翔在片场等待4小时只为与马东锡告别的消息引发关注。这一事件发生在电影《窗前明月,咣!》的拍摄现场。

    5. 微信朋友圈崩了 12月18日,不少网友反映微信朋友圈崩了,对此腾讯客服表示:尝试更换网络环境、清理手机缓存、更新微信版本、重启手机后重试。

    6. 印度女网红用面包做连衣裙惹众怒

    7. 男子帮母亲虐待饿死弟弟判百年 近日,美国一男子因协助母亲虐待并饿死自闭症弟弟,被判处最高100年刑期,母亲被判终身监禁。

    8. 年轻人买谷子4个月花费数十万 近日,二次元“吃”出“谷子经济”,引发热议,更有年轻人买谷子4个月花费数十万。

    9. 11月份国民经济运行5大亮点 据国家统计局最新数据,11月份,在宏观政策组合效应持续释放的作用下,我国经济运行延续回升态势,呈现出“生产升、需求增、就业稳、市场暖、质量优”五大亮点。

    10. 吴奇隆爸爸刘诗诗助理一起带娃

    —- 百度热搜新闻 End —-

    知乎新闻

    新闻来源:知乎日榜

    标题: 宇宙中生命的存在必须有水吗?
    链接: https://daily.zhihu.com/story/9777567
    ———————-
    标题: 是不是四川三星堆没有继续挖了?为什么?
    链接: https://daily.zhihu.com/story/9777727
    ———————-
    标题: 有哪些你想发明却已经被人发明了的东西?
    链接: https://daily.zhihu.com/story/9777732
    ———————-
    标题: 瞎扯 · 如何正确地吐槽
    链接: https://daily.zhihu.com/story/9777468
    ———————-

    —- 知乎新闻 End —-

    IT之家新闻

    新闻来源:ITHome之家科技新闻

    标题: 苹果 iPhone 17 Pro Max 渲染图再曝光:水平三摄、铝合金背板
    发布时间: 2024-12-18T07:57:42.927
    新闻简介: YouTube 频道 Wylsacom 在最新一期视频中,基于此前曝光的供应链物料设计和中框配件,并集合现有的曝料信息,渲染制作了 iPhone 17 Pro Max 的模型渲染图。
    ———————-
    标题: 饿了么:骑手休息措施已在全国上线
    发布时间: 2024-12-18T08:48:24.983
    新闻简介: 饿了么已于今年 8 月在全国上线骑手休息措施,如果骑手连续跑单时长较长,会触发建议休息的弹窗提醒;如果连续跑单时长过长,会有小休要求,相关举措正持续完善中。
    ———————-
    标题: 极越员工曝赔偿方案:吉利已确认,但百度还未审批
    发布时间: 2024-12-18T17:50:29.75
    新闻简介: 据悉,除了可能少有的不能断缴社保或者已经找到工作选择离开,大部分员工仍处于在职状态。
    ———————-
    标题: 消息称苹果 iPhone 17 Pro Max 手机三摄布局依旧是三角,并非横向长条
    发布时间: 2024-12-18T18:07:01.66
    新闻简介: 博主 @刹那数码 今天下午称,其消息源透露,(iPhone 17 Pro Max)背面外观确实有变化,但三摄布局依旧是三角,并不是现在网传的横向长条。
    ———————-
    标题: 三星 Galaxy S25 Ultra 手机颜值再提升:挑战最窄边框,4 种配色方案曝光
    发布时间: 2024-12-18T09:50:33.35
    新闻简介: 消息源 @i冰宇宙 昨日(12 月 17 日)在 X 平台发布推文,曝料称三星 Galaxy S25 Ultra 手机采用超窄边框设计,黑边比小米 15 更窄一圈。
    ———————-
    标题: 国产神话大片《封神第二部:战火西岐》首支预告公布,2025 大年初一上映
    发布时间: 2024-12-18T10:35:50.2
    新闻简介: 《封神第二部:战火西岐》今日公布了首支“大战将至”版预告,预告随着“闻太师凯旋回朝”开场,西岐殷商两军列阵对峙,各出奇招。
    ———————-
    标题: 极越车主遇售后难题,吉利杨学良称可到领克中心处理
    发布时间: 2024-12-18T14:03:33.983
    新闻简介: 昨晚浙江吉利控股集团高级副总裁杨学良(微博账号 @杨学良Victor )通过微博回复了一位需要售后支持的极越车主,他称车主前往附近的领克中心进行车辆评估,表示如果问题仅限于钣金修复,领克中心可以直接处理;若需要更换零部件,则可能需要一定时间,吉利将加速推进相关工作以尽快解决问题。
    ———————-
    标题: 任天堂 Switch 2 游戏掌机渲染图曝光:8 英寸大屏、12GB 内存、磁吸 Joy-Con
    发布时间: 2024-12-18T16:17:16.827
    新闻简介: 消息源 @deckwizardy 于 12 月 16 日在 X 平台发布系列推文,分享了任天堂 Switch 2 游戏掌机的渲染图,从曝光的图片来看,新版底座采用更大的弧形设计,并增加了连接选项。
    ———————-
    标题: 20:00 限量 4777 元:华为 Pura 70 Pro 12+512G 版新低(已结束)
    发布时间: 2024-12-18T19:46:34.597
    新闻简介: 华为・中国移动京东自营(非京东华为自营)华为 Pura 70 Pro 12+512G 版今日 20:00 直降至 5097 元,支持使用京东超级 18 惊喜红包 + 20 元补贴券。
    ———————-
    标题: 小米:Home Assistant 米家集成将不断优化和升级,严格限制仅可用于非商业用途
    发布时间: 2024-12-18T17:32:11.117
    新闻简介: 小米技术公众号今日发文称,HA 米家官方集成将不断优化和升级,为用户带来更加便捷、智能的家居生活体验。
    ———————-
    标题: 华为鸿蒙 HarmonyOS NEXT 系统支持“超远扫码”,自动变焦最大距离 18 米
    发布时间: 2024-12-18T17:38:37.853
    新闻简介: 据华为官方介绍,用户在面对停车场缴费等场景时,无需下车,可下拉菜单开启远距离扫码功能(点开扫码后,双击屏幕进行变焦)。
    ———————-
    标题: 美“星际客机”滞留太空宇航员返程再推迟,至少明年 3 月后才能返回地球
    发布时间: 2024-12-18T11:17:09.51
    新闻简介: 这两名宇航员在6月乘坐“星际飞船”前往空间站,原计划的任务时长为8到10天,但由于飞船出现了多个问题,NASA出于谨慎考虑决定将他们暂时留在空间站,并于9月将飞船空着送回地球。
    ———————-

    —- IT之家新闻 End —-

  • SpringBean 初始化的执行各方法的顺序


    共计 2085 个字符,预计需要花费 6 分钟才能阅读完成。

    Spring 容器在创建 SpringBean 的时候,会帮我们自动给属性赋值,还有一些初始化方法和一些增强的方法。

    因此,了解这些方法的顺序很有必要,这样可以了解方法增强的时机,有助于更好地编写代码。

    通过如下案例可以看见执行的先后顺序:

    MyBean.java

    @Data
    public class MyBean implements InitializingBean {
    
        private String value;
    
        public MyBean() {
            System.out.println("构造方法...");
        }
    
        @Autowired
        public void setValue(@Value("${JAVA_HOME}") String value){
            this.value = value;
            System.out.println("属性赋值...");
        }
    
        public void initMethod(){
            System.out.println("initMethod...");
        }
    
        public void destroyMethod(){
            System.out.println("destroyMethod...");
        }
    
        @Override
        public void afterPropertiesSet() throws Exception {
            System.out.println("afterPropertiesSet...");
        }
    
        @PostConstruct
        public void postConstruct() {
            System.out.println("PostConstruct...");
        }
    
        @PreDestroy
        public void preDestroy() {
            System.out.println("PreDestroy...");
        }
    }

    后处理器 MyBeanPostProcessor.java

    public class MyBeanPostProcessor implements BeanPostProcessor {
    
        @Override
        public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
            System.out.println("BeanPostProcessor的before...");
            return bean;
        }
    
        @Override
        public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
            System.out.println("BeanPostProcessor的after...");
            return bean;
        }
    }

    配置类 Config.java

    @Configuration
    public class Config {
    
        // 这里如果配置 @ComponentScan 就无法得到 initMethod 的执行顺序
        @Bean(initMethod = "initMethod", destroyMethod = "destroyMethod")
        public MyBean myBean(){
            return new MyBean();
        }
    
        @Bean
        public MyBeanPostProcessor myBeanPostProcessor(){
            return new MyBeanPostProcessor();
        }
    }

    测试类 BeanInitTest.java

    public class BeanInitTest {
    
        public static void main(String[] args) {
    
            AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(Config.class);
            System.out.println("#=======================================#");
            applicationContext.close();
        }
    }

    执行结果如下:

    构造方法...
    属性赋值...
    BeanPostProcessor的before...
    PostConstruct...
    afterPropertiesSet...
    initMethod...
    BeanPostProcessor的after...
    #=======================================#
    PreDestroy...
    destroyMethod...

    由此可以得到结论,创建时最开始执行构造函数,再执行属性赋值方法,然后执行 bean 后处理器的 before 方法。再是执行 @PostConstruct 标注的方法,然后执行 InitializingBean 接口的 afterPropertiesSet 方法,再执行 @Bean 指定的初始化方法,再执行 bean 后处理器的 after 方法。

    而执行销毁时,先执行 @PreDestroy 标注的方法,再执行 @Bean 指定的销毁方法。

    Tips:清朝云网络工作室

  • 脚本批量导出 docker 镜像


    共计 633 个字符,预计需要花费 2 分钟才能阅读完成。

    由于经常需要导出多个 docker 镜像,所以想着用脚本来解决一次导出很多镜像的问题。

    按自己需要将镜像导出为 tar 包,命名方式为 镜像名-标签名.tar。如果不传参数则默认使用 docker images | grep service 查找并导出镜像,如果传递参数则使用 docker images | grep 传参命令。

    #!/bin/bash
    
    # 确保 /tmp/images 目录存在
    mkdir -p /tmp/images
    
    # 检查是否有参数传入,如果有则使用参数,否则默认使用 'service'
    search_term="${1:-service}"
    
    # 获取镜像列表并过滤出包含 '$search_term' 的行
    images=$(docker images | grep "$search_term")
    
    # 遍历每一行镜像信息
    echo "$images" | while read -r line; do
      # 提取镜像名和标签
      image_name=$(echo "$line" | awk '{print $1}')
      tag=$(echo "$line" | awk '{print $2}')
    
      # 构建保存文件的完整路径和文件名
      save_path="/tmp/images/${image_name}-${tag}.tar"
    
      # 保存镜像到指定路径
      echo "Saving image $image_name:$tag to $save_path"
      docker save -o "$save_path" "$image_name":"$tag"
    done
    
    echo "所有镜像导出完成!"

    Tips:清朝云网络工作室

  • Logback 使用和配置


    共计 11421 个字符,预计需要花费 29 分钟才能阅读完成。

    一、简介

    之前介绍过Java 的日志框架体系以及 slf4j 的整合logback 则是具体实现,官网地址:http://logback.qos.ch/

    Logback 共定义了8个级别的,除去OFF和ALL,可以说分为6个级别。优先级从高到低依次为:OFF、FATAL、ERROR、WARN、INFO、DEBUG、TRACE、 ALL。目前官方推荐的共四个级别:ERROR、WARN、INFO、DEBUG。

    OFF-第8级,最高等级,虚拟级别,用于关闭所有日志记录。

    FATAL-第7级,不可用的致命级别,已被slf4j弃用,官方解释是和ERROR没有绝对的界限。

    ERROR-第6级,可用的错误级别,很常用,推荐级别,用于捕获错误事件。

    WARN-第5级,可用的告警级别,用得相对较少但位置很关键,表示潜在的可能错误。

    INFO-第4级,可用的信息级别,最常用,推荐级别,用于打出程序的关键信息、阶段性的历程碑信息。

    DEBUG-第3级,可用的调试级别,详尽的、可用于程序调试的级别,看日志类似看代码的执行过程。

    TRACE-第2级,可用的追踪级别,但一般不建议使用,用于极为详尽的step-by-step日志追踪。

    ALL-第1级,最低等级,虚拟级别,用于打开所有日志记录。

    二、配置文件

    1. 配置文件查找顺序

    Logback 查找配置文件的先后顺序是:

    1. logback-test.xml

    2. logback.groovy

    3. logback.xml

    如果上述三个配置文件都没有找到,则使用默认配置打印到控制台。

    Logback 优先读取测试环境配置,后读取生产环境配置。此外,相比于 XML,Groovy 风格的配置文件更加直观,当前已有工具支持自动把 logback.xml 文件迁移至 logback.groovy,但当前使用较少,因此本文的讲述,都基于 logback.xml。

    2. 配置文件结构

    <configuration>
        <property name="LOG_HOME" value="./logs"/>
    
        <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
            <filter>
                ...
            </filter>
            <rollingPolicy>
                ....
            </rollingPolicy>
            <encoder>
                ...
            </encoder>
        </appender>
    
        <logger>
            ...
        </loggers> 
    
         <root>
            ...
         </root>
    </configuration>   

    2.1 configuration

    configuration 元素作为根节点,有 debug、scan、scanPeriod 属性。

    2.2 property

    property 用于定义变量,方便使用。有两个属性:name,value。定义变量后,可以使用${}来使用变量。如下:

    <property name="LOG_HOME" value="./logs"/>

    2.3 appender

    appender 是日志输出目标,如文件、控制台、数据库等。有2个必填属性 name 和 class。name 为节点的名称,class 为的全限定类名,也就是日志输出目的地的处理类。其中,encoder 子节点用来控制日志的输出格式。

    2.3.1 ConsoleAppender

    ch.qos.logback.core.ConsoleAppender 用于将日志输出到控制台。

    <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">  
        <encoder>  
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} %msg%n</pattern>
        </encoder>  
    </appender>

    encoder:用于格式化日志输出。

    2.3.2 FileAppender

    ch.qos.logback.core.FileAppender 用于将日志输出到具体的磁盘文件中。

    
    <appender name="INFO" class="ch.qos.logback.core.FileAppender">  
        <file>${LOG_HOME}/info.log</file>
        <append>true</append>  
        <prudent>false</prudent>
        <filter class="ch.qos.logback.classic.filter.ThresholdFilter">
            <level>info</level>
        </filter>
        <encoder>  
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} %msg%n</pattern>
        </encoder>  
    </appender>

    append:新增的日志是否以追加到文件结尾的方式写入到 info.out 文件中,true 为追加,fasle 为清空现存文件再写入。

    prudent:日志是否被安全的写入磁盘文件,默认为 false。如果为 true,则效率低下。

    filter:过滤日志,只写入 info 级别以上的日志。

    2.3.3 RollingFileAppender

    ch.qos.logback.core.rolling.RollingFileAppender 为滚动记录日志,当符合rollingPolicy 节点中设置的条件时,会将现有日志移到新的文件中去。

    rollingPolicy 节点中可设置的条件为:文件的大小、时间等。

    <appender name="INFO" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <file>${LOG_HOME}/info.log</file>  
        <append>true</append>  
        <prudent>false</prudent>
        <filter class="ch.qos.logback.classic.filter.ThresholdFilter">
            <level>info</level>
        </filter>
        <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
            <fileNamePattern>info-%d{yyyy-MM-dd}-%i.log</fileNamePattern>
            <maxHistory>5</maxHistory>
            <maxFileSize>1MB</maxFileSize>
            <totalSizeCap>10MB</totalSizeCap>
        </rollingPolicy>
        <encoder>
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} %msg%n</pattern>
        </encoder>
    </appender>

    rollingPolicy:用于定义日志滚动策略。可以指定的类如下:

    1.TimeBasedRollingPolicy:基于时间的滚动策略,可以按照天、月、周等时间单位来滚动日志文件。
    2.SizeAndTimeBasedRollingPolicy:同时基于时间和文件大小的滚动策略,允许日志文件达到一定大小后进行滚动,并且可以设置时间单位来进一步归档日志文件。
    3.FixedWindowRollingPolicy:基于固定窗口算法的滚动策略,可以设置最大和最小的窗口索引,配合 triggeringPolicy 可以实现根据文件大小进行滚动的需求。
    4.SizeBasedTriggeringPolicy:基于文件大小的触发策略,通常与 FixedWindowRollingPolicy 一起使用,用于控制日志文件在达到特定大小时触发滚动。

    rollingPolicy节点还包含一些子节点:

    fileNamePattern:必要节点,用于定义滚动后新日志文件的命名规则。文件名中必须包含 %d 转换符,用于表示时间元素,例如:info-%d{yyyy-MM-dd}.log

    maxHistory:用于定义日志文件保留的时间长度。(通常与 TimeBasedRollingPolicy 一起使用,SizeAndTimeBasedRollingPolicy 中可能不生效。)

    totalSizeCap:用于定义所有日志文件的总大小上限。

    maxFileSize:用于定义单个日志文件的最大大小。

    用于触发滚动日志的策略如: TimeBasedTriggeringPolicySizeBasedTriggeringPolicy
    TimeBasedTriggeringPolicy:基于时间的滚动策略,可以设置 interval 属性来指定多久滚动一次,默认是1小时。modulate 属性用来调整时间,使得滚动时间更加均匀分布。
    SizeBasedTriggeringPolicy:基于文件大小的滚动策略,size 属性用来定义每个日志文件的大小。

    DefaultRolloverStrategy:用来指定同一个文件夹下最多可以有多少个日志文件时开始删除最旧的,创建新的日志文件,通过 max 属性来设置。

    2.3.4 AsyncAppender

    ​异步记录日志,内部通过使用缓存的方式来实现异步打印,将日志打印事件event放入缓存中。具体数据结构为BlockingQueue。

    <appender name ="ASYNC" class= "ch.qos.logback.classic.AsyncAppender">  
        <discardingThreshold>0</discardingThreshold>  
        <queueSize>512</queueSize>  
        <appender-ref ref ="FILE"/>  
    </appender>

    queueSize:指的是 BlockingQueue 的队列容量大小,默认为256个。
    discardingThreshold:如果 BlockingQueue 中还剩余 20% 的容量,那么程序会丢弃 TRACE、DEBUG 和 INFO 级别的日志打印事件 event,只保留 WARN 和 ERROR 级别的。为了保留所有的日志打印事件,可以将该值设置为0。

    2.4 root

    root 节点可以说是特殊的 logger,用来指定最基础的日志输出级别,只能配置一个。只有一个 level 属性,用于设置打印级别,可选如下:TRACE,DEBUG,INFO,WARN,ERROR,ALL,OFF

    <root level="info">
        <appender-ref ref="CONSOLE" />
    </root>

    2.5 logger

    logger 可以给特定的包定义日志级别和行为,且可以配置多个。

    比如想让 com.amjun.logback 单独使用 debug 日志级别,配合只输出 debug 日志的输出目标达到只输出 com.amjun.logback 包下的 debug 日志的效果。

    additivity 的作用在于当前 logger 是否使用上级 logeer 输出,设置为 false 则自己必须指定 appender-ref 输出目标。

    <appender name="DEBUG" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <file>${LOG_HOME}/debug.log</file>
        <filter class="ch.qos.logback.classic.filter.LevelFilter">
            <level>DEBUG</level>
            <onMatch>ACCEPT</onMatch>
            <onMismatch>DENY</onMismatch>
        </filter>
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <!-- 每天生成一个日志文件 -->
            <fileNamePattern>${LOG_HOME}/debug-%d{yyyy-MM-dd}.log</fileNamePattern>
            <maxHistory>30</maxHistory>
        </rollingPolicy>
        <encoder>
            <pattern>%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n</pattern>
        </encoder>
    </appender>
    
    <logger name="com.amjun.logback" level="DEBUG" additivity="true">
        <appender-ref ref="DEBUG"/>
    </logger>

    三、logback使用

    这里只介绍 springboot 中使用,首先引入依赖:

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-logging</artifactId>
        </dependency>
    
        <!-- 如果是其他项目,单独引入以下包,且不能使用 application.yaml 作为配置文件-->
        <!-- SLF4J 门面 -->
        <!-- <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-api</artifactId>
            <version>1.7.30</version>
        </dependency> -->
        <!-- Logback 日志具体实现 -->
        <!-- <dependency>
            <groupId>ch.qos.logback</groupId>
            <artifactId>logback-classic</artifactId>
            <version>1.2.3</version>
        </dependency> -->
    </dependencies>

    简单使用,直接在 application.yaml 编写配置:

    logging:
      file:
        # 配置日志存放路径,日志文件名为:spring.log
        path: ./log
        # 设置日志文件大小
        max-size: 20MB
      level:
        # root日志以info级别输出
        root: info
        # 此包下所有class以DEBUG级别输出
        com:
          amjun:
            logback: info

    xml 配置文件方式 logback-spring.xml

    <?xml version="1.0" encoding="UTF-8"?>
    <configuration>
        <property name="LOG_HOME" value="./logs"/>
    
        <!-- 定义控制台输出的日志级别 -->
        <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
            <filter class="ch.qos.logback.classic.filter.ThresholdFilter">
                <level>info</level>
            </filter>
            <encoder>
                <pattern>%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n</pattern>
            </encoder>
        </appender>
    
        <appender name="DEBUG" class="ch.qos.logback.core.rolling.RollingFileAppender">
            <file>${LOG_HOME}/debug.log</file>
            <filter class="ch.qos.logback.classic.filter.LevelFilter">
                <level>DEBUG</level>
                <onMatch>ACCEPT</onMatch>
                <onMismatch>DENY</onMismatch>
            </filter>
            <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
                <!-- 每天生成一个日志文件 -->
                <fileNamePattern>${LOG_HOME}/debug-%d{yyyy-MM-dd}.log</fileNamePattern>
                <maxHistory>30</maxHistory>
            </rollingPolicy>
            <encoder>
                <pattern>%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n</pattern>
            </encoder>
        </appender>
    
        <!-- 配置根日志器的输出 -->
        <root level="DEBUG">
            <!-- 控制台只输出 INFO 及以上 -->
            <appender-ref ref="CONSOLE"/>
            <!-- 文件中仅保存 DEBUG -->
            <appender-ref ref="DEBUG"/>
        </root>
    </configuration>

    测试类:

    public class Test {
    
        private static final Logger log = LoggerFactory.getLogger(Test.class);
    
        public static void main(String[] args) throws InterruptedException {
            int i = 0;
            while (true){
                i++;
                // 防止打印太快
                if(i % 1000 == 0){
                    Thread.sleep(500l);
                }
                log.error("error...");
                log.warn("warn...");
                log.info("info...");
                log.debug("debug...");
                log.trace("trace...");
            }
        }
    }

    四、自用配置

    效果:开发环境 debug 级别以上日志输出到控制台,生产环境只输出 info 级别以上日志到控制台,debug 日志单独输出到文件。

    <?xml version="1.0" encoding="UTF-8"?>
    <!--
    scan:当此属性设置为true时,配置文件如果发生改变,将会被重新加载,默认值为true。
    scanPeriod:设置监测配置文件是否有修改的时间间隔,如果没有给出时间单位,默认单位是毫秒当scan为true时,此属性生效。默认的时间间隔为1分钟。
    debug:当此属性设置为true时,将打印出logback内部日志信息,实时查看logback运行状态。默认值为false。
    -->
    <configuration scan="true" scanPeriod="60 seconds" debug="false">
        <!--自定义颜色配置 此处converterClass引用的是日志颜色类的路径, 此匹配的是第二种控制台色彩输出方式-->
        <conversionRule conversionWord="customcolor" converterClass="com.unipower.common.log4j.LogbackColorful"/>
        <!-- 定义日志的要保存的根目录 -->
        <springProperty scope="context" name="spring.application.name" source="spring.application.name"/>
        <property name="LOG_HOME" value="log/${spring.application.name}"/>
        <!-- 定义日志文件名称-->
        <springProperty scope="context" name="appName" source="spring.application.name" defaultValue="localhost.log"/>
    
        <!-- 控制台输出 -->
        <appender name="consoleAppender" class="ch.qos.logback.core.ConsoleAppender">
            <!--
            日志输出格式:
                %d表示日期时间,
                %thread表示线程名,
                %-5level:级别从左显示5个字符宽度
                %logger{50} 表示logger名字最长50个字符,否则按照句点分割。
                %msg:日志消息,
                %n是换行符
            -->
            <encoder>
                <!--<pattern>%d %p (%file:%line\)- %m%n</pattern>-->
                <!--格式化输出:%d:表示日期    %thread:表示线程名     %-5level:级别从左显示5个字符宽度  %msg:日志消息    %n:是换行符-->
                <pattern>%yellow(%date{yyyy-MM-dd HH:mm:ss}) [%highlight(%-5level)] [%blue(%thread)] [%blue(%logger{50}:%line)]
                    %cyan(%msg%n)
                </pattern>
                <charset>UTF-8</charset>
            </encoder>
        </appender>
    
        <!-- 过滤掉 debug 日志的控制台输出 -->
        <appender name="consoleDenyDebugAppender" class="ch.qos.logback.core.ConsoleAppender">
            <filter class="ch.qos.logback.classic.filter.LevelFilter">
                <level>DEBUG</level>
                <onMatch>DENY</onMatch>
            </filter>
            <encoder>
                <pattern>%yellow(%date{yyyy-MM-dd HH:mm:ss}) [%highlight(%-5level)] [%blue(%thread)] [%blue(%logger{50}:%line)]
                    %cyan(%msg%n)
                </pattern>
                <charset>UTF-8</charset>
            </encoder>
        </appender>
    
        <!-- 滚动记录文件,先将日志记录到指定文件,当符合某个条件时,将日志记录到其他文件   -->
        <!--该配置表示每天生成一个日志文件,保存30天的日志文件。-->
        <appender name="appLogAppender" class="ch.qos.logback.core.rolling.RollingFileAppender">
            <!-- 指定日志文件的名称 -->
            <file>${LOG_HOME}/${appName}.log</file>
            <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
                <fileNamePattern>${LOG_HOME}/${appName}-%d{yyyy-MM-dd}-%i.zip</fileNamePattern>
                <MaxHistory>30</MaxHistory>
                <maxFileSize>50MB</maxFileSize>
                <totalSizeCap>2GB</totalSizeCap>
            </rollingPolicy>
            <!-- 日志输出格式: -->
            <!--        <layout class="ch.qos.logback.classic.PatternLayout">-->
            <!--            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [ %thread ] - [ %-5level ] [ %logger{50} : %line ] - %msg%n</pattern>-->
            <!--        </layout>-->
            <encoder>
                <!--<pattern>%d %p (%file:%line\)- %m%n</pattern>-->
                <!--格式化输出:%d:表示日期    %thread:表示线程名     %-5level:级别从左显示5个字符宽度  %msg:日志消息    %n:是换行符-->
                <pattern>%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n</pattern>
                <charset>UTF-8</charset>
            </encoder>
        </appender>
    
        <!-- 仅输出业务包下的 debug 日志 -->
        <appender name="debugOnlyAppender" class="ch.qos.logback.core.rolling.RollingFileAppender">
            <file>${LOG_HOME}/${appName}-debug.log</file>
            <filter class="ch.qos.logback.classic.filter.LevelFilter">
                <level>DEBUG</level>
                <onMatch>ACCEPT</onMatch>
                <onMismatch>DENY</onMismatch>
            </filter>
            <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
                <fileNamePattern>${LOG_HOME}/${appName}-debug-%d{yyyy-MM-dd}.log</fileNamePattern>
                <maxHistory>30</maxHistory>
            </rollingPolicy>
            <encoder>
                <pattern>%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n</pattern>
                <charset>UTF-8</charset>
            </encoder>
        </appender>
    
        <!-- 开发、测试环境 -->
        <springProfile name="default,dev,test">
            <logger name="org.springframework.web" level="INFO"/>
            <logger name="org.springboot.sample" level="INFO"/>
            <logger name="RocketmqRemoting" level="OFF"/>
            <logger name="com.alibaba.nacos" level="OFF"/>
            <logger name="org.springframework.data.mongodb.core" level="INFO"/>
    
            <logger name="com.unipower" level="DEBUG"/>
            <root level="info">
                <appender-ref ref="consoleAppender"/>
                <!-- 可能不需要,暂时关闭 -->
    <!--            <appender-ref ref="appLogAppender"/>-->
            </root>
        </springProfile>
    
        <!--生产环境-->
        <springProfile name="pro">
            <!-- 将debug日志输出到文件 -->
            <logger name="com.unipower" level="DEBUG" additivity="true">
                <appender-ref ref="debugOnlyAppender"/>
            </logger>
    
            <root level="info">
                <appender-ref ref="consoleDenyDebugAppender"/>
    <!--            <appender-ref ref="appLogAppender"/>-->
            </root>
        </springProfile>
    
    </configuration>

    Tips:清朝云网络工作室

  • 12月18日,星期三, 每天60秒读懂全世界!

    百度热搜新闻

    新闻来源:百度热搜榜

    1. 网友在西藏林芝旅游偶遇姜子牙坐骑 近日,网友在西藏旅游时偶遇姜子牙坐骑“四不像”,并表示:太幸运了,最开始觉得它像狗。

    2. 涉案超30亿 李建平被执行死刑 经最高人民法院核准,2024年12月17日上午,内蒙古自治区兴安盟中级人民法院依法对李建平执行了死刑。

    3. 中国过境免签政策全面放宽优化 国家移民管理局12月17日发布公告,即日起全面放宽优化过境免签政策,将过境免签外国人在境内停留时间由原72小时和144小时均延长为240小时(10天),同时新增21个口岸为过境免签人员入出境口岸。

    4. 贾玲回应减肥100斤后观众缘变差 近日,贾玲回应减肥100斤后观众缘变差:有感觉到,再慢慢和观众培养感情。

    5. 一觉醒来 德国政府垮了 德国朔尔茨政府在2024年12月16日遭遇信任投票未通过,导致政府垮台,德国将面临提前大选。

    6. 专家谈邹市明儿子注射生长激素

    7. 乌干达约300人患怪病全身失控跳舞 近日,乌干达爆发一种神秘疾病,患者会全身虚弱乏力,走路时身体更不能自控地一上一下颤动,而且会出现发烧症状。

    8. 王思聪明年要开100家牛排店 近日,多家王思聪投资的“牛校长”牛排店在北京开业。“牛校长”品牌方表示,明年第一季度有一个百店连开的计划。

    9. 曾黎不愧是把张柏芝迷住的女人 美女对于美女的欣赏的含金量到底有多高!曾黎可是把张柏芝都能够迷住的程度。

    10. 学生冬季校服中扯出薄膜 官方介入 近日,浙江温州文成县黄坦镇中心小学被曝冬季校服中扯出一大块薄膜,针对此事,当地教育、市监等部门已成立联合调查组介入。

    —- 百度热搜新闻 End —-

    知乎新闻

    新闻来源:知乎日榜

    标题: 做完考试卷子,检查的时候发现两个答案不知道选哪个,该不该改答案?
    链接: https://daily.zhihu.com/story/9777565
    ———————-
    标题: 请问如果你的追求者送你很便宜廉价的礼物,你们作何感受?
    链接: https://daily.zhihu.com/story/9777713
    ———————-
    标题: 火车卧铺车厢的床上用品是如何清洗的?
    链接: https://daily.zhihu.com/story/9777722
    ———————-
    标题: 瞎扯 · 如何正确地吐槽
    链接: https://daily.zhihu.com/story/9777463
    ———————-

    —- 知乎新闻 End —-

    IT之家新闻

    新闻来源:ITHome之家科技新闻

    标题: 保时捷中国员工确认公司裁员,有望赔偿 N+6
    发布时间: 2024-12-17T15:54:55.753
    新闻简介: 近日有多方传言称,豪华汽车品牌保时捷开启中国区裁员,包含正式、外包等岗位,赔偿有望按照 N+6 的标准执行。对此,保时捷内部员工向第一财经回应称情况属实,公司计划裁员 30%。
    ———————-
    标题: 英伟达“RTX 4010”显卡亮相:4GB 显存、《孤岛危机》游戏平均帧率 80FPS,海外网友纷纷求购
    发布时间: 2024-12-17T10:44:25.687
    新闻简介: YouTube 频道 Budget-Builds Official 在最新一期视频中,开箱并展示了英伟达 GeForce RTX 4010 显卡,目前该视频已被观看超过 41 万次,有将近 1500 条用户评论,不少人求购下单链接。
    ———————-
    标题: 杭州数码消费券全国可用,iPhone 16 Pro Max 补贴后仅 8999 元
    发布时间: 2024-12-17T14:33:16.39
    新闻简介: 杭州 3C 数码消费券现已开放,支持全国领用,每天 10:00 起限领一张哦。
    ———————-
    标题: 调查显示:73% 的苹果 iPhone 用户认为 Apple Intelligence 对使用体验的提升微乎其微
    发布时间: 2024-12-17T07:07:16.32
    新闻简介: 一项由科技产品回收平台 SellCell 进行的调查显示,尽管近半数 iPhone 用户在购机时会将人工智能(AI)视为重要因素,但实际使用过苹果 AI 功能的用户中,高达 73% 认为这些功能对使用体验的提升微乎其微。这项调查还发现,三星用户也普遍对 Galaxy 设备上的 AI 工具感到不满,比例高达 87%。
    ———————-
    标题: 华为余承东与鸿蒙智行“四界”老总官宣 12 月 19 日首次“合体直播”
    发布时间: 2024-12-17T11:39:40.97
    新闻简介: 鸿蒙智行与央视新闻今日联合宣布,12 月 19 日(周四)中午 12 时,鸿蒙智行携手“四界”联袂登场央视新闻《中国经济引力场》。
    ———————-
    标题: 华为 Pura 70 / Mate 70 / Mate X6 系列手机支持车机连接 HiCar 刷新率不降低
    发布时间: 2024-12-17T16:34:31.887
    新闻简介: 华为 Pura 70 / Mate 70 / Mate X6 系列可将手机系统和 HiCar 智行 App 均升级到最新版本后,可自行设置连接车机时 HiCar 不降低刷新率。
    ———————-
    标题: 消息称英伟达 RTX 5060 / 5060 Ti 显卡分别配备 8/16 GB GDDR7 显存
    发布时间: 2024-12-17T09:33:46.337
    新闻简介: RTX 5060 / 5060 Ti 显卡均将采用代号 PG152 的 PCB 设计,基于 GB206 芯片,目前看来将于 2025 年 2 月底~3 月推出。
    ———————-
    标题: 微信安卓版迎 8.0.55 正式版更新:多人语音通话陆续覆盖、添加朋友界面焕新
    发布时间: 2024-12-17T20:07:29.73
    新闻简介: 经我们IT之家实测,本次微信安卓 8.0.55 正式版的更新数量和力度都较为可观。那么微信究竟又出现了哪些变化,小编这就挨个说说。
    ———————-
    标题: 消息称小米 REDMI 新机正测试百瓦快充 + 7000mAh 电池,但温升不好解决
    发布时间: 2024-12-17T15:03:51.507
    新闻简介: 数码博主 @数码闲聊站 今日爆料,REDMI 正在测试一款搭载超大电池和高功率快充的新机。这款设备在实验室最高配备百瓦快充和 7000mAh± 电池,然而实验过程中发现,设备温升控制成为一大技术难点。
    ———————-
    标题: 拼多多 Temu 连续两年成美国下载量最大的免费苹果 iOS 应用,TikTok 第三
    发布时间: 2024-12-17T11:42:03.977
    新闻简介: 据苹果公司周一发布的 App Store 年度应用和游戏下载排行榜显示,中国购物应用 Temu(拼多多旗下跨境电商平台)再次蝉联美国免费应用下载榜冠军。自去年首次登顶并取代 TikTok 以来,Temu 已连续两年占据榜首位置。
    ———————-
    标题: 索泰曝光英伟达 RTX 50 系列首批阵容:5090 显卡配 32GB GDDR7 显存
    发布时间: 2024-12-17T10:13:06.597
    新闻简介: 科技媒体 videocardz 昨日(12 月 16 日)发布博文,报道称根据索泰(ZOTAC)官网显示的信息,发现了英伟达 GeForce RTX 50 系列显卡的首批阵容。
    ———————-
    标题: 阿里巴巴:拟 74 亿元向雅戈尔集团等出售银泰 100% 股权,预计亏损约 93 亿元
    发布时间: 2024-12-17T12:22:28.26
    新闻简介: 阿里巴巴今日在港交所公告,公司与另一名少数股东同意将银泰 100% 的股权向由雅戈尔集团和银泰管理团队成员组成的购买方财团出售。银泰是中国领先的百货企业之一。阿里巴巴目前持有银泰约 99% 的股权。阿里巴巴就银泰出售的所得款项总额约为人民币 74 亿元(10 亿美元)。阿里巴巴预计因出售银泰而录得的亏损约为人民币 93 亿元(13 亿美元)。银泰出售的完成须通过中国经营者集中审查和其他惯例交割条件。
    ———————-

    —- IT之家新闻 End —-