[{"content":" 在当今快速发展的技术环境中，AI正在变革各行各业并推动创新，理解AI性能指标的复杂性至关重要。过去许多AI模型需要在云端运行。当我们走向由终端侧生成式AI处理定义的未来时，我们必须能够评估计算平台可运行AI模型的性能、准确性和效率。如今，TOPS(每秒万亿次运算)是衡量处理器AI性能的主要方式之一。TOPS是基于处理器所需的架构和频率，衡量处理器潜在AI推理峰值性能的方法，比如神经网络处理器(NPU)。\nNPU是什么？\n在深入探讨TOPS的具体内容之前，让我们先看看NPU的重要性。对于终端侧AI处理，NPU在提高效率、为个人用户和企业提供创新的应用体验方面发挥着关键作用。评估这些专用处理器的性能需要全面了解其能力背后的关键指标。\nNPU的演进改变了人们处理计算的方式。传统上，CPU负责执行AI算法。随着对处理性能的需求飙升，专用NPU应运而生，成为处理AI相关软件应用的专用解决方案。NPU旨在高效处理AI任务所需的复杂数学计算，提供出色的效率、性能和能效。\nAI TOPS是什么？\nTOPS作为展示处理器计算能力的指标，是衡量NPU性能的核心。\nTOPS通过以万亿单位测量一秒钟内执行的运算（加法、乘法等）次数来量化NPU处理能力。 这种标准化测量方式非常明确地显示了NPU的性能，可作为比较不同处理器和架构AI性能的关键指标。因为TOPS是针对NPU的基础性能指标，探索TOPS的计算参数以及它们如何决定性能至关重要，这有助于更深入地了解NPU的能力。\n乘法累加(MAC)运算执行AI工作负载中的核心数学公式。矩阵乘法由两类基础运算组成：累加器的乘法和加法。例如，一个MAC单元可在每个时钟周期内运行两类基础运算各一次，意味着它在每个时钟周期内执行两个运算。一个给定的NPU有一定数量的MAC单元，能够在不同精度级别进行运算，这取决于NPU架构。\n频率决定NPU及其MAC单元（以及CPU或GPU）运算的时钟速度（或每秒周期数），直接影响整体性能。更高的频率允许在单位时间内执行更多运算，从而提高处理速度。但是，提高频率也会导致更高功耗和发热，影响电池续航和用户体验。处理器TOPS计算通常使用峰值运行频率。\n精度指计算的颗粒度，通常精度越高模型准确性就越高，需要的计算强度也越高。最常见的高精度AI模型为32位和16位浮点精度，而速度更快的低精度低功耗模型通常使用8位和4位整数精度。当前行业标准为以INT8精度评估AI推理性能TOPS。\n计算TOPS要从计算OPS开始，OPS等于MAC单元数乘以运行频率的两倍。TOPS数量是OPS除以一万亿的值，将公式更简单地列出，即：\nTOPS = 2×MAC单元数×频率/1万亿。 TOPS和实际性能\n尽管TOPS提供了探索NPU能力的重要信息，我们仍必须将理论指标和实际应用联系起来。\n毕竟，仅仅有高TOPS值并不能保证最佳的AI性能；各种因素协同作用的结果才能真正决定NPU实力。 因此评估NPU性能时要考虑内存带宽、软件优化和系统集成等方面的因素。基准测试可以帮助我们超越数字，了解NPU在实际场景中的表现，其中时延、吞吐量和能效尤为重要。\nProcyon AI基准测试使用真实工作负载来帮助将理论性的TOPS评估转化为用户在使用AI推理的真实应用中对响应和处理能力的预期。它以多个精度运行六个模型，提供NPU不同性能表现的详细洞察。类似模型在生产力、媒体、创作者和其他应用中越来越常见。在Procyon AI和其他基准测试中有更快的性能表现，与实现更快推理和更好用户体验息息相关。\n为此，分析实际性能可以为NPU的能力和局限性提供宝贵洞察。必须从可行性和实用性角度检验性能指标。\n根据用户需求评估NPU性能\n应对快速变化的NPU性能评估领域或许会让人望而生畏，但随着数字化转型（尤其是在AI领域）持续快速发展，深入了解TOPS对行业和个人来说都很重要。\n最终，选择合适的系统级芯片(SoC)取决于用户、客户或组织的工作负载和优先级，而这一决策很可能需要取决于SoC中的NPU。\n无论用户是优先考虑原始算力、能效还是模型准确度，骁龙X系列平台面向笔记本电脑，配备高达45TOPS的NPU，能够强力赋能PC，并将实际可用的AI体验引入用户的工作流程。\n参考 深入了解面向计算的骁龙AI\n面向边缘终端优化生成式AI (qualcomm.cn)\n","permalink":"https://blog.codingforjoy.com/posts/ai%E4%B8%ADtops%E5%92%8Cnpu%E6%80%A7%E8%83%BD%E6%8C%87%E6%A0%87%E6%8C%87%E5%8D%97/","summary":"解释TOPS是什么","title":"AI中TOPS和NPU性能指标指南"},{"content":" 通过实际示例深入了解 CloudWeGo 的 Kitex，其高性能和可扩展性重新定义了微服务卓越标准。\n[译]：https://www.cloudwego.io/blog/2024/02/21/delving-deeper-enriching-microservices-with-golang-with-cloudwego/\n如果存在一个 RPC 框架，它不仅提供高性能和可扩展性，还拥有一套强大的功能和繁荣的社区支持，那会怎样？\nCloudWeGo，一个由字节跳动开发并开源的高性能可扩展 Golang 和 Rust RPC 框架，因其完美契合需求而引起了我的注意。\nCloudWeGo 与其他 RPC 框架对比 尽管 gRPC 和 Apache Thrift 为微服务架构提供了良好的支持，但 CloudWeGo 凭借其先进特性和性能指标，脱颖而出，成为未来有前景的开源解决方案。\nCloudWeGo基于 Golang 和 Rust 打造，适应现代开发环境，提供先进功能与卓越性能指标。性能测试表明，Kitex 在 QPS 和延迟方面超越 gRPC 四倍以上，QPS 和延迟的吞吐量提升 51%至 70%。\n这为开发者提供了一个工具，它不仅满足了现代微服务的性能要求，而且明显超越了这些要求。让我们深入探讨一些具体用例，以理解 CloudWeGo 的潜力。\nBookinfo：《交通处理的故事》 考虑 Bookinfo 这一 Istio 提供的示例应用，通过 CloudWeGo 的 Kitex 进行重写，以获得更卓越的性能和可扩展性。\n此用例展示了高流量服务如何显著受益于 CloudWeGo 的性能承诺。此次集成还展示了在流量处理和性能方面，CloudWeGo 如何超越传统的 Istio 服务网格。\n借助 Kitex 和 Hertz 处理流量重定向，Bookinfo 项目能够高效管理高流量，确保快速响应并提供更佳的用户体验。\nimport ( \u0026#34;github.com/cloudwego/kitex/server\u0026#34; ) func main() { svr := echo.NewServer(new(EchoImpl), server.WithName(\u0026#34;echo\u0026#34;)) listener, _ := net.Listen(\u0026#34;tcp\u0026#34;, \u0026#34;:8888\u0026#34;) svr.Serve(listener) } 上述代码片段是一个简化的示例，展示了如何使用 Kitex 重写 Bookinfo 项目以获得更佳性能。\nEasy Note：简约之魔法 CloudWeGo 在简化复杂任务方面的承诺在 Easy Note 项目中得到了体现。它利用 CloudWeGo 实现了一个全流程的交通车道。这个笔记平台需要具备响应迅速和高效的特点，而 CloudWeGo 的高性能网络库 Netpoll 满足了这一需求。\nCloudWeGo 的整合将 Easy Note 应用提升至能与其他笔记平台有效竞争的水平，证明了简洁确实能带来力量。\nimport ( \u0026#34;github.com/cloudwego/kitex/server\u0026#34; ) type RPCService struct{} func (s *RPCService) Handle(ctx context.Context, req *Request) (*Response, error) { resp := \u0026amp;Response{Message: \u0026#34;Echo \u0026#34; + req.Message} return resp, nil } func main() { rpcHandler := \u0026amp;RPCService{} svr := server.NewServer(rpcHandler) listener, _ := net.Listen(\u0026#34;tcp\u0026#34;, \u0026#34;:8888\u0026#34;) svr.Serve(listener) } 上述片段展示了 CloudWeGo 如何助力提升 Easy Note 应用的效率。\nBook Shop：轻松实现电子商务 在繁忙的电子商务领域，Book Shop 成为 CloudWeGo 无缝集成能力的明证。将 Elasticsearch 和 Redis 等中间件整合到 Kitex 项目中，构建起一个坚实且能与更复杂平台媲美的电子商务系统。\nCloudWeGo 与 Elasticsearch 和 Redis 等流行技术的有效整合能力，确保了企业在选择开源 RPC 框架时无需妥协。\nimport ( \u0026#34;github.com/cloudwego/kitex/server\u0026#34; ) type ItemService struct {} func (s *ItemService) AddItem(ctx context.Context, item *Item) error { // Add to Elasticsearch // Add to Redis // Return error if any return nil } func main() { itemHandler := \u0026amp;ItemService{} svr := server.NewServer(itemHandler) listener, _ := net.Listen(\u0026#34;tcp\u0026#34;, \u0026#34;:8888\u0026#34;) svr.Serve(listener) } 上述代码片段展示了 Book Shop 电子商务系统如何与 CloudWeGo、Elasticsearch 和 Redis 协同运作的基本示例。\nFreeCar：驱动创新 FreeCar 项目是 CloudWeGo 如何革新分时租车系统运营的绝佳例证，为现有的打车应用提供了一个强有力的替代方案。\n这一实际应用展示了 CloudWeGo 强大功能如何优化运营，促进各行各业（不仅仅是科技领域）的效率与可扩展性。\nimport ( \u0026#34;github.com/cloudwego/kitex/server\u0026#34; ) type CarService struct {} func (s *CarService) BookRide(ctx context.Context, rideRequest *RideRequest) (*RideConfirmation, error) { // Business logic to handle ride booking // Return confirmation or error return nil, nil } func main() { rideHandler := \u0026amp;CarService{} svr := server.NewServer(rideHandler) listener, _ := net.Listen(\u0026#34;tcp\u0026#34;, \u0026#34;:8888\u0026#34;) svr.Serve(listener) } 上述代码片段简要展示了 FreeCar 如何利用 CloudWeGo。\n是什么吸引我来到 CloudWeGo？ 当我深入探索替代 RPC 框架的领域，并研究 CloudWeGo 项目时，有几个因素尤为突出：\n性能：在微服务领域，性能可能意味着成功与失败之间的差距。CloudWeGo 在性能方面表现出色，其 QPS 和延迟得分让其他 RPC 框架望尘莫及。 可扩展性：作为开发者，你最欣赏 Kitex 的地方在于其承诺的可扩展性，使项目能够迅速适应不断增长的数据需求和复杂性。 鲁棒性：CloudWeGo 丰富的功能集，包括对多种消息协议、传输协议、负载均衡、断路器和限流的支持，为设计和维护微服务提供了全面的解决方案。 社区支持：CloudWeGo 由字节跳动支持，这让我确信能获得强大的社区支持。丰富的资源和讨论可解决常见问题，并支持持续学习。 实际应用：在多样化的项目中，CloudWeGo 展现出的多功能性和可扩展性，证实了我对其效能的信任。 拥抱微服务架构的未来 随着每个用例的实践，CloudWeGo 的潜力愈发清晰。开发者现在能够构建高性能、可扩展且稳健的应用程序，无论他们偏好使用 Golang 还是 Rust，都能真正掌握微服务的精髓。\n若您正考虑为微服务架构引入新工具，尤其是对 Rust 感兴趣，不妨尝试 CloudWeGo。微服务的未来正等着您。\n","permalink":"https://blog.codingforjoy.com/posts/%E6%B7%B1%E5%85%A5%E6%8E%A2%E7%B4%A2%E5%88%A9%E7%94%A8cloudwego%E4%B8%B0%E5%AF%8Cgo%E8%AF%AD%E8%A8%80%E5%BE%AE%E6%9C%8D%E5%8A%A1/","summary":"介绍CloudWeGo如何构建微服务","title":"深入探索：利用CloudWeGo丰富Go语言微服务"},{"content":"前言 源于看到了这两篇博客基于 Rust 的高性能 RocketMQ Proxy、RocketMQ 5.0：无状态代理模式的探索与实践，兴趣使然，想探究下Proxy现有的设计实现，为自主实现各大MQ协议互转Proxy代理铺垫。也就是第一篇博客实现的Proxy，看起来在RocketMQ未来规划中是有的。关于Coding需要体会下才得道。\n下面开始。。。\n从0到“Hello World”，最快的方式优先从“QuickSstart”体验下。体验下总结就是这麽几个步骤：\n启动NameServer 选择``Broker+Proxy同进程，Broker、Proxy分离(不同进程)部署，不过内部的步骤也是先启动Broker，再Proxy` 测试Producer/Consumer 总所周知，源码不会唬人，所以接下来基于源码按照这个步骤展开。。。。\n这里用的IDE主要是 Idea。\n克隆项目 # 克隆代码 git clone https://github.com/apache/rocketmq.git 切换到 release-2.5.0\n构建Maven依赖\n然后IDE中maven install或者终端mvn clean install -Dmaven.test.skip=true\n最后主要关注项目中中这几个模块\n启动NameServer 复制“distribution”模块的绝对路径\nIDE-配置“NameServStartup”启动项\n找到“namasrv”模块的启动类“NameServStartup”\nmain方法启动，然后会提示需要设置“ROCKETMQ_HOME”\n打开启动配置框\n设置环境变量“ROCKETMQ_HOME”为“distribution”模块绝对路径，确定\n重新启动，看下面打印，启动成功\n选择 Broker、Proxy分离部署 选择分离部署，字面意思也是互不影响，主要是方便调试，因为我们的目标是Proxy。\n启动Broker 找到“broker”模块下的“BrokerStartup”启动类，main函数启动（会提示没有“ROCKETMQ_HOME”）\n打开IDE启动配置，设置启动参数和环境变量“ROCKETMQ_HOME”\n添加VM参数：-Xms512m -Xmx512m -Xmn256m\n-n 是你本机的nameserv，把“192.168.1.128”改成你自己的\n设置环境变量“ROCKETMQ_HOME”为之前复制的“distribution”的绝对路径\n重新启动，启动打印如下\n启动Proxy 找到“proxy”模块中启动类“ProxyStartup”，main函数启动，然后停止\n打开启动配置，设置启动参数和环境变量\n-n “192.168.1.128”修改为你的IP，表示代理的broker-server服务\n重新启动，打印如下\n测试 直连broker Producer\n找到“example”模块“quickstart”包下“Producer”类，main启动（会报错连接不上），停止\n打开启动配置，设置环境变量：NAMESRV_ADDR\n修改“192.168.1.128”为你的IP\n重新启动，推送消息打印\n异常如果是下面截图所示，暂时快速解决方法：重启 BrokerStartup\nConsumer\n找到“example”模块“quickstart”包下“Consumer”类，main启动（会报错连接不上），停止\n打开启动配置，设置环境变量：NAMESRV_ADDR\n重启启动，消费记录如下\n通过proxy测试 根据官方QuickStart中SDK测试消息收发指南进行测试。\n创建Java工程\n引入maven依赖，查看最新版本\n\u0026lt;dependency\u0026gt; \u0026lt;groupId\u0026gt;org.apache.rocketmq\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;rocketmq-client-java\u0026lt;/artifactId\u0026gt; \u0026lt;version\u0026gt;${rocketmq-client-java-version}\u0026lt;/version\u0026gt; \u0026lt;/dependency\u0026gt; 通过mqadmin创建 Topic。\n这里指南中其实使用的Rocketmq源码中的tools模块下``MQAdminStartup`类，所以接下来我们回到源码工程中：\n启动MQAdminStartup类，看结果，是一个命令行工具\n打开启动配置，添加启动参数：updatetopic -n localhost:9876 -t TestTopic -c DefaultCluster\n执行成功如下打印\n出此之外你还可以安装：apache/rocketmq-dashboard，用来可视化查看mq的一些情况，建议使用docker安装，注意web端口8080映射为别的端口，默认和proxy默认端口冲突了\nProducer：回到刚刚的新工程中，创建“ProducerExample”类\nimport org.apache.rocketmq.client.apis.ClientConfiguration; import org.apache.rocketmq.client.apis.ClientConfigurationBuilder; import org.apache.rocketmq.client.apis.ClientException; import org.apache.rocketmq.client.apis.ClientServiceProvider; import org.apache.rocketmq.client.apis.message.Message; import org.apache.rocketmq.client.apis.producer.Producer; import org.apache.rocketmq.client.apis.producer.SendReceipt; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class ProducerExample { private static final Logger logger = LoggerFactory.getLogger(ProducerExample.class); public static void main(String[] args) throws ClientException { // 接入点地址，需要设置成Proxy的地址和端口列表，一般是xxx:8081;xxx:8081。 String endpoint = \u0026#34;192.168.1.128:8081\u0026#34;; // 消息发送的目标Topic名称，需要提前创建。 String topic = \u0026#34;TestTopic\u0026#34;; ClientServiceProvider provider = ClientServiceProvider.loadService(); ClientConfigurationBuilder builder = ClientConfiguration.newBuilder().setEndpoints(endpoint); ClientConfiguration configuration = builder.build(); // 初始化Producer时需要设置通信配置以及预绑定的Topic。 Producer producer = provider.newProducerBuilder() .setTopics(topic) .setClientConfiguration(configuration) .build(); // 普通消息发送。 Message message = provider.newMessageBuilder() .setTopic(topic) // 设置消息索引键，可根据关键字精确查找某条消息。 .setKeys(\u0026#34;messageKey\u0026#34;) // 设置消息Tag，用于消费端根据指定Tag过滤消息。 .setTag(\u0026#34;messageTag\u0026#34;) // 消息体。 .setBody(\u0026#34;messageBody\u0026#34;.getBytes()) .build(); try { // 发送消息，需要关注发送结果，并捕获失败等异常。 SendReceipt sendReceipt = producer.send(message); logger.info(\u0026#34;Send message successfully, messageId={}\u0026#34;, sendReceipt.getMessageId()); } catch (ClientException e) { logger.error(\u0026#34;Failed to send message\u0026#34;, e); } // producer.close(); } } Consumer：创建“PushConsumerExample”类\nimport java.io.IOException; import java.util.Collections; import org.apache.rocketmq.client.apis.ClientConfiguration; import org.apache.rocketmq.client.apis.ClientException; import org.apache.rocketmq.client.apis.ClientServiceProvider; import org.apache.rocketmq.client.apis.consumer.ConsumeResult; import org.apache.rocketmq.client.apis.consumer.FilterExpression; import org.apache.rocketmq.client.apis.consumer.FilterExpressionType; import org.apache.rocketmq.client.apis.consumer.PushConsumer; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class PushConsumerExample { private static final Logger logger = LoggerFactory.getLogger(PushConsumerExample.class); private PushConsumerExample() { } public static void main(String[] args) throws ClientException, IOException, InterruptedException { final ClientServiceProvider provider = ClientServiceProvider.loadService(); // 接入点地址，需要设置成Proxy的地址和端口列表，一般是xxx:8081;xxx:8081。 String endpoints = \u0026#34;192.168.1.128:8081\u0026#34;; ClientConfiguration clientConfiguration = ClientConfiguration.newBuilder() .setEndpoints(endpoints) .build(); // 订阅消息的过滤规则，表示订阅所有Tag的消息。 String tag = \u0026#34;*\u0026#34;; FilterExpression filterExpression = new FilterExpression(tag, FilterExpressionType.TAG); // 为消费者指定所属的消费者分组，Group需要提前创建。 String consumerGroup = \u0026#34;YourConsumerGroup\u0026#34;; // 指定需要订阅哪个目标Topic，Topic需要提前创建。 String topic = \u0026#34;TestTopic\u0026#34;; // 初始化PushConsumer，需要绑定消费者分组ConsumerGroup、通信参数以及订阅关系。 PushConsumer pushConsumer = provider.newPushConsumerBuilder() .setClientConfiguration(clientConfiguration) // 设置消费者分组。 .setConsumerGroup(consumerGroup) // 设置预绑定的订阅关系。 .setSubscriptionExpressions(Collections.singletonMap(topic, filterExpression)) // 设置消费监听器。 .setMessageListener(messageView -\u0026gt; { // 处理消息并返回消费结果。 logger.info(\u0026#34;Consume message successfully, messageId={}\u0026#34;, messageView.getMessageId()); return ConsumeResult.SUCCESS; }) .build(); Thread.sleep(Long.MAX_VALUE); // 如果不需要再使用 PushConsumer，可关闭该实例。 // pushConsumer.close(); } } 启动Producer，打印如下\n启动Consumer，打印如下\n好了，debug环境搭建好了，可以开始愉快的Debug。。。\n","permalink":"https://blog.codingforjoy.com/posts/%E5%A6%82%E4%BD%95%E6%9E%84%E5%BB%BArocketmq-5.2%E6%BA%90%E7%A0%81%E8%B0%83%E8%AF%95%E7%8E%AF%E5%A2%83/","summary":"构建最小可调试环境","title":"如何构建RocketMQ-5.2源码调试环境"},{"content":" 前言\n这两天有个朋友公司有这麽一个情况，有个客户部署环境网络使用的是专线，完全内网，无法外网访问，确实安全，系统是Windows；离他们公司也很远，每次有问题都得开车7、8个小时才能到现场，酒店也不好找，来回一次开销不小；所以朋友就想能不能在有问题时，能远程解决问题。\n朋友立刻想到了USB快捷wifi网卡，里面插电话卡的那种，这卡的话费相比每次来回开销毛毛雨。\n眼看网卡到了，兴奋的插上，习惯性打开浏览器….baidu.com,嘿，百度一下。。。成了。。\n就在这时转头看了下系统大屏（实时的），问题出来了，看板数据没有了。\n出于回滚的思路，朋友马上把wifi网卡拔掉了，嘿。。看板数据恢复了。\n基于这两个现象描述，我简单判断为网络层的问题。\n查看全部路由\n接下来我让朋友把wifi卡插上，然后终端route print\n网络目标 网络掩码 网关 接口 跃点数 0.0.0.0 0.0.0.0 192.168.216.1 192.168.216.100 291 0.0.0.0 0.0.0.0 62.65.32.1 62.65.32.94 281 明显“0.0.0.0”两条路由冲突，然后数据到了机器后，机器不知道发给哪个，所以大屏就没有数据了\n查看网卡信息：ifconfig\n以太网适配器 xxx 2: 连接特定的 DNS 后缀 . . . . . . . : 本地链接 IPv6 地址. . . . . . . . : x::x:x:x:x IPv4 地址 . . . . . . . . . . . . : 62.65.32.94 子网掩码 . . . . . . . . . . . . : 255.255.255.128 默认网关. . . . . . . . . . . . . : 62.65.32.1 以太网适配器 以太网: 连接特定的 DNS 后缀 . . . . . . . : 本地链接 IPv6 地址. . . . . . . . : x::x:x:x:x IPv4 地址 . . . . . . . . . . . . : 192.168.216.100 子网掩码 . . . . . . . . . . . . : 255.255.255.0 默认网关. . . . . . . . . . . . . : 192.168.216.1 解决路由冲突：内网和外网使用不同的网卡\n接下来，我让朋友先把wifi的网卡改成静态IP（之前默认是DHCP）\n然后，删除这两条路由：route delete 0.0.0.0\n然后，添加0.0.0.0路由，加-p防止丢失\nroute -p add 0.0.0.0 mask 0.0.0.0 192.168.216.1 然后，添加62.65.0.0路由，同时也加-p\nroute -p add 62.65.0.0 mask 255.255.0.0 62.65.32.1 这时看数据看板就有数据\n但是还没完，还需要访问另外一个系统，ip是172.19.39.99,这个时候又访问不通了，再没有这个wifi网卡时是可以访问的，由此可以得到，之前访问Ip时，默认路由0.0.0.0是内网。此刻默认路由设置的是这个wifi的，所以无法访问到172.19.39.99。\n指定网段走指定网卡\n那么就在加一条172.19.39.0路由\nroute -p add 172.19.39.0 mask 255.255.255.0 62.65.32.1 此时，再使用浏览器，就可以访问了。\n这里是win的OS，其他的OS遇到此类问题同样可以使用此思路。\n扩展\nroute | Microsoft Learn Mac环境需要使用netstat -nr代替route print,具体可以查看Mac OS路由设置常命令 route(8) - Linux manual page (man7.org) ","permalink":"https://blog.codingforjoy.com/posts/%E6%8C%87%E5%AE%9A%E7%BD%91%E6%AE%B5%E8%B7%AF%E7%94%B1%E6%8C%87%E5%AE%9A%E7%BD%91%E5%8D%A1/","summary":"双卡内外任我行：指定网段路由指定网卡","title":"指定网段路由指定网卡"},{"content":"“top”是一个强大的、轻量级的命令行工具，可以提供有关系统范围资源利用率的实时报告。它在各种 Linux 发行版中普遍可用。然而，我们发现它在 Docker 容器中执行时可能无法准确报告信息。这篇文章旨在引起您对这个问题的关注。\ndocker容器中的CPU压力测试 让我们进行一个简单的实验。我们将使用 Ubuntu 映像部署容器并有意增加 CPU 消耗。执行以下命令：\ndocker run -ti --rm --name tmp-limit --cpus=\u0026#34;1\u0026#34; -m=\u0026#34;1G\u0026#34; ubuntu bash -c \u0026#39;apt update; apt install -y stress; stress --cpu 4\u0026#39; 提供的命令执行以下操作：\n使用 Ubuntu 镜像启动容器 将 CPU 限制设置为 1 设置内存限制为1G 执行命令‘apt update; apt install -y 压力； stress –cpu 4’ 进行 CPU 压力测试 主机顶部报告的 CPU 利用率 现在，让我们在运行该 Docker 容器的主机上启动 top 工具。 top工具的输出如下：\n请注意图 1 中的橙色矩形。该指标显示为 25% CPU 利用率，并且它是正确的值。主机有 4 个核心，我们为容器分配了 1 个核心的限制。由于该单个核心已得到充分利用，报告的主机级别 CPU 利用率为 25%（即总核心的 1/4）。\n容器顶部报告的 CPU 利用率 现在，让我们在容器内执行 top 命令。以下是top命令报告的输出：\n请观察图 2 中的橙色矩形。CPU 利用率为 25%，反映了主机的值。然而，从容器的角度来看，这是不准确的，因为它已充分利用其分配的 100% CPU 限制。\n尽管如此，值得注意的是图 2 中列出的过程是准确的。该工具仅正确报告在此容器内运行的进程，并排除整个主机中的进程。\n如何找到容器中准确的CPU利用率？ 在这样的场景下，要获得容器内准确的CPU利用率，有以下几种解决方案：\nDocker 容器统计信息（docker stats）\ndocker stats 命令提供容器级别的基本资源利用率指标。以下是先前启动的容器的“docker stats”的输出：\n请注意图 3 中的橙色矩形。CPU 利用率显示为 100.64%。然而，挑战在于“docker stats”无法在容器内执行（除非将docker套接字传递到容器中，这种情况不常见并且会带来安全风险）。它必须从主机运行。\n容器顾问（cAdvisor）\n您可以利用本质上支持 Docker 容器的 cAdvisor（容器顾问）工具来提供容器级资源利用率指标。\nyCrash\n此外，您还可以选择使用 yCrash 工具，该工具不仅提供容器级指标，还分析应用程序级转储（例如垃圾收集日志、应用程序日志、线程、内存转储等）并提供全面的根原因分析报告。\n结论 虽然“top”是监控系统范围资源利用率的可靠工具，但它在 Docker 容器中的准确性可能会受到影响。这种差异可能会导致对容器性能的误解，尤其是在 CPU 利用率方面。正如我们的实验所示，“top”报告了容器内 25% 的 CPU 使用率，尽管已充分利用了分配的 CPU 限制。\n为了获得 Docker 容器内的精确指标，Docker Container Stats、cAdvisor 和 yCrash 等替代工具可以提供有关资源利用率的宝贵见解。通过利用这些工具，用户可以确保准确监控和优化容器化环境，最终提高性能和运营效率。\n原文：‘top’ reporting accurate metrics within containers?\n","permalink":"https://blog.codingforjoy.com/posts/%E5%A6%82%E4%BD%95%E6%89%BE%E5%88%B0%E5%AE%B9%E5%99%A8%E4%B8%AD%E5%87%86%E7%A1%AE%E7%9A%84cpu%E5%88%A9%E7%94%A8%E7%8E%87/","summary":"TOP 指令怎么准确定位容器的真实情况.","title":"如何找到容器中准确的CPU利用率？"},{"content":" 本文内容是逐段翻译Spring-Framework 1.1.x 官方文档的第5章的内容。通过在翻译阅读过程中去尝试感受当年作者的意境。为什么不是1.0？因为1.1.x才开始有官方文档，之前的版本只有api文档。\n如果你想知道人为什么要这么搞，那么应该去看书/文档；如果你要知道让机器干了什么？那你应该看代码！——左耳朵耗子\n5.1. 概念 面向方面编程（AOP）提供了另一种思考程序结构的方法，是对 OOP 的补充。OO 将应用程序分解为对象的层次结构，而 AOP 则将程序分解为方面或关注点。这样就能将事务管理等关注点模块化，否则这些关注点就会跨越多个对象。(这类问题通常被称为交叉问题）。\nSpring 中使用了 AOP：\n提供声明式企业服务，尤其是作为 EJB 声明式服务的替代。其中最重要的服务是声明式事务管理，它建立在 Spring 的事务抽象之上。 允许用户实现自定义 AOP，用 AOP 补充他们对 OOP 的使用。 因此，您可以将 Spring AOP 视为一种使能技术，它允许 Spring 在不使用 EJB 的情况下提供声明式事务管理；或者使用 Spring AOP 框架的全部功能来实现自定义方面。\n如果你只对通用声明式服务或其他预打包的声明式中间件服务（如池化服务）感兴趣，就不需要直接使用 Spring AOP，也可以跳过本章的大部分内容。\n5.1.1. AOP 概念 首先，让我们定义一些 AOP 核心概念。这些术语并非 Spring 特有。遗憾的是，AOP 术语并不特别直观。不过，如果 Spring 使用自己的术语，那就更令人困惑了。\n切面(Aspect)：模块化的关注点，否则其实现可能会跨越多个对象。在 J2EE 应用程序中，事务管理就是横切关注点的一个很好的例子。方面使用 Spring 作为顾问或拦截器来实现。 连接点(Joinpoint)：程序执行过程中的点，如方法调用或抛出的特定异常。在 Spring AOP 中，连接点总是方法调用。Spring 并未在显著位置使用连接点一词；连接点信息可通过传递给拦截器的 MethodInvocation 参数上的方法访问，并通过 org.springframework.aop.Pointcut 接口的实现进行评估。 通知(Advice)：AOP 框架在特定连接点采取的行动。不同类型的通知包括 \u0026ldquo;around\u0026rdquo;、\u0026ldquo;before\u0026rdquo;、\u0026ldquo;throws\u0026rdquo; 通知。下面将讨论通知类型。包括 Spring 在内的许多 AOP 框架都将通知建模为拦截器，并在连接点 \u0026ldquo;around\u0026quot;维护一连串拦截器。 切点(Pointcut)：一组连接点，指定何时应触发通知。AOP 框架必须允许开发人员指定切点：例如，使用正则表达式。 引介(Introduction)：为通知类添加方法或字段。Spring 允许你为任何通知对象引介新的接口。例如，你可以使用导言让任何对象实现 IsModified 接口，以简化缓存。 目标对象(Target object)：包含连接点的对象。也称为通知对象或代理对象。 AOP 代理(AOP proxy)：由 AOP 框架创建的对象，包括通知。在 Spring 中，AOP 代理将是 JDK 动态代理或 CGLIB 代理。 编织(Weaving)：组装各个方面以创建通知对象。这可以在编译时完成（例如使用 AspectJ 编译器），也可以在运行时完成。Spring 和其他纯 Java AOP 框架一样，在运行时执行编织。 不同的通知类型包括：\n环绕通知(Around advice)：围绕连接点（如方法调用）的通知。这是最强大的一种通知。环绕通知将在方法调用前后执行自定义行为。它们负责选择是继续执行连接点，还是通过返回自己的返回值或抛出异常来快捷执行。 前置通知(Before advice)：在连接点之前执行的通知，但不能阻止执行流进入连接点（除非抛出异常）。 异常通知(Throws advice)：在方法抛出异常时执行的通知。Spring 提供了强类型的抛出通知，因此您可以编写代码来捕获您感兴趣的异常（和子类），而无需从 Throwable 或 Exception 进行转换。 后置通知(After returning advice)：在连接点正常完成后执行的通知：例如，如果方法返回时没有抛出异常。 环绕通知是最通用的通知。大多数基于拦截的 AOP 框架（如 Nanning Aspects）只提供围绕通知。\n与 AspectJ 一样，Spring 也提供了各种通知类型，因此我们通知您使用功能最少的通知类型来实现所需的行为。例如，如果您只需要用一个方法的返回值更新缓存，那么您最好使用 after returning advice，而不是 around advice，尽管 around advice 也能实现相同的功能。\n使用最特殊的通知类型可以提供更简单的编程模型，减少出错的可能性。例如，您不需要在用于环绕通知的 MethodInvocation 上调用 proceed() 方法，因此也不会调用失败。\n切点概念是 AOP 的关键所在，它将 AOP 与提供拦截功能的旧技术区分开来。切点分使通知的目标不受 OO 层次结构的影响。例如，提供声明式事务管理的环绕通知可应用于跨越多个对象的一组方法。因此，快捷方式提供了 AOP 的结构元素。\n5.1.2. Spring AOP 的功能和目标 Spring AOP 是用纯 Java 实现的。不需要特殊的编译过程。Spring AOP 无需控制类加载器的层次结构，因此适合在 J2EE 网络容器或应用服务器中使用。\nSpring 目前支持方法调用的拦截。虽然可以在不破坏 Spring AOP 核心 API 的情况下添加对字段拦截的支持，但并未实现字段拦截。\n字段拦截可以说违反了 OO 封装。我们认为这在应用程序开发中是不明智的。如果需要字段拦截，请考虑使用 AspectJ。\nSpring 提供了表示指向和不同通知类型的类。Spring 使用 advisor 这个术语来表示代表一个切面的对象，包括通知和针对特定连接点的点切入。\n不同的通知类型包括 MethodInterceptor（来自 AOP 联盟拦截 API）和 org.springframework.aop 包中定义的通知接口。所有通知都必须实现 org.aopalliance.aop.Advice 标签接口。开箱即支持的通知包括 MethodInterceptor ；ThrowsAdvice ；BeforeAdvice 和 AfterReturningAdvice。下面我们将详细讨论通知类型。\nSpring 实现了 AOP 联盟拦截接口 (http://www.sourceforge.net/projects/aopalliance)。环绕通知必须实现 AOP 联盟 org.aopalliance.intercept.MethodInterceptor 接口。该接口的实现可在 Spring 或任何其他符合 AOP 联盟的实现中运行。目前，JAC 实现了 AOP 联盟接口，Nanning 和 Dynaop 也可能在 2004 年初实现。\nSpring 的 AOP 方法与大多数其他 AOP 框架不同。其目的不是提供最完整的 AOP 实现（尽管 Spring AOP 的能力很强），而是提供 AOP 实现与 Spring IoC 之间的紧密集成，以帮助解决企业应用中的常见问题。\n因此，举例来说，Spring 的 AOP 功能通常与 Spring IoC 容器结合使用。AOP 通知使用普通的 bean 定义语法指定（尽管这允许强大的 \u0026ldquo;自动代理\u0026rdquo; 功能）；通知和指向本身由 Spring IoC 管理：这是与其他 AOP 实现的一个重要区别。有些事情是 Spring AOP 无法轻松或高效完成的，例如向非常细粒度的对象提供通知。在这种情况下，AspectJ 可能是最佳选择。不过，根据我们的经验，Spring AOP 可以很好地解决 J2EE 应用程序中适合使用 AOP 的大多数问题。\nSpring AOP 绝不会为了提供全面的 AOP 解决方案而与 AspectJ 或 AspectWerkz 竞争。我们认为，Spring 等基于代理的框架和 AspectJ 等全面的框架都很有价值，它们是互补的，而不是竞争的。因此，Spring 1.1 的首要任务是将 Spring AOP 和 IoC 与 AspectJ 无缝集成，以便在基于 Spring 的一致应用架构中满足 AOP 的所有用途。这种集成不会影响 Spring AOP API 或 AOP Alliance API；Spring AOP 将保持向后兼容。\n5.1.3. Spring 中的 AOP 代理 Spring 默认将 J2SE 动态代理（JDK代理）用于 AOP 代理。这样就可以代理任何接口或接口集。\nSpring 还可以使用 CGLIB 代理。这是代理类而非接口所必需的。如果业务对象没有实现接口，则默认使用 CGLIB。由于针对接口而非类编程是一种良好的做法，因此业务对象通常会实现一个或多个业务接口。\n可以强制使用 CGLIB：我们将在下文讨论，并解释为什么要这样做。\n在 Spring 1.0 之后，Spring 可能会提供更多类型的 AOP 代理，包括完全生成的类。这不会影响编程模型。\n5.2. Spring 中的切点（Pointcut） 让我们看看 Spring 是如何处理关键的点切概念的。\n5.2.1. 概念 Spring 的快捷方式模型实现了与通知类型无关的快捷方式重用。使用相同的快捷方式可以针对不同的通知。\norg.springframework.aop.Pointcut接口是中心接口，用于向特定类和方法提供通知。完整的接口如下所示：\npublic interface Pointcut { ClassFilter getClassFilter(); MethodMatcher getMethodMatcher(); } 将 Pointcut 接口拆分为两部分，可以重复使用类和方法匹配部分，以及细粒度的组合操作（例如与另一个方法匹配器执行 \u0026ldquo;union\u0026rdquo;）。\nClassFilter 接口用于将点切限制在一组给定的目标类上。如果 matches() 方法总是返回 true，那么所有目标类都将被匹配：\npublic interface ClassFilter { boolean matches(Class clazz); } MethodMatcher 接口通常更为重要。完整的接口如下所示：\npublic interface MethodMatcher { boolean matches(Method m, Class targetClass); boolean isRuntime(); boolean matches(Method m, Class targetClass, Object[] args); } matches(Method, Class) 方法用于测试该快捷方式是否与目标类上的给定方法相匹配。该评估可在创建 AOP 代理时执行，以避免每次调用方法时都要进行测试。如果给定方法的 2 参数匹配方法返回 true，且 MethodMatcher 的 isRuntime() 方法返回 true，那么每次方法调用时都会调用 3 参数匹配方法。这样，在执行目标通知之前，点切口就能立即查看传递给方法调用的参数。\n大多数方法匹配器都是静态的，这意味着它们的 isRuntime() 方法会返回 false。在这种情况下，3 参数匹配方法永远不会被调用。\n如果可能的话，尽量让点切分成为静态的，让 AOP 框架在创建 AOP 代理时缓存点切分的评估结果。\n5.2.2. 切点操作 Spring 支持点切分操作：尤其是联合和相交。\n联合是指任一指向匹配的方法。\n交集是指两个点切入相匹配的方法。\n联合通常更有用。\n可以使用org.springframework.aop.support.Pointcuts类中的静态方法，或使用同一软件包中的 ComposablePointcut 类来组成快捷方式。\n5.2.3. 方便的切点实现 Spring 提供了几种方便的点切实现。有些可以开箱即用，有些则需要在特定于应用程序的点切入中进行子类化。\n5.2.3.1. 静态切点 静态快捷方式基于方法和目标类，不能考虑方法的参数。静态快捷方式对于大多数应用来说都是足够的，也是最好的。Spring 只需在首次调用方法时评估一次静态快捷方式即可：此后，每次调用方法时都无需再次评估快捷方式。\n让我们来看看 Spring 包含的一些静态点切实现。\n5.2.3.1.1. 正则表达式关键字 正则表达式是实现特定静态快捷方式的一种显而易见的方法。org.springframework.aop.support.RegexpMethodPointcut 是一种使用 Perl 5 正则表达式语法的通用正则表达式快捷方式。\n使用该类，您可以提供一系列模式字符串。如果其中任何一个匹配，则点切线的值将为 true（因此结果实际上就是这些点切线的组合）。\n使用方法如下：\n\u0026lt;bean id=\u0026#34;settersAndAbsquatulatePointcut\u0026#34; class=\u0026#34;org.springframework.aop.support.RegexpMethodPointcut\u0026#34;\u0026gt; \u0026lt;property name=\u0026#34;patterns\u0026#34;\u0026gt; \u0026lt;list\u0026gt; \u0026lt;value\u0026gt;.*get.*\u0026lt;/value\u0026gt; \u0026lt;value\u0026gt;.*absquatulate\u0026lt;/value\u0026gt; \u0026lt;/list\u0026gt; \u0026lt;/property\u0026gt; \u0026lt;/bean\u0026gt; RegexpMethodPointcut 的方便子类 RegexpMethodPointcutAdvisor 也允许我们引用通知。(请记住，Advice 可以是拦截器、before advice、throws advice 等）这简化了布线，因为一个 bean 既是快捷方式，又是顾问，如下所示：\n\u0026lt;bean id=\u0026#34;settersAndAbsquatulateAdvisor\u0026#34; class=\u0026#34;org.springframework.aop.support.RegexpMethodPointcutAdvisor\u0026#34;\u0026gt; \u0026lt;property name=\u0026#34;advice\u0026#34;\u0026gt; \u0026lt;ref local=\u0026#34;beanNameOfAopAllianceInterceptor\u0026#34;/\u0026gt; \u0026lt;/property\u0026gt; \u0026lt;property name=\u0026#34;patterns\u0026#34;\u0026gt; \u0026lt;list\u0026gt; \u0026lt;value\u0026gt;.*get.*\u0026lt;/value\u0026gt; \u0026lt;value\u0026gt;.*absquatulate\u0026lt;/value\u0026gt; \u0026lt;/list\u0026gt; \u0026lt;/property\u0026gt; \u0026lt;/bean\u0026gt; RegexpMethodPointcutAdvisor 可用于任何通知类型。\nRegexpMethodPointcut 类需要 Jakarta ORO 正则表达式包。\n5.2.3.1.2. 属性驱动的指向 静态快捷方式的一个重要类型是元数据驱动快捷方式。它使用元数据属性的值：通常是源代码级元数据。\n5.2.3.2. 动态切点 与静态快捷方式相比，动态快捷方式的评估成本更高。它们会考虑方法参数以及静态信息。这意味着每次调用方法时都必须对其进行评估；由于参数会发生变化，因此无法缓存评估结果。\n主要的例子是控制流快捷方式。\n5.2.3.2.1. 控制流指向 Spring 控制流快捷方式在概念上类似于 AspectJ cflow 快捷方式，但功能没那么强大。(目前还无法指定一个快捷方式在另一个快捷方式下面执行）。控制流快捷方式与当前的调用堆栈相匹配。例如，如果连接点被 com.mycompany.web 包中的方法或 SomeCaller 类调用，它就会触发。控制流快捷方式使用 org.springframework.aop.support.ControlFlowPointcut 类指定。\n注意：在运行时对控制流指向进行评估的成本甚至要比其他动态指向高得多。在 Java 1.4 中，成本约为其他动态指向式的 5 倍；在 Java 1.3 中则超过 10 倍。\n5.2.4. 切点的超类 Spring 提供了有用的快捷方式超类，帮助你实现自己的快捷方式。\n由于静态快捷方式最为有用，因此您可能会如下图所示对 StaticMethodMatcherPointcut 进行子类化。这只需要实现一个抽象方法（当然也可以覆盖其他方法来定制行为）：\nclass TestStaticPointcut extends StaticMethodMatcherPointcut { public boolean matches(Method m, Class targetClass) { // return true if custom criteria match } } 此外，还有用于动态插入点的超类。\n在 Spring 1.0 RC2 及以上版本中，您可以在任何通知类型中使用自定义点切入。\n5.2.5. 自定义切点 由于 Spring 中的切点是 Java 类，而不是语言特性（如 AspectJ），因此可以声明自定义切点，无论是静态还是动态切点。不过，Spring 并不支持用 AspectJ 语法编码的复杂点切分表达式。不过，Spring 中的自定义快捷方式可以任意复杂。\nSpring 的后续版本可能会支持 JAC 提供的 \u0026ldquo;语义指向\u0026rdquo;：例如，\u0026ldquo;改变目标对象中实例变量的所有方法\u0026rdquo;。\n5.3. Spring 中的通知类型 现在让我们看看 Spring AOP 是如何处理建通知的。\n5.3.1. 通知生命周期 Spring 通知可以在所有通知对象中共享，也可以为每个通知对象独有。这相当于按类或按实例提供通知。\n最常用的是按类通知。它适用于通用通知，如事务顾问。这些通知不依赖于被代理对象的状态，也不添加新的状态；它们只是作用于方法和参数。\n每实例通知适用于引介，以支持混合体。在这种情况下，通知会为代理对象添加状态。\n在同一个 AOP 代理中，可以混合使用共享通知和按实例通知。\n5.3.2. Spring 中的通知类型 Spring 提供了几种开箱即用的通知类型，并可扩展以支持任意通知类型。让我们来看看基本概念和标准通知类型。\n5.3.2.1. Around 通知的拦截 Spring 中最基本的通知类型是围绕通知的拦截。\nSpring 符合 AOP 联盟关于使用方法拦截的环绕通知的接口。实现环绕通知的方法拦截器应实现以下接口：\npublic interface MethodInterceptor extends Interceptor { Object invoke(MethodInvocation invocation) throws Throwable; } invoke() 方法的 MethodInvocation 参数公开了被调用的方法、目标连结点、AOP 代理以及方法的参数。invoke() 方法应返回调用的结果：连接点的返回值。\n一个简单的 MethodInterceptor 实现如下：\npublic class DebugInterceptor implements MethodInterceptor { public Object invoke(MethodInvocation invocation) throws Throwable { System.out.println(\u0026#34;Before: invocation=[\u0026#34; + invocation + \u0026#34;]\u0026#34;); Object rval = invocation.proceed(); System.out.println(\u0026#34;Invocation returned\u0026#34;); return rval; } } 注意对 MethodInvocation 的 proceed() 方法的调用。该方法在拦截器链中向下进行，直到连接点。大多数拦截器都会调用此方法，并返回其返回值。然而，MethodInterceptor 与其他拦截器一样，可以返回不同的值或抛出异常，而不是调用 proceed 方法。但是，如果没有充分的理由，您不希望这样做！\nMethodInterceptors 提供了与其他 AOP 联盟兼容的 AOP 实现的互操作性。本节余下部分讨论的其他通知类型以 Spring 特有的方式实现了常见的 AOP 概念。虽然使用最具体的通知类型有其优势，但如果您可能希望在其他 AOP 框架中运行该方面，请坚持环绕通知使用 MethodInterceptor。请注意，目前各框架之间还不能互操作，而且 AOP 联盟目前也没有定义点切接口。\n5.3.2.2. Before 通知 更简单的通知类型是Before 通知。它不需要 MethodInvocation 对象，因为它只会在进入方法之前被调用。\nBefore 通知的主要优点是不需要调用 proceed() 方法，因此也就不会在无意中导致拦截器链无法继续。\nMethodBeforeAdvice 接口如下所示。(Spring框架的API设计理论上支持在字段操作之前提供“前置通知”（field before advice），也就是说，可以在字段被访问或修改之前执行一些自定义的逻辑。然而，通常情况下，Spring框架主要用于处理方法的拦截（field interception），而不是字段的拦截。因此，尽管技术上Spring的API允许这种设计，但实际上Spring框架不太可能实现这种字段级别的前置通知功能。简而言之，Spring的API设计上是支持字段级别的前置通知的，但实际上Spring更倾向于处理方法级别的拦截，并且不太可能去实现字段级别的拦截功能）。\npublic interface MethodBeforeAdvice extends BeforeAdvice { void before(Method m, Object[] args, Object target) throws Throwable; } 请注意，返回类型是 void。Before 通知可以在连接点执行前插入自定义行为，但不能更改返回值。如果 before 通知抛出异常，将中止拦截器链的进一步执行。异常会沿着拦截器链向上传播。如果该异常未被选中，或在被调用方法的签名上，它将直接传递给客户端；否则，它将被 AOP 代理封装在一个未被选中的异常中。\n在 Spring 中，before 通知是一个示例，它会统计所有正常返回的方法：\npublic class CountingBeforeAdvice implements MethodBeforeAdvice { private int count; public void before(Method m, Object[] args, Object target) throws Throwable { ++count; } public int getCount() { return count; } } Before 通知可用于任何快捷方式。\n5.3.2.3. Throws 通知 如果连接点抛出异常，则在连接点返回后调用 Throws 通知。Spring 提供类型化的抛出通知。请注意，这意味着 org.springframework.aop.ThrowsAdvice 接口不包含任何方法：它是一个标签接口，用于标识给定对象实现了一个或多个类型化抛出提示方法。这些方法应为\nafterThrowing([Method], [args], [target], subclassOfThrowable) 只需要最后一个参数。因此，根据通知方法是否对方法和参数感兴趣，可以有一到四个参数。以下是抛出通知的示例。\n如果抛出 RemoteException（包括子类），将调用此通知：\npublic class RemoteThrowsAdvice implements ThrowsAdvice { public void afterThrowing(RemoteException ex) throws Throwable { // Do something with remote exception } } 如果抛出 ServletException，将调用以下通知。与上述通知不同的是，它声明了 4 个参数，因此可以访问调用的方法、方法参数和目标对象：\npublic static class ServletThrowsAdviceWithArguments implements ThrowsAdvice { public void afterThrowing(Method m, Object[] args, Object target, ServletException ex) { // Do something will all arguments } } 最后一个示例说明了如何在处理 RemoteException 和 ServletException 的单个类中使用这两种方法。在一个类中可以组合任意数量的抛出通知方法。\npublic static class CombinedThrowsAdvice implements ThrowsAdvice { public void afterThrowing(RemoteException ex) throws Throwable { // Do something with remote exception } public void afterThrowing(Method m, Object[] args, Object target, ServletException ex) { // Do something will all arguments } } 抛出通知可与任何切点一起使用。\n5.3.2.4. After Returning 通知 Spring 中的返回后通知必须实现org.springframework.aop.AfterReturningAdvice接口，如下所示：\npublic interface AfterReturningAdvice extends Advice { void afterReturning(Object returnValue, Method m, Object[] args, Object target) throws Throwable; } 返回后通知可以访问返回值（不能修改）、调用方法、方法参数和目标。\n下面的返回通知会统计所有未抛出异常的成功方法调用次数：\npublic class CountingAfterReturningAdvice implements AfterReturningAdvice { private int count; public void afterReturning(Object returnValue, Method m, Object[] args, Object target) throws Throwable { ++count; } public int getCount() { return count; } } 这个通知不会改变执行路径。如果出现异常，异常将被抛向拦截器链，而不是返回值。\n返回通知后，可与任何切点一起使用。\n5.3.2.5. Introduction 通知 Spring 将引介通知视为一种特殊的拦截通知。\n引介需要一个IntroductionAdvisor和一个IntroductionInterceptor，实现下面的接口：\npublic interface IntroductionInterceptor extends MethodInterceptor { boolean implementsInterface(Class intf); } 从 AOP 联盟 MethodInterceptor 接口继承的 invoke() 方法必须实现引介：也就是说，如果被调用的方法位于引介接口上，则引介拦截器负责处理方法调用\u0026ndash;它不能调用 proceed()。\n引介通知不能与任何点切点一起使用，因为它只适用于类而非方法级别。只有InterceptionIntroductionAdvisor才能使用引介通知，它有以下方法：\npublic interface InterceptionIntroductionAdvisor extends InterceptionAdvisor { ClassFilter getClassFilter(); IntroductionInterceptor getIntroductionInterceptor(); Class[] getInterfaces(); } 没有 MethodMatcher，因此也没有与导入通知相关的 Pointcut。只有类过滤才符合逻辑。\ngetInterfaces() 方法返回该顾问引介的接口。\n让我们看看 Spring 测试套件中的一个简单示例。假设我们要为一个或多个对象引介以下接口：\npublic interface Lockable { void lock(); void unlock(); boolean locked(); } 这说明了一个 mixin。我们希望能够将通知对象转换为 Lockable，无论其类型如何，并调用锁定和解锁方法。如果我们调用 lock() 方法，我们希望所有设置器方法都抛出 LockedException。这样，我们就可以添加一个方面，提供使对象不可变的能力，而对象对此一无所知：这就是 AOP 的一个很好的例子。\n首先，我们需要一个 IntroductionInterceptor 来完成繁重的工作。在这种情况下，我们扩展 org.springframework.aop.support.DelegatingIntroductionInterceptor 方便类。我们可以直接实现 IntroductionInterceptor，但在大多数情况下，使用 DelegatingIntroductionInterceptor 是最好的选择。\n委托引介拦截器（DelegatingIntroductionInterceptor）旨在将引介委托给引介接口的实际实现，同时隐藏拦截的使用。可以使用构造函数参数将委托设置为任何对象；默认委托（使用无参数构造函数时）是 this。因此，在下面的示例中，委托是 DelegatingIntroductionInterceptor 的 LockMixin 子类。给定一个委托（默认情况下本身就是），DelegatingIntroductionInterceptor 实例会查找委托实现的所有接口（IntroductionInterceptor 除外），并支持针对其中任何接口的引介。LockMixin 等子类可以调用 suppressInterflace(Class intf) 方法来抑制不应暴露的接口。然而，无论引介拦截器准备支持多少接口，所使用的引介拦截器都将控制实际暴露的接口。引介的接口将隐藏目标程序对同一接口的任何实现。\n因此，LockMixin 子类化了 DelegatingIntroductionInterceptor 并实现了 Lockable 本身。超类会自动识别出 Lockable 可用于支持引介，因此我们不需要指定这一点。我们可以用这种方法引介任意数量的接口。\n请注意锁定实例变量的使用。这实际上是在目标对象的状态之外添加了额外的状态。\npublic class LockMixin extends DelegatingIntroductionInterceptor implements Lockable { private boolean locked; public void lock() { this.locked = true; } public void unlock() { this.locked = false; } public boolean locked() { return this.locked; } public Object invoke(MethodInvocation invocation) throws Throwable { if (locked() \u0026amp;\u0026amp; invocation.getMethod().getName().indexOf(\u0026#34;set\u0026#34;) == 0) throw new LockedException(); return super.invoke(invocation); } } 通常没有必要重载 invoke() 方法：DelegatingIntroductionInterceptor 实现\u0026ndash;如果方法被引介，则调用委托方法，否则向连接点前进\u0026ndash;通常就足够了。在本例中，我们需要添加一个检查：如果处于锁定模式，则不能调用 setter 方法。\n所需的引介顾问很简单。它所需要做的就是持有一个不同的 LockMixin 实例，并指定引介的接口\u0026ndash;在本例中，只是 Lockable。更复杂的示例可能需要引用引介拦截器（将被定义为原型）：在这种情况下，没有与 LockMixin 相关的配置，因此我们只需使用 new 来创建它。\npublic class LockMixinAdvisor extends DefaultIntroductionAdvisor { public LockMixinAdvisor() { super(new LockMixin(), Lockable.class); } } 我们可以非常简单地应用这个顾问：它不需要任何配置。(不过，配置是必要的：没有 IntroductionAdvisor 就无法使用 IntroductionInterceptor）。与导入程序一样，该顾问必须按实例设置，因为它是有状态的。我们需要为每个通知对象创建不同的 LockMixinAdvisor 实例，因此也需要为每个通知对象创建不同的 LockMixin 实例。顾问包含被通知对象的部分状态。\n我们可以使用 Advised.addAdvisor() 方法或（推荐方法）XML 配置，像其他顾问一样，以编程方式应用该顾问。下面讨论的所有代理创建选择，包括 \u0026ldquo;自动代理创建器\u0026rdquo;，都能正确处理介绍和有状态的混合体。\n5.4. Spring 中的 Advisor 在 Spring 中，Advisor是对切面编程的一种模块化封装。Advisor通常包含一个通知和一个切点。\norg.springframework.aop.support.DefaultPointcutAdvisor 是最常用的顾问类。例如，它可与 MethodInterceptor、BeforeAdvice 或 ThrowsAdvice 一起使用。\n在同一个 AOP 代理中，可以混合使用 Spring 中的Advisor和通知类型。例如，您可以在一个代理配置中使用环绕通知、抛出通知和在通知之前的拦截：Spring 会自动创建必要的创建拦截器链。\n5.5. 使用 ProxyFactoryBean 创建 AOP 代理 如果您正在使用 Spring IoC 容器（ApplicationContext 或 BeanFactory）来处理业务对象，那么您应该使用 Spring 的 AOP FactoryBean。(请记住，工厂 Bean 引入了一层间接层，使其能够创建不同类型的对象）。\n在 Spring 中创建 AOP 代理的基本方法是使用 org.springframework.aop.framework.ProxyFactoryBean。这可以完全控制将应用的指向和建议，以及它们的排序。不过，如果你不需要这样的控制，还有更简单的选择。\n5.5.1. 基础知识 ProxyFactoryBean与其他 Spring FactoryBean实现一样，引入了一定程度的间接性。如果你定义了一个名称为 foo 的 ProxyFactoryBean，那么引用 foo 的对象看到的并不是ProxyFactoryBean实例本身，而是由ProxyFactoryBean 实现的getObject()方法创建的一个对象。该方法将创建一个封装目标对象的 AOP 代理。\n使用 ProxyFactoryBean 或其他 IoC 感知类来创建 AOP 代理的一个最重要的好处是：这意味着通知和指向也可以由 IoC 来管理。这是一个强大的功能，可以实现其他 AOP 框架难以实现的某些方法。例如，通知本身可以引用应用程序对象（除目标对象外，任何 AOP 框架都应提供目标对象），从而受益于依赖注入提供的所有可插拔性。\n5.5.2. JavaBean 属性 与 Spring 提供的大多数 FactoryBean 实现一样，ProxyFactoryBean 本身也是一个 JavaBean。其属性用于\n指定要代理的目标 指定是否使用 CGLIB 一些关键属性继承自org.springframework.aop.framework.ProxyConfig：所有 AOP 代理工厂的超类。这些属性包括：\nproxyTargetClass：如果我们应该代理目标类而不是其接口，则为 true。如果为 true，我们就需要使用 CGLIB。 optimize：是否对创建的代理进行积极优化。除非了解相关 AOP 代理如何处理优化，否则不要使用此设置。此设置目前仅用于 CGLIB 代理；对 JDK 动态代理（默认值）没有影响。 frozen：配置代理工厂后，是否禁止更改通知。默认为 false。 exposeProxy：是否应在 ThreadLocal 中公开当前代理，以便目标可以访问它。(如果目标需要获取代理，且 exposeProxy 为 true，则目标可以使用 AopContext.currentProxy() 方法。 aopProxyFactory：AopProxyFactory 的实现。提供了一种自定义使用动态代理、CGLIB 或其他代理策略的方法。默认实现将适当选择动态代理或 CGLIB。应该不需要使用此属性；它的目的是允许在 Spring 1.1 中添加新的代理类型。 ProxyFactoryBean 特有的其他属性包括：\nproxyInterfaces：字符串接口名称数组。如果没有提供，将使用目标类的 CGLIB 代理 interceptorNames：字符串数组，包含要应用的顾问、拦截器或其他建议名称。排序很重要。这些名称是当前工厂中的 Bean 名称，包括来自祖先工厂的 Bean 名称。 singleton：无论 getObject() 方法被调用多少次，工厂是否都应返回一个对象。一些 FactoryBean 实现提供了这种方法。默认值为 true。如果要使用有状态的建议（例如，有状态的混合体），请使用原型建议，并将单例值设为 false。 5.5.3. 代理接口 让我们来看一个 ProxyFactoryBean 运行的简单示例。该示例涉及：\n将被代理的目标 Bean。这就是下面示例中的 \u0026ldquo;personTarget \u0026ldquo;Bean 定义。\n通知器和拦截器用于提供建议。\nAOP 代理 Bean 定义指定了目标对象（personTarget Bean）和要代理的接口，以及要应用的通知。\n\u0026lt;bean id=\u0026#34;personTarget\u0026#34; class=\u0026#34;com.mycompany.PersonImpl\u0026#34;\u0026gt; \u0026lt;property name=\u0026#34;name\u0026#34;\u0026gt;\u0026lt;value\u0026gt;Tony\u0026lt;/value\u0026gt;\u0026lt;/property\u0026gt; \u0026lt;property name=\u0026#34;age\u0026#34;\u0026gt;\u0026lt;value\u0026gt;51\u0026lt;/value\u0026gt;\u0026lt;/property\u0026gt; \u0026lt;/bean\u0026gt; \u0026lt;bean id=\u0026#34;myAdvisor\u0026#34; class=\u0026#34;com.mycompany.MyAdvisor\u0026#34;\u0026gt; \u0026lt;property name=\u0026#34;someProperty\u0026#34;\u0026gt;\u0026lt;value\u0026gt;Custom string property value\u0026lt;/value\u0026gt;\u0026lt;/property\u0026gt; \u0026lt;/bean\u0026gt; \u0026lt;bean id=\u0026#34;debugInterceptor\u0026#34; class=\u0026#34;org.springframework.aop.interceptor.DebugInterceptor\u0026#34;\u0026gt; \u0026lt;/bean\u0026gt; \u0026lt;bean id=\u0026#34;person\u0026#34; class=\u0026#34;org.springframework.aop.framework.ProxyFactoryBean\u0026#34;\u0026gt; \u0026lt;property name=\u0026#34;proxyInterfaces\u0026#34;\u0026gt;\u0026lt;value\u0026gt;com.mycompany.Person\u0026lt;/value\u0026gt;\u0026lt;/property\u0026gt; \u0026lt;property name=\u0026#34;target\u0026#34;\u0026gt;\u0026lt;ref local=\u0026#34;personTarget\u0026#34;/\u0026gt;\u0026lt;/property\u0026gt; \u0026lt;property name=\u0026#34;interceptorNames\u0026#34;\u0026gt; \u0026lt;list\u0026gt; \u0026lt;value\u0026gt;myAdvisor\u0026lt;/value\u0026gt; \u0026lt;value\u0026gt;debugInterceptor\u0026lt;/value\u0026gt; \u0026lt;/list\u0026gt; \u0026lt;/property\u0026gt; \u0026lt;/bean\u0026gt; 请注意：interceptorNames 属性包含一个字符串列表：当前工厂中拦截器或通知器的bean名称。可以使用通知器、拦截器、返回前、返回后和抛出通知对象。通知器的排序很重要。\n您可能想知道为什么列表中不包含 Bean 引用？原因是如果 ProxyFactoryBean 的单例属性设置为 false，它就必须能够返回独立的代理实例。如果任何顾问本身就是一个原型，那么就需要返回一个独立的实例，因此必须能够从工厂获得原型的实例；仅持有引用是不够的。\n上面的 \u0026ldquo;person \u0026ldquo;Bean 定义可以用来代替 \u0026ldquo;Person \u0026ldquo;的实现，如下所示：\nPerson person = (Person) factory.getBean(\u0026#34;person\u0026#34;); 同一 IoC 上下文中的其他 Bean 可以表达对它的强类型依赖，就像对普通 Java 对象一样：\n\u0026lt;bean id=\u0026#34;personUser\u0026#34; class=\u0026#34;com.mycompany.PersonUser\u0026#34;\u0026gt; \u0026lt;property name=\u0026#34;person\u0026#34;\u0026gt;\u0026lt;ref local=\u0026#34;person\u0026#34; /\u0026gt;\u0026lt;/property\u0026gt; \u0026lt;/bean\u0026gt; 本例中的 PersonUser 类将公开 Person 类型的属性。就其本身而言，AOP 代理可以透明地代替 \u0026ldquo;真正\u0026rdquo; Person 的实现。不过，它的类将是一个动态代理类。我们可以将其转换为 Advised 接口（下文将讨论）。\n使用匿名内部 Bean 可以隐藏目标和代理之间的区别，如下所示。只有 ProxyFactoryBean 的定义是不同的；包含建议只是为了完整：\n\u0026lt;bean id=\u0026#34;myAdvisor\u0026#34; class=\u0026#34;com.mycompany.MyAdvisor\u0026#34;\u0026gt; \u0026lt;property name=\u0026#34;someProperty\u0026#34;\u0026gt;\u0026lt;value\u0026gt;Custom string property value\u0026lt;/value\u0026gt;\u0026lt;/property\u0026gt; \u0026lt;/bean\u0026gt; \u0026lt;bean id=\u0026#34;debugInterceptor\u0026#34; class=\u0026#34;org.springframework.aop.interceptor.DebugInterceptor\u0026#34;\u0026gt; \u0026lt;/bean\u0026gt; \u0026lt;bean id=\u0026#34;person\u0026#34; class=\u0026#34;org.springframework.aop.framework.ProxyFactoryBean\u0026#34;\u0026gt; \u0026lt;property name=\u0026#34;proxyInterfaces\u0026#34;\u0026gt;\u0026lt;value\u0026gt;com.mycompany.Person\u0026lt;/value\u0026gt;\u0026lt;/property\u0026gt; \u0026lt;!-- Use inner bean, not local reference to target --\u0026gt; \u0026lt;property name=\u0026#34;target\u0026#34;\u0026gt; \u0026lt;bean class=\u0026#34;com.mycompany.PersonImpl\u0026#34;\u0026gt; \u0026lt;property name=\u0026#34;name\u0026#34;\u0026gt;\u0026lt;value\u0026gt;Tony\u0026lt;/value\u0026gt;\u0026lt;/property\u0026gt; \u0026lt;property name=\u0026#34;age\u0026#34;\u0026gt;\u0026lt;value\u0026gt;51\u0026lt;/value\u0026gt;\u0026lt;/property\u0026gt; \u0026lt;/bean\u0026gt; \u0026lt;/property\u0026gt; \u0026lt;property name=\u0026#34;interceptorNames\u0026#34;\u0026gt; \u0026lt;list\u0026gt; \u0026lt;value\u0026gt;myAdvisor\u0026lt;/value\u0026gt; \u0026lt;value\u0026gt;debugInterceptor\u0026lt;/value\u0026gt; \u0026lt;/list\u0026gt; \u0026lt;/property\u0026gt; \u0026lt;/bean\u0026gt; 这样做的好处是只有一个 Person 类型的对象：如果我们想防止应用程序上下文中的用户获取对未建议对象的引用，或者需要避免与 Spring IoC 自动布线产生任何歧义，这样做就很有用。可以说，ProxyFactoryBean 定义自成一体也是一个优点。不过，在某些时候，从工厂中获取未建议的目标可能实际上是一种优势：例如，在某些测试场景中。\n5.5.4. 代理类 如果需要代理一个类，而不是一个或多个接口，该怎么办？\n想象一下，在我们上面的示例中，没有 Person 接口：我们需要建议一个名为 Person 的类，该类没有实现任何业务接口。在这种情况下，可以将 Spring 配置为使用 CGLIB 代理，而不是动态代理。只需将上述 ProxyFactoryBean 上的 proxyTargetClass 属性设置为 true 即可。虽然编程时最好使用接口而非类，但在处理遗留代码时，建议使用未实现接口的类的功能还是很有用的。(总的来说，Spring 并不具有规范性。虽然它让应用良好实践变得更容易，但也避免了强迫使用特定的方法）。\n如果您愿意，您可以在任何情况下强制使用 CGLIB，即使您有接口。\nCGLIB 代理的工作原理是在运行时生成目标类的子类。Spring 会对生成的子类进行配置，以便将方法调用委托给原始目标类：该子类用于实现装饰器模式，编织建议。\nCGLIB 代理一般来说对用户是透明的。不过，也有一些问题需要考虑：\n不能建议使用 final 方法，因为它们不能被覆盖。 您需要在类路径上安装 CGLIB 2 二进制文件；动态代理可通过 JDK CGLIB 代理和动态代理的性能差别不大。从 Spring 1.0 开始，动态代理速度稍快。不过，未来可能会有所改变。在这种情况下，性能不应成为决定性的考虑因素。\n5.6. 方便的代理创建 通常情况下，我们并不需要 ProxyFactoryBean 的全部功能，因为我们只对其中一个方面感兴趣：例如，事务管理。\n当我们想专注于某个特定方面时，可以使用许多方便的工厂来创建 AOP 代理。我们将在其他章节中讨论这些工厂，因此在此仅对其中的一些工厂进行简要介绍。\n5.6.1. TransactionProxyFactoryBean 随 Spring 一起提供的 jPetStore 示例应用程序展示了 TransactionProxyFactoryBean 的使用。\nTransactionProxyFactoryBean 是 ProxyConfig 的子类，因此基本配置与 ProxyFactoryBean 共享。(请参阅上面的 ProxyConfig 属性列表）。\n下面这个来自 jPetStore 的示例说明了它是如何工作的。与代理工厂 Bean 一样，也有一个目标 Bean 定义。应根据代理工厂 Bean 定义（此处为 \u0026ldquo;petStore\u0026rdquo;）而不是目标 POJO（\u0026ldquo;petStoreTarget\u0026rdquo;）来表达依赖关系。\nTransactionProxyFactoryBean 需要一个目标和有关 \u0026ldquo;事务属性 \u0026ldquo;的信息，指定哪些方法应该是事务性的，以及所需的传播和其他设置：\n\u0026lt;bean id=\u0026#34;petStoreTarget\u0026#34; class=\u0026#34;org.springframework.samples.jpetstore.domain.logic.PetStoreImpl\u0026#34;\u0026gt; \u0026lt;property name=\u0026#34;accountDao\u0026#34;\u0026gt;\u0026lt;ref bean=\u0026#34;accountDao\u0026#34;/\u0026gt;\u0026lt;/property\u0026gt; \u0026lt;!-- Other dependencies omitted --\u0026gt; \u0026lt;/bean\u0026gt; \u0026lt;bean id=\u0026#34;petStore\u0026#34; class=\u0026#34;org.springframework.transaction.interceptor.TransactionProxyFactoryBean\u0026#34;\u0026gt; \u0026lt;property name=\u0026#34;transactionManager\u0026#34;\u0026gt;\u0026lt;ref bean=\u0026#34;transactionManager\u0026#34;/\u0026gt;\u0026lt;/property\u0026gt; \u0026lt;property name=\u0026#34;target\u0026#34;\u0026gt;\u0026lt;ref local=\u0026#34;petStoreTarget\u0026#34;/\u0026gt;\u0026lt;/property\u0026gt; \u0026lt;property name=\u0026#34;transactionAttributes\u0026#34;\u0026gt; \u0026lt;props\u0026gt; \u0026lt;prop key=\u0026#34;insert*\u0026#34;\u0026gt;PROPAGATION_REQUIRED\u0026lt;/prop\u0026gt; \u0026lt;prop key=\u0026#34;update*\u0026#34;\u0026gt;PROPAGATION_REQUIRED\u0026lt;/prop\u0026gt; \u0026lt;prop key=\u0026#34;*\u0026#34;\u0026gt;PROPAGATION_REQUIRED,readOnly\u0026lt;/prop\u0026gt; \u0026lt;/props\u0026gt; \u0026lt;/property\u0026gt; \u0026lt;/bean\u0026gt; 与 ProxyFactoryBean 一样，我们可能会选择使用内部 bean 来设置目标属性的值，而不是引用顶级目标 bean。\nTransactionProxyFactoryBean 会自动创建事务顾问，包括基于事务属性的切入点，因此只建议使用事务方法。\nTransactionProxyFactoryBean 允许使用 preInterceptors 和 postInterceptors 属性指定 \u0026ldquo;前 \u0026ldquo;和 \u0026ldquo;后 \u0026ldquo;建议。这些属性使用拦截器、其他建议或顾问的对象数组，以便将其放在事务拦截器之前或之后的拦截链中。可以使用 XML Bean 定义中的 \u0026lt;list\u0026gt; 元素填充这些属性，如下所示：\n\u0026lt;property name=\u0026#34;preInterceptors\u0026#34;\u0026gt; \u0026lt;list\u0026gt; \u0026lt;ref local=\u0026#34;authorizationInterceptor\u0026#34;/\u0026gt; \u0026lt;ref local=\u0026#34;notificationBeforeAdvice\u0026#34;/\u0026gt; \u0026lt;/list\u0026gt; \u0026lt;/property\u0026gt; \u0026lt;property name=\u0026#34;postInterceptors\u0026#34;\u0026gt; \u0026lt;list\u0026gt; \u0026lt;ref local=\u0026#34;myAdvisor\u0026#34;/\u0026gt; \u0026lt;/list\u0026gt; \u0026lt;/property\u0026gt; 这些属性可以添加到上述 \u0026ldquo;petStore \u0026ldquo;Bean 定义中。一种常见的用法是将事务性与声明安全性结合起来：这与 EJB 提供的方法类似。\n由于使用的是实际实例引用，而不是 ProxyFactoryBean 中的 Bean 名称，前拦截器和后拦截器只能用于共享实例建议。因此，它们不能用于有状态建议：例如，在混合体中。这与 TransactionProxyFactoryBean 的目的是一致的。它提供了一种进行普通事务设置的简单方法。如果您需要更复杂、更个性化的 AOP，请考虑使用通用的 ProxyFactoryBean 或自动代理创建器（见下文）。\n特别是如果我们将 Spring AOP 视为 EJB 的替代品，我们会发现大多数建议都相当通用，并使用共享实例模型。声明式事务管理和安全检查就是典型的例子。\nTransactionProxyFactoryBean 依赖于通过其 transactionManager JavaBean 属性实现的 PlatformTransactionManager。这就允许基于 JTA、JDBC 或其他策略的可插拔事务实现。这与 Spring 事务抽象有关，而非 AOP。我们将在下一章讨论事务基础架构。\n如果你只对声明式事务管理感兴趣，TransactionProxyFactoryBean 是一个很好的解决方案，而且比使用 ProxyFactoryBean 更简单。\n5.6.2. EJB 代理 其他专用代理为 EJB 创建代理，使调用代码可以直接使用 EJB \u0026ldquo;业务方法 \u0026ldquo;接口。调用代码无需执行 JNDI 查找或使用 EJB 创建方法：大大提高了可读性和架构灵活性。\n有关详细信息，请参阅本手册中有关 Spring EJB 服务的章节。\n5.7. 精炼的代理定义 特别是在定义事务代理时，可能会出现许多类似的代理定义。使用父类和子类 bean 定义以及内部 bean 定义，可以使代理定义更加简洁明了。\n首先要为代理创建一个父类、模板、Bean 定义：\n{ \u0026#34;parent\u0026#34;: \u0026#34;parent\u0026#34;, \u0026#34;template\u0026#34;: \u0026#34;template\u0026#34;, \u0026#34;beanDefinition\u0026#34;: \u0026#34;bean definition\u0026#34; } \u0026lt;bean id=\u0026#34;txProxyTemplate\u0026#34; abstract=\u0026#34;true\u0026#34; class=\u0026#34;org.springframework.transaction.interceptor.TransactionProxyFactoryBean\u0026#34;\u0026gt; \u0026lt;property name=\u0026#34;transactionManager\u0026#34;\u0026gt;\u0026lt;ref local=\u0026#34;transactionManager\u0026#34;/\u0026gt;\u0026lt;/ref\u0026gt;\u0026lt;/property\u0026gt; \u0026lt;property name=\u0026#34;transactionAttributes\u0026#34;\u0026gt; \u0026lt;props\u0026gt; \u0026lt;prop key=\u0026#34;*\u0026#34;\u0026gt;PROPAGATION_REQUIRED\u0026lt;/prop\u0026gt; \u0026lt;/props\u0026gt; \u0026lt;/property\u0026gt; \u0026lt;/bean\u0026gt; 这将永远不会被实例化，因此实际上可能是不完整的。然后，需要创建的每个代理都只是一个子 Bean 定义，它将代理的目标封装为一个内部 Bean 定义，因为目标本身永远不会被使用。\n\u0026lt;bean id=\u0026#34;myService\u0026#34; parent=\u0026#34;txProxyTemplate\u0026#34;\u0026gt; \u0026lt;property name=\u0026#34;target\u0026#34;\u0026gt; \u0026lt;bean class=\u0026#34;org.springframework.samples.MyServiceImpl\u0026#34;\u0026gt; \u0026lt;/bean\u0026gt; \u0026lt;/property\u0026gt; \u0026lt;/bean\u0026gt; 当然，也可以覆盖父模板的属性，比如本例中的事务传播设置：\n\u0026lt;bean id=\u0026#34;mySpecialService\u0026#34; parent=\u0026#34;txProxyTemplate\u0026#34;\u0026gt; \u0026lt;property name=\u0026#34;target\u0026#34;\u0026gt; \u0026lt;bean class=\u0026#34;org.springframework.samples.MySpecialServiceImpl\u0026#34;\u0026gt; \u0026lt;/bean\u0026gt; \u0026lt;/property\u0026gt; \u0026lt;property name=\u0026#34;transactionAttributes\u0026#34;\u0026gt; \u0026lt;props\u0026gt; \u0026lt;prop key=\u0026#34;get*\u0026#34;\u0026gt;PROPAGATION_REQUIRED,readOnly\u0026lt;/prop\u0026gt; \u0026lt;prop key=\u0026#34;find*\u0026#34;\u0026gt;PROPAGATION_REQUIRED,readOnly\u0026lt;/prop\u0026gt; \u0026lt;prop key=\u0026#34;load*\u0026#34;\u0026gt;PROPAGATION_REQUIRED,readOnly\u0026lt;/prop\u0026gt; \u0026lt;prop key=\u0026#34;store*\u0026#34;\u0026gt;PROPAGATION_REQUIRED\u0026lt;/prop\u0026gt; \u0026lt;/props\u0026gt; \u0026lt;/property\u0026gt; \u0026lt;/bean\u0026gt; 请注意，在上面的示例中，我们已通过使用 abstract 属性将父 Bean 定义明确标记为抽象（如前所述），因此它实际上可能不会被实例化。应用程序上下文（而非简单的 Bean 工厂）默认情况下会预先实例化所有单例。因此，如果您有一个（父）Bean 定义，而您只打算将其用作模板，并且该定义指定了一个类，那么您必须确保将 abstract 属性设置为 true，否则应用程序上下文将实际尝试预实例化它，这一点非常重要（至少对于单例 Bean 而言）。\n5.8. 使用 ProxyFactory 以编程方式创建 AOP 代理 使用 Spring 以编程方式创建 AOP 代理非常简单。这样，您就可以使用 Spring AOP，而无需依赖 Spring IoC。\n下面的列表显示了为目标对象创建代理的过程，其中包含一个拦截器和一个顾问。目标对象实现的接口将自动被代理：\nProxyFactory factory = new ProxyFactory(myBusinessInterfaceImpl); factory.addInterceptor(myMethodInterceptor); factory.addAdvisor(myAdvisor); MyBusinessInterface tb = (MyBusinessInterface) factory.getProxy(); 第一步是构建一个 org.springframework.aop.framework.ProxyFactory 类型的对象。您可以使用目标对象创建该对象（如上面的示例），也可以在另一个构造函数中指定要代理的接口。\n您可以添加拦截器或顾问，并在代理服务器工厂的整个生命周期内操纵它们。如果添加了引入拦截器（IntroductionInterceptionAroundAdvisor），就可以使代理实现其他接口。\nProxyFactory 上还有一些方便的方法（继承自 AdvisedSupport），允许您添加其他建议类型，如 before 建议和 throws 建议。AdvisedSupport 是 ProxyFactory 和 ProxyFactoryBean 的超类。\n在大多数应用程序中，将 AOP 代理创建与 IoC 框架集成是最佳做法。我们建议您使用 AOP 将配置从 Java 代码中外部化，这与一般情况下的做法相同。\n5.9. 操作通知对象 无论如何创建 AOP 代理，都可以使用 org.springframework.aop.framework.Advised 接口来操作它们。任何 AOP 代理都可以投向此接口，无论它实现了其他什么接口。该接口包括以下方法：\nAdvisor[] getAdvisors(); void addAdvice(Advice advice) throws AopConfigException; void addAdvice(int pos, Advice advice) throws AopConfigException; void addAdvisor(Advisor advisor) throws AopConfigException; void addAdvisor(int pos, Advisor advisor) throws AopConfigException; int indexOf(Advisor advisor); boolean removeAdvisor(Advisor advisor) throws AopConfigException; void removeAdvisor(int index) throws AopConfigException; boolean replaceAdvisor(Advisor a, Advisor b) throws AopConfigException; boolean isFrozen(); getAdvisors() 方法将为添加到工厂的每个顾问、拦截器或其他建议类型返回一个顾问。如果您添加了一个顾问，在此索引处返回的顾问将是您添加的对象。如果添加了拦截器或其他建议类型，Spring 会将其封装在一个顾问中，并使用一个总是返回 true 的点切线。因此，如果您添加了一个 MethodInterceptor，那么此索引返回的顾问将是一个 DefaultPointcutAdvisor，它将返回您的 MethodInterceptor 和一个与所有类和方法相匹配的 pointcut。\naddAdvisor() 方法可用于添加任何顾问。通常，持有快捷方式和建议的顾问是通用的 DefaultPointcutAdvisor，它可以与任何建议或快捷方式一起使用（但不能用于导入）。\n默认情况下，即使代理已创建，也可以添加或删除顾问或拦截器。唯一的限制是无法添加或删除引入顾问，因为工厂中的现有代理不会显示接口的变化。(您可以从工厂获取一个新的代理来避免这个问题）。\n将 AOP 代理转换为 Advised 接口并检查和操作其建议的一个简单示例：\nAdvised advised = (Advised) myObject; Advisor[] advisors = advised.getAdvisors(); int oldAdvisorCount = advisors.length; System.out.println(oldAdvisorCount + \u0026#34; advisors\u0026#34;); // Add an advice like an interceptor without a pointcut // Will match all proxied methods // Can use for interceptors, before, after returning or throws advice advised.addAdvice(new DebugInterceptor()); // Add selective advice using a pointcut advised.addAdvisor(new DefaultPointcutAdvisor(mySpecialPointcut, myAdvice)); assertEquals(\u0026#34;Added two advisors\u0026#34;, oldAdvisorCount + 2, advised.getAdvisors().length); 在生产中修改业务对象的建议是否可取（不是双关语）值得商榷，尽管毫无疑问存在合理的使用情况。不过，它在开发过程中可能非常有用：例如，在测试中。我有时会发现，以拦截器或其他建议的形式添加测试代码，进入我要测试的方法调用内部，是非常有用的。(例如，建议可以进入为该方法创建的事务内部：例如，在标记事务回滚之前，运行 SQL 检查数据库是否已正确更新）。\n根据创建代理的方式，通常可以设置冻结标记，在这种情况下，建议 isFrozen() 方法将返回 true，任何通过添加或删除来修改建议的尝试都将导致 AopConfigException 异常。冻结建议对象状态的功能在某些情况下非常有用：例如，防止调用代码删除安全拦截器。在 Spring 1.1 中，如果已知不需要在运行时修改建议，也可以使用该功能进行积极优化。\n5.10. 使用 “autoproxy” 工具 到目前为止，我们已经考虑过使用 ProxyFactoryBean 或类似的工厂 Bean 来显式创建 AOP 代理。\nSpring 还允许我们使用 \u0026ldquo;自动代理 \u0026ldquo;Bean 定义，它可以自动代理选定的 Bean 定义。这是建立在 Spring \u0026ldquo;Bean 后置处理器 \u0026ldquo;基础架构之上的，它可以在容器加载时修改任何 Bean 定义。\n在这种模式下，您需要在 XML Bean 定义文件中设置一些特殊的 Bean 定义，以配置自动代理基础架构。这样，您只需声明符合自动代理条件的目标即可：您无需使用 ProxyFactoryBean。\n有两种方法可以做到这一点：\n使用自动代理创建器，在当前上下文中引用特定的 bean 值得单独考虑的自动代理创建特例；由源级元数据属性驱动的自动代理创建 5.10.1. 自动代理 bean 定义 org.springframework.aop.framework.autoproxy 包提供了以下标准自动代理创建器。\n5.10.1.1. BeanNameAutoProxyCreator BeanNameAutoProxyCreator 会自动为名称符合字面值或通配符的 Bean 创建 AOP 代理。\n\u0026lt;bean id=\u0026#34;jdkBeanNameProxyCreator\u0026#34; class=\u0026#34;org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator\u0026#34;\u0026gt; \u0026lt;property name=\u0026#34;beanNames\u0026#34;\u0026gt;\u0026lt;value\u0026gt;jdk*,onlyJdk\u0026lt;/value\u0026gt;\u0026lt;/property\u0026gt; \u0026lt;property name=\u0026#34;interceptorNames\u0026#34;\u0026gt; \u0026lt;list\u0026gt; \u0026lt;value\u0026gt;myInterceptor\u0026lt;/value\u0026gt; \u0026lt;/list\u0026gt; \u0026lt;/property\u0026gt; \u0026lt;/bean\u0026gt; 与 ProxyFactoryBean 一样，拦截器也有一个拦截器名称（interceptorNames）属性，而不是一个拦截器列表，以便为原型顾问提供正确的行为。已命名的 \u0026ldquo;拦截器 \u0026ldquo;可以是顾问或任何建议类型。\n与一般的自动代理一样，使用 BeanNameAutoProxyCreator 的主要目的是将相同的配置一致地应用于多个对象，并尽量减少配置量。它是将声明式事务应用于多个对象的常用选择。\n名称匹配的 Bean 定义（如上例中的 \u0026ldquo;jdkMyBean \u0026ldquo;和 \u0026ldquo;onlyJdk\u0026rdquo;）是目标类的普通旧 Bean 定义。BeanNameAutoProxyCreator 将自动创建一个 AOP 代理。相同的建议将应用于所有匹配的 Bean。请注意，如果使用的是建议（而不是上例中的拦截器），那么不同的豆可能会应用不同的指向。\n5.10.1.2. DefaultAdvisorAutoProxyCreator DefaultAdvisorAutoProxyCreator 是一种更通用、功能更强大的自动代理创建器。它会在当前上下文中自动应用符合条件的顾问，而无需在自动代理顾问的 Bean 定义中包含特定的 Bean 名称。它与 BeanNameAutoProxyCreator 一样，都具有一致配置和避免重复的优点。\n使用这一机制包括：\n指定 DefaultAdvisorAutoProxyCreator Bean 定义 在相同或相关上下文中指定任意数量的顾问。请注意，这些顾问必须是顾问，而不仅仅是拦截器或其他建议。这样做是必要的，因为必须有一个评估点切入，以检查候选 bean 定义的每个建议是否合格。 DefaultAdvisorAutoProxyCreator 会自动评估每个顾问中包含的点切，以确定它应该对每个业务对象（例如示例中的 \u0026ldquo;businessObject1 \u0026ldquo;和 \u0026ldquo;businessObject2\u0026rdquo;）应用哪些建议（如果有的话）。\n这意味着每个业务对象可以自动应用任意数量的顾问。如果顾问中没有任何点切与业务对象中的任何方法相匹配，则不会代理该对象。在为新业务对象添加 bean 定义时，如有必要，将自动代理这些 bean 定义。\n一般来说，自动代理的优点是使调用者或依赖程序无法获得未经建议的对象。在此 ApplicationContext 上调用 getBean(\u0026ldquo;businessObject1\u0026rdquo;) 将返回一个 AOP 代理，而不是目标业务对象。(前面展示的 \u0026ldquo;内部 bean \u0026ldquo;习例也具有这种优势）。\n\u0026lt;bean id=\u0026#34;autoProxyCreator\u0026#34; class=\u0026#34;org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator\u0026#34;\u0026gt; \u0026lt;/bean\u0026gt; \u0026lt;bean id=\u0026#34;txAdvisor\u0026#34; autowire=\u0026#34;constructor\u0026#34; class=\u0026#34;org.springframework.transaction.interceptor.TransactionAttributeSourceAdvisor\u0026#34;\u0026gt; \u0026lt;property name=\u0026#34;order\u0026#34;\u0026gt;\u0026lt;value\u0026gt;1\u0026lt;/value\u0026gt;\u0026lt;/property\u0026gt; \u0026lt;/bean\u0026gt; \u0026lt;bean id=\u0026#34;customAdvisor\u0026#34; class=\u0026#34;com.mycompany.MyAdvisor\u0026#34;\u0026gt; \u0026lt;/bean\u0026gt; \u0026lt;bean id=\u0026#34;businessObject1\u0026#34; class=\u0026#34;com.mycompany.BusinessObject1\u0026#34;\u0026gt; \u0026lt;!-- Properties omitted --\u0026gt; \u0026lt;/bean\u0026gt; \u0026lt;bean id=\u0026#34;businessObject2\u0026#34; class=\u0026#34;com.mycompany.BusinessObject2\u0026#34;\u0026gt; \u0026lt;/bean\u0026gt; 如果您想对许多业务对象一致地应用相同的建议，DefaultAdvisorAutoProxyCreator 将非常有用。一旦基础架构定义到位，您就可以简单地添加新的业务对象，而无需包含特定的代理配置。您还可以非常容易地添加其他方面，例如跟踪或性能监控方面，而只需对配置进行最小的更改。\nDefaultAdvisorAutoProxyCreator 支持过滤（使用命名约定，以便只评估某些顾问，允许在同一工厂中使用多个不同配置的 AdvisorAutoProxyCreator）和排序。顾问可以实现 org.springframework.core.Ordered 接口，以确保在出现问题时能正确排序。上例中使用的 TransactionAttributeSourceAdvisor 有一个可配置的排序值；默认为无序。\n5.10.1.3. AbstractAdvisorAutoProxyCreator 这是 DefaultAdvisorAutoProxyCreator 的超类。如果顾问定义对框架 DefaultAdvisorAutoProxyCreator 的行为定制不足，您可以通过子类化该类来创建自己的自动代理创建器。\n5.10.2. 使用元数据驱动的自动代理 一种特别重要的自动代理是由元数据驱动的。这产生了一种类似于 .NET ServicedComponents 的编程模型。它不像 EJB 那样使用 XML 部署描述符，而是将事务管理和其他企业服务的配置保存在源代码级属性中。\n在这种情况下，您可以将 DefaultAdvisorAutoProxyCreator 与能理解元数据属性的顾问结合使用。元数据的具体内容保存在候选顾问的点切部分，而不是自动代理创建类本身。\n这实际上是 DefaultAdvisorAutoProxyCreator 的一个特例，但值得单独考虑。(元数据感知代码是在顾问中包含的指向代码中，而不是 AOP 框架本身）。\njPetStore 示例应用程序的 /attributes 目录显示了属性驱动自动代理的使用。在这种情况下，无需使用 TransactionProxyFactoryBean。只需在业务对象上定义事务属性就足够了，因为使用了元数据感知的指向切入。bean 定义包括 /WEB-INF/declarativeServices.xml 中的以下代码。请注意，这是通用代码，可以在 jPetStore 之外使用：\n\u0026lt;bean id=\u0026#34;autoProxyCreator\u0026#34; class=\u0026#34;org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator\u0026#34;\u0026gt; \u0026lt;/bean\u0026gt; \u0026lt;bean id=\u0026#34;transactionAttributeSource\u0026#34; class=\u0026#34;org.springframework.transaction.interceptor.AttributesTransactionAttributeSource\u0026#34; autowire=\u0026#34;constructor\u0026#34;\u0026gt; \u0026lt;/bean\u0026gt; \u0026lt;bean id=\u0026#34;transactionInterceptor\u0026#34; class=\u0026#34;org.springframework.transaction.interceptor.TransactionInterceptor\u0026#34; autowire=\u0026#34;byType\u0026#34;\u0026gt; \u0026lt;/bean\u0026gt; \u0026lt;bean id=\u0026#34;transactionAdvisor\u0026#34; class=\u0026#34;org.springframework.transaction.interceptor.TransactionAttributeSourceAdvisor\u0026#34; autowire=\u0026#34;constructor\u0026#34; \u0026gt; \u0026lt;/bean\u0026gt; \u0026lt;bean id=\u0026#34;attributes\u0026#34; class=\u0026#34;org.springframework.metadata.commons.CommonsAttributes\u0026#34; /\u0026gt; DefaultAdvisorAutoProxyCreator Bean 定义\u0026ndash;本例中称为 \u0026ldquo;autoProxyCreator\u0026rdquo;，但名称并不重要（甚至可以省略）\u0026ndash;将拾取当前应用程序上下文中所有符合条件的指向。在这种情况下，TransactionAttributeSourceAdvisor 类型的 \u0026ldquo;transactionAdvisor\u0026rdquo; Bean 定义将适用于携带事务属性的类或方法。TransactionAttributeSourceAdvisor 通过构造函数依赖性依赖于 TransactionInterceptor。示例通过自动接线解决了这一问题。AttributesTransactionAttributeSource 依赖于 org.springframework.metadata.Attributes 接口的实现。在本片段中，\u0026ldquo;attributes\u0026rdquo; Bean 使用 Jakarta Commons Attributes API 获取属性信息，从而满足了这一要求。(应用程序代码必须已使用 Commons Attributes 编译任务编译）。\n这里定义的 TransactionInterceptor 依赖于 PlatformTransactionManager 定义，该定义没有包含在本通用文件中（尽管可以包含），因为它将针对应用程序的事务要求（通常是 JTA，如本示例中的 JTA，或 Hibernate、JDO 或 JDBC）：\n\u0026lt;bean id=\u0026#34;transactionManager\u0026#34; class=\u0026#34;org.springframework.transaction.jta.JtaTransactionManager\u0026#34;/\u0026gt; 如果您只需要声明式事务管理，那么使用这些通用 XML 定义将导致 Spring 自动代理所有带有事务属性的类或方法。您无需直接使用 AOP，编程模型与 .NET ServicedComponents 相似。 这种机制是可扩展的。可以根据自定义属性进行自动代理。您需要\n定义自定义属性。 指定一个带有必要建议的顾问，包括一个因类或方法上存在自定义属性而触发的快捷方式。您也可以使用现有的建议，只需执行一个静态快捷方式，即可获取自定义属性。 这些顾问可以是每个建议类（例如混合类）所独有的：它们只需被定义为原型而非单例 Bean 定义。例如，如上图所示，Spring 测试套件中的 LockMixin 引入拦截器可与属性驱动的点切法结合使用，以实现 mixin 目标，如图所示。我们使用通用的 DefaultPointcutAdvisor，并使用 JavaBean 属性进行配置：\n\u0026lt;bean id=\u0026#34;lockMixin\u0026#34; class=\u0026#34;org.springframework.aop.LockMixin\u0026#34; singleton=\u0026#34;false\u0026#34; /\u0026gt; \u0026lt;bean id=\u0026#34;lockableAdvisor\u0026#34; class=\u0026#34;org.springframework.aop.support.DefaultPointcutAdvisor\u0026#34; singleton=\u0026#34;false\u0026#34; \u0026gt; \u0026lt;property name=\u0026#34;pointcut\u0026#34;\u0026gt; \u0026lt;ref local=\u0026#34;myAttributeAwarePointcut\u0026#34;/\u0026gt; \u0026lt;/property\u0026gt; \u0026lt;property name=\u0026#34;advice\u0026#34;\u0026gt; \u0026lt;ref local=\u0026#34;lockMixin\u0026#34;/\u0026gt; \u0026lt;/property\u0026gt; \u0026lt;/bean\u0026gt; \u0026lt;bean id=\u0026#34;anyBean\u0026#34; class=\u0026#34;anyclass\u0026#34; ... 如果属性感知点切匹配 anyBean 或其他 Bean 定义中的任何方法，就会应用 mixin。请注意，lockMixin 和 lockableAdvisor 定义都是原型。myAttributeAwarePointcut 可以是一个单例定义，因为它不保存单个建议对象的状态。\n5.11. 使用 TargetSources Spring 提供了 TargetSource 的概念，用 org.springframework.aop.TargetSource 接口表示。该接口负责返回实现连接点的 \u0026ldquo;目标对象\u0026rdquo;。每次 AOP 代理处理方法调用时，都会要求 TargetSource 实现提供一个目标实例。\n使用 Spring AOP 的开发人员通常不需要直接使用 TargetSource，但它提供了支持池化、热插拔和其他复杂目标的强大手段。例如，池化 TargetSource 可以使用池管理实例，为每次调用返回不同的目标实例。\n如果未指定 TargetSource，则会使用封装本地对象的默认实现。每次调用都会返回相同的目标（如您所料）。\n让我们来看看 Spring 提供的标准目标源，以及如何使用它们。\n使用自定义目标源时，目标通常需要是一个原型，而不是一个单例 Bean 定义。这样，Spring 就能在需要时创建一个新的目标实例。\n5.11.1. 热插拔目标源 org.springframework.aop.target.HotSwappableTargetSource 允许切换 AOP 代理的目标，同时允许调用者保留对它的引用。\n更改目标源的目标会立即生效。HotSwappableTargetSource 是线程安全的。\n您可以通过 HotSwappableTargetSource 上的 swap() 方法更改目标，具体方法如下：\nHotSwappableTargetSource swapper = (HotSwappableTargetSource) beanFactory.getBean(\u0026#34;swapper\u0026#34;); Object oldTarget = swapper.swap(newTarget); 所需的 XML 定义如下：\n\u0026lt;bean id=\u0026#34;initialTarget\u0026#34; class=\u0026#34;mycompany.OldTarget\u0026#34;\u0026gt; \u0026lt;/bean\u0026gt; \u0026lt;bean id=\u0026#34;swapper\u0026#34; class=\u0026#34;org.springframework.aop.target.HotSwappableTargetSource\u0026#34;\u0026gt; \u0026lt;constructor-arg\u0026gt;\u0026lt;ref local=\u0026#34;initialTarget\u0026#34;/\u0026gt;\u0026lt;/constructor-arg\u0026gt; \u0026lt;/bean\u0026gt; \u0026lt;bean id=\u0026#34;swappable\u0026#34; class=\u0026#34;org.springframework.aop.framework.ProxyFactoryBean\u0026#34; \u0026gt; \u0026lt;property name=\u0026#34;targetSource\u0026#34;\u0026gt; \u0026lt;ref local=\u0026#34;swapper\u0026#34;/\u0026gt; \u0026lt;/property\u0026gt; \u0026lt;/bean\u0026gt; 上述 swap() 调用会更改可交换 Bean 的目标。持有该 bean 引用的客户端不会察觉到这一变化，但会立即开始访问新的目标。 虽然这个示例没有添加任何建议\u0026ndash;使用 TargetSource 也不一定要添加建议\u0026ndash;当然，任何 TargetSource 都可以与任意建议结合使用。\n5.11.2. 池化 Target Sources 使用池目标源可提供与无状态会话 EJB 类似的编程模型，即维护一个由相同实例组成的池，方法调用将指向池中的空闲对象。 Spring 池与 SLSB 池的一个重要区别是，Spring 池可以应用于任何 POJO。与一般的 Spring 一样，这种服务可以以非侵入的方式应用。 Spring 为 Jakarta Commons Pool 1.1 提供了开箱即用的支持，它提供了相当高效的池化实现。要使用此功能，您需要在应用程序的 classpath 中安装 commons-pool Jar。您也可以子类化 org.springframework.aop.target.AbstractPoolingTargetSource 以支持任何其他池 API。 配置示例如下：\n\u0026lt;bean id=\u0026#34;businessObjectTarget\u0026#34; class=\u0026#34;com.mycompany.MyBusinessObject\u0026#34; singleton=\u0026#34;false\u0026#34;\u0026gt; ... properties omitted \u0026lt;/bean\u0026gt; \u0026lt;bean id=\u0026#34;poolTargetSource\u0026#34; class=\u0026#34;org.springframework.aop.target.CommonsPoolTargetSource\u0026#34;\u0026gt; \u0026lt;property name=\u0026#34;targetBeanName\u0026#34;\u0026gt;\u0026lt;value\u0026gt;businessObjectTarget\u0026lt;/value\u0026gt;\u0026lt;/property\u0026gt; \u0026lt;property name=\u0026#34;maxSize\u0026#34;\u0026gt;\u0026lt;value\u0026gt;25\u0026lt;/value\u0026gt;\u0026lt;/property\u0026gt; \u0026lt;/bean\u0026gt; \u0026lt;bean id=\u0026#34;businessObject\u0026#34; class=\u0026#34;org.springframework.aop.framework.ProxyFactoryBean\u0026#34; \u0026gt; \u0026lt;property name=\u0026#34;targetSource\u0026#34;\u0026gt;\u0026lt;ref local=\u0026#34;poolTargetSource\u0026#34;/\u0026gt;\u0026lt;/property\u0026gt; \u0026lt;property name=\u0026#34;interceptorNames\u0026#34;\u0026gt;\u0026lt;value\u0026gt;myInterceptor\u0026lt;/value\u0026gt;\u0026lt;/property\u0026gt; \u0026lt;/bean\u0026gt; 请注意，目标对象\u0026ndash;即示例中的 \u0026ldquo;businessObjectTarget\u0026rdquo;\u0026ndash;必须是一个原型。这允许 PoolingTargetSource 实现创建新的目标实例，以便在必要时扩大池。有关其属性的信息，请参阅 AbstractPoolingTargetSource 的 Javadoc 以及您希望使用的具体子类：maxSize 是最基本的属性，并且始终保证存在。 在这种情况下，\u0026ldquo;myInterceptor \u0026ldquo;是需要在同一 IoC 上下文中定义的拦截器的名称。不过，使用池化并不需要指定拦截器。如果你只想使用池化，而不需要其他建议，那就根本不要设置拦截器名称（interceptorNames）属性。 可以对 Spring 进行配置，使其能够将任何池对象投向 org.springframework.aop.target.PoolingConfig 接口，该接口通过一个介绍来公开有关池的配置和当前大小的信息。你需要像这样定义一个顾问：\n\u0026lt;bean id=\u0026#34;poolConfigAdvisor\u0026#34; class=\u0026#34;org.springframework.beans.factory.config.MethodInvokingFactoryBean\u0026#34;\u0026gt; \u0026lt;property name=\u0026#34;targetObject\u0026#34;\u0026gt;\u0026lt;ref local=\u0026#34;poolTargetSource\u0026#34; /\u0026gt;\u0026lt;/property\u0026gt; \u0026lt;property name=\u0026#34;targetMethod\u0026#34;\u0026gt;\u0026lt;value\u0026gt;getPoolingConfigMixin\u0026lt;/value\u0026gt;\u0026lt;/property\u0026gt; \u0026lt;/bean\u0026gt; 该顾问通过调用 AbstractPoolingTargetSource 类上的方便方法获得，因此使用了 MethodInvokingFactoryBean。该顾问的名称（此处为 \u0026ldquo;poolConfigAdvisor\u0026rdquo;）必须位于暴露池对象的 ProxyFactoryBean 中的拦截器名称列表中。 演员阵容如下：\nPoolingConfig conf = (PoolingConfig) beanFactory.getBean(\u0026#34;businessObject\u0026#34;); System.out.println(\u0026#34;Max pool size is \u0026#34; + conf.getMaxSize()); 通常没有必要对无状态服务对象进行池化。我们认为不应将其作为默认选择，因为大多数无状态对象都具有天然的线程安全性，如果资源被缓存，实例池就会出现问题。 使用自动代理可以实现更简单的池化。可以设置任何自动代理创建者使用的 TargetSources。\n5.11.3. 原型 Target Sources 设置 \u0026ldquo;原型\u0026rdquo; 目标源与池化目标源类似。在这种情况下，每次方法调用都会创建一个新的目标实例。虽然在现代 JVM 中创建一个新对象的成本并不高，但连接新对象（满足其 IoC 依赖关系）的成本可能会更高。因此，如果没有充分的理由，您不应该使用这种方法。 为此，您可以对上图中的 poolTargetSource 定义进行如下修改。(为了清晰起见，我还更改了名称）。\n\u0026lt;bean id=\u0026#34;prototypeTargetSource\u0026#34; class=\u0026#34;org.springframework.aop.target.PrototypeTargetSource\u0026#34;\u0026gt; \u0026lt;property name=\u0026#34;targetBeanName\u0026#34;\u0026gt;\u0026lt;value\u0026gt;businessObjectTarget\u0026lt;/value\u0026gt;\u0026lt;/property\u0026gt; \u0026lt;/bean\u0026gt; 只有一个属性：目标 Bean 的名称。TargetSource 实现中使用继承来确保命名的一致性。与池化目标源一样，目标 Bean 必须是一个原型 Bean 定义。\n5.12. 定义新的 Advice 类型 Spring AOP 的设计具有可扩展性。虽然目前内部使用的是拦截实现策略，但除了拦截周围的建议、之前的建议、抛出的建议和返回建议之后的建议之外，还可以支持任意建议类型。 org.springframework.aop.framework.adapter 包是一个 SPI 包，允许在不更改核心框架的情况下添加新的自定义建议类型支持。对自定义建议类型的唯一限制是它必须实现 org.aopalliance.aop.Advice 标签接口。 请参阅 org.springframework.aop.framework.adapter 软件包的 Javadocs 了解更多信息\n5.13. 延伸阅读和资源 我推荐 Ramnivas Laddad 所著的《AspectJ in Action》（Manning，2003 年）作为 AOP 的入门读物。 有关 Spring AOP 的更多示例，请参阅 Spring 示例应用程序：\nJPetStore 的默认配置说明了如何使用 TransactionProxyFactoryBean 进行声明式事务管理 JPetStore 的 /attributes 目录说明了属性驱动的声明式事务管理的使用情况 如果您对 Spring AOP 更高级的功能感兴趣，请查看测试套件。测试覆盖率超过 90%，这说明了本文档未讨论的高级功能。\n5.14. 路线图 Spring AOP 与 Spring 的其他部分一样，正在积极开发中。核心 API 非常稳定。与 Spring 的其他部分一样，AOP 框架也非常模块化，在保留基本设计的同时实现了扩展。我们计划在 Spring 1.1 版本中进行多项改进，以保持向后兼容性。这些改进包括\n性能改进：AOP 代理的创建由工厂通过策略接口来处理。因此，我们可以支持更多的 AOPProxy 类型，而不会影响用户代码或核心实现。我们计划在 1.0.3 版本中对 CGLIB 代理进行重大性能优化，并在 Spring 1.1 中对运行时不会更改建议的情况进行进一步优化。这将大大减少 AOP 框架的开销。不过请注意，在正常使用中，AOP 框架的开销并不是问题。 更具表现力的快捷方式Spring 目前提供了一个表现力丰富的 Pointcut 接口，但我们可以通过添加更多的 Pointcut 实现来增加价值。我们正在考虑与 AspectJ 集成，以便在 Spring 配置文件中使用 AspectJ 的 Pointcut 表达式。如果您希望贡献有用的 Pointcut，请联系我们！ 最重要的改进可能涉及与 AspectJ 的集成，这将与 AspectJ 社区合作完成。我们相信，这将在以下方面为 Spring 和 AspectJ 用户带来巨大的好处：\n允许使用 Spring IoC 配置 AspectJ 方面。这有可能在适当的地方将 AspectJ 方面集成到应用程序中，就像将 Spring 方面集成到应用程序 IoC 上下文中一样。 允许在 Spring 配置中使用 AspectJ 的点切表达式来针对 Spring 建议。这比我们自己设计点切表达式语言有很大的好处；AspectJ 经过了深思熟虑，而且文档齐全。 这两种集成都应在 Spring 1.1 中提供。\n","permalink":"https://blog.codingforjoy.com/posts/spring%E6%A0%B8%E5%BF%83aop%E9%9D%A2%E5%90%91%E5%88%87%E9%9D%A2%E7%BC%96%E7%A8%8B/","summary":"本文内容是逐段翻译Spring-Framework 1.1.x 官方文档的第5章的内容。通过在翻译阅读过程中去尝试感受当年作者的意境。为什么不是1.0？因为1.1.x才开始有官方文档，之前的版本只有api文档。\n如果你想知道人为什么要这么搞，那么应该去看书/文档；如果你要知道让机器干了什么？那你应该看代码！——左耳朵耗子\n5.1. 概念 面向方面编程（AOP）提供了另一种思考程序结构的方法，是对 OOP 的补充。OO 将应用程序分解为对象的层次结构，而 AOP 则将程序分解为方面或关注点。这样就能将事务管理等关注点模块化，否则这些关注点就会跨越多个对象。(这类问题通常被称为交叉问题）。\nSpring 中使用了 AOP：\n提供声明式企业服务，尤其是作为 EJB 声明式服务的替代。其中最重要的服务是声明式事务管理，它建立在 Spring 的事务抽象之上。 允许用户实现自定义 AOP，用 AOP 补充他们对 OOP 的使用。 因此，您可以将 Spring AOP 视为一种使能技术，它允许 Spring 在不使用 EJB 的情况下提供声明式事务管理；或者使用 Spring AOP 框架的全部功能来实现自定义方面。\n如果你只对通用声明式服务或其他预打包的声明式中间件服务（如池化服务）感兴趣，就不需要直接使用 Spring AOP，也可以跳过本章的大部分内容。\n5.1.1. AOP 概念 首先，让我们定义一些 AOP 核心概念。这些术语并非 Spring 特有。遗憾的是，AOP 术语并不特别直观。不过，如果 Spring 使用自己的术语，那就更令人困惑了。\n切面(Aspect)：模块化的关注点，否则其实现可能会跨越多个对象。在 J2EE 应用程序中，事务管理就是横切关注点的一个很好的例子。方面使用 Spring 作为顾问或拦截器来实现。 连接点(Joinpoint)：程序执行过程中的点，如方法调用或抛出的特定异常。在 Spring AOP 中，连接点总是方法调用。Spring 并未在显著位置使用连接点一词；连接点信息可通过传递给拦截器的 MethodInvocation 参数上的方法访问，并通过 org.springframework.aop.Pointcut 接口的实现进行评估。 通知(Advice)：AOP 框架在特定连接点采取的行动。不同类型的通知包括 \u0026ldquo;around\u0026rdquo;、\u0026ldquo;before\u0026rdquo;、\u0026ldquo;throws\u0026rdquo; 通知。下面将讨论通知类型。包括 Spring 在内的许多 AOP 框架都将通知建模为拦截器，并在连接点 \u0026ldquo;around\u0026quot;维护一连串拦截器。 切点(Pointcut)：一组连接点，指定何时应触发通知。AOP 框架必须允许开发人员指定切点：例如，使用正则表达式。 引介(Introduction)：为通知类添加方法或字段。Spring 允许你为任何通知对象引介新的接口。例如，你可以使用导言让任何对象实现 IsModified 接口，以简化缓存。 目标对象(Target object)：包含连接点的对象。也称为通知对象或代理对象。 AOP 代理(AOP proxy)：由 AOP 框架创建的对象，包括通知。在 Spring 中，AOP 代理将是 JDK 动态代理或 CGLIB 代理。 编织(Weaving)：组装各个方面以创建通知对象。这可以在编译时完成（例如使用 AspectJ 编译器），也可以在运行时完成。Spring 和其他纯 Java AOP 框架一样，在运行时执行编织。 不同的通知类型包括：","title":"5. Spring 核心：AOP，面向切面编程"},{"content":"性能监控工具 服务端的配置和性能 show profile # 案例 # all：显示所有性能信息 show profile all for query n # block io：显示块io操作的次数 show profile block io for query n # context switches：显示上下文切换次数，被动和主动 show profile context switches for query n # cpu：显示用户cpu时间、系统cpu时间 show profile cpu for query n # IPC：显示发送和接受的消息数量 show profile ipc for query n # page faults：显示页错误数量 show profile page faults for query n # source：显示源码中的函数名称与位置 show profile source for query n # swaps：显示swap的次数 show profile swaps for query n 运行时性能 performance_schema 简介 它是数据库中的库，使用的存储引擎是performance_schema，主要关注的是数据库运行过程中的性能相关的数据，与information_schema不同，关注的是数据库表的元数据 server内部在发生函数调用、操作系统的等待、SQL语句的执行阶段、单个SQL或者多个SQL的集合的事件来触发采集消耗、耗时、活动执行次数等信息，并且事件的采集可以方便的提供server中的相关存储引擎对磁盘文件、表I/O、表锁等资源同步多用相关信息 它的记录只会在server本地，不会记录到binlog，同时也不复制到其他的server，其实在mysql源代码实现过程中主要是通过检查点（埋点）的方式收集 可以通过select查询，同时也还可以修改相关收集配置，动态修改setup_*开头的几个配置表 表分类 -- 事件表：当前语句、历史语句、长语句历史、聚合后的摘要summary -- 其中，summary表还可以根据帐号(account),主机(host),程序(program), -- 线程(thread),用户(user)和全局(global)再进行细分) show tables like \u0026#39;%statement%\u0026#39;; -- 等待事件记录表，与语句事件类型的相关记录表类似： show tables like \u0026#39;%wait%\u0026#39;; -- 阶段事件记录表，记录语句执行的阶段事件的表 show tables like \u0026#39;%stage%\u0026#39;; -- 事务事件记录表，记录事务相关的事件的表 show tables like \u0026#39;%transaction%\u0026#39;; -- 监控文件系统层调用的表 show tables like \u0026#39;%file%\u0026#39;; -- 监视内存使用的表 show tables like \u0026#39;%memory%\u0026#39;; -- 动态对performance_schema进行配置的配置表 show tables like \u0026#39;%setup%\u0026#39;; 入门使用 首先需要查看是否开启此功能，需要显示的修改[my.cnf]配置文件\n-- 查看performance_schema的属性 mysql\u0026gt; SHOW VARIABLES LIKE \u0026#39;performance_schema\u0026#39;; +--------------------+-------+ | Variable_name | Value | +--------------------+-------+ | performance_schema | ON | +--------------------+-------+ 1 row in set (0.01 sec) -- 在配置文件中修改performance_schema的属性值，on表示开启，off表示关闭 [mysqld] performance_schema=ON -- 切换数据库 use performance_schema; -- 查看当前数据库下的所有表,会看到有很多表存储着相关的信息 show tables; -- 可以通过show create table tablename来查看创建表的时候的表结构 mysql\u0026gt; show create table setup_consumers; +-----------------+--------------------------------- | Table | Create Table +-----------------+--------------------------------- | setup_consumers | CREATE TABLE `setup_consumers` ( `NAME` varchar(64) NOT NULL, `ENABLED` enum(\u0026#39;YES\u0026#39;,\u0026#39;NO\u0026#39;) NOT NULL ) ENGINE=PERFORMANCE_SCHEMA DEFAULT CHARSET=utf8 | +-----------------+--------------------------------- 1 row in set (0.00 sec) 两个概念 instruments：生产者，用于采集mysql中各种各样的操作产生的事件信息，对应配置表中的配置项我们可以称为监控采集配置项，前面动态表setup_*配置 consumers：消费者，对应的消费者表用于存储来自instruments采集的数据，对应配置表中的配置项我们可以称为消费存储配置项 常用配置项 启动配置 # 是否在mysql server启动时就开启events_statements_current表的记录功能(该表记录当前的语句事件信息)， # 启动之后也可以在setup_consumers表中使用UPDATE语句进行动态更新setup_consumers配置 # 表中的events_statements_current配置项，默认值为TRUE performance_schema_consumer_events_statements_current=TRUE # 与performance_schema_consumer_events_statements_current选项类似， # 但该选项是用于配置是否记录语句事件短历史信息，默认为TRUE performance_schema_consumer_events_statements_history=TRUE # 与performance_schema_consumer_events_statements_current选项类似， # 但该选项是用于配置是否记录语句事件长历史信息，默认为FALSE performance_schema_consumer_events_stages_history_long=FALSE # 除了statement(语句)事件之外，还支持：wait(等待)事件、state(阶段)事件、transaction(事务)事件， # 他们与statement事件一样都有三个启动项分别进行配置，但这些等待事件默认未启用， # 如果需要在MySQL Server启动时一同启动，则通常需要写进my.cnf配置文件中 # 是否在MySQL Server启动时就开启全局表（如： # mutex_instances、rwlock_instances、cond_instances、file_instances、 # users、hostsaccounts、socket_summary_by_event_name、file_summary_by_instance # 等大部分的全局对象计数统计和事件汇总统计信息表 ）的记录功能， # 启动之后也可以在setup_consumers表中使用UPDATE语句进行动态更新全局配置项,默认值为TRUE performance_schema_consumer_global_instrumentation=TRUE # 是否在MySQL Server启动时就开启events_statements_summary_by_digest 表的记录功能， # 启动之后也可以在setup_consumers表中使用UPDATE语句进行动态更新digest配置项,默认值为TRUE performance_schema_consumer_statements_digest=TRUE # 是否在MySQL Server启动时就开启 performance_schema_consumer_thread_instrumentation=TRUE # events_xxx_summary_by_yyy_by_event_name表的记录功能， # 启动之后也可以在setup_consumers表中使用UPDATE语句进行动态更新线程配置项,默认值为TRUE performance_schema_instrument[=name] # 是否在MySQL Server启动时就启用某些采集器，由于instruments配置项多达数千个， # 所以该配置项支持key-value模式，还支持%号进行通配等，如下: # [=name]可以指定为具体的Instruments名称（但是这样如果有多个需要指定的时候，就需要使用该选项多次）， # 也可以使用通配符，可以指定instruments相同的前缀+通配符， # 也可以使用%代表所有的instruments指定开启单个instruments performance-schema-instrument=\u0026#39;instrument_name=value\u0026#39; # 使用通配符指定开启多个instruments performance-schema-instrument=\u0026#39;wait/synch/cond/%=COUNTED\u0026#39; # 开关所有的instruments performance-schema-instrument=\u0026#39;%=ON\u0026#39; performance-schema-instrument=\u0026#39;%=OFF\u0026#39; # 注意，这些启动选项要生效的前提是，需要设置performance_schema=ON。 # 另外，这些启动选项虽然无法使用show variables语句查看， # 但我们可以通过setup_instruments和setup_consumers表查询这些选项指定的值。 系统变量 # 查询是否开启运行时事件记录功能 show variables like \u0026#39;%performance_schema%\u0026#39;; # 重要的属性解释 # 控制performance_schema功能的开关，要使用MySQL的performance_schema，需要在mysqld启动时启用，以启用事件收集功能，该参数 # 在5.7.x之前支持performance_schema的版本中默认关闭，5.7.x版本开始默认开启 # 注意：如果mysqld在初始化performance_schema时发现无法分配任何相关的内部缓冲区，则performance_schema将自动禁用，并将performance_schema设置为OFF performance_schema=ON # 控制events_statements_summary_by_digest表中的最大行数。如果产生的语句摘要信息超过此最大值， # 便无法继续存入该表，此时performance_schema会增加状态变量 performance_schema_digests_size=10000 # 控制events_statements_history_long表中的最大行数， # 该参数控制所有会话在events_statements_history_long表中能够存放的 # 总事件记录数，超过这个限制之后，最早的记录将被覆盖全局变量， # 只读变量，整型值，5.6.3版本引入 * 5.6.x版本中，5.6.5及其之前的版 # 本默认为10000，5.6.6及其之后的版本默认值为-1，通常情况下， # 自动计算的值都是10000 * 5.7.x版本中，默认值为-1，通常情况下，自动 # 计算的值都是10000 performance_schema_events_statements_history_long_size=10000 # 控制events_statements_history表中单个线程（会话）的最大行数，该参数控制单个会话在 # events_statements_history表中能够存放的事件记录数，超过这个限制之后，单个会话最早的记录将被覆盖 # 全局变量，只读变量，整型值，5.6.3版本引入 * 5.6.x版本中，5.6.5及其之前的版本默认为10，5.6.6及其之后的版本默认值为-1， # 通常情况下，自动计算的值都是10 * 5.7.x版本中，默认值为-1，通常情况下，自动计算的值都是10 # 除了statement(语句)事件之外，wait(等待)事件、state(阶段)事件、transaction(事务)事件， # 他们与statement事件一样都有三个参数分别进行存储限制配置，有兴趣的同学自行研究，这里不再赘述 performance_schema_events_statements_history_size=10 # 用于控制标准化形式的SQL语句文本在存入performance_schema时的限制长度， # 该变量与max_digest_length变量相关(max_digest_length变量含义请自行查阅相关资料)全局变量， # 只读变量，默认值1024字节，整型值，取值范围0~1048576 performance_schema_max_digest_length=1024 # 控制存入events_statements_current，events_statements_history和events_statements_history_long语句事件表中的 # SQL_TEXT列的最大SQL长度字节数。 超出系统变量performance_schema_max_sql_text_length的部分将被丢弃，不会记录，一般情况 # 下不需要调整该参数，除非被截断的部分与其他SQL比起来有很大差异 # 全局变量，只读变量，整型值，默认值为1024字节，取值范围为0~1048576，5.7.6版本引入 # 降低系统变量performance_schema_max_sql_text_length值可以减少内存使用，但如果汇总的SQL中，被截断部分有较大差异，会导致没 # 有办法再对这些有较大差异的SQL进行区分。 增加该系统变量值会增加内存使用，但对于汇总SQL来讲可以更精准地区分不同的部分。 performance_schema_max_sql_text_length=1024 setup_*配置表说明 /* performance_timers表中记录了server中有哪些可用的事件计时器 字段解释： timer_name:表示可用计时器名称，CYCLE是基于CPU周期计数器的定时器 timer_frequency:表示每秒钟对应的计时器单位的数量,CYCLE计时器的换算值与CPU的频率相关、 timer_resolution:计时器精度值，表示在每个计时器被调用时额外增加的值 timer_overhead:表示在使用定时器获取事件时开销的最小周期值 */ select * from performance_timers; /* setup_timers表中记录当前使用的事件计时器信息 字段解释： name:计时器类型，对应某个事件类别 timer_name:计时器类型名称 */ select * from setup_timers; /* setup_consumers表中列出了consumers可配置列表项 字段解释： NAME：consumers配置名称 ENABLED：consumers是否启用，有效值为YES或NO，此列可以使用UPDATE语句修改。 */ select * from setup_consumers; /* setup_instruments 表列出了instruments 列表配置项，即代表了哪些事件支持被收集： 字段解释： NAME：instruments名称，instruments名称可能具有多个部分并形成层次结构 ENABLED：instrumetns是否启用，有效值为YES或NO，此列可以使用UPDATE语句修改。如果设置为NO，则这个instruments不会被执行， 不会产生任何的事件信息 TIMED：instruments是否收集时间信息，有效值为YES或NO，此列可以使用UPDATE语句修改，如果设置为NO，则这个instruments不会收 集时间信息 */ SELECT * FROM setup_instruments; /* setup_actors表的初始内容是匹配任何用户和主机，因此对于所有前台线程，默认情况下启用监视和历史事件收集功能 字段解释： HOST：与grant语句类似的主机名，一个具体的字符串名字，或使用“％”表示“任何主机” USER：一个具体的字符串名称，或使用“％”表示“任何用户” ROLE：当前未使用，MySQL 8.0中才启用角色功能 ENABLED：是否启用与HOST，USER，ROLE匹配的前台线程的监控功能，有效值为：YES或NO HISTORY：是否启用与HOST， USER，ROLE匹配的前台线程的历史事件记录功能，有效值为：YES或NO */ SELECT * FROM setup_actors; /* setup_objects表控制performance_schema是否监视特定对象。默认情况下，此表的最大行数为100行。 字段解释： OBJECT_TYPE：instruments类型，有效值为：“EVENT”（事件调度器事件）、“FUNCTION”（存储函数）、“PROCEDURE”（存储过程）、 “TABLE”（基表）、“TRIGGER”（触发器），TABLE对象类型的配置会影响表I/O事件（wait/io/table/sql/handler instrument）和 表锁事件（wait/lock/table/sql/handler instrument）的收集 OBJECT_SCHEMA：某个监视类型对象涵盖的数据库名称，一个字符串名称，或“％”(表示“任何数据库”) OBJECT_NAME：某个监视类型对象涵盖的表名，一个字符串名称，或“％”(表示“任何数据库内的对象”) ENABLED：是否开启对某个类型对象的监视功能，有效值为：YES或NO。此列可以修改 TIMED：是否开启对某个类型对象的时间收集功能，有效值为：YES或NO，此列可以修改 */ SELECT * FROM setup_objects; /* threads表对于每个server线程生成一行包含线程相关的信息， 字段解释： THREAD_ID：线程的唯一标识符（ID） NAME：与server中的线程检测代码相关联的名称(注意，这里不是instruments名称) TYPE：线程类型，有效值为：FOREGROUND、BACKGROUND。分别表示前台线程和后台线程 PROCESSLIST_ID：对应INFORMATION_SCHEMA.PROCESSLIST表中的ID列。 PROCESSLIST_USER：与前台线程相关联的用户名，对于后台线程为NULL。 PROCESSLIST_HOST：与前台线程关联的客户端的主机名，对于后台线程为NULL。 PROCESSLIST_DB：线程的默认数据库，如果没有，则为NULL。 PROCESSLIST_COMMAND：对于前台线程，该值代表着当前客户端正在执行的command类型，如果是sleep则表示当前会话处于空闲状态 PROCESSLIST_TIME：当前线程已处于当前线程状态的持续时间（秒） PROCESSLIST_STATE：表示线程正在做什么事情。 PROCESSLIST_INFO：线程正在执行的语句，如果没有执行任何语句，则为NULL。 PARENT_THREAD_ID：如果这个线程是一个子线程（由另一个线程生成），那么该字段显示其父线程ID ROLE：暂未使用 INSTRUMENTED：线程执行的事件是否被检测。有效值：YES、NO HISTORY：是否记录线程的历史事件。有效值：YES、NO * THREAD_OS_ID：由操作系统层定义的线程或任务标识符（ID）： */ select * from threads 实践案例 了解相关的参数配置后，可以对表进行一些实际分析，\n-- 1、哪类的SQL执行最多？ SELECT DIGEST_TEXT,COUNT_STAR,FIRST_SEEN,LAST_SEEN FROM events_statements_summary_by_digest ORDER BY COUNT_STAR DESC -- 2、哪类SQL的平均响应时间最多？ SELECT DIGEST_TEXT,AVG_TIMER_WAIT FROM events_statements_summary_by_digest ORDER BY COUNT_STAR DESC -- 3、哪类SQL排序记录数最多？ SELECT DIGEST_TEXT,SUM_SORT_ROWS FROM events_statements_summary_by_digest ORDER BY COUNT_STAR DESC -- 4、哪类SQL扫描记录数最多？ SELECT DIGEST_TEXT,SUM_ROWS_EXAMINED FROM events_statements_summary_by_digest ORDER BY COUNT_STAR DESC -- 5、哪类SQL使用临时表最多？ SELECT DIGEST_TEXT,SUM_CREATED_TMP_TABLES,SUM_CREATED_TMP_DISK_TABLES FROM events_statements_summary_by_digest ORDER BY COUNT_STAR DESC -- 6、哪类SQL返回结果集最多？ SELECT DIGEST_TEXT,SUM_ROWS_SENT FROM events_statements_summary_by_digest ORDER BY COUNT_STAR DESC -- 7、哪个表物理IO最多？ SELECT file_name,event_name,SUM_NUMBER_OF_BYTES_READ,SUM_NUMBER_OF_BYTES_WRITE FROM file_summary_by_instance ORDER BY SUM_NUMBER_OF_BYTES_READ + SUM_NUMBER_OF_BYTES_WRITE DESC -- 8、哪个表逻辑IO最多？ SELECT object_name,COUNT_READ,COUNT_WRITE,COUNT_FETCH,SUM_TIMER_WAIT FROM table_io_waits_summary_by_table ORDER BY sum_timer_wait DESC -- 9、哪个索引访问最多？ SELECT OBJECT_NAME,INDEX_NAME,COUNT_FETCH,COUNT_INSERT,COUNT_UPDATE,COUNT_DELETE FROM table_io_waits_summary_by_index_usage ORDER BY SUM_TIMER_WAIT DESC -- 10、哪个索引从来没有用过？ SELECT OBJECT_SCHEMA,OBJECT_NAME,INDEX_NAME FROM table_io_waits_summary_by_index_usage WHERE INDEX_NAME IS NOT NULL AND COUNT_STAR = 0 AND OBJECT_SCHEMA \u0026lt;\u0026gt; \u0026#39;mysql\u0026#39; ORDER BY OBJECT_SCHEMA,OBJECT_NAME; -- 11、哪个等待事件消耗时间最多？ SELECT EVENT_NAME,COUNT_STAR,SUM_TIMER_WAIT,AVG_TIMER_WAIT FROM events_waits_summary_global_by_event_name WHERE event_name != \u0026#39;idle\u0026#39; ORDER BY SUM_TIMER_WAIT DESC -- 12-1、剖析某条SQL的执行情况，包括statement信息，stege信息，wait信息 SELECT EVENT_ID,sql_text FROM events_statements_history WHERE sql_text LIKE \u0026#39;%count(*)%\u0026#39;; -- 12-2、查看每个阶段的时间消耗 SELECT event_id,EVENT_NAME,SOURCE,TIMER_END - TIMER_START FROM events_stages_history_long WHERE NESTING_EVENT_ID = 1553; -- 12-3、查看每个阶段的锁等待情况 SELECT event_id,event_name,source,timer_wait,object_name,index_name,operation,nesting_event_id FROM events_waits_history_longWHERE nesting_event_id = 1553; 参考官方performance_schema教程\nshow processlist 当前由服务器内执行的线程集执行的操作情况，更多可以参考官网：Sources of Process Information\nSechema与数据类型优化 数据类型优化 三原则：占用越小越好，足够简单，避免为null\n实际操作建议\n整型：TINYINT，SMALLEST，MEDIUMINT，INT，BIGINT分别使用8，16，24，32，64位存储空间\n字符和字符串：char（255字）、vachar（65535字）、text类（TINYTEXT:2^8^-1、TEXT:2^16^ -1、MEDIUMTEXT:2^24^-1、LONGTEXT:4G或2^32^-1）单位字节\nBLOB和TEXT类型，分别是二进制和字符串格式来存储\ndatatime和timestamp\ndatatime（8字节） 与时区无关，数据库底层对datetime无效 可以精确到毫秒 可保存的范围大 字符串存储会导致确实时间的精度 date（3字节） 占用的字节数比字符串、datatime、int少 可以通过date类型进行日期计算 保存范围是1000-01-01到9999-12-31 timestamp（4字节） 时间范围是linux元年1970-01-01开始到2038-01-19 精确到秒 采用整型存储 依赖数据库的时区 自动更新timestamp的列值 尝试使用枚举代替字符串，mysql存储枚举类型非常的紧凑，有利于提升IO读取\n存储特殊类型通过可以通过函数转换如IP存储\n# 序列化 mysql\u0026gt; select inet_aton(\u0026#39;1.1.1.1\u0026#39;); +----------------------+ | inet_aton(\u0026#39;1.1.1.1\u0026#39;) | +----------------------+ | 16843009 | +----------------------+ 1 row in set (0.00 sec) # 反序列化 mysql\u0026gt; select inet_ntoa(16843009); +---------------------+ | inet_ntoa(16843009) | +---------------------+ | 1.1.1.1 | +---------------------+ 1 row in set (0.00 sec) 合理的范式和反范式 合理的范式 优点：更新更快，基本不会出现重复的数据，在内存中操作比较快 缺点：需要不断的关联，增加IO消耗 反范式 优点：所有的信息都在一张表中，可以避免关联，减少IO消耗 缺点：冗余较多，删除操作时会删除不必要信息 实践 项目前：先范式设计后分析业务反范式冗余存储，更新优先级根据业务来看（及时和延迟） 项目中：分析业务代码逻辑，是否需要优化数据表增加冗余字段减少IO的开销 主键选择 代理主键\n与业务无关，无意义的数字序列，如ID 自然主键\n和业务相关，事物属性唯一标识，如身份证 如何选择\n代理主键更好，不与业务耦合，更加容易维护，统一的策略，也减少源代码数量 字符集选择 mysql精确到字段取优化，合适的字符集，一定程度上也会减少IO\nlatin*：纯拉丁内容适合 utf-8*：多语言内容 存储引擎选择 适当的拆分 比如有字段类型为TEXT之类大型字段，可以尝试拆到单独的表，当查询不需要此字段的查询是，可以大大减少IO的处理时间 执行计划EXPLAIN 格式：explain + SQL EXPLAIN字段解析 mysql\u0026gt; explain select inet_aton(\u0026#39;1.1.1.1\u0026#39;)\\G *************************** 1. row *************************** id: 1 select_type: SIMPLE table: NULL partitions: NULL type: NULL possible_keys: NULL key: NULL key_len: NULL ref: NULL rows: NULL filtered: NULL Extra: No tables used 1 row in set, 1 warning (0.00 sec) id有几种情况 id相同，顺序执行 id不同，递增，id号越大，越先执行 id相同和id不同同时存在，即第一种和第二种结合 null，代表是中间结果集（在8.x有些情况被优化了，5.x中还是存在的，比如 UNION RESULT） select_type -- sample:简单的查询，不包含子查询和union explain select * from emp; -- primary:查询中若包含任何复杂的子查询，最外层查询则被标记为Primary explain select staname,ename supname from (select ename staname,mgr from emp) t join emp on t.mgr=emp.empno ; -- union:若第二个select出现在union之后，则被标记为union explain select * from emp where deptno = 10 union select * from emp where sal \u0026gt;2000; -- dependent union:跟union类似，此处的depentent表示union或union all联合而成的结果会受外部表影响 explain select * from emp e where e.empno in ( select empno from emp where deptno = 10 union select empno from emp where sal \u0026gt;2000) -- union result:从union表获取结果的select explain select * from emp where deptno = 10 union select * from emp where sal \u0026gt;2000; -- subquery:在select或者where列表中包含子查询 explain select * from emp where sal \u0026gt; (select avg(sal) from emp) ; -- dependent subquery:subquery的子查询要受到外部表查询的影响 explain select * from emp e where e.deptno in (select distinct deptno from dept); -- DERIVED: from子句中出现的子查询，也叫做派生类， explain select staname,ename supname from (select ename staname,mgr from emp) t join emp on t.mgr=emp.empno ; -- UNCACHEABLE SUBQUERY：表示使用子查询的结果不能被缓存 explain select * from emp where empno = (select empno from emp where deptno=@@sort_buffer_size); -- uncacheable union:表示union的查询结果不能被缓存：sql语句未验证 table 如果是具体的表名，则表明从实际的物理表中获取数据，当然也可以是表的别名 表名是derivedN的形式，表示使用了id为N的查询产生的衍生表 当有union result的时候，表名是union n1,n2等的形式，n1,n2表示参与union的id type system \u0026gt; const \u0026gt; eq_ref \u0026gt; ref \u0026gt; fulltext \u0026gt; ref_or_null \u0026gt; index_merge \u0026gt; unique_subquery \u0026gt; index_subquery \u0026gt; range \u0026gt; index \u0026gt; ALL -- all:全表扫描，一般情况下出现这样的sql语句而且数据量比较大的话那么就需要进行优化。 explain select * from emp; -- index：全索引扫描这个比all的效率要好，主要有两种情况，一种是当前的查询时覆盖索引， -- 即我们需要的数据在索引中就可以索取，或者是使用了索引进行排序，这样就避免数据的重排序 explain select empno from emp; -- range：表示利用索引查询的时候限制了范围，在指定范围内进行查询， -- 这样避免了index的全索引扫描，适用的操作符： =, \u0026lt;\u0026gt;, \u0026gt;, \u0026gt;=, \u0026lt;, \u0026lt;=, IS NULL, BETWEEN, LIKE, or IN() explain select * from emp where empno between 7000 and 7500; -- index_subquery：利用索引来关联子查询，不再扫描全表 explain select * from emp where emp.job in (select job from t_job); -- unique_subquery:该连接类型类似与index_subquery,使用的是唯一索引 explain select * from emp e where e.deptno in (select distinct deptno from dept); -- index_merge：在查询过程中需要多个索引组合使用，没有模拟出来 -- ref_or_null：对于某个字段即需要关联条件，也需要null值的情况下，查询优化器会选择这种访问方式 explain select * from emp e where e.mgr is null or e.mgr=7369; -- fulltext：全文索引 -- ref：使用了非唯一性索引进行数据的查找 create index idx_3 on emp(deptno); explain select * from emp e,dept d where e.deptno =d.deptno; -- eq_ref：使用唯一性索引进行数据查找 explain select * from emp,emp2 where emp.empno = emp2.empno; -- const：这个表至多有一个匹配行， explain select * from emp where empno = 7369; -- system：表只有一行记录（等于系统表），这是const类型的特例，平时不会出现 possible_keys 这张table可能用的索引，不一定使用了\nkey 实际用到的索引，如果为null，则没有用索引，如果使用了覆盖索引，则会与select重叠\nkey_len 表示使用索引的字节长度，不损失精度情况，越短越好\nref 显示哪一列被使用了索引，是一个常数\nrows 根据表的统计信息及索引情况，大致的计算需要读取的行数\nextra -- using filesort:说明mysql无法利用索引进行排序，只能利用排序算法进行排序，会消耗额外的位置 explain select * from emp order by sal; -- using temporary:建立临时表来保存中间结果，查询完成之后把临时表删除 explain select ename,count(*) from emp where deptno = 10 group by ename; -- using index:这个表示当前的查询时覆盖索引的，直接从索引中读取数据， -- 而不用访问数据表。如果同时出现using where 表名索引被用来执行索引键值的查找， -- 如果没有，表面索引被用来读取数据，而不是真的查找 explain select deptno,count(*) from emp group by deptno limit 10; -- using where:使用where进行条件过滤 explain select * from t_user where id = 1; -- using join buffer:使用连接缓存，情况没有模拟出来 -- impossible where：where语句的结果总是false explain select * from emp where empno = 7469; 如何应用 type判断是否有使用索引，尽可能的到达range\u0026lt;type\u0026lt;ref ref判断是否用到索引 extra判断是否覆盖索引、排序是否用到索引 索引优化 索引基础 比如一本书的目录，可以让你快速的找到你感兴趣的内容\n优点 减少服务器扫描数据量 帮助服务器避免排序和临时表 将随机IO变成顺序IO 用处 快速的找到WHERE字句的行 优化器会找到最优索引 如果表具有多列索引，优化器会使用索引的任何最左前缀来找到行 当有表连接时，，从其他表检索行数据 找到特定的索引列min和max值 如果索引和分组时在可用的索引最左前缀上完成的，则对进行排序和分组 某些情况下，可以查询检索值，无需检索行，如覆盖索引的情况 分类 主键索引：PRIMARY KEY 唯一索引：UNIQUE 普通索引：NORMAL 全文索引：FULLTEXT 组合索引：KEY name(字段1,字段2,字段3) 一些名词 回表：比如我们需要查用户信息，通过name去查（name字段列是普通索引），普通索引中的data存储的是主键索ID,主键索引data存放的是数据行，执行器会通过普通索引定位到data后，再拿着ID去主键索引去查用户信息数据行(最后去主键索引查数据行的行为叫回表)，遍历2次B+树 覆盖索引：还是刚刚的例子，如果查询的不是用户信息，而是用户ID（主键ID），去掉上面例子中去主键索引的过程，就是覆盖索引，因为普通索引data就是用户ID，遍历1次B+树 最左匹配：B+树索引是有序的，从左往右递增，而组合索引就是从表的字段的左边字段开始匹配，遇到范围停止匹配 索引下推：将where后面的条件需要在server层过滤的变为在server层之前过滤完成，交给server层的就是结果集 索引常用的数据结构 哈希表+链表 B+树 发展历程 =\u0026gt;哈希表，查询时间复杂度（O(N)），为了降低时间复杂度人们想到了（logn）-\u0026gt; 二叉树 =\u0026gt;二叉树（分支倾斜严重） =\u0026gt;平衡二叉树（平衡分支耗时） =\u0026gt;红黑树（减少平衡距离，降低插入速度） \u0026mdash;分割线\u0026mdash;无论怎么优化二叉树，随着时间的推移，树的深度总会越来越深\u0026mdash;分割线\u0026mdash; =\u0026gt;B树： 每个节点（主键（主键+data）+指针（主键子节点范围））16K 这样的缺点，节点容量固定条件下，data越大，能存放的指针变小，从而导致索引容纳的数少 =\u0026gt;B+树 让最终的子节点去存放data，子节点的父节点都是指针 最底层data之间还通过链表相互连接，方便遍历 索引匹配的方式 name，age组合索引\n全值匹配：name=‘张三’ 匹配最左前缀：name=‘张三’ AND age=10 匹配列前缀：name like ‘张%’ 匹配范围值：age\u0026gt;10 精确匹配某一列+范围匹配：name=‘’ AND age \u0026gt; 10 只访问索引列：select name,age from user where name=‘张三’ AND age=10(覆盖索引) 哈希索引 结构：哈希表 + 链表 优点 结构非常的紧凑，然后查询很快 缺点 哈希索引结构只包含行指针和哈希值，不存储值 哈希不是顺序存储，无法排序 不支持分列匹配查找 只支持等值 =，or查找，不支持范围查找 冲突严重时，链表特别长耗时，维护耗时 案例 比如需要存放一个很长的URL，需求需要通过URL查询 我们可以通过一些哈希函数，如CRC32 这样可以减小体积 组合索引 多列共同组成索引树，where最左使用 聚族索引和非聚族索引 聚族索引：索引中data=元数据 INNODB中的主键索引存放的就是元数据 非聚族索引：索引中data!=元数据，而是元数据的地址 如MyISAM存储引擎的B+树，data存放的是元数据的文件地址 覆盖索引 一个索引data中有查询字段的值 实现方式和存储引擎有关，Momery存储引擎不支持 优势 减少IO，B+索引本就有顺序，在IO密集型范围内，读取一次数据IO减少很多 聚族索引支持更好，非聚族索引只做地址缓存严重影响性能 细节优化 单表6个以内索引 一个索引5个字段内 根据实际业务优化 少用表达式，计算放到业务层 优先主键索引，不会触发回表 使用前缀索引 使用索引排序 union all、in、or都能使用索引，in最好 范围，\u0026lt;,\u0026gt;，但是后面的字段就无法用索引了 索引监控 show status like \u0026lsquo;Handler_read%\u0026rsquo;; 参数解析 Handler_read_first：读取索引第一个条目的次数 Handler_read_key：通过index获取数据的次数 Handler_read_last：读取索引最后一个条目的次数 Handler_read_next：通过索引读取下一条数据的次数 Handler_read_prev：通过索引读取上一条数据的次数 Handler_read_rnd：从固定位置读取数据的次数 Handler_read_rnd_next：从数据节点读取下一条数据的次数 案例 预备数据 SET FOREIGN_KEY_CHECKS=0; DROP TABLE IF EXISTS `itdragon_order_list`; CREATE TABLE `itdragon_order_list` ( `id` bigint(11) NOT NULL AUTO_INCREMENT COMMENT \u0026#39;主键id，默认自增长\u0026#39;, `transaction_id` varchar(150) DEFAULT NULL COMMENT \u0026#39;交易号\u0026#39;, `gross` double DEFAULT NULL COMMENT \u0026#39;毛收入(RMB)\u0026#39;, `net` double DEFAULT NULL COMMENT \u0026#39;净收入(RMB)\u0026#39;, `stock_id` int(11) DEFAULT NULL COMMENT \u0026#39;发货仓库\u0026#39;, `order_status` int(11) DEFAULT NULL COMMENT \u0026#39;订单状态\u0026#39;, `descript` varchar(255) DEFAULT NULL COMMENT \u0026#39;客服备注\u0026#39;, `finance_descript` varchar(255) DEFAULT NULL COMMENT \u0026#39;财务备注\u0026#39;, `create_type` varchar(100) DEFAULT NULL COMMENT \u0026#39;创建类型\u0026#39;, `order_level` int(11) DEFAULT NULL COMMENT \u0026#39;订单级别\u0026#39;, `input_user` varchar(20) DEFAULT NULL COMMENT \u0026#39;录入人\u0026#39;, `input_date` varchar(20) DEFAULT NULL COMMENT \u0026#39;录入时间\u0026#39;, PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=10003 DEFAULT CHARSET=utf8; INSERT INTO itdragon_order_list VALUES (\u0026#39;10000\u0026#39;, \u0026#39;81X97310V32236260E\u0026#39;, \u0026#39;6.6\u0026#39;, \u0026#39;6.13\u0026#39;, \u0026#39;1\u0026#39;, \u0026#39;10\u0026#39;, \u0026#39;ok\u0026#39;, \u0026#39;ok\u0026#39;, \u0026#39;auto\u0026#39;, \u0026#39;1\u0026#39;, \u0026#39;itdragon\u0026#39;, \u0026#39;2017-08-28 17:01:49\u0026#39;); INSERT INTO itdragon_order_list VALUES (\u0026#39;10001\u0026#39;, \u0026#39;61525478BB371361Q\u0026#39;, \u0026#39;18.88\u0026#39;, \u0026#39;18.79\u0026#39;, \u0026#39;1\u0026#39;, \u0026#39;10\u0026#39;, \u0026#39;ok\u0026#39;, \u0026#39;ok\u0026#39;, \u0026#39;auto\u0026#39;, \u0026#39;1\u0026#39;, \u0026#39;itdragon\u0026#39;, \u0026#39;2017-08-18 17:01:50\u0026#39;); INSERT INTO itdragon_order_list VALUES (\u0026#39;10002\u0026#39;, \u0026#39;5RT64180WE555861V\u0026#39;, \u0026#39;20.18\u0026#39;, \u0026#39;20.17\u0026#39;, \u0026#39;1\u0026#39;, \u0026#39;10\u0026#39;, \u0026#39;ok\u0026#39;, \u0026#39;ok\u0026#39;, \u0026#39;auto\u0026#39;, \u0026#39;1\u0026#39;, \u0026#39;itdragon\u0026#39;, \u0026#39;2017-09-08 17:01:49\u0026#39;); 案例一 select * from itdragon_order_list where transaction_id = \u0026#34;81X97310V32236260E\u0026#34;; -- 通过查看执行计划发现type=all,需要进行全表扫描 explain select * from itdragon_order_list where transaction_id = \u0026#34;81X97310V32236260E\u0026#34;; -- 优化一、为transaction_id创建唯一索引 create unique index idx_order_transaID on itdragon_order_list (transaction_id); -- 当创建索引之后，唯一索引对应的type是const，通过索引一次就可以找到结果，普通索引对应的type是ref，表示非唯一性索引赛秒，找到值还要进行扫描，直到将索引文件扫描完为止，显而易见，const的性能要高于ref explain select * from itdragon_order_list where transaction_id = \u0026#34;81X97310V32236260E\u0026#34;; -- 优化二、使用覆盖索引，查询的结果变成 transaction_id,当extra出现using index,表示使用了覆盖索引 explain select transaction_id from itdragon_order_list where transaction_id = \u0026#34;81X97310V32236260E\u0026#34;; 案例二 -- 创建复合索引 create index idx_order_levelDate on itdragon_order_list (order_level,input_date); -- 创建索引之后发现跟没有创建索引一样，都是全表扫描，都是文件排序 explain select * from itdragon_order_list order by order_level,input_date; -- 可以使用force index强制指定索引 explain select * from itdragon_order_list force index(idx_order_levelDate) order by order_level,input_date; -- 其实给订单排序意义不大，给订单级别添加索引意义也不大 -- 因此可以先确定order_level的值，然后再给input_date排序 explain select * from itdragon_order_list where order_level=3 order by input_date; 查询SQL优化 查询慢的原因 网络、CPU、IO、上下文切换、系统调用、生成统计信息、锁等待时间\n数据访问优化 在无法避免的大量数据过程中，检查应用程序和mysql服务器是否在检索大量无需的字段 是请求了无关的字段，如下 查询不需要的字段 多表返回了全部列 总是取出全部列 重复查询相同的数据 执行过程优化 =\u0026gt;查询缓存（8.x已经完全删除） mysql连接器后尝试去命中缓存，命中返回\n=\u0026gt;解析SQL和预处理：分析器 通过关键字将SQL语句进行解析并生成一颗解析树 mysql解析器将使用mysql语法规则验证和解析查询 =\u0026gt;优化SQL执行计划：优化器 初略统计 表和索引的页面数、索引个数 索引和数据行长度 索引分布情况 大多情况下会选择错误的执行计划，原因如下 统计信息不准确：innodb的MVCC会有过个版本 执行计划成本估算不等于实际执行成本：mysql不知道那些数据实际在内存中和磁盘中 优化器认为的最优的和现实不一样：基于成本模型，但不是我们认为的最优 不考虑并发执行的查询 不考虑不受其控制操作的成本：比如自定义函数 优化器策略 动态优化：与查询的上下文、取值、索引的函数有关，优化N次 静态优化：直接优化解析树，只优化一次 优化器类型 重新定义关联顺序\n外连接转化为内连接\n使用等价代换规则，可以使用一些等价简化的表达式\ncount（）、Min（）、Max（）：索引列不为NULL可以优化这类\n预估并转化常数表达式，检查到可以是一个常数，即转化为常数\n覆盖索引扫描，符合‘覆盖索引’条件即用\n子查询优化：在某些情况下，子查询会变为缓存\n等值传播\n如果两个列的值通过等式关联，那么mysql能够把其中一个列的where条件传递到另一个上： explain select film.film_id from film inner join film_actor using(film_id) where film.film_id \u0026gt; 500; 这里使用film_id字段进行等值关联，film_id这个列不仅适用于film表而且适用于film_actor表 explain select film.film_id from film inner join film_actor using(film_id) where film.film_id \u0026gt; 500 and film_actor.film_id \u0026gt; 500; 关联查询优化 简单关联\n有索引关联\n无索引关联\n（1）Join Buffer会缓存所有参与查询的列而不是只有Join的列。 （2）可以通过调整join_buffer_size缓存大小 （3）join_buffer_size的默认值是256K，join_buffer_size的最大值在MySQL 5.1.22版本前是4G-1，而之后的版本才能在64位操作系统下申请大于4G的Join Buffer空间。 （4）使用Block Nested-Loop Join算法需要开启优化器管理配置的optimizer_switch的设置block_nested_loop为on，默认为开启。\n查询optimizer_switch设置情况：show variables like \u0026lsquo;%optimizer_switch%\u0026rsquo;;\n-- 案例 -- 查看不同的顺序执行方式对查询性能的影响： explain select film.film_id, film.title, film.release_year, actor.actor_id, actor.first_name, actor.last_name from film inner join film_actor using(film_id) inner join actor using(actor_id); 查看执行的成本： show status like \u0026#39;last_query_cost\u0026#39;; -- 按照自己预想的规定顺序执行： explain select straight_join film.film_id, film.title, film.release_year, actor.actor_id, actor.first_name, actor.last_name from film inner join film_actor using(film_id) inner join actor using(actor_id); -- 查看执行的成本： show status like \u0026#39;last_query_cost\u0026#39;; 排序优化 ​\t无论如何排序都是一个成本很高的操作，所以从性能的角度出发，应该尽可能避免排序或者尽可能避免对大量数据进行排序。推荐使用利用索引进行排序，但是当不能使用索引的时候，mysql就需要自己进行排序，如果数据量小则再内存中进行，如果数据量大就需要使用磁盘，mysql中称之为filesort。如果需要排序的数据量小于排序缓冲区(show variables like '%sort_buffer_size%';),mysql使用内存进行快速排序操作，如果内存不够排序，那么mysql就会先将树分块，对每个独立的块使用快速排序进行排序，并将各个块的排序结果存放再磁盘上，然后将各个排好序的块进行合并，最后返回排序结果\n单次排序\n先读取查询所需要的所有列，然后再根据给定列进行排序，最后直接返回排序结果；\n此方式只需要一次顺序IO读取所有的数据，而无须任何的随机IO，问题在于查询的列特别多的时候，会占用大量的存储空间，无法存储大量的数据\n两次排序\n第一次数据读取是将需要排序的字段读取出来，然后进行排序；第二次是将排好序的结果按照需要去读取数据行。 这种方式效率比较低，原因是第二次读取数据的时候因为已经排好序，需要去读取所有记录而此时更多的是随机IO，读取数据成本会比较高 两次传输的优势，在排序的时候存储尽可能少的数据，让排序缓冲区可以尽可能多的容纳行数来进行排序操作\n当需要排序的列的总大小超过**max_length_for_sort_data**定义的字节，mysql会选择双次排序，反之使用单次排序，当然，用户可以设置此参数的值来选择排序的方式\n=\u0026gt;执行器 特定类型查询优化 优化COUNT() MYISAM存储引擎在没有where的条件下count(*)最快 近视值，通过EXPLAIN的row取值 更复杂优化，考虑覆盖索引扫描或者增加汇总表 优化关联查询 确保ON后者using字句中有索引，考虑顺序 group by和order by中的表达式中涉及一个表中的列相同排序方式才会用到索引 优化子查询 尽可能通过关联查询代替 优化Limit查询 在很多应用场景中我们需要将数据进行分页，一般会使用limit加上偏移量的方法实现，同时加上合适的orderby 的子句，如果这种方式有索引的帮助，效率通常不错，否则的化需要进行大量的文件排序操作， 还有一种情况，当偏移量非常大的时候，前面的大部分数据都会被抛弃，这样的代价太高，要优化这种查询的话，要么是在页面中限制分页的数量，要么优化大偏移量的性能 优化union查询 mysql总是通过创建并填充临时表的方式来执行union查询，因此很多优化策略在union查询中都没法很好的使用。 经常需要手工的将where、limit、order by等子句下推到各个子查询中，以便优化器可以充分利用这些条件进行优化 用户自定义变量 在查询中混合使用过程化和关系话逻辑的时候，自定义变量会非常有用；用户自定义变量是一个用来存储内容的临时容器，在连接mysql的整个过程中都存在。\n自定义变量使用\nset @one :=1 set @min_actor :=(select min(actor_id) from actor) set @last_week :=current_date-interval 1 week; 自定义变量的限制\n无法使用查询缓存 不能在使用常量或者标识符的地方使用自定义变量，例如表名、列名或者limit子句 用户自定义变量的生命周期是在一个连接中有效，所以不能用它们来做连接间的通信 不能显式地声明自定义变量地类型 mysql优化器在某些场景下可能会将这些变量优化掉，这可能导致代码不按预想地方式运行 赋值符号：=的优先级非常低，所以在使用赋值表达式的时候应该明确的使用括号 使用未定义变量不会产生任何语法错误 使用案例\n优化排名\n-- 1、在给一个变量赋值的同时使用这个变量 select actor_id,@rownum:=@rownum+1 as rownum from actor limit 10; -- 2、查询获取演过最多电影的前10名演员 -- 然后根据出演电影次数做一个排名 select actor_id,count(*) as cnt from film_actor group by actor_id order by cnt desc limit 10; 避免重新查询更新值\n-- 当需要高效的更新一条记录的时间戳，同时希望查询当前记录中存放的时间戳是什么 -- 分开写法 update t1 set lastUpdated=now() where id =1; select lastUpdated from t1 where id =1; -- 自定义变量写法 update t1 set lastupdated = now() where id = 1 and @now:=now(); select @now; 确定取值顺序\n-- 在赋值和读取变量的时候可能是在查询的不同阶段 set @rownum:=0; select actor_id,@rownum:=@rownum+1 as cnt from actor where @rownum\u0026lt;=1; -- 因为where和select在查询的不同阶段执行，所以看到查询到两条记录，这不符合预期 set @rownum:=0; select actor_id,@rownum:=@rownum+1 as cnt from actor where @rownum\u0026lt;=1 order by first_name -- 当引入了order by之后，发现打印出了全部结果， -- 这是因为order by引入了文件排序，而where条件是在文件排序操作之前取值的 -- 解决这个问题的关键在于让变量的赋值和取值发生在执行查询的同一阶段： set @rownum:=0; select actor_id,@rownum as cnt from actor where (@rownum:=@rownum+1)\u0026lt;=1; ","permalink":"https://blog.codingforjoy.com/posts/mysql%E8%B0%83%E4%BC%98%E7%90%86%E8%AE%BA%E7%AF%87/","summary":"关于MySQL调优理论.","title":"Mysql调优理论篇"},{"content":" 不以规矩，不成方圆\n“Write Once，Run Everywhere”，功归于虚拟机的“规范”\n基本数据类型 u1、u2、u4分别代表占用1字节、2字节、4字节 Class文件结构 自上而下\nClassFile { u4 magic; // 魔术 0xCAFEBABE u2 minor_version;// 次要版本，标识 m u2 major_version;// JDK主要版本,标识 M u2 constant_pool_count;// 常量池个数 cp_info constant_pool[constant_pool_count-1];// 常量池元素数组 下标从1开始 u2 access_flags;// 此类修饰符 u2 this_class;// 此包类或接口路径 如 com/amk/App u2 super_class;// 父类,至少是Object超类 u2 interfaces_count;// 实现接口个数 u2 interfaces[interfaces_count];// 具体的接口名数组 u2 fields_count;// 属性个数 field_info fields[fields_count];// 属性值 u2 methods_count;// 方法个数 method_info methods[methods_count];// 具体方法 u2 attributes_count;// 其他属性计数 attribute_info attributes[attributes_count];// 具体其他属性数组 } 十六进制码 具体看下Class文件\n需要的工具 Sublime 或者 01Editer\n预设知识：图解进制转换\n// 类 public class ClassStructure {} //javac ClassStructure 编译输出 Class文件 public class ClassStructure { // 默认无参构造 public ClassStructure() {} } 通过 Sublime打开,会看到是这样的,这就是Class 十六进制\ncafe babe 0000 0034 0010 0a00 0300 0d07 000e 0700 0f01 0006 3c69 6e69 743e 0100 0328 2956 0100 0443 6f64 6501 000f 4c69 6e65 4e75 6d62 6572 5461 626c 6501 0012 4c6f 6361 6c56 6172 6961 626c 6554 6162 6c65 0100 0474 6869 7301 0010 4c43 6c61 7373 5374 7275 6374 7572 653b 0100 0a53 6f75 7263 6546 696c 6501 0013 436c 6173 7353 7472 7563 7475 7265 2e6a 6176 610c 0004 0005 0100 0e43 6c61 7373 5374 7275 6374 7572 6501 0010 6a61 7661 2f6c 616e 672f 4f62 6a65 6374 0021 0002 0003 0000 0000 0001 0001 0004 0005 0001 0006 0000 002f 0001 0001 0000 0005 2ab7 0001 b100 0000 0200 0700 0000 0600 0100 0000 0400 0800 0000 0c00 0100 0000 0500 0900 0a00 0000 0100 0b00 0000 0200 0c 我们看到\u0026quot;cafe babe\u0026ldquo;开头,这个就是魔数,可以理解为文件类型描述符,从上文得到魔数数据类型是u4,占用4个字节,得到一个码占用4位,下面我们来尝试读取一下\n方法:先看上面的ClassFile中的数据类型u*,在看对应的代表什么\ncafebabe : magic u4 0000: minor_version u2 0 0034: major_version u2 52=4x16^0+3x16^1 表格 0010:constant_pool_count u2 16 \u0026hellip; 以此借助基本数据类型读取进制码 接下来看另一种数据类型读取方法: cp_info,需要借助IDEA\n常量池\njavap -v ClassStructure # 会看到如下结果 Classfile /target/test-classes/ClassStructure.class Last modified 2021年5月9日; size 267 bytes SHA-256 checksum d6a39a3bf56d982b11d79c38256f2f6b2a2f8745c38a4f86440fb9d701f44078 Compiled from \u0026#34;ClassStructure.java\u0026#34; public class ClassStructure # 次版本 minor version: 0 # JDK版本 major version: 52 # 类修饰符 flags: (0x0021) ACC_PUBLIC, ACC_SUPER # 类名 this_class: #2 // ClassStructure # 父类 super_class: #3 // java/lang/Object # 接口 变量 方法 其他属性 计数 interfaces: 0, fields: 0, methods: 1, attributes: 1 # 重点看这儿常量池 Constant pool: # 井号后面是索引 #1 = Methodref 方法 #3(指向3).#13(指向索引13) // java/lang/Object.\u0026#34;\u0026lt;init\u0026gt;\u0026#34;:()V #2 = Class #14 // ClassStructure #3 = Class #15 // java/lang/Object #4 = Utf8 \u0026lt;init\u0026gt; #5 = Utf8 ()V #6 = Utf8 Code #7 = Utf8 LineNumberTable #8 = Utf8 LocalVariableTable #9 = Utf8 this #10 = Utf8 LClassStructure; #11 = Utf8 SourceFile #12 = Utf8 ClassStructure.java #13 = NameAndType #4:#5 // \u0026#34;\u0026lt;init\u0026gt;\u0026#34;:()V #14 = Utf8 ClassStructure #15 = Utf8 java/lang/Object { public ClassStructure(); descriptor: ()V flags: (0x0001) ACC_PUBLIC Code: stack=1, locals=1, args_size=1 0: aload_0 1: invokespecial #1 // Method java/lang/Object.\u0026#34;\u0026lt;init\u0026gt;\u0026#34;:()V 4: return LineNumberTable: line 4: 0 LocalVariableTable: Start Length Slot Name Signature 0 5 0 this LClassStructure; } SourceFile: \u0026#34;ClassStructure.java\u0026#34; constant_pool_count常量计数后面是常量元素(cp_info),通过规范查到的结构是\ncp_info { u1 tag; u1 info[]; } 所以接下来的u1一个字节0a为tag 10,然后在规定的17个tags得到是\u0026quot;CONSTANT_Methodref\u0026quot;在点击Section这一栏的超链接得到对应的结构为\n// 先知道是什么,后面有宏观解释 CONSTANT_Methodref_info { u1 tag; u2 class_index; // 指向常量池的下一个 类索引 联系上文[Constant pool]#3 u2 name_and_type_index; // 入参:返回类型 索引 练习上文[Constant pool]#15 } 宏观解释无参构造方法:\n// 联系 javap -v 输出结果 Constant pool { u1 tag; 0a // 代表17个Tags中tag为10的CONSTANT_Methodref // #3-\u0026gt;#15-\u0026gt;java/lang/Object 表示类为Object 但是实际类型并不是暂时理解为通用类 // class_index 类为Object占位符,猜想运行时才会转化为具体的类型,TODO 待验证 u2 class_index; 0003 #3 -\u0026gt; [#3 = Class #15] -\u0026gt; [#15 = Utf8 java/lang/Object] // #13-\u0026gt;#13 #4name:#5type -\u0026gt; \u0026#34;\u0026lt;init\u0026gt;\u0026#34;:()V 表示无参构造方法 u2 name_and_type_index; 000d #13 -\u0026gt; [#13 = NameAndType #4:#5]-\u0026gt; [\u0026#34;\u0026lt;init\u0026gt;\u0026#34;:()V] } 以上读取了 \u0026ldquo;0a00 0300 0d\u0026quot;常量池第一个元素的含义,后面的参照此方法\n总结读取步骤:\nu1 查tags表 链接Section栏的超链接查看具体的结构字节数 带着字节数索引在javap -v 总常量池[Constant pool]总向后推进 最后介绍个IDEA插件:jclasslib Bytecode Viewer代替Javap -v 更加直观\n插件参构造表示方式 参考:Oracle Class文件结构\nNext：用Java Buffer解析字节码\n","permalink":"https://blog.codingforjoy.com/posts/%E6%B5%85%E5%85%A5class%E5%AD%97%E8%8A%82%E7%A0%81/","summary":"关于Class字节码.","title":"浅入Class字节码"},{"content":"实践得出结论 小程序™： /** * @author Chunming Liu */ public class HeapOOMTest { public static void main(String[] args) { new Thread ( () -\u0026gt; { ArrayList\u0026lt;byte[]\u0026gt; bytes = new ArrayList\u0026lt;\u0026gt; (); while (true) { System.out.println ( \u0026#34;[\u0026#34; + LocalDateTime.now () + \u0026#34;] \u0026#34; + Thread.currentThread () ); bytes.add ( new byte[1024 * 1024] ); try { Thread.sleep ( 1000 ); } catch (InterruptedException e) { e.printStackTrace (); } } } ).start (); new Thread ( () -\u0026gt; { while (true) { System.out.println ( LocalDateTime.now () + \u0026#34;==\u0026#34; + Thread.currentThread () ); try { Thread.sleep ( 1000 ); } catch (InterruptedException e) { e.printStackTrace (); } } } ).start (); } } 运行之前添加JVM参数 -Xms16m -Xmx32m\n通过运行期间发生了 OOM但是可以出程序还是在正常运行 Jconsole监控堆信息 JVM运行时内存情况 结论 触发堆异常不会影响其他线的运行，通过VM的内存情况，可以看出第一次发生的GC会将堆信息移动到老年代，后面的GC伊甸区活动，猜测和GC的策略有关（当前的GC策略：ParScav:MSC）\n","permalink":"https://blog.codingforjoy.com/posts/%E5%A4%9A%E7%BA%BF%E7%A8%8B%E4%B8%8B%E5%8F%91%E7%94%9Foom%E5%85%B6%E4%BB%96%E7%BA%BF%E7%A8%8B%E4%B8%8D%E4%BC%9A%E5%81%9C%E6%AD%A2/","summary":"关于多线程下发生OOM其他线程不会停止.","title":"多线程下发生OOM其他线程不会停止"},{"content":"C、C++、Java 操作系统层面 C、C++直接在操作系统上执行\nJava需要编程编译成字节码基于JVM去执行，而JVM是运行在操作系统与C\\C++平级\n:computer:JVM虚拟机:可以理解为模拟的操作系统，独立在操作系统之上的一个拥有独立内存的子系统\n最初是JVM是将字节码解释为C语言去执行，后来发现这样的性能特别低，现在直接解释为机器语言提高效率\n语言关系 为了设计符合人类思想行为的高级语言，C语言面向过程，C++面向对象，符合人类思想，由于是直接对操作编程容易导致系统的安全性问题，需要Coder自己手动管理物理内存，复杂的指针语法，而Java语言解决了C++的这些安全问题，并且更加的如意上手 跨平台性 C、C++在同一个功能API每个操作系统中各不相同，程序无法在其他环境中直接执行 Java是运行在JVM上的，统一了JVM的输入源规范根据不同的平台解释成对应的平台的API，即实现了“write once，run everywhere” 两种编程思想 面向过程 关注的是数据的流向过程 面向对象 关注的是对象之间关系的交互 面向对象解决问题的步骤：首先对事件中的对象进行OOA，分析各个对象的行为及属性-\u0026gt;第二步骤对事件中对象之间的交互关系进行合理的设计OOD-\u0026gt;最后对合理的设计进行编码实现OOP\n三大特性 封装 对象本质进行包装，通过public、private、protected关键字语法去实现，保护对象的数据安全 继承 古代君王世袭制，Java所有类都默认继承超类Object 多态 猿人不断进化成人类，人类不仅继承了猿人的特征，还具有独立的思考 数据类型 基本数据类型 整型：\nbyte:1 short:2 int:4 long:8 char:2(UTF-16) 浮点型：\nfloat:4 double:8 特殊型：\nboolean：true和false\nretrunadress：JVM规范定义的基本数据类型，用来标识finally和return的\n包装类 Integer、Double、Long 引用型 接口，对象 JVM中默认locals中一个槽为32位\n","permalink":"https://blog.codingforjoy.com/posts/java-core/","summary":"关于Java核心.","title":"Java Core"},{"content":"一名服务端开发者，热爱技术，目前专注于AIOT、嵌入式、流媒体服务端。\n主要编程语言：Java、Golang、Python、C/C++。\n涉及过开发板：RK3399 Pro、RK1808、ESP32、ESP8266、Milk-V Duo。\n实现过AI分析业务：人脸识别、人体检测、区域入侵、越线检测、机动车检测、车牌号识别、车身颜色识别、骨骼识别。\n","permalink":"https://blog.codingforjoy.com/about/","summary":"一名服务端开发者，热爱技术，目前专注于AIOT、嵌入式、流媒体服务端。\n主要编程语言：Java、Golang、Python、C/C++。\n涉及过开发板：RK3399 Pro、RK1808、ESP32、ESP8266、Milk-V Duo。\n实现过AI分析业务：人脸识别、人体检测、区域入侵、越线检测、机动车检测、车牌号识别、车身颜色识别、骨骼识别。","title":"About"},{"content":" github.com/nanxiaobei lee.so ","permalink":"https://blog.codingforjoy.com/contact/","summary":" github.com/nanxiaobei lee.so ","title":"Contact"},{"content":"流媒体网络协议系列：RTP 概述 RFC3550\nRTP协议 RTP-视频流 RTP-h264 RTP-音频流 RTP-AAC ","permalink":"https://blog.codingforjoy.com/posts/%E6%B5%81%E5%AA%92%E4%BD%93%E7%BD%91%E7%BB%9C%E5%8D%8F%E8%AE%AE%E7%B3%BB%E5%88%97rtp/","summary":"流媒体网络协议系列：RTP 概述 RFC3550\nRTP协议 RTP-视频流 RTP-h264 RTP-音频流 RTP-AAC ","title":""},{"content":"","permalink":"https://blog.codingforjoy.com/tags/","summary":"tags","title":"Tags"}]