Youzi - 笔记
type: channel
link: https://autisn.com/index.php/category/%E7%AC%94%E8%AE%B0/
items: Nginx的简单使用
items: ElasticSearch在SpringBoot中的简单运用
items: 网站迁移步骤
items: EasyExcel的导入功能的使用总结
items: RabbitMQ知识点
items: Docker安装RabbitMQ并简单简单使用
items: 计算时间间隔
items: 知识点小结
items: JDK 动态代理和 CGLIB 动态代理的区别
items: Spring AOP and AspectJ AOP的区别

Nginx的简单使用
type: item
date: 2023-10-24T11:01:00+08:00
description: 1.Docker安装Nginx并启动#没有安装的情况下会自动安装，可以指定版本 docker run -p 80:80 --name nginx -d nginx:1.10 #将容器内的配置文件拷贝到当前目录 #别忘了后面的点 docker container cp nginx:/etc/nginx . #修改文件名称并把这个 conf 移动到/mydata/nginx 下 mv nginx conf #终止原容器 docker stop nginx #执行命令删除原容器 docker rm nginx #创建新的 nginx；执行以下命令 docker run -p 80:80 --name nginx \ -v /mydata/nginx/html:/usr/share/nginx/html \ -v /mydata/nginx/logs:/var/log/nginx \ -v /mydata/nginx/conf:/etc/nginx \ -d nginx:1.10 #给 nginx 的 html 下面放的所有资源可以直接访问 drwxr-xr-x. 3 root root 34 Oct 17 07:49 html2.搭建域名访问要求是 ： 访问gulimall.com 能访问到我们自己本机idea中启动的product服务思路：我们通过配置本地的host域名映射我们虚拟机的中的nginx，然后nginx在根据转发规把请求转发回我们自己的idea中的本地服务。前置说明：因为我们的nginx的安装在云服务器上的，本地Host文件解析域名后访问云服务器, 结果请求被拦截虽然可以用谷歌浏览器安装ModHeader插件解决，但是我们在把请求映射到nginx后，在让nginx转发回我们本地的服务是无法完成的，因为云服务器无法ping通我们本地的ip 原因 本地pc属于移动宽带的内网环境，说白了就是cmd下ipconfig里的ip是你电脑宽带运营商的一个大网络（外网）下的一个内网ip，服务器无法主动与内网ip建立连接，连路由都做不到。所以不可能ping通的。可以考虑使用VPN在您的电脑和服务器之间建立VPN连接，这样服务器和您的电脑就能像在同一个内网一样互相访问了2.1配置本地host可以利用SwitchHost配置工具。需要注意的是这个软件要用管理员打开，然后C:\Windows\System32\drivers\etc把hosts的文件权限放开，才能进行修改如下图配置，前面是虚拟机的地址，后面是需要解析的域名2.2配置nginx# 查看我们nginx的总配置文件 [root@ecs-284198 conf]# cat /mydata/nginx/conf/nginx.conf user nginx; worker_processes 1; error_log /var/log/nginx/error.log warn; pid /var/run/nginx.pid; events { worker_connections 1024; } http { include /etc/nginx/mime.types; default_type application/octet-stream; log_format main '$remote_addr - $remote_user [$time_local] "$request" ' '$status $body_bytes_sent "$http_referer" ' '"$http_user_agent" "$http_x_forwarded_for"'; access_log /var/log/nginx/access.log main; sendfile on; #tcp_nopush on; keepalive_timeout 65; #gzip on; include /etc/nginx/conf.d/*.conf; } [root@ecs-284198 conf]# # 当前总文件的最后一行 include /etc/nginx/conf.d/*.conf; 他会加载这个路径下的所有以 .conf结尾的文件作为配置文件，并且在包含到我们的当前的总配置文件中，这样就不会导致一个配置文件很大，所以接下来我们到 conf.d 的目录下新建一个gulimall.conf文件 # cd 到 /mydata/nginx/conf/conf.d/会有一个default.conf，然后我们复制一个default.conf为gulimall.conf [root@ecs-284198 conf.d]# cd /mydata/nginx/conf/conf.d/ [root@ecs-28…

ElasticSearch在SpringBoot中的简单运用
type: item
date: 2023-10-23T22:33:00+08:00
description: 1.给es中建立索引。product,建立好映射关系PUT product { "mappings": { "properties": { "skuId": { "type": "long" }, "spuId": { "type": "long" }, "skuTitle": { "type": "text", "analyzer": "ik_smart" }, "skuPrice": { "type": "keyword" }, "skuImg": { "type": "keyword", "index": false, "doc_values": false }, "saleCount": { "type": "long" }, "hosStock": { "type": "boolean" }, "hotScore": { "type": "long" }, "brandId": { "type": "long" }, "catelogId": { "type": "long" }, "brandName": { "type": "keyword", "index": false, "doc_values": false }, "brandImg": { "type": "keyword", "index": false, "doc_values": false }, "catelogName": { "type": "keyword", "index": false, "doc_values": false }, "attrs": { "type": "nested", "properties": { "attrId": { "type": "long" }, "attrName": { "type": "keyword", "index": false, "doc_values": false }, "attrValue": { "type": "keyword" } } } } } }2.Service类package com.atguigu.gulimall.lsearch.service; import com.atguigu.common.to.es.SkuEsModel; import java.io.IOException; import java.util.List; public interface ProductSaveService { boolean productStatusUp(List<SkuEsModel> skuEsModels) throws IOException; }3.ServiceImpl类package com.atguigu.gulimall.lsearch.service.iml; import com.alibaba.fastjson.JSON; import com.atguigu.common.to.es.SkuEsModel; import com.atguigu.gulimall.lsearch.config.GulimallElasticSearchConfig; import com.atguigu.gulimall.lsearch.constant.EsConstant; import com.atguigu.gulimall.lsearch.service.ProductSaveService; import lombok.extern.slf4j.Slf4j; import org.elasticsearch.action.bulk.BulkRequest; import org.elasticsearch.action.bulk.BulkResponse; import org.elasticsearch.action.index.IndexRequest; import org.elasticsearch.client.RestHighLevelClient; import org.elasticsearch.common.xcontent.XContentType; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import java.io.IOException; import java.util.Arrays; import java.util.List; import java.util.stream.Collectors; @Slf4j @Service("pro…

网站迁移步骤
type: item
date: 2023-08-24T11:47:00+08:00
description: 1.安装和原服务器一样的环境1.1我用的是CentOS 系统，首先安装系统环境1.2我使用的是宝塔面板，所以宝塔面板也要安装一致（版本不一致也可以用，不过最好用的是一样的，避免出现未知原因引起的错误）2.迁移前做好备案准备分两种情况在同一个服务器厂商的情况下，只需要把网站的文件迁移到新的服务器（网站文件的路径要和旧服务器的一致）。在不同的服务器厂商的情况下，首先要先进行网站的备案，大概七个工作日，所以不要等到旧服务器快要过期才迁移，这样会导致时间上的不充裕，在迁移过程中会使网站暂停运行，剩下的就是把文件从旧服务器上的文件上传到新的服务器（路径要一致）。3.数据库的迁移在旧服务器上怎么安装的数据库，在新服务器上就怎么安装，最好就是版本都是一样的，然后导出旧数据上的表结构和数据，然后在新服务器上导入，要注意的就是数据库名称和密码要和旧数据库的要一致，这样就方便在新的服务器上运行的时候，不用重新修改代码。4.迁移完成迁移结束后，域名解析要换成新服务器的公网IP，会有缓存，并不是一修改好就马上解析上去的，24-72小时内，所以解析成功后要记得有空看看你的网站能否正常运行。PS1.迁移工具在宝塔面板有一个这个工具，我测试了一下，也是可以迁移成功的2.迁移时图床出现的问题我在迁移过程中是没有发现什么问题，主要是我自己的两个图床工具突然就是上传失败了，完全不能用，报错信息是Call to undefined function think\finfo open()，主要是php中的一个拓展没有安装。2.1找到这个2.2在设置里找到fileinfo并安装2.3重新测试

EasyExcel的导入功能的使用总结
type: item
date: 2022-09-29T10:55:00+08:00
description: 监听器（用MyBatis的IService接口做了封装）package io.github.talelin.latticy.listener; import com.alibaba.excel.context.AnalysisContext; import com.alibaba.excel.event.AnalysisEventListener; import com.alibaba.excel.util.ListUtils; import com.alibaba.fastjson.JSON; import com.baomidou.mybatisplus.extension.service.IService; import lombok.extern.slf4j.Slf4j; import java.util.List; import java.util.Map; @Slf4j public class ExcelReaderListener<T> extends AnalysisEventListener<T> { /** * 每隔5条存储数据库，实际使用中可以100条，然后清理list ，方便内存回收 */ private static final int BATCH_COUNT = 100; /** * 缓存的数据 */ private List<T> cachedDataList = ListUtils.newArrayListWithExpectedSize(BATCH_COUNT); /** * 假设这个是一个DAO，当然有业务逻辑这个也可以是一个service。当然如果不用存储这个对象没用。 */ private IService<T> service; /** * 如果使用了spring,请使用这个构造方法。每次创建Listener的时候需要把spring管理的类传进来 * * @param service */ public ExcelReaderListener(IService<T> service) { this.service = service; } /** * 这个每一条数据解析都会来调用 * * @param data one row value. Is is same as {@link AnalysisContext#readRowHolder()} * @param context */ @Override public void invoke(T data, AnalysisContext context) { log.info("解析到一条数据:{}", JSON.toJSONString(data)); cachedDataList.add(data); // 达到BATCH_COUNT了，需要去存储一次数据库，防止数据几万条数据在内存，容易OOM if (cachedDataList.size() >= BATCH_COUNT) { saveData(); // 存储完成清理 list //注意ListUtils.newArrayListWithExpectedSize是在EasyExcel3.1.1版本之后才有的 cachedDataList = ListUtils.newArrayListWithExpectedSize(BATCH_COUNT); } } @Override public void invokeHeadMap(Map<Integer, String> headMap, AnalysisContext context) { log.info("表头数据 excelHead= {}", headMap); } /** * 所有数据解析完成了 都会来调用 * * @param context */ @Override public void doAfterAllAnalysed(AnalysisContext context) { // 这里也要保存数据，确保最后遗留的数据也存储到数据库 saveData(); log.info("所有数据解析完成！"); } /** * 加上存储数据库 */ private void saveData() { log.info("{}条数据，开始存储数据库！", cachedDataList.size()); service.saveBatch(cachedDataList); log.info("存储数据库成功！"); } }控制层代码@PostMapping("/import") public AjaxResult importExcel(MultipartFile file) throws IOException { A…

RabbitMQ知识点
type: item
date: 2022-07-05T15:06:00+08:00
description: 1、消息应答的方法：Channel.basicAck(用于肯定确认)，RabbitMQ已知道该消息并且成功处理，可以将其丢弃Channel.basicNack(用于否定确认)Channel.basicReject(用于否定确认)，与Channel.basicNack相比少了一个参数，不处理该消息了，直接拒绝，可以将其丢弃了。2、Multiple的解释true表示批量应答channel上未应答的消息，比如channel上有传送tag的消息5,6,7,8,，当前tag是8，那么此时5-8的这些还未应答的消息就会被确认收到消息应答false同上面相比只会应答tag=8的消息，5,6,7这三个消息依然不会被确认收到消息应答手动应答的好处是可以批量应答并且减少网络拥堵 。3、消息手动应答代码//getEnvelope()属性，getDeliveryTag()标记 /* * 参数1：消息的标记 tag * 参数2：是否批量应答，false：不批量应答；true：批量 * */ channel.basicAck(message.getEnvelope().getDeliveryTag(), false); // 采用手动应答 boolean autoAck = false; channel.basicConsume(QUEUE_NAME,autoAck,deliverCallback,(consumerTag) -> { System.out.println(consumerTag+"消费者取消消费接口回调逻辑"); });4、持久化// 声明队列 // 持久化 需要让Queue持久化 boolean durable = true; channel.queueDeclare(TASK_QUEUE_NAME,durable,false,false,null);注意：如果之前声明的队列不是持久化的，需要把原先队列先删除，或者重新创建一个持久化的队列，不然就会出现错误。将消息标记为持久化并不能完全保证不会丢失消息。尽管它告诉 RabbitMQ 将消息保存到磁盘，但是这里依然存在当消息刚准备存储在磁盘的时候但是还没有存储完，消息还在缓存的一个间隔点。此时并没有真正写入磁盘。持久性保证并不强，但是对于我们的简单任务队列而言，这已经绰绰有余了。5、不公平分发//不公平分发channel //basicQos(0) --> 轮询分发（公平分发），默认值为0 //channel.basicQos(1) --> 不公平分发，能者多劳 //channel.basicQos(>2) --> 预取值 int prefetchCount = 1; channel.basicQos(prefetchCount); //采用手动应答 boolean autoAck = false; channel.basicConsume(TASK_QUEUE_NAME, autoAck, deliverCallback, cancelCallback);6、发布确认发布确认的策略:开启发布确认的方法：发布确认默认是没有开启的，如果要开启，需要调用方法 confirmSelect，每当你要想使用发布确认，都需要在 channel 上调用该方法。//开启发布确认 channel.confirmSelect();6.1单个确认发布这是一种简单的确认方式，它是一种同步确认发布的方式，也就是发布一个消息之后只有它被确认发布，后续的消息才能继续发布，waitForConfirmsOrDie(long) 这个方法只有在消息被确认的时候才返回，如果在指定时间范围内这个消息没有被确认那么它将抛出异常。这种确认方式有一个最大的缺点就是：发布速度特别的慢，因为如果没有确认发布的消息就会阻塞所有后续消息的发布，这种方式最多提供每秒不超过数百条发布消息的吞吐量。/** * 单个发送 */ public static void publishMessageIndividually() throws Exception { Channel channel = RabbitMqUtils.getChannel(); //队列声明 String queueName = UUID.randomUUID().toString(); channel.queueDeclare(queueName, true, false, false, null); //开启发布确认 channel.confirmSelect(); long begin = System.currentTimeMillis(); for (int i = 0; i < MESSAGE_COUNT; i++) { String message = i + "…

Docker安装RabbitMQ并简单简单使用
type: item
date: 2022-06-30T17:33:00+08:00
description: 1.Docker安装RabbitMQ1.1下载RabbitMQ镜像docker pull rabbitmq1.2创建并启动RabbitMQ容器docker run -id --hostname myrabbit --name rabbitmq -p 15672:15672 -p 5672:5672 rabbitmq1.3进入容器交互页面docker exec -it rabbitmq /bin/bash1.4下载插件rabbitmq-plugins enable rabbitmq_management1.5登录ip+端口号访问账号密码均为：guest1.6拓展# 创建账号和密码 rabbitmqctl add_user 用户名 密码 # 设置用户角色 rabbitmqctl set_user_tags admin administrator # 为用户添加资源权限 # set_permissions [-p <vhostpath>] <user> <conf> <write> <read> # 添加配置、写、读权限 rabbitmqctl set_permissions -p "/" admin ".*" ".*" ".*"用户级别：administrator：可以登录控制台、查看所有信息、可以对 rabbitmq 进行管理。monitoring：监控者 登录控制台，查看所有信息。policymaker：策略制定者 登录控制台，指定策略。managment：普通管理员 登录控制台。注：有可能会出现下面这个问题访问Docker的rabbitMq容器的Web-UI界面无法查看消息的状态及出现Stats in management UI are disabled on this node解决办法：进入RabbitMQ容器docker exec -it rabbitmq /bin/bash进入容器后，cd到路径/etc/rabbitmq/conf.d/cd /etc/rabbitmq/conf.d/再修改management_agent.disable_metrics_collectorecho management_agent.disable_metrics_collector = false > management_agent.disable_metrics_collector.conf退出容器exit输入指令docker exec -it rabbitmq rabbitmq-plugins enable rabbitmq_management重启RabbitMQdocker restart rabbitmq2.简单使用2.1pom.xml文件添加依赖(本环境是jdk1.8的)<dependencies> <dependency> <groupId>com.rabbitmq</groupId> <artifactId>amqp-client</artifactId> <version>5.8.0</version> </dependency> <dependency> <groupId>commons-io</groupId> <artifactId>commons-io</artifactId> <version>2.6</version> </dependency> </dependencies>2.2新建Producer类为生产者package com.youzi.rabbitmq.one; import com.rabbitmq.client.Channel; import com.rabbitmq.client.Connection; import com.rabbitmq.client.ConnectionFactory; import java.io.IOException; import java.util.concurrent.TimeoutException; /** * @Author youzi * @ClassName Producer * @Date 2022/6/30 15:41 * @Description //TODO 生产者 */ public class Producer { //设置队列名称 public static final String QUEUE_NAME = "hello"; public static void main(String[] args) throws IOException, TimeoutException { //1.创建连接工厂 ConnectionFactory factory = new ConnectionFactory(); //2.设置IP fa…

计算时间间隔
type: item
date: 2022-06-22T14:18:00+08:00
description: 首先<dependencies> <dependency> <groupId>joda-time</groupId> <artifactId>joda-time</artifactId> <version>2.10.1</version> </dependency> </dependencies>其次import org.joda.time.Interval; import org.joda.time.Period; import java.text.DateFormat; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.Calendar; import java.util.Date; import java.util.HashMap; import java.util.Map; /** * @Author youzi * @ClassName TestDateUtil * startDate 开始时间 * endDate 结束时间 * dateFormat 时间格式（形如 yyyy-MM-dd） * @return 相差的月份数和天数 months 、 days * @throws ParseException * @Date 2022/6/22 14:02 * @Description 计算两个时间差 */ public class TestDateUtil { public static void main(String[] args) throws ParseException { DateFormat df = new SimpleDateFormat("yyyy-MM-dd"); Date dateStart = df.parse("2022-03-04"); Date dateEnd = df.parse("2022-06-03"); if (dateStart.getTime() > dateEnd.getTime()) { Date temp = dateStart; dateStart = dateEnd; dateEnd = temp; } Calendar c = Calendar.getInstance(); c.setTime(dateEnd); c.add(Calendar.DAY_OF_MONTH, 1); //这是明天 dateEnd = c.getTime(); Interval interval = new Interval(dateStart.getTime(), dateEnd.getTime()); Period p = interval.toPeriod(); Integer years = p.getYears(); Integer months = p.getMonths(); Integer weeks = p.getWeeks(); Integer days = p.getDays(); Integer resultMonths = years * 12 + months; Integer resultDays = weeks * 7 + days; Map<String, Integer> result = new HashMap<>(); result.put("months", resultMonths); result.put("days", resultDays); System.out.println(result); } }

知识点小结
type: item
date: 2022-06-21T15:23:00+08:00
description: 重载（Overload）和重写（Override）的区别方法的重载和重写都是实现多态的方式，区别在于前者实现的是编译时的多态性，而后者实现的是运行时的多态性。重载：一个类中有多个同名的方法，但是具有有不同的参数列表（参数类型不同、参数个数不同或者二者都不同）。重写：发生在子类与父类之间，子类对父类的方法进行重写，参数都不能改变，返回值类型可以不相同，但是必须是父类返回值的派生类。即外壳不变，核心重写！重写的好处在于子类可以根据需要，定义特定于自己的行为。注：重载的概念是：方法名称相同，参数个数、次序、类型不同因此重载对返回值没有要求，可以相同，也可以不同，但是如果参数的个数、类型、次序都相同，方法名也相同，仅返回值不同，则无法构成重载如：//这2个方法不能构成重载，会有编译错误。 public int A(int i); public double A(int i); //这2个方法可以形成重载 public int A(int i); public double A(double i);不能根据返回类型来区分重载的原因：如果我们有两个方法如下，当我们调用：test(1) 时，编译器无法确认要调用的是哪个。方法的返回值只是作为方法运行之后的一个“状态”，但是并不是所有调用都关注返回值，所以不能将返回值作为重载的唯一区分条件。// 方法1 int test(int a); // 方法2 long test(int a);抽象类（abstract class）和接口（interface）的区别抽象类只能单继承，接口可以多实现。抽象类可以有构造方法，接口中不能有构造方法。抽象类中可以有成员变量，接口中没有成员变量，只能有常量（默认就是 public static final）。抽象类中可以包含非抽象的方法，在 Java 7 之前接口中的所有方法都是抽象的，在 Java 8 之后，接口支持非抽象方法：default 方法、静态方法等。Java 9 支持私有方法、私有静态方法。抽象类中的方法类型可以是任意修饰符，Java 8 之前接口中的方法只能是 public 类型，Java 9 支持 private 类型。设计思想的区别：接口是自上而下的抽象过程，接口规范了某些行为，是对某一行为的抽象。我需要这个行为，我就去实现某个接口，但是具体这个行为怎么实现，完全由自己决定。抽象类是自下而上的抽象过程，抽象类提供了通用实现，是对某一类事物的抽象。我们在写实现类的时候，发现某些实现类具有几乎相同的实现，因此我们将这些相同的实现抽取出来成为抽象类，然后如果有一些差异点，则可以提供抽象方法来支持自定义实现。打个比方：普通类像亲爹 ，他有啥都是你的。抽象类像叔伯，有一部分会给你，还能指导你做事的方法。接口像干爹，可以给你指引方法，但是做成啥样得你自己努力实现。wait() 和 sleep() 方法的区别来源不同：sleep() 来自 Thread 类，wait() 来自 Object 类。对于同步锁的影响不同：sleep() 不会该表同步锁的行为，如果当前线程持有同步锁，那么 sleep 是不会让线程释放同步锁的。wait() 会释放同步锁，让其他线程进入 synchronized 代码块执行。使用范围不同：sleep() 可以在任何地方使用。wait() 只能在同步控制方法或者同步控制块里面使用，否则会抛 IllegalMonitorStateException。恢复方式不同：两者会暂停当前线程，但是在恢复上不太一样。sleep() 在时间到了之后会重新恢复；wait() 则需要其他线程调用同一对象的 notify()、nofityAll() 才能重新恢复。为什么要使用线程池？为什么不直接new个线程？如果我们在方法中直接new一个线程来处理，当这个方法被调用频繁时就会创建很多线程，不仅会消耗系统资源，还会降低系统的稳定性，一不小心把系统搞崩了，就可以直接去财务那结帐了。如果我们合理的使用线程池，则可以避免把系统搞崩的窘境。总得来说，使用线程池可以带来以下几个好处：降低资源消耗。通过重复利用已创建的线程，降低线程创建和销毁造成的消耗。提高响应速度。当任务到达时，任务可以不需要等到线程创建就能立即执行。增加线程的可管理型。线程是稀缺资源，使用线程池可以进行统一分配，调优和监控。List、Set、MapHashMap 和Hashtable 的区别HashMap 允许 key 和 value 为 null，Hashtable 不允许。HashMap 的默认初始容量为 16，Hashtable 为 11。HashMap 的扩容为原来的 2 倍，Hashtable 的扩容为原来的 2 倍加 1。HashMap 是非线程安全的，Hashtable是线程安全的。H…

JDK 动态代理和 CGLIB 动态代理的区别
type: item
date: 2022-06-17T11:36:49+08:00
description: Spring AOP 中的动态代理主要有两种方式，JDK 动态代理和 CGLIB 动态代理：JDK动态代理只提供接口的代理，不支持类的代理。核心 InvocationHandler接口和Proxy类，InvocationHandler 通过 invoke()方法反射来调用目标类中的代码，动态地将横切逻辑和业务编织在一起；接着，Proxy 利用 InvocationHandler 动态创建 一个符合某一接口的的实例, 生成目标类的代理对象。如果代理类没有实现 InvocationHandler 接口，那么 Spring AOP 会选择使用 CGLIB 来动态代理目标类。CGLIB（Code Generation Library），是一个代码生成的类库，可以在运行时动态的生成指定类的一个子类对象，并覆盖其中特定方法并添加增强代码，从而实现 AOP。CGLIB 是通过继承的方式做的动态代理，因此如果某个类被标记为 final，那么它是无法使用 CGLIB 做动态代理的。静态代理与动态代理区别在于生成 AOP 代理对象的时机不同，相对来说 AspectJ 的静态代理方式具有更好的性能，但是 AspectJ 需要特定的编译器进行处理，而 Spring AOP 则无 需特定的编译器处理。注：InvocationHandler 的 invoke(Object proxy,Method method,Object[] args)：proxy 是最终生成的代理实例; method 是被代理目标实例的某个具体方法; args 是被代理目标实例某个方法的具体入参, 在方法反射调用时使用。

Spring AOP and AspectJ AOP的区别
type: item
date: 2022-06-17T11:23:38+08:00
description: AOP实现的关键在于代理模式，AOP代理主要分为静态代理和动态代理。静态代理的代表为AspectJ AOP；动态代理则以Spring AOP为代表。AspectJ AOP使用的静态代理的增强，所谓静态代理，就是AOP框架会在编译阶段生成AOP代理类，因此也称为编译时增强，他会在编译阶段将AspectJ(切面)织入到Java字节码中， 运行的时候就是增强之后的AOP对象。Spring AOP 使用的是动态代理，所谓的动态代理就是说AOP框架不会去修改字节码，而是每次运行时在内存中临时为方法生成一个AOP对象，这个AOP对象包含了目标对象的全部方法，并且在特定的切点做了增强处理，并回调原对象的方法。