[笔记]Spring AOP

Spring AOPAspect Oriented Programming

  • AOP将应用程序分为核心业务和非核心的公共功能,AOP的关注点是系统中的非核心的公共功能;

  • AOP可以通过预编译或者运行期动态代理的方式,为横跨多个对象(没有继承关系..)的业务逻辑添加统一的功能横切关注点Cross Cutting Concern:事务、日志记录、权限检查、异常处理等)

  • 使用AOP可以实现在不修改核心业务源代码的情况下为核心业务添加统一的功能,实现了核心业务和非核心的公共功能之间的解耦,提高了代码的重用性;

基于注解的AOP

在Spring Boot中,AOP主要通过注解来实现,以简化配置和提升代码的可读性。

@Aspect注解
  • 作用:将一个类定义为切面类。

  • 用法:与@Component@Service等注解结合使用,以便Spring容器管理这个切面。

定义切点(Pointcut)
  • @Pointcut:定义了何处应用AOP(比如哪些方法)。

  • 表达式:使用execution表达式指定方法模式。

@Pointcut("execution(* com.example.service.*.*(..))")
public void serviceLayer() {
}
定义通知(Advice)
  • @Before:在方法执行之前执行。

  • @AfterReturning:方法成功执行后执行。

  • @AfterThrowing:方法抛出异常后执行。

  • @After:方法执行后执行,无论其结果如何。

  • @Around:在方法执行前后执行,提供了最大的灵活性。

@Before("serviceLayer()")
public void logBeforeService(JoinPoint joinPoint) {// 实现逻辑
}
实际应用示例
  • 切面类:结合@Aspect@Component定义切面。

  • 通知方法:使用@Before@After等注解定义不同类型的通知。

@Aspect
@Component
public class LoggingAspect {@Before("serviceLayer()")public void logBefore(JoinPoint joinPoint) {// 日志逻辑}// 其他通知定义
}
在Spring Boot中的使用
  • 依赖:确保已添加AOP相关依赖(如spring-boot-starter-aop)。

  • 注解:直接在类和方法上使用@Aspect和通知相关的注解。

  • 无需XML:不需要XML配置,Spring Boot会自动处理这些注解。

静态代理 vs 动态代理

  1. 静态代理

    1. 实现方式:

      • 通过继承或实现接口。

      • 需要为每个目标类创建一个代理类。

    2. 缺点:

      • 高耦合度:代理类和目标类紧密绑定。

      • 可扩展性差:每个新类都需要新的代理类。

  2. 动态代理

    1. 在运行时动态创建代理类,不需要显式地为每个目标类编写代理类。

    2. 分为两种类型:

      • JDK动态代理:

        • 只能代理实现了接口的类。

        • 使用java.lang.reflect.Proxy类和java.lang.reflect.InvocationHandler接口实现。

      • CGLIB动态代理:

        • 可以代理没有实现接口的类。

        • 通过继承目标类来实现代理。

        • 使用net.sf.cglib.proxy.Enhancer类和net.sf.cglib.proxy.MethodInterceptor接口实现。

补充示例:静态代理与动态代理

静态代理示例:

在Spring框架中,静态代理通常不通过XML配置来实现,因为静态代理涉及到手动创建代理类,而不是在运行时动态生成代理对象。在静态代理中,你会直接编写一个代理类,它实现与目标对象相同的接口,并在代理类中显式调用目标对象的方法。

由于这种方式并不涉及Spring的AOP特性,所以没有特定的XML配置来声明静态代理。

public interface UserService {void addUser();void deleteUser();
}public class UserServiceImpl implements UserService {public void addUser() {System.out.println("添加用户");}public void deleteUser() {System.out.println("删除用户");}
}public class UserServiceProxy implements UserService {private UserServiceImpl userService;public UserServiceProxy(UserServiceImpl userService) {this.userService = userService;}public void addUser() {System.out.println("执行前置逻辑");userService.addUser();System.out.println("执行后置逻辑");}public void deleteUser() {System.out.println("执行前置逻辑");userService.deleteUser();System.out.println("执行后置逻辑");}
}
XML配置示例
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans.xsd"><!-- 声明原始的UserService实现 --><bean id="userService" class="com.example.service.impl.UserServiceImpl"/><!-- 声明UserService的静态代理类 --><bean id="userServiceProxy" class="com.example.service.impl.UserServiceProxy"><!-- 注入原始的UserService --><constructor-arg ref="userService"/></bean></beans>
动态代理示例:
  JDK动态代理

  JDK动态代理,也称为Java动态代理,是基于接口的代理方式,使用Java自带的代理机制来实现。

  • 原理: 通过实现目标对象的接口并在调用处理器(InvocationHandler)中定义拦截逻辑。

  • 使用场景: 适用于目标对象实现了接口的情况。

  • 示例代码:

public interface Teacher {void teach();
}
public class Wang implements Teacher {@Overridepublic void teach() {System.out.println("老师讲课");}
}
/*** 代理对象的执行逻辑*/
public class MyHandler implements InvocationHandler {Teacher teacher;public MyHandler(Teacher teacher) {this.teacher = teacher;}/*** Object proxy: 代理对象* Method method: 目标方法* Object[] args: 方法参数*/@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {/*// 前置 公共的功能System.out.println("上课前 - 检查手机");// 核心业务  -  执行目标对象的业务teacher.teach();// 后置 公共的功能System.out.println("放学后 - 解决问题");*/System.out.println("上课前 - 检查手机");// 执行代理对象的业务逻辑Object result = method.invoke(teacher, args);System.out.println("放学后 - 解决问题");// 代理对象的方法返回值return result;}
}

 

XML配置

使用JDK动态代理时,通常不需要特别指定,因为这是Spring的默认行为。

但可以通过设置[aop:aspectj-autoproxy](aop:aspectj-autoproxy)标签的proxy-target-class属性为false来明确指示使用JDK动态代理。

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns:aop="http://www.springframework.org/schema/aop"xsi:schemaLocation="http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/aophttp://www.springframework.org/schema/aop/spring-aop.xsd"><!-- 管理目标类 --><bean id="userService" class="com.example.service.impl.UserServiceImpl"/><!-- 管理切面类 --><bean id="myAspect" class="com.example.aspect.MyAspect"/><!-- AOP配置 --><aop:config><aop:aspect id="aspect" ref="myAspect"><!-- 配置通知和切点 --><!-- 示例: 前置通知 --><aop:before method="beforeAdvice" pointcut="execution(* com.example.service.*.*(..))"/><!-- 其他通知配置 --></aop:aspect></aop:config><!-- 明确指定使用JDK动态代理 --><aop:aspectj-autoproxy proxy-target-class="false"/>
</beans>
CGLIB动态代理:

CGLIB(Code Generation Library)代理是一种基于类的代理方式,能够代理没有实现接口的类。

  • 原理: 通过继承目标类并在方法拦截器(MethodInterceptor)中定义拦截逻辑。

  • 使用场景: 适用于目标对象没有实现任何接口的情况。

  • 示例代码:

/*** 目标对象 - 被代理对象*/
public class JayZhou {public void teach() {System.out.println("老师讲课!");}}
public class MyHandler implements MethodInterceptor {/*** @param proxy 代理对象* @param method 被拦截的方法* @param args argument 方法参数* @param methodProxy 用于执行父类的方法*/@Overridepublic Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {System.out.println("上课前检查手机");// 执行目标对象的方法   super.methodObject result = methodProxy.invokeSuper(proxy, args);System.out.println("放学后解决问题");return result;}
}
XML配置

要使用CGLIB代理,您需要将[aop:aspectj-autoproxy](aop:aspectj-autoproxy)标签的

proxy-target-class属性设置为true

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns:aop="http://www.springframework.org/schema/aop"xsi:schemaLocation="http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/aophttp://www.springframework.org/schema/aop/spring-aop.xsd"><!-- 管理目标类 --><bean id="userService" class="com.example.service.impl.UserServiceImpl"/><!-- 管理切面类 --><bean id="myAspect" class="com.example.aspect.MyAspect"/><!-- AOP配置 --><aop:config><aop:aspect id="aspect" ref="myAspect"><!-- 配置通知和切点 --><!-- 示例: 前置通知 --><aop:before method="beforeAdvice" pointcut="execution(* com.example.service.*.*(..))"/><!-- 其他通知配置 --></aop:aspect></aop:config><!-- 明确指定使用CGLIB代理 --><aop:aspectj-autoproxy proxy-target-class="true"/>
</beans>

JDK代理和CGLIB代理的比较

  • 应用场景:

    • JDK代理只适用于接口方法。

    • CGLIB可以代理类方法,无需接口。

  • 性能:

    • CGLIB通常性能更优,直接操作字节码。

    • JDK代理使用反射,性能略逊。

  • 实现方式:

    • JDK代理使用Java原生反射。

    • CGLIB通过字节码生成。

无论是使用JDK代理还是CGLIB代理,Spring的配置方式基本相同,主要区别在于[aop:aspectj-autoproxy](aop:aspectj-autoproxy)标签的proxy-target-class属性的设置。


AOP的用途

  1. 横切关注点

    1. AOP允许将应用程序中跨越多个点的功能(如日志、事务管理、安全等)模块化为特殊的类,称为"切面"(Aspects)。

    2. 这些横切关注点通常与业务逻辑无关,但对多个模块都有影响。

  2. 解耦

    1. 通过将非业务代码(如日志和安全)从业务代码中分离,AOP有助于降低模块间的耦合度。

    2. 提高了代码的可维护性和可重用性。

Spring AOP 关键术语和概念

  1. JoinPoint(连接点): 指那些可能被拦截到的点。在Spring AOP中,这通常指的是方法的执行。

  2. PointCut(切点): 指明哪些JoinPoint(方法)需要被拦截的规则集合。切点的表达式决定了哪些方法会被增强。

  3. Advice(通知/增强): 绑定到特定JoinPoint(通过PointCut选择)上的动作。常见的类型有:

    1. Before(前置通知)

    2. After Returning(后置通知)

    3. After Throwing(异常通知)

    4. After(最终通知)

    5. Around(环绕通知)

  4. Target(目标对象): 包含JoinPoint的对象。它是被代理的对象。

  5. Introduction(引介): 用于给类添加新的方法和属性的AOP概念。

  6. Proxy(代理): 为Target对象提供的代理,用于拦截对Target对象的调用。

  7. Weaving(织入): 将通知应用到目标对象以创建新的代理对象的过程。织入可以在编译时、类加载时或运行时进行。

  8. Aspect(切面): 通知(Advice)和切点(PointCut)的结合。切面定义了何时(切点)和如何(通知)进行跨越应用程序多个点的行为。

依赖:

        <!--aspectJ的依赖--><dependency><groupId>org.aspectj</groupId><artifactId>aspectjweaver</artifactId><version>1.8.7</version></dependency>

Spring AOP与AspectJ密切相关,但在实际使用中,Spring AOP通常被认为是AspectJ的简化版,专注于方法拦截。

xml演示:

<!--管理目标类--><bean id="userService" class="com.xq.service.impl.UserServiceImpl"></bean><!--管理切面类--><bean id="myAspect" class="com.xq.aspect.MyAspect"></bean><!--配置目标类和切面类--><aop:config><!--配置切点aop:pointcut  配置切点的标签id: 切点的名称 自定义的 唯一即可expression: 切点表达式execution: 切点表达式的固定写法  切点方法的形参类型 包名+类名+方法名(参数类型)* 切点方法的返回值任意(..) 代表的是切点方法的参数任意--><aop:pointcut id="p1" expression="execution(* com.xq.service.impl.UserServiceImpl.deleteUser(..))"></aop:pointcut><aop:pointcut id="p2" expression="execution(* com.xq.service.impl.UserServiceImpl.findAll(..))"></aop:pointcut><aop:pointcut id="p3" expression="execution(* com.xq.service.impl.UserServiceImpl.updateUser(..))"></aop:pointcut><aop:pointcut id="p4" expression="execution(* com.xq.service.impl.UserServiceImpl.addUser(..))"></aop:pointcut><!--配置代理对象 将通知应用到切点方法上aop:aspect标签  配置代理对象ref: 引用的就是切面类的id--><aop:aspect ref="myAspect"><!--aop:before 前置通知的配置method: 增强的方法的名称pointcut-ref:引用哪一个切点--><aop:before method="checkPrivilege" pointcut-ref="p1"></aop:before><!--后置通知的配置aop:after-returning: 描述后置通知的标签--><aop:after-returning method="printLog" pointcut-ref="p2"></aop:after-returning><!--配置环绕通知aop:around  环绕通知的标签--><aop:around method="around" pointcut-ref="p3"></aop:around><!--抛出异常通知aop:after-throwing 抛出异常通知的标签--><aop:after-throwing method="throwing" pointcut-ref="p4"></aop:after-throwing><!--最终通知的标签 aop:after--><aop:after method="after" pointcut-ref="p4"></aop:after></aop:aspect></aop:config>

注解:

  别忘了先对切面类扛注释

  @Aspect注解标记了一个类作为切面。

  @Before, @AfterReturning, @Around, @AfterThrowing, 和 @After 注解分别用于定义不同类型的通知。

@Component
@Aspect //标识当前类是一个切面类

  逐条注解:

/*** 切面类*/
@Component
@Aspect //标识当前类是一个切面类
public class MyAspect {//权限校验的方法  @Before注解: 前置通知的注解@Before(value = "execution(* com.xq.service.impl.UserServiceImpl.deleteUser(..))")public void checkPrivilege(){System.out.println("开启了权限校验......");}//打印日志的方法 @AfterReturning注解  后置通知的注解@AfterReturning(value = "execution(* com.xq.service.impl.UserServiceImpl.findAll(..))")public void printLog(){System.out.println("开启了日志打印功能");}//开启环绕通知的方法 环绕通知:就是在目标方法前后都执行的方法  @Around 环绕通知的注解@Around(value = "execution(* com.xq.service.impl.UserServiceImpl.updateUser(..))")public void around(ProceedingJoinPoint joinPoint){try {System.out.println("开启了环绕通知1");joinPoint.proceed();System.out.println("开启了环绕通知2");} catch (Throwable throwable) {throwable.printStackTrace();}}//抛出异常通知 只有目标方法出现异常之后,才会执行的增强方法 @AfterThrowing 抛出异常通知的注解@AfterThrowing(value = "execution(* com.xq.service.impl.UserServiceImpl.addUser(..))")public void throwing(){System.out.println("抛出了运行时异常....");}//最终通知 不管目标方法有没有出现异常 都会执行该增强方法@After(value = "execution(* com.xq.service.impl.UserServiceImpl.addUser(..))")public void after(){System.out.println("最终通知的方法执行了.....");}
}

  抽象方法注解:

  • @Pointcut 注解用于定义切点表达式。

  • 其他注解(如 @Before, @Around 等)则用于将方法标记为特定类型的通知,并通过它们的value属性指向对应的切点。

/*** 切面类*/
@Component
@Aspect //标识当前类是一个切面类
public class MyAspect {//定义切点@Pointcut(value = "execution(* com.xq.service.impl.UserServiceImpl.deleteUser(..))")public void pointCut1(){}//权限校验的方法  @Before注解: 前置通知的注解//@Before(value = "execution(* com.xq.service.impl.UserServiceImpl.deleteUser(..))")@Before(value = "pointCut1()")public void checkPrivilege(){System.out.println("开启了权限校验......");}}

applicationContext.xml配置:

applicationContext.xml文件中的配置启用了基于注解的AOP。这样,Spring可以自动检测带有@Aspect注解的类,并根据其中定义的注解来配置AOP。

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns:context="http://www.springframework.org/schema/context"xmlns:aop="http://www.springframework.org/schema/aop"xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd"><!--开启包扫描--><context:component-scan base-package="com.xq"></context:component-scan><!--开启spring对aop的注解支持--><aop:aspectj-autoproxy></aop:aspectj-autoproxy>
</beans>

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

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

相关文章

UCAS-AOD遥感旋转目标检测数据集——基于YOLOv8obb,map50已达96.7%

1.UCAS-AOD简介 1.1数据说明 遥感图像&#xff0c;又名高分辨率遥感图像。遥感图像的分类依据是根据成像的介质不同来进行分类的。UCAS-AOD (Zhu et al.&#xff0c;2015)用于飞机和汽车的检测&#xff0c;包含飞机与汽车2类样本以及一定数量的反例样本&#xff08;背景&…

【立创EDA-PCB设计基础】6.布线铺铜实战及细节详解

前言&#xff1a;本文进行布线铺铜实战及详解布线铺铜的细节 在本专栏中【立创EDA-PCB设计基础】前面完成了布线铺铜前的设计规则的设置&#xff0c;接下来进行布线 布局原则是模块化布局&#xff08;优先布局好确定位置的器件&#xff0c;例如排针、接口、主控芯片&#xff…

k8s-基础知识(Pod,Deployment,ReplicaSet)

k8s职责 自动化容器部署和复制随时扩展或收缩容器容器分组group&#xff0c;并且提供容器间的负载均衡实时监控&#xff0c;即时故障发现&#xff0c;自动替换 k8s概念及架构 pod pod是容器的容器&#xff0c;可以包含多个container pod是k8s最小可部署单元&#xff0c;容器…

基于python和定向爬虫的商品比价系统实现

目录 前言 一、系统设计 1. 系统需求分析 2. 系统设计思路 二、系统实现 1. 爬虫部分 2. 比价部分 3. 完整系统代码 三、系统优化 1. 多线程爬取 2. 引入数据库 四、总结 前言 商品比价系统是一种可以帮助用户快速找到最优价格商品的系统。本文将介绍如何使用pyth…

查询文件hash值

查询文件hash值 1 Windows 查询文件hash值1.1 certutil -hashfile 文件名 2 Linux 环境查询文件hash值2.1 sha256sum 文件名2.2 md5sum 文件名 1 Windows 查询文件hash值 在某些环境要对比两个文件是否完全一致 1.1 certutil -hashfile 文件名 certutil -hashfile C:\Users\…

【C++ 自写String】

CString.hpp #include <iostream> #include <string.h>#pragma warning(disable:4996) using namespace std;class CString { private:int len;char* data;public:CString():data(nullptr),len(0) {cout << "0空构造\n";}CString(const char* _da…

计算机服务器中了mallox勒索病毒解密方案计划,勒索病毒解密措施

计算机技术的不断应用与发展&#xff0c;为企业的生产运营提供了有利条件&#xff0c;但网络安全威胁无处不在。近期&#xff0c;广西某生物制药企业的计算机服务器遭到了mallox勒索病毒攻击&#xff0c;导致企业的计算机所有重要数据被加密&#xff0c;严重影响企业的生产运营…

【jetson笔记】torchaudio报错

原因是因为pip安装的包与jetson不兼容导致 自己安装或者cmake编译也会报错 需要拉取官方配置好的docker镜像 拉取docker镜像 具体容器可以看官网&#xff0c;按照自己需求拉取即可 https://catalog.ngc.nvidia.com/orgs/nvidia/containers/l4t-ml 如果其他包不需要只需要torc…

2024区块链应用最趋势,RWA实物资产化

作者 张群&#xff08;赛联区块链教育首席讲师&#xff0c;工信部赛迪特聘资深专家&#xff0c;CSDN认证业界专家&#xff0c;微软认证专家&#xff0c;多家企业区块链产品顾问&#xff09;关注张群&#xff0c;为您提供一站式区块链技术和方案咨询。 实物资产通证化&#xff0…

Conda python运行的包和环境管理 入门

Conda系列&#xff1a; 翻译: Anaconda 与 miniconda的区别Miniconda介绍以及安装 Conda 是一个功能强大的命令行工具&#xff0c;用于在 Windows、macOS 和 Linux 上运行的包和环境管理。 本 conda 入门指南介绍了启动和使用 conda 创建环境和安装包的基础知识。 1. 准备…

解决Android Studio gradle下载超时和缓慢问题(win10)

解决超时问题 一般配置阿里云代理就可以解决。 具体配置方法&#xff0c;参考&#xff1a;https://blog.csdn.net/zhangjin1120/article/details/121739782 解决下载缓慢问题 直接去腾讯云镜像下载&#xff1a; https://mirrors.cloud.tencent.com/gradle/ 下载好了之后&…

Spring 的 IOC 和 AOP

题目 Spring 的 IOC 和 AOP 推荐解析 IOC 是什么&#xff1f; IoC&#xff08;Inversion of Control&#xff09; 控制反转&#xff0c;是一种常见的设计思想&#xff0c;主要就是将手动创建对象的控制权&#xff0c;交给 Spring 框架来管理。 为什么需要存在一个容器&…

SpringCloud Alibaba Sentinel 与 SpringCloud Gateway 的限流有什么差别?(三种限流算法原理分析)

目录 一、Sentinel 与 Gateway 的限流有什么差别&#xff1f; 1.1、前置知识 - 四种常见的限流算法 1.1.1、Tips 1.1.2、计数器算法 1&#xff09;固定窗口计数器算法 2&#xff09;滑动窗口计数器算法 1.1.3、令牌桶算法 1.1.4、漏桶算法 1.2、解决问题 一、Sentinel…

Qt Designer教程

文章目录 创建一个 ui 文件选择控件Qt Designer基本控件介绍1、Layouts1.1、Layouts 布局1.2、参数配置 2、Spacers2.1、 Spacers 弹簧介绍2.2、 参数设置 3、Buttons 按键3.1、 Buttons 按键分类 4、Item Views&#xff08;Model-Based&#xff09; 项目视图(基于模型)4.1、 B…

12.for 条件循环语句 (3)

for 循环语句 允许脚本一次性读取多个信息&#xff0c;然后逐一对信息进行操作处理。当要处理的数据有范围时&#xff0c;使用for循环语句。 使用 for 循环语句从列表文件中读取多个用户名&#xff0c;然后为其逐一创建用户账户并设 置密码。首先创建用户名称的列表文件users.…

android studio从空白开始

对我来说&#xff0c;真正的第一步是清理电脑C盘。从剩余8G清理到25G&#xff0c;把原来看不顺眼又不敢删的文件夹和软件全删了&#xff0c;删爽了的后果就是&#xff0c;用两天的时间在把一些环境配置慢慢装回来&#xff0c;node.js&#xff0c;jdk&#xff0c;npm。努力把它们…

密码学中的承诺原语(Commitment Scheme)

1背景介绍 让我们考虑以下情况&#xff1a;Alice在佳士得&#xff08;Christies&#xff09;购买Banksy的最后一件杰作&#xff0c;在这之前&#xff0c;她会确保艺术品在售出后不会被销毁。 佳士得选择了维克里封闭竞标的拍卖方式&#xff0c;这是一种相当常见的做法&#x…

Vue+Element(el-upload+el-form的使用)+springboot

目录 1、编写模板 2、发请求调接口 3、后端返回数据 1.编写实体类 2.Controller类 3、interface接口&#xff08;Service层接口&#xff09; 4.Service&#xff08;接口实现&#xff09; 5、interface接口&#xff08;Mapper层接口&#xff09; 6、xml 4、upload相关参…

前端 防止浏览器提示记住密码以及自动填充密码

当前端 <input /> 的 type’password‘ 时&#xff0c;浏览器为了优化用户体验&#xff0c;会在表单提交后提示用户记住密码 如果不想要这样的行为&#xff0c;最简单的当然是提示用户自己在浏览器设置中进行相关配置 如果希望在代码层面阻止浏览器提示是否记住密码或者…

.git 文件夹结构解析

.git 文件夹结构解析 在这篇文章就让我们来看看这个 Git 仓库里的文件分别都是用来干什么的&#xff0c;以及在执行了相关的 Git 命令后这些文件会如何响应。 hooks&#xff08;钩&#xff09;&#xff1a;存放一些shell脚本info&#xff1a;存放仓库的一些信息logs&#xff…