SpringAop详解
文章目录
- 一、Spring自定义注解
- 1、什么是注解👨🏫
- 2、注解的目的或作用💞
- 3、JDK内置注解💫 【内置+元注解 == 一共八个固定注解】
- 4、元注解 🎯
- 5、自定义注解📸
- 5+、Java反射API和类加载过程
- 5+1、什么是反射+基本原理
- 5+2、反射的应用场景有哪些
- 5+3、API方法
- 1、获得Class对象
- 2、从 Class 中获取信息
- 5+4、应用实例
- 参考
- 6、自定义注解实战🐱🏍
- 二、AOP切面开发
- 2.1 为什么用AOP编程
- 2.2 AOP的基础术语
- 2.3 常见的AOP五种通知
- 2.4 实际运用+环绕通知
- AOP参考:
- 三、【事务踩坑8+12场景】Spring声明事务&编程事务(最好)
- Transaction参考
首先了解基本的自定义注解(配置项参考网址) + 切面(配置项参考) + 切面由浅入深 + 具体的@Transaction详解

一、Spring自定义注解

第 1-5 小节均偏向于理论知识,若只是想要了解如何自定义注解和如何应用注解,请跳转至第6小节开始阅读。
在本篇中,主要是针对注解的概念和运行时注解进行解释说明,附带有三个实战的案例,尽可能的让大家能够理解透彻并且能够加以应用。
1、什么是注解👨🏫
Java 注解(Annotation)用于为 Java 代码提供元数据。作为元数据,注解不直接影响你的代码执行,但也有一些类型的注解实际上可以用于这一目的。Java 注解是从 Java5 开始添加到 Java 的。–官方文档
1.1、注解
Annotion(注解)是一个接口,程序可以通过反射来获取指定程序元素的Annotion对象,然后通过Annotion对象来获取注解里面的元数据。
我们常常使用的注解,@Data、@Controller等等,这些都是注解,创建一个注解,也很简单,创建一个类,然后将class改为 @interface就是一个注解啦。
1.2、注解出现的位置
Java代码中的包、类型、构造方法、方法、成员变量、参数、本地变量的声明都可以用注解来修饰。注解本质上可以看作是一种特殊的标记,程序在编译或者运行时可以检测到这些标记而进行一些特殊的处理。
1.3、关于注解的处理
我们一般将利用反射来处理注解的方式称之为运行时注解。
另外一种则是编译时注解,如我们常常使用的 lombok 里的注解,@Data,它能够帮我们省略set/get方法,我们在Class上加上这个注解后,在编译的时候,lombok其实是修改了.class文件的,将set/get方法放进去了,不然的话,你可以看看编译完后的.class文件。诸如这种,我们常称为编译时注解,也就是使用javac处理注解。

–图:来自于极客学院
这幅图就是从.java文件到class文件的,再到class文件被 JVM 加载的过程。
而其中的注解抽象语法树这一阶段,就是去解析注解,然后根据定义的注解处理器进行相关的逻辑处理。
这一块不是我的关注点,略过略过啦,朋友们,好奇可以去研究研究噢
2、注解的目的或作用💞
- 生成文档。这是最常见的,也是 Java 最早提供的注解。如@param、@return等等
- 跟踪代码依赖性,实现替代配置文件功能。**作用就是减少配置,如 Spring中Bean的装载注入,而且现在的框架基本上都是使用注解来减少配置文件的数量,同时这样也使得编程更加简洁,代码更加清晰。
- 在编译时进行格式检查。**如@Override放在方法前,如果你这个方法并不是覆盖了超类方法,则编译时就能检查出;
- 标识作用。**当Java编译时或运行时,检测到这里的注解,做什么的处理,自定义注解一般如此。
携带信息。 注解的成员提供了程序元素的关联信息,Annotation 的成员在 Annotation类型中以无参数的方法的形式被声明。其方法名和返回值定义了该成员的名字和类型。在此有一个特定的默认 语法:允许声明任何Annotation成员的默认值。一个Annotation可以将name=value对作为没有定义默认值的Annotation 成员的值,当然也可以使用name=value对来覆盖其它成员默认值。这一点有些近似类的继承特性,父类的构造函数可以作为子类的默认构造函数,但是也 可以被子类覆盖。
这么一大段话,其实就是关于注解中成员的解释。
说了这么多,其实一句话也能表达完。
注解就是一张便利贴,它贴在那里,你看到的那一刻,就明白该做什么事啦。
如出门前,门上贴着一张便利贴📌,上面写着"出门记得带钥匙",当你看到的那一刻,你就会去检查一下自己是否带钥匙啦。
在Java中也是一样的,你定义了一个注解,注解上可以写一些东西,然后你再将它贴在某个上面,说明白执行规则,当编译到这里的时候需要干嘛干嘛,又或者是当运行到这里的时候需要干嘛干嘛。
因为注解写的东西的不同,或者是处理注解的规则不同,而产生了不同的注解及作用。
3、JDK内置注解💫 【内置+元注解 == 一共八个固定注解】
Java中 内置的注解有5类,具体包括:
@Deprecated:过时注解,用于标记已过时 & 被抛弃的元素(类、方法等)
@Override:复写注解,用于标记该方法需要被子类复写
@SuppressWarnings:阻止警告注解,用于标记的元素会阻止编译器发出警告提醒
@SafeVarargs:参数安全类型注解,用于提醒开发者不要用参数做不安全的操作 & 阻止编译器产生 unchecked警告,Java 1.7 后引入
4、元注解 🎯
何为元注解?就是注解的注解,就是给你自己定义的注解添加注解,你自己定义了一个注解,但你想要你的注解有什么样的功能,此时就需要用元注解对你的注解进行说明了。
接着上一个比喻
注解有很多很多吗,门上贴一个,冰箱上贴一个,书桌上贴一个等等
元注解勒就是把他们整合起来称呼的,像上面这些可以统称为生活类注解啊。所以也就是注解的注解。
4.1、@Target
在 @Target 注解中指定的每一个 ElementType 就是一个约束,它告诉编译器,这 个自定义的注解只能用于指定的类型。
说明了注解所修饰的对象范围:注解可被用于 packages、types(类、接口、枚举、Annotation类型)、类型成员(方法、构造方法、成员变量、枚举值)、方法参数和本地变量(如循环变量、catch参数)。
4.2、@Retention
定义了该注解的生命周期:
某些注解仅出现在源代码中,而被编译器丢弃; (源码级)
而另一些却被编译在class文件中; (字节码级)
编译在class文件中的注解可能会被虚拟机忽略,而另一些在class被装载时将被读取(请注意并不影响class的执行,因为注解与class在使用上是被分离的)。绝大多数开发者都是使用RUNTIME,因为我们期望在程序运行时,能够获取到这些注解,并干点有意思的事儿,而只有RetentionPolicy.RUNTIME,能确保自定义的注解在运行时依然可见。(运行级)
使用这个元注解可以对自定义注解的“生命周期”进行限制。
RetentionPolicy.SOURCE 一般开发者很少用到,大都是Java内置的注解。如@Override
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {
}
@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE})
@Retention(RetentionPolicy.SOURCE)
public @interface SuppressWarnings {
}
这些注解只是在编译的时候用到,一旦编译完成后,运行时没有任何意义,所以他们被称作源码级别注解。
如果有了解过 lombok 一些简单原理的开发者, 都知道它是通过注解在编译时自动生成一部分代码,让源码看起来更简洁,字节码却很强大。
当然,这种方式有它自身的缺陷,譬如不一致性,问题排解时的困扰,以及依赖问题,不是本篇重点,扯回来。
提供信息给编译器: 编译器可以利用注解来检测出错误或者警告信息,打印出日志。
编译阶段时的处理: 软件工具可以用来利用注解信息来自动生成代码、文档或者做其它相应的自动处理。
运行时处理: 某些注解可以在程序运行的时候接受代码的提取,自动做相应的操作。
4.3、@Documented
用于描述其它类型的annotation应该被作为被标注的程序成员的公共API,因此可以被例如 javadoc此类的工具文档化。是一个标记注解,没有成员。
4.4、@Inherited
是一个标记注解阐述了某个被标注的类型是被继承的。使用了@Inherited修饰的注解类型被用于一个class时该class的子类也有了该注解。
4.5、@Repeatable
允许一个注解可以被使用一次或者多次(Java 8)。
5、自定义注解📸
自定义注解实际上就是一种类型而已,也就是引用类型(Java中除了8种基本类型之外,我们见到的任何类型都是引用类型)
5.1、定义注解
自定义注解过程:
声明一个类MyAnnotation
把class关键字改为@interface
这样我们就声明了一个自定义的注解,当我们用@interface声明一个注解的时候,实际上是声明了一个接口,这个接口自动的继承了java.lang.annotation.Annotation,但是我们只需要@interface这个关键字来声明注解,编译器会自动的完成相关的操作,不需要我们手动的指明继承Annotation接口
另外在定义注解时,不能再继承其他的注解或接口。
我举了四个例子,这四个注解分别是放在 类(接口、枚举类上)、构造函数、方法级别、成员属性上的。
@Documented //定义可以被文档工具文档化
@Retention(RetentionPolicy.RUNTIME)//声明周期为runtime,运行时可以通过反射拿到
@Target(ElementType.TYPE)//注解修饰范围为类、接口、枚举
public @interface ClassAnnotation {public String name() default "defaultService";public String version() default "1.1.0";
}@Documented
@Target(ElementType.CONSTRUCTOR)
@Retention(RetentionPolicy.RUNTIME)
public @interface ConstructorAnnotatin {String constructorName() default "";String remark() default "构造器";
}@Documented
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface FieldAnnotation {public String name() default "defaultName";public String value() default "defaultValue";
}@Documented@Retention(RetentionPolicy.RUNTIME)@Target(ElementType.METHOD)public @interface MethodAnnotation {public String name() default "defaultName";public MethodTypeEnum type() default MethodTypeEnum.TYPE1;}public enum MethodTypeEnum {TYPE1,TYPE2
}
5.2、注解的成员变量
成员以无参数无异常的方式声明 String constructorName() default “”;
可以使用default为成员指定一个默认值 public String name() default “defaultName”;
成员类型是受限的,合法的类型包括原始类型以及String、Class、Annotation、Enumeration (JAVA的基本数据类型有8种:byte(字节)、short(短整型)、int(整数型)、long(长整型)、float(单精度浮点数类型)、double(双精度浮点数类型)、char(字符类型)、boolean(布尔类型)
public MethodTypeEnum type() default MethodTypeEnum.TYPE1;
注解类可以没有成员,没有成员的注解称为标识注解,例如JDK注解中的@Override、@Deprecation
如果注解只有一个成员,并且把成员取名为value(),则在使用时可以忽略成员名和赋值号“=”
例如JDK注解的@SuppviseWarnings ;如果成员名 不为value,则使用时需指明成员名和赋值号"="
5.3、使用注解
因为我们在注解中声明了属性,所以在使用注解的时候必须要指明属性值 ,多个属性之间没有顺序,多个属性之间通过逗号分隔
@ClassAnnotation(name = "personBean", version = "1.2.1")
public class Person {// 告诉大家是可以用的,但是影响我测试,我就又注释掉了.
// @ConstructorAnnotatin(constructorName="Person()")
// public Person(String description) {
// this.description = description;
// }@FieldAnnotation(name = "description", value = "This is my personal annotation")private String description;public String getDescription() {return description;}public void setDescription(String description) {this.description = description;}@MethodAnnotation(name = "sayHello", type = MethodTypeEnum.TYPE2)public void sayHello() {System.out.println("Hello Annotation!");}
}
5+、Java反射API和类加载过程

类的三个主要特性是它的字段(变量)、方法和构造函数。为了描述和访问对象,这三个特性在反射API中由单独的类表示:java.lang.reflect.Field,java.lang.reflect.Method,以及java.lang.reflect.Constructor。我们可以通过class对象查找类的这些成员。
5+1、什么是反射+基本原理
Java反射API,位于java.lang.reflect包。顾名思义,反射是类或对象检查自身的能力。反射允许Java代码查看对象(更准确地说,对象的类)并确定其结构。在安全管理器所施加的限制内,您可以找出一个类有哪些构造函数、方法和字段,以及它们的属性。
在运行状态中,对于任意一个类,都能够知道这个类中的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为java的反射机制。

Class加载过程·:
Java类加载主要分为这么几个阶段
编译阶段
将.java源文件编译成.class字节码文件。
运行阶段
- 当JVM第一次加载某一个用到的类时,会使用ClassLoader类加载器加.class文件加载到内存中。
- 连接-验证阶段:对class进行验证 头文件元数据等。
- 连接-准备阶段:收集字节码中的静态属性与静态代码块,并把静态属性赋值默认值。
- 连接-解析阶段:解析阶段主要是把符号引用转换为地址应用。
- 初始化:初始化阶段主要是运行class的静态代码块内容。
所有阶段都运行完成之后,此时JVM会把二进制的class文件加载到方法区,同时创建一个Class对象放入堆区。这个时候仅仅是完成了类的加载过程,之后才是创建对象。
5+2、反射的应用场景有哪些
-
在运行时判断任意一个对象所属的类。
-
在运行时构造任意一个类的对象。
-
在运行时判断任意一个类所具有的成员变量和方法.
-
在运行时调用任意一个对象的方法。
-
生成动态代理。
-
Spring 框架中的反射机制主要是通过 Java 的反射 API
实现的,它允许在运行时动态地加载、检查、创建、访问和修改类、对象、属性和方法等信息。在 Spring 中,反射机制常常用于实现依赖注入、AOP、事务管理等功能。具体来说,通过反射机制可以实现以下几个方面的功能:
-
实例化对象:通过 Class 对象的 newInstance() 方法可以创建一个类的实例对象。
-
获取类信息:通过 Class 对象的 getMethods()、getFields()、getConstructors()
等方法可以获取类的属性、方法、构造函数等信息。 -
调用方法:通过 Method 对象的 invoke() 方法可以调用方法。
-
访问属性:通过 Field 对象的 get()、set() 方法可以访问和修改对象的属性。
在 Spring 中,反射机制常用于实现依赖注入。例如,通过读取 XML 配置文件中的 bean
定义信息,可以获取类的全限定名、构造函数参数、属性值等信息,然后使用反射机制动态地创建类的实例对象,并将其注入到其他对象中。另外,在 Spring AOP 中也常常用到反射机制。例如,在基于方法的 AOP 中,通过获取目标对象的 Method
对象,可以在方法执行前后进行一些增强操作。总之,Spring 框架中的反射机制为实现各种高级特性提供了基础支持,也为开发人员提供了方便的工具和技术。
-
5+3、API方法
1、获得Class对象
每个类被加载之后,系统就会为该类生成一个对应的Class对象。通过该Class对象就可以访问到JVM中的这个类。
在Java程序中获得Class对象通常有如下三种方式:
使用 Class 类的forName(String clazzName)静态方法。该方法需要传入字符串参数,该字符串参数的值是某个类的全限定名(必须添加完整包名)。
调用某个类的class属性来获取该类对应的 Class 对象。
调用某个对象的getClass()方法。该方法是java.lang.Object类中的一个方法。
//第一种方式 通过Class类的静态方法——forName()来实现
class1 = Class.forName("com.lvr.reflection.Person");
//第二种方式 通过类的class属性
class1 = Person.class;
//第三种方式 通过对象getClass方法
Person person = new Person();
Class<?> class1 = person.getClass();
对于方式一和方式二都是直接根据类来取得该类的 Class 对象,相比之下,方式二有如下的两种优势:
代码跟安全。程序在编译阶段就能够检查需要访问的 Class 对象是否存在。
线程性能更好。因为这种方式无须调用方法,所以性能更好。
可以通过类的类类型创建该类的对象实例。
Class.newInstance();
//
Foot foot = (Foot) c1.newInstance();
2、从 Class 中获取信息
一旦获得了某个类所对应的Class 对象之后,就可以调用 Class 对象的方法来获得该对象的和该类的真实信息了。
1、获取 Class 对应类的成员变量
Field[] getDeclaredFields(); // 获取 Class 对象对应类的所有属性,与成员变量的访问权限无关。
Field[] getFields(); // 获取 Class 对象对应类的所有 public 属性。
Field getDeclaredField(String name); // 获取 Class 对象对应类的指定名称的属性,与成员变量的访问权限无关。
Field getField(String name); // 获取 Class 对象对应类的指定名称的 public 属性。
2、获取 Class 对应类的方法
Method[] getDeclaredMethods(); // 获取 Class 对象对应类的所有声明方法,于方法的访问权限无关。
Method[] getMethods(); // 获取 Class 对象对应类的所有 public 方法,包括父类的方法。
Method getMethod(String name, Class<?>...parameterTypes); // 返回此 Class 对象对应类的、带指定形参列表的 public 方法。 Method getDeclaredMethod(String name, Class<?>…parameterTypes); // 返回此 Class 对象对应类的、带指定形参列表的方法,与方法的访问权限无关。
3、获取 Class 对应类的构造函数
Constructor<?>[] getDeclaredConstructors(); // 获取 Class 对象对应类的所有声明构造函数,于构造函数的访问权限无关。 Constructor<?>[] getConstructors(); // 获取 Class 对象对应类的所有 public 构造函数。
Constructor getDeclaredConstructor(Class<?>...parameterTypes); // 返回此 Class 对象对应类的、带指定形参列表的构造函数,与构造函数的访问权限无关。 Constructor getConstructor(Class<?>…parameterTypes); // 返回此 Class 对象对应类的、带指定形参列表的 public 构造函数。
4、获取 Class 对应类的 Annotation(注释)
A getAnnotation(Class annotationClass); // 尝试获取该 Class 对对象对应类存在的、指定类型的 Annotation;如果该类型的注解不存在,则返回 null。
A getDeclaredAnnotation(Class annotationClass); // 这是Java8新增的方法,该方法尝试获取直接修饰该 Class 对象对应类、指定类型的Annotation;如果该类型的注解不存在,则返回 null。
Annotation[] getAnnotations(); // 返回修饰该 Class 对象对应类存在的所有Annotation。
Annotation[] getDeclaredAnnotations(); // 返回直接修饰该 Class 对应类的所有Annotation。
A[] getAnnotationsByType(Class annotationClass); // 获取修饰该类的、指定类型的多个Annotation。
A[] getDeclaredAnnotationsByType(Class annotationClass); // 获取直接修饰该类的、指定类型的多个Annotation。
5、获取 Class 对应类的内部类
Class<?>[] getDeclaredClasses(); // 返回该 Class 对象对应类包含的全部内部类。
6、获取 Class 对应类的外部类
Class<?> getDeclaringClass(); // 返回该 Class 对象对应类所在的外部类。
7、获取 Class 对应类所实现的接口
Class<?>[] getInterfaces();
8、获取 Class 对应类所继承的父类
Class<? super T> getSuperClass();
9、获取 Class 对应类的修饰符、所在包、类名等基本信息
int getModifiers(); // 返回此类或接口的所有修饰符。修饰符由 public、protected、private、final、static、abstract 等对应的常量组成,返回的整数应使用 Modifier 工具类的方法来解码,才可以获取真实的修饰符。
Package getPackage() // 获取该类的包。
String getName() // 以字符串的形式返回此 Class 对象所表示的类的名称。
String getSimpleName() // 以字符串的形式返回此 Class 对象所表示的类的简称。
10、判断该类是否为接口、枚举、注解类型等
boolean isAnnotation() // 返此 Class 对象是否是一个注解类型(由@interface定义)。
boolean isAnnotationPresent(Class<? extends Annotation> annotationClass) // 判断此 Class 对象是否使用了Annotation修饰。
boolean isAnonymousClass() // 此 Class 对象是否是一个匿名类。
boolean isArray() //此 Class 对象是否是一个数组。
boolean isEnum() // 此 Class 对象是否是一个枚举(由 enum 关键字定义)。
boolean isInterface() // 此 Class 对象是否是一个接口(由 interface 关键字定义)。
boolean isInstance(Object obj) // 判断 obj 是否是此 Class 对象的实例。该方法完全可以替代 instanceof 操作符。
5+4、应用实例
获取Class对象的几种方式
//一、获取class的几种方式Class<?> cls1 = Class.forName("com.example.module01.reflect.Human"); //常用于动态的加载某个class,例如从配置文件获取Class<Human> cls2 = Human.class;//常用于调用某个需要class类型参数的方法时的参数传递Class<? extends Human> cls3 = new Human().getClass();//常用于在明确类型的情况下获取classClass<?> cls4 = RefTest01.class.getClassLoader().loadClass("com.example.module01.reflect.Human");//常用于动态System.out.println(cls1 == cls2 ? cls1 == cls3 ? cls1 == cls4 : "不相等" : "不相等"); //true
Constructor类API
// 2.1 构造方法相关Class<Student> clazz = Student.class;Constructor<?>[] declaredConstructors = clazz.getDeclaredConstructors();System.out.println("声明的构造方法个数:" + declaredConstructors.length);Constructor<?>[] constructors = clazz.getConstructors();//获取所有的构造方法for (Constructor<?> constructor : constructors) {Class<?> declaringClass = constructor.getDeclaringClass();//获取此构造方法的声明类Class<?>[] parameterTypes = constructor.getParameterTypes();//获取不带泛型的构造方法Type[] genericParameterTypes = constructor.getGenericParameterTypes();//获取带泛型的构造方法Object instance = null;
// instance = constructor.newInstance();//调用newInstanceSystem.out.println("构造方法的声明位置:" + declaringClass + ",构造方法参数个数:" + parameterTypes.length+ ",带泛型的构造方法参数个数:" + genericParameterTypes.length + ",创建的对象:" + instance);}
Method API
//2.2 method方法API
Method[] methods = clazz.getMethods(); //可以获取所有的公开的方法,包括本类的和继承的
for (Method method : methods) {System.out.println("公开的方法:" + method.getName() + "," + method.getDeclaringClass());
}
Method[] declaredMethods = clazz.getDeclaredMethods();//获取声明在本类中的所有方法,包括静态方法
for (Method declaredMethod : declaredMethods) {declaredMethod.setAccessible(true);//破解private权限declaredMethod.invoke(clazz.newInstance());//反射执行方法System.out.println("本类所有方法:" + declaredMethod);
}System.out.println("========================Field=======================");
Field API
//2.3 属性相关Field[] fields = clazz.getFields();for (Field field : fields) {System.out.println("公开的属性:" + field.getName());}for (Field declaredField : clazz.getDeclaredFields()) {Student student = clazz.newInstance();declaredField.setAccessible(true);//设置可访问declaredField.set(student,1);//给属性赋值Object value = declaredField.get(student);System.out.println("本类的所有属性:" + declaredField.getName() + " val ===> " + value);}
获取父类 接口API相关信息,通过获取superClass给继承的父类
属性赋值//2.4获取父类、接口信息Class<? super Student> superclass = clazz.getSuperclass();Class<?>[] interfaces = clazz.getInterfaces();System.out.println("父类是:" + superclass); //Object类的superClass是null。System.out.println("本类实现的接口:" + interfaces.length);//2.5 实现Spring自动注入效果,将父类的protected类型属性也自动注入。Class<Student> stuClass = Student.class;//通过反射创建对象Student instance = stuClass.newInstance();//获取本类的方法for (Field field : stuClass.getDeclaredFields()) {//开启访问权限field.setAccessible(true);//动态赋值field.set(instance,111);}//继续获取父类的class对象Class<? super Student> superClass = stuClass.getSuperclass();//继续获取父类的成员属性Field[] declaredFields = superClass.getFields();for (Field declaredField : declaredFields) {//给父类属性赋值declaredField.set(instance,222);}//打印输出System.out.println(instance);
参考
- Java–反射机制(一)——反射 API
- Java反射API详解
- Java反射-反射API、类加载过程
5.4、浅提一下反射【重点】
想要去获取注解就不得不提到反射啦,但 Java 反射会带来一定的耗时,因此使用运行注解需要考虑对性能的影响。
我们声明一个Student类用来描述学生对象的信息的
class Student{String name;String school;//...set/get
}
当我们创建一个学生对象时,学生对象的信息是保存在 Student 类中,所以 Student 类会提供获取这些信息的方法。
在Java类中,每个类都会有对应的Class,要想执行反射操作,必须先要获取指定类名的Class
了解Class对象:
类是程序的一部分,每个类都有一个 Class 对象。换言之,每当我们编写并且编译 了一个新类,就会产生一个 Class 对象(更恰当的说,是被保存在一个同名的 .class 文件中)。为了生成这个类的对象,Java 虚拟机 (JVM) 先会调用 “类加载器” 子系统把 这个类加载到内存中。
Class类:简单说就是用来描述类对象的信息的
类对象的信息包括:
类的基本信息:包名、修饰符、类名、基类,实现的接口
属性的信息:修饰符、属性类型、属性名称、属性值,
方法的信息:修饰符、返回类型、方法名称、参数列表、抛出的异常
构造方法的信息:修饰符、类名、参数列表、抛出的异常
注解的相关信息:
因为:类对象的相关信息全部保存在Class类
所以:Class类会提供获取这些信息的方法
一旦某个类的 Class 对象被载入内存,它就可以用来创建这个类的所有对象。
通过 Class 获取类的相关信息,通过Class创建对象,通过 Class 调用对象上面的属性,调用对象上面的方法,这种操作就称为反射,要想执行反射操作,必须先要获取到指定的类名的 Class
获取Class的不同方式
获取基本类型的Class
1)基本类型Class:如 int.Class获取的就是 int 类型的 Class
获取引用类型的Class:
1)引用类型的Class:如String.Class获取的就是String类对应的Class
2)通过对象来获取:如:String obj="hello",Class calz = obj.getClass(),获取的就是String类对应的Class
3)Class.forName("java.lang.String"),获取的就是对应的Class
5.5、提取注解
public class TestClassAnnotation {private static Person person = new Person();public static void main(String[] args) {Class<?> clazz = person.getClass();//因为注解是作用于类上面的,所以可以通过isAnnotationPresent来判断是否是一个具有指定注解的类if (clazz.isAnnotationPresent(ClassAnnotation.class)) {System.out.println("This is a class with annotation ClassAnnotation!");//通过getAnnotation可以获取注解对象ClassAnnotation annotation = clazz.getAnnotation(ClassAnnotation.class);if (null != annotation) {System.out.println("BeanName = " + annotation.name());System.out.println("BeanVersion = " + annotation.version());} else {System.out.println("the annotation that we get is null");}} else {System.out.println("This is not the class that with ClassAnnotation");}}
}
This is a class with annotation ClassAnnotation!
BeanName = personBean
BeanVersion = 1.2.1scss复制代码public class AnnotationTest {public static void main(String[] args) throws ClassNotFoundException {Class<?> clazz = Class.forName("com.nzc.my_annotation.shang.Person");System.out.println("==============类注解解析==============");printClassAnno(clazz);System.out.println("==============成员变量注解解析==============");printFieldAnno(clazz);System.out.println("==============成员方法注解解析==============");printMethodAnno(clazz);System.out.println("==============构造器注解解析==============");printConstructorAnno(clazz);}/*** 打印类的注解*/private static void printClassAnno(Class<?> clazz) throws ClassNotFoundException {//判断是否有AuthorAnnotatin注解if(clazz.isAnnotationPresent(ClassAnnotation.class)) {//获取AuthorAnnotatin类型的注解ClassAnnotation annotation = clazz.getAnnotation(ClassAnnotation.class);System.out.println(annotation.name()+"\t"+annotation.version());}}/*** 打印成员变量的注解*/private static void printFieldAnno(Class<?> clazz) throws ClassNotFoundException {Field[] fields = clazz.getDeclaredFields();for (Field field : fields) {if(field.isAnnotationPresent(FieldAnnotation.class)) {FieldAnnotation annotation = field.getAnnotation(FieldAnnotation.class);System.out.println(annotation.name()+"\t"+annotation.value());}}}/*** 打印成员变量的注解*/private static void printMethodAnno(Class<?> clazz) throws ClassNotFoundException {Method[] methods = clazz.getDeclaredMethods();for (Method method : methods) {if(method.isAnnotationPresent(MethodAnnotation.class)) {MethodAnnotation annotation = method.getAnnotation(MethodAnnotation.class);System.out.println(annotation.name()+"\t"+annotation.type());}}}/*** 打印成员变量的注解*/private static void printConstructorAnno(Class<?> clazz) throws ClassNotFoundException {Constructor<?>[] constructors = clazz.getDeclaredConstructors();for (Constructor<?> constructor : constructors) {if(constructor.isAnnotationPresent(ConstructorAnnotatin.class)) {ConstructorAnnotatin annotation = constructor.getAnnotation(ConstructorAnnotatin.class);System.out.println(annotation.constructorName()+"\t"+annotation.remark());}}System.out.println("无");}}ini复制代码==============类注解解析==============
personBean 1.2.1
==============成员变量注解解析==============
description This is my personal annotation
==============成员方法注解解析==============
sayHello TYPE2
==============构造器注解解析==============
无
6、自定义注解实战🐱🏍
注解大多时候与反射或者 AOP 切面结合使用,它的作用有很多,比如标记和检查,最重要的一点就是简化代码,降低耦合性,提高执行效率。
6.1、自定义注解 + SpringMVC 拦截器实现权限控制功能
还有一种应用场景,权限判断或者说是登录校验。
这个是我当时还没有学习市面上的权限框架,就是使用了这种自定义注解+拦截器的方式来实现简单的权限控制。
注意:此案例不可CV直接运行,代码很容易实现,大家理解思路即可。
定义注解:
@Target({ElementType.METHOD,ElementType.TYPE}) // 这个注解可以放在也可以放在方法上的。
@Retention(RetentionPolicy.RUNTIME)
public @interface Authority {Role[] roles() ;
}arduino复制代码public enum Role {SADMIN,ADMIN,TEACHER,STUDENT
}
使用注解:
@Authority(roles = {Role.ADMIN, Role.SADMIN}) // 放在类上 说明这个类下所有的方法都需要有这个权限才可以进行访问
@RestController
@RequestMapping("/admin")
public class AdminController {@GetMapping("/hello")public String Hello(){return "hello 你最近还好吗";}
}
@Controller
@RequestMapping("/student")
public class StudentController {@Authority(roles = {Role.STUDENT}) // 放在方法上则说明此方法需要注解上的权限才能进行访问@GetMapping("/test")public String test(){return "你好,我已经不是一名学生啦";}}
编写 SpringMVC 拦截器及处理注解的Handler
在其中进行 Token 的判断,和访问方法的权限判断,看方法上是否有注解,有的话,
就和当前用户对比,成功就可以访问,失败就直接拒绝。
当时用的是SSM框架,所以才会看到有 response.sendRedirect(contextPath + “/login”);这样的。
public class LoginInterceptor extends HandlerInterceptorAdapter {private static final Logger log = LoggerFactory.getLogger(WebExceptionHandler.class);@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {String url = request.getRequestURI();
// log.info(request.getMethod()+" 请求URL:"+url);//从Token中解析User信息User user = TokenUtil.verifyToken(request);String contextPath = request.getContextPath();//user 为空则 表示 Token 不存在if (user != null) {if (user.getRole().equals("sadmin")) {//检查方法上 是否有注解的 Role.SADMIN 或者 Role.ADMIN 权限 , 没有则检查类上有没有 如果符合要求则放行if (HandlerUitl.checkAuthority(handler, new Role[]{Role.SADMIN, Role.ADMIN})) {request.setAttribute("user", user);return true;}}if (user.getRole().equals("admin")) {if (HandlerUitl.checkAuthority(handler, new Role[]{Role.ADMIN})) {request.setAttribute("user", user);return true;}else {response.sendRedirect(contextPath + "/login");}}if (user.getRole().equals("teacher")) {if (HandlerUitl.checkAuthority(handler, new Role[]{Role.TEACHER})) {return true;} else {response.sendRedirect(contextPath + "/login");}}if (user.getRole().equals("student")) {if (HandlerUitl.checkAuthority(handler, new Role[]{Role.STUDENT})) {return true;} else {response.sendRedirect(contextPath + "/student/login");}}}else {response.sendRedirect(contextPath + "/login");}return false;}
}
用于检查 方法 或者 类 是否需要权限
并和 拥有的权限做对比
如果方法上有 ,则以方法的 优先
public class HandlerUitl {public static boolean checkAuthority(Object handler, Role[] roles1){if (handler instanceof HandlerMethod) {HandlerMethod handlerMethod = (HandlerMethod) handler;// 获取方法上的注解Authority authority = handlerMethod.getMethod().getAnnotation(Authority.class);// 如果方法上的注解为空 则获取类的注解if (authority == null) {authority = handlerMethod.getMethod().getDeclaringClass().getAnnotation(Authority.class);}// 如果标记了注解,则判断权限if (authority != null) {Role[] roles = authority.roles();//如果 方法权限为 0 则通过if(roles.length==0){return true;}//判断 拥有的权限 是否 符合 方法所需权限for(int i = 0; i < roles.length; i++){for(int j = 0; j < roles1.length; j++){if(roles[i]==roles1[j]){
// System.out.println("可以访问");return true;}}}}return false;}return true;}}
6.2、自定义注解+AOP+Redis 防止重复提交
先简单说一下防止重复提交注解的逻辑:
在需要防止重复提交的接口的方法,加上注解。
发送请求写接口携带 Token
请求的路径+ Token 拼接程 key,value 值为生成的 UUID 码
然后 set Redis 分布式锁,能获取到就顺利提交(分布式锁默认 5 秒过期),不能获取就是重复提交了,报错。
定义注解
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface NoRepeatSubmit {/*** 设置请求锁定时间* @return*/int lockTime() default 5;
}定义处理注解的切面类
java复制代码import com.eshop.api.ApiResult;
import com.eshop.common.aop.NoRepeatSubmit;
import com.eshop.common.util.RedisLock;
import com.eshop.common.util.RequestUtils;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.util.Assert;import javax.servlet.http.HttpServletRequest;
import java.util.UUID;/*** 重复提交aop*/
@Aspect
@Component
@Slf4j
public class RepeatSubmitAspect {@Autowiredprivate RedisLock redisLock;@Pointcut("@annotation(noRepeatSubmit)")public void pointCut(NoRepeatSubmit noRepeatSubmit) {}@Around("pointCut(noRepeatSubmit)")public Object around(ProceedingJoinPoint pjp, NoRepeatSubmit noRepeatSubmit) throws Throwable {int lockSeconds = noRepeatSubmit.lockTime();HttpServletRequest request = RequestUtils.getRequest();Assert.notNull(request, "request can not null");String bearerToken = request.getHeader("Authorization");String[] tokens = bearerToken.split(" ");String token = tokens[1];String path = request.getServletPath();String key = getKey(token, path);String clientId = getClientId();boolean isSuccess = redisLock.tryLock(key, clientId, lockSeconds);log.info("tryLock key = [{}], clientId = [{}]", key, clientId);if (isSuccess) {log.info("tryLock success, key = [{}], clientId = [{}]", key, clientId);// 获取锁成功Object result;try {// 执行进程result = pjp.proceed();} finally {// 解锁redisLock.releaseLock(key, clientId);log.info("releaseLock success, key = [{}], clientId = [{}]", key, clientId);}return result;} else {// 获取锁失败,认为是重复提交的请求log.info("tryLock fail, key = [{}]", key);return ApiResult.fail("重复请求,请稍后再试");}}private String getKey(String token, String path) {return token + path;}private String getClientId() {return UUID.randomUUID().toString();}}
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.params.SetParams;import java.util.Collections;/*** Redis 分布式锁实现*/
@Service
public class RedisLock {private static final Long RELEASE_SUCCESS = 1L;private static final String LOCK_SUCCESS = "OK";private static final String SET_IF_NOT_EXIST = "NX";// 当前设置 过期时间单位, EX = seconds; PX = millisecondsprivate static final String SET_WITH_EXPIRE_TIME = "EX";// if get(key) == value return del(key)private static final String RELEASE_LOCK_SCRIPT = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";@Autowiredprivate StringRedisTemplate redisTemplate;/*** 该加锁方法仅针对单实例 Redis 可实现分布式加锁* 对于 Redis 集群则无法使用** 支持重复,线程安全** @param lockKey 加锁键* @param clientId 加锁客户端唯一标识(采用UUID)* @param seconds 锁过期时间* @return*/public boolean tryLock(String lockKey, String clientId, long seconds) {return redisTemplate.execute((RedisCallback<Boolean>) redisConnection -> {Jedis jedis = (Jedis) redisConnection.getNativeConnection();SetParams setParams = new SetParams();String result = jedis.set(lockKey, clientId, setParams.nx().px(seconds));if (LOCK_SUCCESS.equals(result)) {return true;}return false;});}/*** 与 tryLock 相对应,用作释放锁** @param lockKey* @param clientId* @return*/public boolean releaseLock(String lockKey, String clientId) {return redisTemplate.execute((RedisCallback<Boolean>) redisConnection -> {Jedis jedis = (Jedis) redisConnection.getNativeConnection();Object result = jedis.eval(RELEASE_LOCK_SCRIPT, Collections.singletonList(lockKey),Collections.singletonList(clientId));if (RELEASE_SUCCESS.equals(result)) {return true;}return false;});}
}
使用注解
/*** 添加收藏*/
@NoRepeatSubmit
@PostMapping("/collect/add")
@ApiOperation(value = "添加收藏",notes = "添加收藏")
public ApiResult<Boolean> collectAdd(@Validated @RequestBody StoreProductRelationQueryParam param){// 处理业务逻辑return ApiResult.ok();
}
本篇主要是针对Java运行时的注解的讲解及应用,(但是你想一想,我们使用lombok的注解时,它的实现原理又是什么样的呢?为什么可以帮我们自动生成代码呢?是谁给我们做了这件事情呢?)
二、AOP切面开发
结合@AspectJ 切入点指示符 + 自定义注解实现各种 面向切面+反射 的去冗余话的业务编程。
类似于 在任何时候都可以按照 切点+通知(增强的代码) === 面向切面(多个连接点合成的地方) 进行动态代理生成对应 方法执行的编程技巧。
2.1 为什么用AOP编程
为什么使用AOP编程范式?
(1)分离功能需求和非功能需求;
(2)集中处理某一关注点;
(3)侵入性少,增强代码可读性及可维护性。
下边记录AOP切面在springboot中的使用。
AOP的应用场景
权限控制、缓存控制、事务控制、分布式追踪、异常处理等
2.2 AOP的基础术语
(1)Target:目标类,要被代理的类,例如,UserService;
(2)JoinPoint(连接点):所谓的连接点,是指那些被拦截到的方法;
(3)PointCut(切入点):被增强的连接点(所谓的增强其实就是添加的新功能);
(4)Advice(通知、增强),增强代码;
(5)Weaving(织入):是指把增强的advice应用到目标对象target来创建新的代理对象proxy的 过程。
(6)proxy:代理类;
(7)Aspect(切面):是切入点pointcut和通知advice的结合。
2.3 常见的AOP五种通知
(1)前置通知(@Before):在我们执行目标方法之前运行
(2)后置通知(@After):在我们执行目标方法结束之后,不管有没有异常
@After(value="execution(* com.example.aspectJ.demo1.ProductDao.findAll(..))")
public void after(){System.out.println("最终通知==================");
}
(3)返回通知(@AfterReturning):在我们的目标方法正常返回值后运行
(4)异常通知(AfterThrowing):在我们的目标方法出现异常后运行
(5)环绕通知(@Around):动态代理,需要手动执行jionPoint.process(),其实就是执行我们的目标方法执行之前,相当于前置通知,执行之后就相当于我们的后置通知
2.4 实际运用+环绕通知
@Around 环绕通知(等于前置通知 + 返回通知 + 异常通知 + 后置通知)
环绕通知是通知类行中功能最强大的,它是JoinPoint的子接口,环绕通知需要携带 ProceedingJoinPoint 类型的参数。
环绕通知的几个重点:
(1)环绕通知类似于动态代理的全过程,ProceedingJoinPoint pjp 类型的参数可以决定是否执行目标方法(也就是说必须要手动调用 pjp.proceed() 方法,目标方法才能执行), 如果忘记这样做就会导致通知被执行了 , 但目标方法没有被执行 ,且让环绕通知必须要有返回值,返回值即目标方法的返回值。
(2)如果环绕通知没有返回值,会出现空指针异常的情况。
// ···· 此处省略引入包
@Aspect // 标注是一个切面
@Component
public class Aspact {// 定义一个切入点,关于切入点如何定义?@Pointcut("@annotation(com.lxc.springboot.annotation.MyAnnotation)")public void pointFn(){}@Around("pointFn()")public void aroundFn(ProceedingJoinPoint pjp) {String methodName = pjp.getSignature().getName();System.out.println("==========环绕通知执行了==========");Object result = null;try{// == 前置通知System.out.println("【目标方法】"+methodName);// 执行目标方法result = pjp.proceed();// == 结果通知System.out.println("目标方法返回结果为:"+result);}catch (Throwable e) {// == 异常通知System.out.println(e.getMessage());}// == 后置通知System.out.println("后置通知执行");}
}

除了环绕通知和异常通知,来看下 前置通知、结果通知和后置通知的执行顺序:前置–结果通知–后置通知。

AOP参考:
- AOP开发入门
- Spring AOP 所有切入点指示符详解(execution,within,this,target,args,@within,@target,@args,@annotation)
3.官方AOP英文文档
三、【事务踩坑8+12场景】Spring声明事务&编程事务(最好)
JDBC实现事务伪代码:
1//Get database connection
2Connection connection = DriverManager.getConnection();
3//Set autoCommit is false
4connection.setAutoCommit(false);
5//use sql to operate database
6.........
7//Commit or rollback
8connection.commit()/connection.rollback
9
10connection.close();
需要在各个业务代码中编写代码如commit()、close()来控制事务。
但是Spring不乐意这么干了,这样对业务代码侵入性太大了,所有就用一个事务注解@Transactional来控制事务,底层实现是基于切面编程AOP实现的,而Spring中实现AOP机制采用的是动态代理,具体分为JDK动态代理和CGLIB动态代理两种模式。

- Spring的bean的初始化过程中,发现方法有Transactional注解,就需要对相应的Bean进行代理,生成代理对象。
- 然后在方法调用的时候,会执行切面的逻辑,而这里切面的逻辑中就包含了开启事务、提交事务或者回滚事务等逻辑。
另外注意一点的是,Spring 本身不实现事务,底层还是依赖于数据库的事务。没有数据库事务的支持,Spring事务是不会生效的。
具体过程原理:
@Transactional实现原理三要素切面、切点、通知
-
InfrastructureAdvisorAutoProxyCreator后置处理器拦截所有Bean
-
遍历所有类型为Advisor的切面
-
返回满足切点条件的切面列表
-
选择代理方法
-
生成代理
-
调用通知的invoke()方法
- 开启事务
- 调用其它通知的invoke()方法,如果没有执行目标方法
- 执行异常,回滚事务
- 执行成功,提交事务
-
执行目标方法
了解@Transactional注解实现原理,不仅可以让我们对切面、切点、通知有一个清晰的认识,还可以让我们通过其思想实现类似功能,如@Cache注解实现应用缓存,@Async注解实现业务异步执行
接下来我们进入正题,看看哪些场景会导致Spring事务失败:

注意仅有图中红色的才生效如果不重写注解;@Transactional(rollbackFor=Exception.class),如果方法上加了这个注解,那么当这个方法抛出异常时(运行时和非运行时)就会回滚,数据库里面的数据也会回滚。如果不配置rollbackFor属性,那么事物只会在遇到运行时异常才会回滚。
默认情况下,SPRING只有在抛出的异常为运行时且为unchecked 异常才会回滚事务,也就是抛出的异常为RuntimeException 的子类(Errors也会)时才会回滚事务,而抛出 checked 异常则不会回滚事务 ,当然可以通过 @Transactional rollbackFor进行配置。
(6.1)checked异常: 一般是指程序不能直接控制的外界情况,是指在编译的时候就需要检查的一类异常,用户程序中必须采用try-catch机制处理或者通过throws交由调用者来处理。这类异常主要是指除了Error以及RuntimeException及其子类之外的异常。
(6.2)unchecked异常:一般是那些不在编译的时就要处理的一类异常。在JAVA体系里,所有的Error以及RuntimeException及其子类都是unchecked异常。
—
总结:
- 事务内部调用:@EnableAspectJAutoProxy(exposeProxy = true)在启动类中添加,会由Cglib代理实现。 或者:自身使用 proxy:
- ((ServiceA)AopContext.currentProxy()).doSave(user);
- 自己既想要保留事务tryCatch应对业务异常,又想要事务回滚使用:
- //假设这是一个service类的片段 try{ //出现异常 } catch (Exception e) { // 捕获异常,打印异常,或其他处理。但不抛出新的异常 e.printStackTrace(); // 手动回滚 TransactionAspectSupport.currentTransactionStatus().setRollbackOnly(); } //此时return语句能够执行 return xxx;
- 少用@Transactional注解
- 将查询(select)方法放到事务外
- 事务中避免远程调用
- 事务中避免一次性处理太多数据
- 非事务执行
- 我们可以看到add方法的访问权限被定义成了private,这样会导致事务失效,spring要求被代理方法必须是public的。说白了,在AbstractFallbackTransactionAttributeSource类的computeTransactionAttribute方法中有个判断,如果目标方法不是public,则TransactionAttribute返回null,即不支持事务。
- 【注意第一种和self. 自注入方法——是指在方法内部调用另一个事务失效!】外部经过spring容器调用service的方法事务才生效,service类内部方法间相互调用事务不生效,也就是传说中的自调用失效问题。主要原因是 Spring数据库事务的约定,其实现原理是AOP,而AOP的原理是动态代理,在自调用的过程中,是类自身的调用,而不是代理对象去调用,那么就不会产生AOP,这样 Spring就不能把你的代码织入到约定的流程中,于是就产生了现在看到的失败场景。
Transaction参考
1.Transactional 事务注解实现原理
2.你必须懂也可以懂的@Transactional原理
相关文章:
SpringAop详解
文章目录 一、Spring自定义注解1、什么是注解👨🏫2、注解的目的或作用💞3、JDK内置注解💫 【内置元注解 一共八个固定注解】4、元注解 🎯5、自定义注解📸5、Java反射API和类加载过程51、什么是反射基本原…...
对XYctf的一些总结
对XYctf的一些总结 WEB 1.http请求头字段 此次比赛中出现的: X-Forwarded-For/Client-ip:修改来源ip via:修改代理服务器 还有一些常见的字段: GET:此方法用于请求指定的资源。GET请求应该安全且幂等,…...
Visual Studio和Visual Studio Code适用于哪些编程语言
Visual Studio和Visual Studio Code都适用于多种编程语言,它们的适用编程语言如下: Visual Studio适用于: C#Visual Basic .NETF#CJavaScriptTypeScriptPythonHTML/CSSJava(通过插件支持) Visual Studio Code适用于…...
缓存菜品操作
一:问题说明 用户端小程序展示的菜品数据都是通过查询数据库获得,如果用户端访问量比较大,数据库访问压力随之增大。 二:实现思路 通过Redis来缓存菜品数据,减少数据库查询操作。 缓存逻辑分析: 每个分…...
达梦数据库常用命令整理
1.数据库自身信息 1.1 查询实例信息 SQL> select name inst_name from v$instance;行号 INST_NAME ---------- --------- 1 DMSERVER已用时间: 11.211(毫秒). 执行号:15.1.2 查询数据库当前状态 SQL> select status$ from v$instance;行号 STATUS$ -…...
Vue 组件的三大组成部分
Vue 组件通常由三大组成部分构成:模板(Template)、脚本(Script)、样式(Style) 模板部分是组件的 HTML 结构,它定义了组件的外观和布局。Vue 使用基于 HTML 的模板语法来声明组件的模…...
MoneyPrinter中的文字转声音国内替换方案
背景: 在进行MoneyPrinter项目国内环境搭建中,发现框架本身的TikTok文字转语音部分的代码已经不能用了,最好是能够找到国内网站的替换方案。 实现: 感谢网站:https://www.text-to-speech.cn/ 代码: # -*…...
消除试卷手写笔迹的软件免费的有哪些?这几款都不错
消除试卷手写笔迹的软件免费的有哪些?在数字化学习的浪潮中,学生们越来越频繁地利用电子设备来完成学习任务。然而,当纸质试卷需要被数字化并再次利用时,如何高效地消除手写笔迹便成为了一个有待解决的问题。那么,今天…...
智能创作时代:AI 如何重塑内容生成游戏规则
文章目录 前言一:自动化内容生成文章生成视频制作音频创作 二:内容分发与推广智能推荐系统社交媒体优化 三:内容分析与优化数据分析用户反馈质量控制 结语 前言 在数字化时代的浪潮中,内容生产与消费已成为信息传播的核心。随着人…...
大数据------JavaWeb------Tomcat(完整知识点汇总)
Web服务器——Tomcat Web服务器定义 它是一个应用程序(软件),对HTTP协议的操作进行封装,使得程序员不必直接对协议进行操作,让Web开发更便捷 Web服务器主要功能 封装HTTP协议操作,简化开发将Web项目部署到…...
LMDeploy笔记
随谈模型部署 模型部署包含的内容很多,来聊聊。 访存bottleneck 首先,基于transformer的计算是访存密集型任务。 so? 过去,我们表达模型的性能,通常会用ops,macs这些指标,也计算量来衡量模型的推理时间ÿ…...
Unity 状态机
文章目录 前言一、状态机二、应用1、场景切换2、人物行为切换3、宝箱、机关切换4、AI 三、人物行为总结 前言 提到Unity状态机,接触不久的开发者会想到Unity的动画状态机,而对于老油条来说,可能会回忆起自己实现的动画状态机。当然ÿ…...
一毛钱不到的FH8208C单节锂离子和锂聚合物电池一体保护芯片
前言 目前市场上电池保护板,多为分体方案,多数场合使用没有问题,部分场合对空间有进一步要求,或者你不想用那么多器件,想精简一些,那么这个芯片就很合适,对于充电电池来说,应在使用…...
python数据可视化:显示两个变量间的关系散点图scatterplot()
【小白从小学Python、C、Java】 【计算机等考500强证书考研】 【Python-数据分析】 python数据可视化: 显示两个变量间的关系 散点图 scatterplot() [太阳]选择题 请问关于以下代码表述错误的选项是? import seaborn as sns import matplotlib.pyplot …...
【QT教程】QT6硬件高级编程入门 QT硬件高级编程
QT6硬件高级编程入门 使用AI技术辅助生成 QT界面美化视频课程 QT性能优化视频课程 QT原理与源码分析视频课程 QT QML C扩展开发视频课程 免费QT视频课程 您可以看免费1000个QT技术视频 免费QT视频课程 QT统计图和QT数据可视化视频免费看 免费QT视频课程 QT性能优化视频免费看…...
Android 蓝牙实战——蓝牙电话通话状态同步(二十四)
前面分析了蓝牙电话通话状态的广播,我们可以在蓝牙电话中实时监听蓝牙电话的状态,但如果是其他音乐类 APP 呢,在播放的时候也需要知道当前是否有通话正在进行,但是有完全没必要实时监听电话的状态,这就需要一个获取通话状态的方法。 一、通话状态处理 1、CallsManager …...
docker 指定根目录 迁移根目录
docker 指定根目录 迁移根目录 1、问题描述2、问题分析3、解决方法3.1、启动docker程序前就手动指定docker根目录为一个大的分区(支持动态扩容),事前就根本上解决根目录空间不够问题3.1.0、方法思路3.1.1、docker官网安装文档3.1.2、下载docker安装包3.1.3、安装doc…...
React 项目报错解决办法收录
React 使用 引入文件报错 (react 别名配置craco) react ,vue 初始项目都是不支持 别名引入文件的。 vue 一般项目初始化的时候会 在 vue.config.js 文件中配置好,所以不需要我们自己配置react 初始化的时候是没有配置的, 需要我们自己配置 …...
Linux专题-Makefile(1)
1.Makefile中的注释使用 # 2. Makefile中的静默执行。 makefile中,默认情况下执行一行命令前会先把这一行命令打印出来,然后再执行这条命令。如果不想看到打印的命令,则可以使用静默执 行的功能,即仅打印出命令执行的结果。使用方…...
机器学习算法应用——CART决策树
CART决策树(4-2) CART(Classification and Regression Trees)决策树是一种常用的机器学习算法,它既可以用于分类问题,也可以用于回归问题。CART决策树的主要原理是通过递归地将数据集划分为两个子集来构建决…...
【Axure高保真原型】引导弹窗
今天和大家中分享引导弹窗的原型模板,载入页面后,会显示引导弹窗,适用于引导用户使用页面,点击完成后,会显示下一个引导弹窗,直至最后一个引导弹窗完成后进入首页。具体效果可以点击下方视频观看或打开下方…...
相机Camera日志实例分析之二:相机Camx【专业模式开启直方图拍照】单帧流程日志详解
【关注我,后续持续新增专题博文,谢谢!!!】 上一篇我们讲了: 这一篇我们开始讲: 目录 一、场景操作步骤 二、日志基础关键字分级如下 三、场景日志如下: 一、场景操作步骤 操作步…...
Debian系统简介
目录 Debian系统介绍 Debian版本介绍 Debian软件源介绍 软件包管理工具dpkg dpkg核心指令详解 安装软件包 卸载软件包 查询软件包状态 验证软件包完整性 手动处理依赖关系 dpkg vs apt Debian系统介绍 Debian 和 Ubuntu 都是基于 Debian内核 的 Linux 发行版ÿ…...
解决本地部署 SmolVLM2 大语言模型运行 flash-attn 报错
出现的问题 安装 flash-attn 会一直卡在 build 那一步或者运行报错 解决办法 是因为你安装的 flash-attn 版本没有对应上,所以报错,到 https://github.com/Dao-AILab/flash-attention/releases 下载对应版本,cu、torch、cp 的版本一定要对…...
大模型多显卡多服务器并行计算方法与实践指南
一、分布式训练概述 大规模语言模型的训练通常需要分布式计算技术,以解决单机资源不足的问题。分布式训练主要分为两种模式: 数据并行:将数据分片到不同设备,每个设备拥有完整的模型副本 模型并行:将模型分割到不同设备,每个设备处理部分模型计算 现代大模型训练通常结合…...
【开发技术】.Net使用FFmpeg视频特定帧上绘制内容
目录 一、目的 二、解决方案 2.1 什么是FFmpeg 2.2 FFmpeg主要功能 2.3 使用Xabe.FFmpeg调用FFmpeg功能 2.4 使用 FFmpeg 的 drawbox 滤镜来绘制 ROI 三、总结 一、目的 当前市场上有很多目标检测智能识别的相关算法,当前调用一个医疗行业的AI识别算法后返回…...
Maven 概述、安装、配置、仓库、私服详解
目录 1、Maven 概述 1.1 Maven 的定义 1.2 Maven 解决的问题 1.3 Maven 的核心特性与优势 2、Maven 安装 2.1 下载 Maven 2.2 安装配置 Maven 2.3 测试安装 2.4 修改 Maven 本地仓库的默认路径 3、Maven 配置 3.1 配置本地仓库 3.2 配置 JDK 3.3 IDEA 配置本地 Ma…...
重启Eureka集群中的节点,对已经注册的服务有什么影响
先看答案,如果正确地操作,重启Eureka集群中的节点,对已经注册的服务影响非常小,甚至可以做到无感知。 但如果操作不当,可能会引发短暂的服务发现问题。 下面我们从Eureka的核心工作原理来详细分析这个问题。 Eureka的…...
安卓基础(aar)
重新设置java21的环境,临时设置 $env:JAVA_HOME "D:\Android Studio\jbr" 查看当前环境变量 JAVA_HOME 的值 echo $env:JAVA_HOME 构建ARR文件 ./gradlew :private-lib:assembleRelease 目录是这样的: MyApp/ ├── app/ …...
嵌入式学习笔记DAY33(网络编程——TCP)
一、网络架构 C/S (client/server 客户端/服务器):由客户端和服务器端两个部分组成。客户端通常是用户使用的应用程序,负责提供用户界面和交互逻辑 ,接收用户输入,向服务器发送请求,并展示服务…...

