【JavaEE】AOP实现原理

概述

  • Spring AOP 是基于动态代理来实现AOP的, 此处主要介绍代理模式和Spring AOP的源码剖析

一.代理模式

  • 代理模式是一种常用的设计模式,它允许为其他对象提供代理,以控制对这个对象的访问。这种结构在不改变原始类的基础上,通过引入代理类来扩展功能,广泛应用于各种编程场景和框架中。

  • 使用代理前:
    在这里插入图片描述

  • 使用代理后:
    在这里插入图片描述

  • 在生活当中也有很多代理模式的例子:

    • 艺人经纪人: 广告商找艺人拍广告, 需要经过经纪人,由经纪人来和艺人进行沟通.
    • 房屋中介: 房屋进行租赁时, 卖方会把房屋授权给中介, 由中介来代理看房, 房屋咨询等服务.
    • 经销商: 厂商不直接对外销售产品, 由经销商负责代理销售.
    • 秘书/助理: 合作伙伴找老板谈合作, 需要先经过秘书/助理预约.
  • 代理模式的主要角色

    • Subject: 业务接口类. 可以是抽象类或者接口(不⼀定有)
    • RealSubject: 业务实现类. 具体的业务执行, 也就是被代理对象.
    • Proxy: 代理类. RealSubject的代理.

    比如房屋租赁
    Subject 就是提前定义了房东做的事情, 交给中介代理, 也是中介要做的事情
    RealSubject: 房东
    Proxy: 中介

  • 根据代理的创建时期, 代理模式分为静态代理动态代理.

    • 静态代理: 由程序员创建代理类或特定工具自动生成源代码再对其编译, 在程序运行前代理类的.class 文件就已经存在了.
    • 动态代理: 在程序运行时, 运用反射机制动态创建而成
    • 更形象的描述就是就是好比你去买房, 静态代理没买房前就已经分配好销售了(可以是根据一些因素进行划分), 动态代理就是买房的时候随机给你发分配销售

二. 静态代理

  • 静态代理是指在程序运行前就已经存在代理类的字节码文件,由程序员手动编写或特定工具自动生成源代码实现的代理方式。

代码实现

我们通过代码来加深对静态代理的理解. 以房租租赁为例:

  1. 定义接口(定义房东要做的事情, 也是中介需要做的事情)
public interface houseSubject {
    void rentHouse();
    void saleHouse();
}
  1. 实现接口(房东出租房子)
public class RealHouseSubject implements houseSubject{
    @Override
    public void rentHouse() {
        System.out.println("我是房东,我出租房子");
    }
    
    @Override
    public void saleHouse() {
        System.out.println("我是房东,我出售房子");
    }
}
  1. 代理(中介, 帮房东出租房子)
public class HouseProxy implements houseSubject{
    
    private RealHouseSubject target;
    
    public HouseProxy(RealHouseSubject realHouseSubject) {
        this.target = realHouseSubject;
    }

    @Override
    public void rentHouse() {
        //出租前
        System.out.println("我是中介,我开始代理");
        //出租房子
        target.rentHouse();
        //出租后
        System.out.println("我是中介,结束代理");
    }

    @Override
    public void saleHouse() {
        //出售前
        System.out.println("我是中介,我开始代理");
        //出售房子
        target.saleHouse();
        //出售后
        System.out.println("我是中介,结束代理");
    }
}

  1. 使用静态代理
public class Main {
    public static void main(String[] args) {
        //创建代理类
        HouseProxy houseProxy = new HouseProxy(new RealHouseSubject());
        //通过代理类访问目标方法
        houseProxy.rentHouse();
    }
}
  • 运行结果:
    在这里插入图片描述

上面这个代理实现方式就是静态代理(仿佛啥也没干). 从上述程序可以看出, 虽然静态代理也完成了对目标对象的代理, 但是由于代码都写死了, 对目标对象的每个方法的增强都是手动完成的,非常不灵活. 所以日常开发几乎看不到静态代理的场景

接下来新增需求: 中介又新增了其他业务, 我们修改接口(Subject)和业务实现类(RealSubject)时, 还需要修改代理类(Proxy). 同样的, 如果有新增接口(Subject)和业务实现类(RealSubject), 也需要对每一个业务实现类新增代理类(Proxy).

三.动态代理.

  • 动态代理是指我们不需要针对每个目标对象都单独创建一个代理对象, 而是把这个创建代理对象的工作推迟到程序运行时由JVM来实现. 也就是说动态代理在程序运行时, 根据需要动态创建生成.

  • 在Java中实现动态代理的方式主要有两种,一种是JDK动态代理,一种是CGLib. 接下来就主要介绍这两种方式.

3.1 JDK动态代理.

  • JDK动态代理的实现步骤:
      1. 定义一个接口及其实现类(静态代理中的 HouseSubject 和 RealHouseSubject )
      1. 自定义 InvocationHandler 并重写 invoke 方法,在 invoke 方法中我们会调用目标方法(被代理类的方法)并自定义一些处理逻辑.
      1. 通过 Proxy.newProxyInstance(ClassLoader loader,Class<?>[]
        interfaces,InvocationHandler h) 方法创建代理对象

JDK动态代理的代码实现

1. 实现 InvocationHandler 接口
public class JDKInvocationHandler implements InvocationHandler {
    private Object target;
    //目标对象
    public JDKInvocationHandler(Object target) {
        this.target = target;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("我是代理,开始代理");
        //通过反射调用目标函数
        Object result = method.invoke(target, args);
        System.out.println("我是代理,结束代理");
        return result;
    }
}
2. 创建一个代理对象并使用
public class Main {
    public static void main(String[] args) {
        /**
         *public static Object newProxyInstance(ClassLoader loader,
         *                                           Class<?>[] interfaces,
         *                                           InvocationHandler h) {
         * 加载代理类的classloader
         * 要实现的接口,
         * 代理要做的事情,InvocationHandler这个接口
         */
        //真实的目标对象
        RealHouseSubject targer = new RealHouseSubject();
        //动态生成代理对象
        houseSubject proxy = (houseSubject)Proxy.newProxyInstance(targer.getClass().getClassLoader(),
                new Class[]{houseSubject.class},
                new JDKInvocationHandler(targer));
        proxy.rentHouse();
        proxy.saleHouse();

JDK动态代理过程中涉及方法的参数讲解

  1. InvocationHandler接口
    InvocationHandler 接口是Java动态代理的关键接口之一, 它定义了一个单一方法 invoke() , 用于处理被代理对象的方法调用.
public interface InvocationHandler {
 /**
 * 参数说明
 * proxy:代理对象
 * method:代理对象需要实现的⽅法,即其中需要重写的⽅法
 * args:method所对应⽅法的参数
 */
 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable;
}

通过实现 InvocationHandler 接口, 可以对被代理对象的方法进行功能增强.

  1. Proxy
    Proxy 类中使用频率最高的方法是: newProxyInstance() , 这个方法主要用来生成一个代理对象.
public static Object newProxyInstance(ClassLoader loader,
 									Class<?>[] interfaces,
 									InvocationHandler h)
 									throws IllegalArgumentException{
 		//...代码省略
 }
  • 这个方法一共有 3 个参数:
    • Loader: 类加载器, 用于加载代理对象.
    • interfaces : 被代理类实现的一些接口(这个参数的定义, 也决定了JDK动态代理只能代理实现了接口的一些类)
    • h : 实现了 InvocationHandler 接口的对象.

3.2 CGLib动态代理

  • CGLib动态代理的实现步骤:
    • 定义一个类(被代理类).
    • 自定义 MethodInterceptor 并重写 intercept 方法, intercept 用于增强目标
      方法,和 JDK 动态代理中的 invoke 方法类似.
    • 通过 Enhancer 类的 create()创建代理类.

CGLib动态代理的代码实现

1. 自定义 MethodInterceptor(方法拦截器)实现MethodInterceptor接口.
public class CGLibMethodInterceptor implements MethodInterceptor {
    private Object target;
    public CGLibMethodInterceptor(Object target) {
        this.target = target;
    }
    /**
     * 调用代理对象的方法
     */
    @Override
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
        System.out.println("我是中介开始代理~~");
        Object result = method.invoke(target, args);
        //proxy.invokeSuper(obj, args);
        System.out.println("我是中介,结束代理~~");
        return result;
    }
}
2.创建代理类, 并使用
  • 目标对象,用来代理接口
//        目标对象 代理接口
        HouseSubject targer = new RealHouseSubject();
        houseSubject houseSubject = (houseSubject) Enhancer.create(targer.getClass(), new CGLibMethodInterceptor(targer));
        houseSubject.rentHouse();
        houseSubject.saleHouse();
  • 目标对象,用来代理类.
		HouseSubject targer = new RealHouseSubject();
//目标对象 代理类
        RealHouseSubject realHouseSubject = (RealHouseSubject) Enhancer.create(targer.getClass(), new CGLibMethodInterceptor(targer));
        realHouseSubject.rentHouse();
        realHouseSubject.saleHouse();

CGLib动态代理过程中涉及方法的参数讲解.

  1. MethodInterceptor
    MethodInterceptor 和 JDK动态代理中的 InvocationHandler 类似, 它只定义了一个方法 intercept() , 用于增强目标方法.
public interface MethodInterceptor extends Callback {
 	/**
 	* 参数说明:
 	* o: 被代理的对象
	 * method: ⽬标⽅法(被拦截的⽅法, 也就是需要增强的⽅法)
 	* objects: ⽅法⼊参
 	* methodProxy: ⽤于调⽤原始⽅法
	 */
 	Object intercept(Object o,
 				 Method method, 
 				 Object[] objects, 
 				 MethodProxy methodProxy) throws Throwable;
}
  1. Enhancer.create()
    Enhancer.create() 用来生成一个代理对象
public static Object create(Class type, Callback callback) {
 //...代码省略
}
  • 参数说明:
    • type: 被代理类的类型(类或接口)
    • callback: 自定义方法拦截器 MethodInterceptor

四.Spring AOP源码剖析

  • Spring AOP 主要基于两种方式实现的: JDK 及 CGLIB 的方式
  • Spring 源码过于复杂, 此处只摘出一些主要内容, 以了解为主.
    Spring对于AOP的实现,基本上都是靠 AnnotationAwareAspectJAutoProxyCreator 去完成. 生成代理对象的逻辑在父类 AbstractAutoProxyCreator 中
	protected Object createProxy(Class<?> beanClass, 
								@Nullable String beanName,
								@Nullable Object[] specificInterceptors, 
								TargetSource targetSource) {

		return buildProxy(beanClass, beanName, specificInterceptors, targetSource, false);
	}
private Object buildProxy(Class<?> beanClass, @Nullable String beanName,
			@Nullable Object[] specificInterceptors, TargetSource targetSource, boolean classOnly) {

		if (this.beanFactory instanceof ConfigurableListableBeanFactory clbf) {
			AutoProxyUtils.exposeTargetClass(clbf, beanName, beanClass);
		}
		//创建代理对象
		ProxyFactory proxyFactory = new ProxyFactory();
		proxyFactory.copyFrom(this);
		/**
 		* 检查proxyTargetClass属性值,spring默认为false
 		* proxyTargetClass 检查接⼝是否对类代理, ⽽不是对接⼝代理
 		* 如果代理对象为类, 设置为true, 使⽤cglib代理
 		*/
		if (proxyFactory.isProxyTargetClass()) {
			//是否有设置cglib代理
			if (Proxy.isProxyClass(beanClass) || ClassUtils.isLambdaClass(beanClass)) {
				//设置proxyTargetClass为true,使⽤cglib代理
				for (Class<?> ifc : beanClass.getInterfaces()) {
					proxyFactory.addInterface(ifc);
				}
			}
		}
		else {
			/**
 			* 如果beanClass实现了接⼝,且接⼝⾄少有⼀个⾃定义⽅法,则使⽤JDK代理
 			* 否则CGLIB代理(设置ProxyTargetClass为true )
		 	* 即使我们配置了proxyTargetClass=false, 经过这⾥的⼀些判断还是可能会将其
			设为true
 			*/
			if (shouldProxyTargetClass(beanClass, beanName)) {
				proxyFactory.setProxyTargetClass(true);
			}
			else {
				evaluateProxyInterfaces(beanClass, proxyFactory);
			}
		}

		Advisor[] advisors = buildAdvisors(beanName, specificInterceptors);
		proxyFactory.addAdvisors(advisors);
		proxyFactory.setTargetSource(targetSource);
		customizeProxyFactory(proxyFactory);

		proxyFactory.setFrozen(this.freezeProxy);
		if (advisorsPreFiltered()) {
			proxyFactory.setPreFiltered(true);
		}

		// Use original ClassLoader if bean class not locally loaded in overriding class loader
		ClassLoader classLoader = getProxyClassLoader();
		if (classLoader instanceof SmartClassLoader smartClassLoader && classLoader != beanClass.getClassLoader()) {
			classLoader = smartClassLoader.getOriginalClassLoader();
		}
		return (classOnly ? proxyFactory.getProxyClass(classLoader) : proxyFactory.getProxy(classLoader));
	}
  • 代理工厂有一个重要的属性: proxyTargetClass, 默认值为false. 也可以通过程序设置
    在这里插入图片描述
  • 可以通过 @EnableAspectJAutoProxy(proxyTargetClass = true) 来设置

注意:
Spring Boot 2.X开始, 默认使用CGLIB代理. 可以通过配置项 spring.aop.proxy-target-class=false 来进行修改,设置默认为jdk代理. SpringBoot设置 @EnableAspectJAutoProxy 无效, 因为Spring Boot 默认使用AopAutoConfiguration进行装配

@SpringBootApplication
public class DemoApplication {
 	public static void main(String[] args) {
 		ApplicationContext context = SpringApplication.run(DemoApplication.class, args);
 		/**
 		* HouseProxy houseProxy = context.getBean(HouseProxy.class);
 		* 设置spring.aop.proxy-target-class=true cglib代理, 运⾏成功
 		* 设置spring.aop.proxy-target-class=false jdk代理, 运⾏失败, 不能代理类
 		* 因为 HouseProxy 是⼀个类, ⽽不是接⼝, 需要修改为
 		* HouseSubject houseProxy = (HouseSubject) context.getBean("realHouseSubject")
 		* 
 		*/
 		HouseProxy houseProxy = context.getBean(HouseProxy.class);
 		//HouseSubject houseProxy = (HouseSubject) context.getBean("realHouseSubject");//正确运⾏
 		System.out.println(houseProxy.getClass().toString());
 }
 }

使用context.getBean() 需要添加注解,使HouseProxy,RealHouseSubject被Spring管理测试AOP代理, 需要把这些类交给AOP管理(自定义注解或使用@Aspect)

我看点进去看代理工厂的代码

public class ProxyFactory extends ProxyCreatorSupport {
 	//...代码省略
 	//获取代理
 	public Object getProxy(@Nullable ClassLoader classLoader) {
		 //分两步 先createAopProxy,后getProxy
 		return createAopProxy().getProxy(classLoader);
 	}
 
 	protected final synchronized AopProxy createAopProxy() {
 		if (!this.active) {
 			activate();
 		}
 		return getAopProxyFactory().createAopProxy(this);
 	}
 	//...代码省略
}

createAopProxy的实现在 DefaultAopProxyFactory中

public class DefaultAopProxyFactory implements AopProxyFactory, Serializable {
 	//...代码省略
 	@Override
 	public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException {
 	/**
 	* 根据proxyTargetClass判断
 	* 如果⽬标类是接⼝, 使⽤JDK动态代理
 	* 否则使⽤cglib动态代理
 	*/
 	if (!NativeDetector.inNativeImage() && (config.isOptimize() || config.isProxyTargetClass() || 
hasNoUserSuppliedProxyInterfaces(config))) {
 		Class<?> targetClass = config.getTargetClass();
 		if (targetClass == null) {
			 throw new AopConfigException("TargetSource cannot determine target class: " + "Either an interface or a target is required for proxy 
creation.");
 		}
 		if (targetClass.isInterface() || Proxy.isProxyClass(targetClass) || ClassUtils.isLambdaClass(targetClass)) {
 			return new JdkDynamicAopProxy(config);
 		}
 			return new ObjenesisCglibAopProxy(config);
	 }
 		else {
 			return new JdkDynamicAopProxy(config);
 		}
 }
 //...代码省略
}

接下来就是创建代理了
JDK动态代理

final class JdkDynamicAopProxy implements AopProxy, InvocationHandler, 
Serializable {
 //...代码省略
 @Override
 public Object getProxy(@Nullable ClassLoader classLoader) {
 if (logger.isTraceEnabled()) {
 logger.trace("Creating JDK dynamic proxy: " + 
this.advised.getTargetSource());
 }
 return Proxy.newProxyInstance(determineClassLoader(classLoader), 
this.proxiedInterfaces, this);
 }
 //...代码省略
}

CGLIB动态代理

class CglibAopProxy implements AopProxy, Serializable {
        //...代码省略
        @Override
        public Object getProxy(@Nullable ClassLoader classLoader) {

            //...代码省略

            // Configure CGLIB Enhancer...
            Enhancer enhancer = createEnhancer();

            // Generate the proxy class and create a proxy instance.
            return createProxyClassAndInstance(enhancer, callbacks);

        }
        //...代码省略
    }

五.总结

  1. 什么是AOP?
  2. Spring AOP的实现方式有哪些?
    • 基于注解
    • 基于XML
    • 基于代理
  3. Spring AOP的实现原理.?
  4. Spring 使用哪种代理方式?
  5. JDK和CGLib的区别?

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/797379.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

CentOS 7:停止更新后如何下载软件?

引言 CentOS 7 是一个广受欢迎的 Linux 发行版&#xff0c;它为企业和开发者提供了一个稳定、安全、且免费的操作系统环境。然而&#xff0c;随着时间的推移&#xff0c;CentOS 7 的官方支持已经进入了维护阶段&#xff0c;这意味着它将不再收到常规的更新和新功能&#xff0c;…

「网络通信」HTTP 协议

HTTP &#x1f349;简介&#x1f349;抓包工具&#x1f349;报文结构&#x1f34c;请求&#x1f34c;响应&#x1f34c;URL&#x1f95d;URL encode &#x1f34c;方法&#x1f34c;报文字段&#x1f95d;Host&#x1f95d;Content-Length & Content-Type&#x1f95d;User…

千帆模型申请方法

第一步&#xff1a;注册千帆云账号 百度智能云-云智一体深入产业 第二步&#xff1a;申请实名认证 第三步&#xff1a;开通服务 第四步&#xff1a;配置到网方Ai的设置里去&#xff0c;网方Ai的下载地址见下面链接。 网方Ai的软件下载地址见论坛地址&#xff1a; 网创有方官…

Spark调度底层执行原理详解(第35天)

系列文章目录 一、Spark应用程序启动与资源申请 二、DAG&#xff08;有向无环图&#xff09;的构建与划分 三、Task的生成与调度 四、Task的执行与结果返回 五、监控与容错 六、优化策略 文章目录 系列文章目录前言一、Spark应用程序启动与资源申请1. SparkContext的创建2. 资…

TS真的比JS更好吗?

前言 在讨论TypeScript&#xff08;TS&#xff09;是否比JavaScript&#xff08;JS&#xff09;更好时&#xff0c;我们需要明确“更好”这一概念的上下文和衡量标准。TypeScript和JavaScript在多个方面有着明显的区别&#xff0c;但它们并不是简单的“好”与“不好”的关系&a…

接口安全配置

问题点&#xff1a; 有员工在工位在某个接口下链接一个集线器&#xff0c;从而扩展上网接口&#xff0c;这种行为在某些公司是被禁止的&#xff0c;那么网络管理员如何控制呢&#xff1f;可以配置接口安全来限制链接的数量&#xff0c;切被加入安全的mac地址不会老化&#xff…

宜春旅游集散中心展厅OLED透明屏方案设计

一、项目概述 为提升宜春旅游集散中心展厅的现代化展示水平&#xff0c;增强游客的参观体验&#xff0c;我们计划在展厅的核心区域引入OLED透明屏技术。该方案旨在通过高科技的视觉呈现方式&#xff0c;将展品信息以虚拟与现实相结合的方式展现&#xff0c;打造出一个既具科技感…

IDEA 2024 maven 配置

1 查看IDEA默认的maven版本 2 下载对应的maven maven 官网&#xff1a;Maven – Welcome to Apache Maven 找到对应的版本(可以选择更高一点的版本&#xff0c;但是不能差太大&#xff0c;可能会有不兼容的情况 复制下载连接&#xff0c;并打开新标签&#xff0c;只保留链接…

STL 提供的容器可以有多快?(下)「榨干最后一滴」

以下内容为本人的烂笔头&#xff0c;如需要转载&#xff0c;请声明原文链接 微信公众号「ENG八戒」https://mp.weixin.qq.com/s/QWgA97TDMGBnwR4hKA7BwA 查表的消耗 某些场景下需要用到大量的 (string, X) 键值对来存储数据&#xff0c;标准库提供了关联容器 std::map 来解决键…

【MySQL 进阶】MySQL 程序 -- 详解

一、MySQL 程序简介 MySQL 安装完成通常会包含如下程序&#xff1a; 1、Linux 系统 程序⼀般在 /usr/bin 目录下&#xff0c;可以通过命令查看&#xff1a; 2、Windows系统 目录&#xff1a;你的安装路径\MySQL Server 8.0\bin&#xff0c;可以通过命令查看&#xff1a; 可…

图像处理:使用 OpenCV-Python 卡通化你的图像(2)

一、说明 在图像处理领域&#xff0c;将图像卡通化是一种新趋势。人们使用不同的应用程序将他们的图像转换为卡通图像。如今&#xff0c;玩弄图像是许多人的爱好。人们通常会点击图片并添加滤镜或使用不同的东西自定义图像并将其发布到社交媒体上。但我们是程序员&#xff0c;…

QML界面控件加载与显示顺序

一、QML界面控件加载顺序 QML在界面加载时的顺序和我们认知的有很大的不同&#xff0c;有时候会对我们获取参数以及界面实现造成很大的困扰 1、加载顺序 import QtQuick 2.12 import QtQml 2.12 import QtQuick.Window 2.12 import QtQuick.VirtualKeyboard 2.4Window {id: …

java.sql.SQLException: Before start of result set

情况描述&#xff0c;在通过JDBC连接数据库时&#xff0c;想直接判断获取的值是否存在&#xff0c;运行时报错。 翻译&#xff1a; 在开始结果集之前 报错截图 解决问题的方法&#xff1a;对结果集ResultSet进行操作之前&#xff0c;一定要先用ResultSet.next()将指针移动至…

CSS学习碎碎念之卡片展示

效果展示&#xff1a; 代码展示 <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title>图片展示</title…

UART编程

Q:为什么使用串口前要先在电脑上安装CH340驱动&#xff1f; 中断的作用&#xff1f; 环形buffer的作用&#xff1f; static和valitate的作用 三种编程方式简介 也可以通过DMA方式减小CPU资源的消耗 直接把数据在SRAM内存和UART模块进行传输 &#xff0c;流程&#xff1a; …

【算法】平衡二叉树

难度&#xff1a;简单 题目 给定一个二叉树&#xff0c;判断它是否是 平衡二叉树 示例&#xff1a; 示例1&#xff1a; 输入&#xff1a;root [3,9,20,null,null,15,7] 输出&#xff1a;true 示例2&#xff1a; 输入&#xff1a;root [1,2,2,3,3,null,null,4,4] 输出&…

调整网络安全策略以适应不断升级的威胁形势

关键网络安全统计数据和趋势 当今数字时代网络安全的重要性

项目收获总结--本地缓存方案选型及使用缓存的坑

本地缓存方案选型及使用缓存的坑 一、摘要二、本地缓存三、本地缓存实现方案3.1 自己编程实现一个缓存3.2 基于 Guava Cache 实现本地缓存3.3 基于 Caffeine 实现本地缓存3.4 基于 Encache 实现本地缓存3.5 小结 四、使用缓存的坑4.1 缓存穿透4.2 缓存击穿4.3 缓存雪崩4.4 数据…

游戏的无边框模式是什么?有啥用?

现在很多游戏的显示设置中&#xff0c;都有个比较特殊的选项“无边框”。小伙伴们如果尝试过&#xff0c;就会发现这个效果和全屏几乎一毛一样&#xff0c;于是就很欢快地用了起来&#xff0c;不过大家也许会发现&#xff0c;怎么和全屏比起来&#xff0c;似乎有点不够爽快&…

【2024_CUMCM】时间序列1

目录 概念 时间序列数据 时期和时点时间序列 数值变换规律 长期趋势T 季节趋势S 循环变动C 不规则变动I 叠加和乘积模型 叠加模型 相互独立 乘积模型 相互影响 注 spss缺失值填补 简单填补 五种填补方法 填补原则 1.随机缺失 2.完全随机缺失 3.非随机缺失…