Spring之AOP源码(一)

news2024/11/13 12:44:56

文章目录

    • 一、动态代理
      • 1. 概念
      • 2. Cglib动态代理的使用
      • 3. JDK动态代理的使用
    • 二、SpringAOP
      • 1. 简介
      • 2. Spring AOP使用

一、动态代理

1. 概念

动态代理(Dynamic Proxy)是一种在运行时动态生成代理对象的技术。它是一种设计模式,用于在不修改原始对象的情况下,通过代理对象来间接访问原始对象,并在访问前后执行额外的操作。

在Spring中实现动态代理无非就两种方式,分别是Cglib和Jdk动态代理。

2. Cglib动态代理的使用

CGLIB(Code Generation Library)是一个功能强大且高性能的代码生成库,它是用Java编写的,用于在运行时生成字节码并创建新的类。CGLIB广泛用于AOP(面向切面编程)和其他需要在运行时生成类的场景。以下是CGLIB的一些主要特点和用法:

继承式代理: CGLIB使用继承方式生成代理类,而不是像JDK动态代理那样要求目标类必须实现接口。这意味着你可以代理没有实现任何接口的类。

字节码生成: CGLIB通过使用ASM库来生成字节码,实现了在运行时创建新类的功能。这使得可以在运行时修改类的行为,例如添加新的方法、修改字段等。

性能高效: 由于CGLIB是直接生成字节码,相对于反射,它的性能通常更高。这使得它在一些要求高性能的场景中得到广泛应用。

AOP支持: CGLIB常用于实现AOP,通过在运行时生成代理类,在目标方法的前后添加额外的逻辑。

Spring框架中的应用: Spring框架的AOP模块就使用了CGLIB,当目标对象没有实现接口时,Spring会默认使用CGLIB来创建代理类。

下面是Cglib使用的一个案例

UserService target = new UserService();
//构建增强器对象
Enhancer enhancer = new Enhancer(); // cglib
//设置要代理的那个类
enhancer.setSuperclass(UserService.class);
//设置代理逻辑
enhancer.setCallbacks(new Callback[]{new MethodInterceptor() { 
@Override
      public Object intercept(Object o, Method method, Object[] objects, MethodProxy
  methodProxy) throws Throwable {
          System.out.println("before...");
          //执行被代理对象的方法
          Object result = methodProxy.invoke(target, objects);
          System.out.println("after...");
          return result;
}
}});
//获得一个代理对象
UserService userService = (UserService) enhancer.create(); UserService
userService.test();

在这里插入图片描述
cglib底层是操作ASM码的,这部分源码在我的这篇博客详细分析了,现在我们来理解上的代码,首先我们知道Object result = methodProxy.invoke(target, objects);,target是我们实际被代理的对象,Object o就是我们生成的代理对象。下面对于这句代码,我提供3种写法,来分析一下:

//1. 
Object result = method.invoke(target, objects);
//2.
Object result = method.invoke(o, objects);
//3. 
Object result = methodProxy.invokeSuper(o, objects);

对于写法一其实和案例中正常写法一样,也是执行代理目标对象的方法,写法二这个会报错,它会执行代理对象的方法也就是,它会执行new MethodInterceptor() { 后面的代码,由于内部一直调用自己会陷入死循环,最后一个就是执行代理对象的父类的方法,这个是没问题的,我们前面说过cglib动态代理是基于继承的机制,这里想代理的目标对象是UserService类,所以执行它父类的方法就等同于执行UserService方法。

下面讲解一下cgblib其它的一些功能,我们知道如果我们代理了一个类,那么通常这个类中的所有的方法都会被代理,现在我们修改Userservice类。

public class UserService {

	public void test(){
		System.out.println("执行UserService的test方法");
	}
	public void a(){
		System.out.println("执行UserService的a方法");
	}
}

现在提出一个需求,只需要代理test方法而不需要代理a方法,或者执行test方法是一段代理逻辑,执行a方法是另一段代理逻辑。思路是每个方法对应一个执行器:

public class Test {

	public static void main(String[] args) {
		UserService target = new UserService();
		Enhancer enhancer = new Enhancer(); // cglib
		enhancer.setSuperclass(UserService.class);
		enhancer.setCallbacks(new Callback[]{new MethodInterceptor() {
			@Override
			public Object intercept(Object o, Method method, Object[] objects, MethodProxy
					methodProxy) throws Throwable {
				System.out.println("before...");
				Object result = methodProxy.invoke(target, objects);
				System.out.println("after...");
				return result;
			}
		}, NoOp.INSTANCE});
		enhancer.setCallbackFilter(new CallbackFilter() {
			@Override
			public int accept(Method method) {
			//返回值对应Callback中的方法处理器的下标
				if(method.getName().equals("test"))
				  return 0;
				else{
					return 1;
				}
			}
		});

		UserService userService = (UserService) enhancer.create();
		userService.test();
		userService.a();
	}
}

NoOp.INSTANCE你可以理解为啥事也没干就行了。我们看一下输出结果:
在这里插入图片描述

3. JDK动态代理的使用

JDK动态代理是Java提供的一种实现动态代理的机制。它是通过Java反射机制来实现的,主要用于在运行时动态生成代理类和对象。动态代理使得我们可以在不事先知道目标对象的情况下,通过代理类来调用目标对象的方法。以下是JDK动态代理的一些关键概念和使用方式:

接口必须实现: JDK动态代理要求目标对象必须实现一个或多个接口。代理类会实现这些接口,并且在调用方法时将请求委托给实际的目标对象。

Proxy类和InvocationHandler接口: JDK动态代理通过Proxy类和InvocationHandler接口实现。Proxy类提供了创建代理对象的静态方法,而InvocationHandler接口中有一个方法invoke,该方法在代理对象的方法被调用时执行。

newProxyInstance方法: Proxy类的newProxyInstance方法用于创建代理对象。该方法接受三个参数:ClassLoader(用于加载代理类的类加载器)、Class数组(目标对象实现的接口列表)、和InvocationHandler接口的实现类实例。

public class Test {

	public static void main(String[] args) {
		UserService target = new UserService();
		Aservice aservice = (Aservice) Proxy.newProxyInstance(Test.class.getClassLoader(), new Class[]{Aservice.class}, new InvocationHandler() {
			@Override
			public Object invoke(Object o, Method method, Object[] objects) throws Throwable {
				System.out.println("before");
				//执行被Userservice的方法
				method.invoke(target,objects);
				System.out.println("after");
				return null;
			}
		});
		aservice.test();
	}
}

注意JDK动态代理被代理的对象其实是那个接口类型。

二、SpringAOP

1. 简介

上面介绍了两种常用的动态代理方式,而Spring底层就对这两种代理方式进行了封装,关键对象是ProxyFactory。下面介绍一下这个类的使用:

public static void main(String[] args) {
		UserService target = new UserService();
		//创建代理工厂
		ProxyFactory proxyFactory=new ProxyFactory();
		//设置代理对象
		proxyFactory.setTarget(target);
		//设置代理逻辑
		proxyFactory.addAdvice(new MethodBeforeAdvice() {
			@Override
			public void before(Method method, Object[] args, Object target) throws Throwable {
				System.out.println("before");
			}
		});
		//返回代理对象
		UserService proxy = (UserService) proxyFactory.getProxy();
		proxy.test();
	}

ProxyFactory默认是使用Cglib动态代理技术
在这里插入图片描述

我们稍微改一下,它就会使用jdk动态代理技术

public static void main(String[] args) {
		UserService target = new UserService();
		//创建代理工厂
		ProxyFactory proxyFactory=new ProxyFactory();
		//设置代理对象
		proxyFactory.setTarget(target);
		//设置接口
		proxyFactory.setInterfaces(Aservice.class);
		//设置代理逻辑
		proxyFactory.addAdvice(new MethodBeforeAdvice() {
			@Override
			public void before(Method method, Object[] args, Object target) throws Throwable {
				System.out.println("before");
			}
		});
		//返回代理对象
		Aservice proxy = (Aservice) proxyFactory.getProxy();
		proxy.test();
	}

在这里插入图片描述
此时我们可以发现,我们压根就不需要了解我们到底使用的是哪种代理模式,我们只需要知道通过这个代理工厂我们就可以拿到我们所需要的代理对象。

关于通知(Advice)的逻辑我们上面只使用了MethodBeforeAdvice,即方法执行前被调用,当然还有很多种,这里我们先稍微了解一下:

  • AfterReturningAdvice:方法执行完之后执行的代理逻辑
  • ThrowsAdvice:被代理方法执行抛异常之后执行的代理逻辑(这个接口没有提供重写方法,我们可以自己写方法逻辑,关键点是方法中的参数要和抛出的异常匹配才会调用这个方法)

在这里插入图片描述

  • MethodInterceptor(环绕通知):这个灵活性就很强我们可以收到去实现自己的代理逻辑下面用代码介绍一下这个通知的使用
public class Test {

	public static void main(String[] args) {
		UserService target = new UserService();
		//创建代理工厂
		ProxyFactory proxyFactory=new ProxyFactory();
		//设置代理对象
		proxyFactory.setTarget(target);
		//设置接口
		proxyFactory.setInterfaces(Aservice.class);
		//设置代理逻辑
		proxyFactory.addAdvice(new MethodInterceptor() {
			@Nullable
			@Override
			public Object invoke(@NotNull MethodInvocation invocation) throws Throwable {
				System.out.println("before1");
				Object proceed = invocation.proceed();
				System.out.println("after1");
				return null;
			}
	    });
		proxyFactory.addAdvice(new MethodInterceptor() {
			@Nullable
			@Override
			public Object invoke(@NotNull MethodInvocation invocation) throws Throwable {
				System.out.println("before2");
				Object proceed = invocation.proceed();
				System.out.println("after2");
				return null;
			}
		});
		//返回代理对象
		Aservice proxy = (Aservice) proxyFactory.getProxy();
		proxy.test();
	}
}

上面代码我给UserService加了两段代理逻辑,然后我们看一下输出:
在这里插入图片描述
分析输出我们大致能猜想出上面代理逻辑的执行顺序:

  1. 执行顺序是按照添加顺序来的
  2. 在执行前面一个代理逻辑的Object proceed = invocation.proceed();时候,它里面会执行后面添加的代理逻辑,就像一个洋葱一样,后面的代理逻辑被前面的代理逻辑包住,这也是环绕通知这一名字的由来。

还是回到上面提到的一个问题,如果我们需要一些方法被代理,而其它方法不被代理或被其它代理逻辑代理,那么在AOP中该怎么做?其实就是AOP中的切点:

public static void main(String[] args) {
		UserService target = new UserService();
		//创建代理工厂
		ProxyFactory proxyFactory=new ProxyFactory();
		//设置代理对象
		proxyFactory.setTarget(target);
		//设置接口
		proxyFactory.setInterfaces(Aservice.class);
		//添加切点
		proxyFactory.addAdvisor(new PointcutAdvisor() {
			//获得切点
			@Override
			public Pointcut getPointcut() {
				return new StaticMethodMatcherPointcut() {
					@Override
					public boolean matches(Method method, Class<?> targetClass) {
						return method.getName().equals("test");
					}
				};
			}

			@Override
			public Advice getAdvice() {
				return new MethodBeforeAdvice() {
					@Override
					public void before(Method method, Object[] args, Object target) throws Throwable {
						System.out.println("before test ");
					}
				};
			}

			@Override
			public boolean isPerInstance() {
				return false;
			}
		});
		//添加切点
		proxyFactory.addAdvisor(new PointcutAdvisor() {
			//获得切点
			@Override
			public Pointcut getPointcut() {
				return new StaticMethodMatcherPointcut() {
					@Override
					public boolean matches(Method method, Class<?> targetClass) {
						return method.getName().equals("a");
					}
				};
			}

			@Override
			public Advice getAdvice() {
				return new MethodBeforeAdvice() {
					@Override
					public void before(Method method, Object[] args, Object target) throws Throwable {
						System.out.println("before a ");
					}
				};
			}

			@Override
			public boolean isPerInstance() {
				return false;
			}
		});
		//返回代理对象
		Aservice proxy = (Aservice) proxyFactory.getProxy();
		proxy.test();
		proxy.a();
	}

在这里插入图片描述

Advisor=切点+通知

2. Spring AOP使用

前面介绍了ProxyFactory来创建代理对象的过程,但前面我们始终没有和Spring联系起来,都是我们收动的在创建自己的代理对象,添加自己的代理逻辑,这一部分我们将Spring和AOP紧密联系起来进行分析。

  • 方式一:

使用FactoryBean将代理对象作为一个bean存到AOP容器中,底层类型是ProxyFactoryBean

    @Bean
	public ProxyFactoryBean userService(){
		ProxyFactoryBean proxyFactoryBean=new ProxyFactoryBean();
		proxyFactoryBean.addAdvice(new MethodBeforeAdvice() {
			@Override
			public void before(Method method, Object[] args, Object target) throws Throwable {
				System.out.println("before");
			}
		});
		proxyFactoryBean.setTarget(new UserService());
		return proxyFactoryBean;
	}

在这里插入图片描述

  • 方式二

先说需求,如果我们要被代理的对象UserService是一个Bean,然后我们的代理逻辑也是一个Bean,那么怎么将这两个bean联系起来,生成UserService的代理对象。首先我们先将通知(Advice)封装成一个bean。

@Component
public class Aservice implements MethodBeforeAdvice {

	@Override
	public void before(Method method, Object[] args, Object target) throws Throwable {
		System.out.println("方法执行前的逻辑");
	}
}

@Component
public class UserService {


	public void test() {
		System.out.println("test");
	}

	public void a() {
		System.out.println("a");
	}
}

要达到上面的需求我们要使用到一个Bean,为BeanNameAutoProxyCreator

	@Bean
	public BeanNameAutoProxyCreator beanNameAutoProxyCreator(){
		BeanNameAutoProxyCreator beanNameAutoProxyCreator=new BeanNameAutoProxyCreator();
		//要被代理的Bean的名称前缀
		beanNameAutoProxyCreator.setBeanNames("User*");
		//设置代理逻辑的bean
		beanNameAutoProxyCreator.setInterceptorNames("AService");
		return beanNameAutoProxyCreator;
	}

其实BeanNameAutoProxyCreator本质上就是一个BeanPostProcessor。通过BeanNameAutoProxyCreator可以对批量的Bean进行AOP,并且指定了代理逻辑,指定了一个 InterceptorName,也就是一个Advise,前提条件是这个Advise也得是一个Bean,这样Spring才能找到的,但是BeanNameAutoProxyCreator的缺点很明显,它只能根据beanName来指定想要代理 的Bean。

  • 方式三

除了使用BeanNameAutoProxyCreator,还可以使用DefaultAdvisorAutoProxyCreator

@ComponentScan("com.zhouyu")
@EnableScheduling
@PropertySource("classpath:spring.properties")
@EnableTransactionManagement
@Configuration
public class AppConfig {
	@Bean
	public DefaultPointcutAdvisor defaultPointcutAdvisor(){
		NameMatchMethodPointcut pointcut = new NameMatchMethodPointcut();
		pointcut.addMethodName("test");
		DefaultPointcutAdvisor defaultPointcutAdvisor = new DefaultPointcutAdvisor();
		defaultPointcutAdvisor.setPointcut(pointcut);
		defaultPointcutAdvisor.setAdvice(new AService());
		return defaultPointcutAdvisor;
	}
	@Bean
	public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() {
		DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator = new
				DefaultAdvisorAutoProxyCreator();
		return defaultAdvisorAutoProxyCreator;
	}
}

上面DefaultAdvisorAutoProxyCreator也是一个BeanPostProcessor在bean的生命周期中,调用DefaultAdvisorAutoProxyCreator,然后判断当前创建bean有没有test方法,如果有就执行AService代理逻辑。

OOP表示面向对象编程,是一种编程思想,AOP表示面向切面编程,也是一种编程思想,而我们上 面所描述的就是Spring为了让程序员更加方便的做到面向切面编程所提供的技术支持,换句话说,就 是Spring提供了一套机制,可以让我们更加容易的来进行AOP,所以这套机制我们也可以称之为 Spring AOP。但是值得注意的是,上面所提供的注解的方式来定义Pointcut和Advice,Spring并不是首创,首创是 AspectJ,而且也不仅仅只有Spring提供了一套机制来支持AOP,还有比如 JBoss 4.0、aspectwerkz 等技术都提供了对于AOP的支持。而刚刚说的注解的方式,Spring是依赖了AspectJ的,或者说, Spring是直接把AspectJ中所定义的那些注解直接拿过来用,自己没有再重复定义了,不过也仅仅只 是把注解的定义赋值过来了,每个注解具体底层是怎么解析的,还是Spring自己做的,所以我们在用 Spring时,如果你想用@Before、@Around等注解,是需要单独引入aspecj相关jar包的。

意思是,AOP中的这些概念不是Spring特有的,不幸的是,AOP中的概念不是特别直观的,但是, 如果Spring重新定义自己的那可能会导致更加混乱

  1. Aspect:表示切面,比如被@Aspect注解的类就是切面,可以在切面中去定义Pointcut、 Advice等等
  2. Join point:表示连接点,表示一个程序在执行过程中的一个点,比如一个方法的执行,比如一 个异常的处理,在Spring AOP中,一个连接点通常表示一个方法的执行。
  3. Advice:表示通知,表示在一个特定连接点上所采取的动作。Advice分为不同的类型,后面详 细讨论,在很多AOP框架中,包括Spring,会用Interceptor拦截器来实现Advice,并且在连接 点周围维护一个Interceptor链
  4. Pointcut:表示切点,用来匹配一个或多个连接点,Advice与切点表达式是关联在一起的, Advice将会执行在和切点表达式所匹配的连接点上
  5. Introduction:可以使用@DeclareParents来给所匹配的类添加一个接口,并指定一个默认实现
  6. Target object:目标对象,被代理对象
  7. AOP proxy:表示代理工厂,用来创建代理对象的,在Spring Framework中,要么是JDK动态 代理,要么是CGLIB代理
  8. Weaving:表示织入,表示创建代理对象的动作,这个动作可以发生在编译时期(比如 Aspejctj),或者运行时,比如Spring AOP

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

安卓手机变iOS!

Launcher iOS 16 - 安卓手机秒变iOS Launcher iOS 16 是一款iOS启动器&#xff0c;可以将安卓手机桌面变成iOS样子&#xff0c;还有iOS的开机动画和景深效果&#xff01; 下载链接&#xff1a;【Launcher iOS 16】 ​

【CCNet】《CCNet:Criss-Cross Attention for Semantic Segmentation》

ICCV-2019 文章目录 1 Background and Motivation2 Related Work3 Advantages / Contributions4 Method5 Experiments5.1 Datasets and Metrics5.2 Experiments on Cityscapess5.3 Experiments on ADE20K5.4 Experiments on COCO 6 Conclusion&#xff08;own&#xff09; 1 Ba…

也谈人工智能——AI科普入门

文章目录 1. 科普入门人工智能的定义人工智能的类型 - 弱 AI 与强 AI人工智能、深度学习与机器学习人工智能的应用和使用场景语音识别计算机视觉客户服务建议引擎数据分析网络安全 行业应用人工智能发展史![img](https://img-blog.csdnimg.cn/img_convert/66aeaaeac6870f432fc4…

Vue的api接口封装以及使用说明、模块说明

在Api目录下面建立user.js&#xff0c;如果以后有不同的接口请求地址都可以单独创建不同的&#xff0c;目的是方便维护&#xff01; import request from /utils/request 这个代码是引入之前封装好的 request.js 文件&#xff0c;具体可以参考上门一篇文档 Vue的request.js模…

深入剖析开源大模型+Langchain框架,智能问答系统性能下降原因

大模型&#xff08;LLM&#xff09;相关理论研究与工程实践随着 GPT3 的发布&#xff0c;在学术界、工业界大爆发&#xff0c;备受各行各业关注&#xff0c;并涌现出一些赋能行业、促进生产力、生产关系变革的实践。GPT3 [1] 以及斯坦福计算机学院近 100 教授联名论文 [2] 将大…

pymssql 报错误解决办法:20002, severity 9

错误 解决办法 python3.6&#xff0c;安装pymssql低版本&#xff08;pymssql-2.1.5-cp36-cp36m-win32.whl&#xff09;

腾讯云有没有免费云服务器?如何申请?

腾讯云免费服务器申请入口 https://curl.qcloud.com/FJhqoVDP 免费服务器可选轻量应用服务器和云服务器CVM&#xff0c;轻量配置可选2核2G3M、2核8G7M和4核8G12M&#xff0c;CVM云服务器可选2核2G3M和2核4G3M配置&#xff0c;腾讯云百科txybk.com分享2024年最新腾讯云免费服务器…

【前后端的那些事】快速上手富文本+富文本图片上传

文章目录 fullText富文本1. 后端接口1.1 定义常量1.2 定义返回实体类1.3 上传图片接口1.4 下载图片接口 2. 前端代码编写2.1 安装2.2 快速使用 3. 配置富文本图片上传地址3.1 配置图片上传配置 4. 全部代码展示 前言&#xff1a;最近写项目&#xff0c;发现了一些很有意思的功能…

【JaveWeb教程】(23) MySQL数据库开发之事务与索引 详细代码示例讲解(最全面)

目录 2. 事务2.1 介绍2.2 操作2.3 四大特性 3. 索引3.1 介绍3.2 结构3.3 语法 2. 事务 场景&#xff1a;学工部整个部门解散了&#xff0c;该部门及部门下的员工都需要删除了。 操作&#xff1a; -- 删除学工部 delete from dept where id 1; -- 删除成功-- 删除学工部的员工…

定制一套ERP系统大概要多少钱?ERP软件定制报价

定制一套ERP系统大概要多少钱&#xff1f;ERP软件定制报价 每个企业的需求和情况都是独特的&#xff0c;在不清楚题主所在企业的规模、业务流程、所需功能等情况时&#xff0c;确实没办法给出项目预算。 我们公司也定制过管理系统&#xff0c;经验就是&#xff0c;建议在开始…

MySQL数据库设计原则

0.简单的处理逻辑 一.MySQL完整性约束 主键约束 primary key 自增键约束 auto_increment 唯一键约束 unique 非空约束 not null 默认值约束 default 外键约束 foreign key 下面是一个sql语句创建一个表,可以看出来了使用了哪几个约束吗? create table user( id int…

如何将重复方法封装为Aop切面并结合注解使用

首先要导入依赖 <dependency><groupId>org.aspectj</groupId><artifactId>aspectjweaver</artifactId> </dependency> 编写注解 package com.yg.domain.note;import java.lang.annotation.ElementType; import java.lang.annotation.Rete…

u盘监控系统—公司电脑如何监控U盘使用?【详解】

在当今的办公环境中&#xff0c;U盘等移动存储设备已成为数据传输和存储的重要工具。 然而&#xff0c;随着U盘的广泛使用&#xff0c;也带来了潜在的安全风险&#xff0c;如数据泄露、病毒传播等。 因此&#xff0c;对于随时会有数据泄露风险的企业而言&#xff0c;U盘的使用…

Jetpack Compose -> 声明式UI Modifier

前言 本章主要介绍下 Compose 的声明式 UI 以及初级写法&#xff1b; 什么是声明式UI 传统UI 传统 UI 方式来声明UI <androidx.appcompat.widget.LinearLayoutCompat android:layout_width"match_parent" android:layout_height"match_parent&quo…

(Java企业 / 公司项目)分布式事务Seata详解(含Seata+Nacos组合使用)

一. Seata介绍 Seata 是一款开源的分布式事务解决方案&#xff0c;致力于在微服务架构下提供高性能和简单易用的分布式事务服务。在 Seata 开源之前&#xff0c;其内部版本在阿里系内部一直扮演着应用架构层数据一致性的中间件角色&#xff0c;帮助经济体平稳的度过历年的双11&…

Ubuntu22.04,Nvidia4070配置llama2

大部分内容参考了这篇非常详细的博客&#xff0c;是我最近看到的为数不多的保姆级别的教学博客&#xff0c;建议大家去给博主点个赞【Ubuntu 20.04安装和深度学习环境搭建 4090显卡】_ubuntu20.04安装40系显卡驱动-CSDN博客 本篇主要是基于这篇博客结合自己配置的过程中一些注…

soc算法【周末总结】

1 实验一&#xff08;SOC误差30%放电实验&#xff09; 1.1 实验过程 1、对电池包进行充电&#xff0c;将昨天放空的电池包进行充电&#xff0c;充电至SOC40%左右&#xff1b; 2、电池包SOC为38%时&#xff0c;手动修改SOC值为70%&#xff0c;开始放电 3、SOC由70%缓慢降至4…

BUUCTFMisc (我flag呢???)

刚好&#xff0c;更完密码学&#xff0c;然后就到下一个对新手还算 “友好” 的地方了--->Misc&#xff08;但他还是比密码学难&#xff09; 找不出flag belike &#xff1a; 看了别人的找法以后be like&#xff1a; 这里我就来讲几道我觉得比较有意思的题目吧&…

一键批量翻译,文件夹名称翻译器

文件夹名称往往是我们初步了解文件内容的重要窗口。有时&#xff0c;为了更好地与国际合作伙伴交流或是管理个人文件&#xff0c;我们需要对文件夹名称进行翻译。传统的逐一修改方法既费时又费力&#xff0c;还要借助翻译工具。现在有了【文件批量改名高手】&#xff0c;上面的…

PMP学习考试经验总结

PMP备考日程计划表 我的PMP的备考大概花了三个月的时间, 可以分为以下几个阶段&#xff1a; Week 1-4: 读完PMBoK 前面7个知识领域&#xff08;中英文版PMBoK一起看&#xff09;。每看完一个知识领域&#xff0c;就看参考书里面的相应章节&#xff08;汪博士那本&#xff09;…