小白都能学会的Java注解与反射机制
前言
Java注解和反射是很基础的Java知识了,为何还要讲它呢?因为我在面试应聘者的过程中,发现不少面试者很少使用过注解和反射,甚至有人只能说出@Override
这一个注解。我建议大家还是尽量能在开发中使用注解和反射,有时候使用它们能让你事半功倍,简化代码提高编码的效率。很多优秀的框架都基本使用了注解和反射,在Spring AOP中,就把注解和反射用得淋漓尽致。
什么是注解
Java注解(Annotation)亦叫Java标注,是JDK5.0开始引入的一种注释机制。 注解可以用在类、接口,方法、变量、参数以及包等之上。注解可以设置存在于不同的生命周期中,例如SOURCE(源码中),CLASS(Class文件中,默认是此保留级别),RUNTIME(运行期中)。
注解以@注解名
的形式存在于代码中,Java中内置了一些注解,例如@Override
,当然我们也可以自定义注解。注解也可以有参数,例如@MyAnnotation(value = "陈皮")。
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {
}
那注解有什么作用呢?其一是作为一种辅助信息,可以对程序做出一些解释,例如@Override注解作用于方法上,表示此方法是重写了父类的方法。其二,注解可以被其他程序读取,例如编译器,例如编译器会对被@Override注解的方法检测判断方法名和参数等是否与父类相同,否则会编译报错;而且在运行期可以通过反射机制访问某些注解信息。
内置注解
Java中有10个内置注解,其中6个注解是作用在代码上的,4个注解是负责注解其他注解的(即元注解),元注解提供对其他注解的类型说明。
注解 | 作用 | 作用范围 |
---|---|---|
@Override | 检查该方法是否是重写方法。如果其继承的父类或者实现的接口中并没有该方法时,会报编译错误。 | 作用在代码上 |
@Deprecated | 标记表示过时的,不推荐使用。可以用于修饰方法,属性,类。如果使用被此注解修饰的方法,属性或类,会报编译警告。 | 作用在代码上 |
@SuppressWarnings | 告诉编译器忽略注解中声明的警告。 | 作用在代码上 |
@SafeVarargs | Java 7开始支持,忽略任何使用参数为泛型变量的方法或构造函数调用产生的警告。 | 作用在代码上 |
@FunctionalInterface | Java 8开始支持,标识一个匿名函数或函数式接口。 | 作用在代码上 |
@Repeatable | Java 8开始支持,标识某注解可以在同一个声明上使用多次。 | 作用在代码上 |
@Retention | 标识这个注解的保存级别,是只在代码中,还是编入class文件中,或者是在运行时可以通过反射访问。包含关系runtime>class>source。 | 作用在其他注解上,即元注解 |
@Documented | 标记这些注解是否包含在用户文档中javadoc。 | 作用在其他注解上,即元注解 |
@Target | 标记某个注解的使用范围,例如作用方法上,类上,属性上等等。如果注解未使用@Target,则注解可以用于任何元素上。 | 作用在其他注解上,即元注解 |
@Inherited | 说明子类可以继承父类中的此注解,但这不是真的继承,而是可以让子类Class对象使用getAnnotations()获取父类被@Inherited修饰的注解 | 作用在其他注解上,即元注解 |
自定义注解
使用@interface关键字自定义注解,其实底层就是定义了一个接口,而且自动继承java.lang.annotation.Annotation
接口。
我们自定义一个注解如下:
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface MyAnnotation {
String value();
}
我们使用命令javap反编译我们定义的MyAnnotation注解的class文件,结果显示如下。虽然注解隐式继承了Annotation接口,但是Java不允许我们显示通过extends关键字继承Annotation接口甚至其他接口,否则编译报错。
D:\>javap MyAnnotation.class
Compiled from "MyAnnotation.java"
public interface com.nobody.MyAnnotation extends java.lang.annotation.Annotation {
public abstract java.lang.String value();
}
注解的定义内容如下:
- 格式为public @interface 注解名 {定义内容}
- 内部的每一个方法实际是声明了一个参数,方法的名称就是参数的名称。
- 返回值类型就是参数的类型,而且返回值类型只能是基本类型(int,float,long,short,boolean,byte,double,char),Class,String,enum,Annotation以及上述类型的数组形式。
- 如果定义了参数,可通过default关键字声明参数的默认值,若不指定默认值,使用时就一定要显示赋值,而且不允许使用null值,一般会使用空字符串或者0。
- 如果只有一个参数,一般参数名为value,因为使用注解时,赋值可以不显示写出参数名,直接写参数值。
import java.lang.annotation.*;
/**
* @Description 自定义注解
* @Author Mr.nobody
* @Date 2021/3/30
* @Version 1.0
*/
@Target(ElementType.METHOD) // 此注解只能用在方法上。
@Retention(RetentionPolicy.RUNTIME) // 此注解保存在运行时期,可以通过反射访问。
@Inherited // 说明子类可以继承此类的此注解。
@Documented // 此注解包含在用户文档中。
public @interface CustomAnnotation {
String value(); // 使用时需要显示赋值
int id() default 0; // 有默认值,使用时可以不赋值
}
/**
* @Description 测试注解
* @Author Mr.nobody
* @Date 2021/3/30
* @Version 1.0
*/
public class TestAnnotation {
// @CustomAnnotation(value = "test") 只能注解在方法上,这里会报错
private String str = "Hello World!";
@CustomAnnotation(value = "test")
public static void main(String[] args) {
System.out.println(str);
}
}
Java8 注解
在这里讲解下Java8之后的几个注解和新特性,其中一个注解是@FunctionalInterface,它作用在接口上,标识是一个函数式接口,即只有有一个抽象方法,但是可以有默认方法。
@FunctionalInterface
public interface Callback<P,R> {
public R call(P param);
}
还有一个注解是@Repeatable,它允许在同一个位置使用多个相同的注解,而在Java8之前是不允许的。
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Repeatable(OperTypes.class)
public @interface OperType {
String[] value();
}
// 可以理解@OperTypes注解作为接收同一个类型上重复@OperType注解的容器
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface OperTypes {
OperType[] value();
}
@OperType("add")
@OperType("update")
public class MyClass {
}
注意,对于重复注解,不能再通过clz.getAnnotation(Class<A> annotationClass)方法来获取重复注解,Java8之后,提供了新的方法来获取重复注解,即clz.getAnnotationsByType(Class<A> annotationClass)方法。
package com.nobody;
import java.lang.annotation.Annotation;
/**
* @Description
* @Author Mr.nobody
* @Date 2021/3/31
* @Version 1.0
*/
@OperType("add")
@OperType("update")
public class MyClass {
public static void main(String[] args) {
Class<MyClass> clz = MyClass.class;
Annotation[] annotations = clz.getAnnotations();
for (Annotation annotation : annotations) {
System.out.println(annotation.toString());
}
OperType operType = clz.getAnnotation(OperType.class);
System.out.println(operType);
OperType[] operTypes = clz.getAnnotationsByType(OperType.class);
for (OperType type : operTypes) {
System.out.println(type.toString());
}
}
}
// 输出结果为
@com.nobody.OperTypes(value=[@com.nobody.OperType(value=[add]), @com.nobody.OperType(value=[update])])
null
@com.nobody.OperType(value=[add])
@com.nobody.OperType(value=[update])
在Java8中,ElementType枚举新增了两个枚举成员,分别为TYPE_PARAMETER和TYPE_USE,TYPE_PARAMETER标识注解可以作用于类型参数,TYPE_USE标识注解可以作用于标注任意类型(除了Class)。
Java反射机制
我们先了解下什么是静态语言和动态语言。动态语言是指在运行时可以改变其自身结构的语言。例如新的函数,对象,甚至代码可以被引进,已有的函数可以被删除或者结构上的一些变化。简单说即是在运行时代码可以根据某些条件改变自身结构。动态语言主要有C#,Object-C,JavaScript,PHP,Python等。静态语言是指运行时结构不可改变的语言,例如Java,C,C++等。
Java不是动态语言,但是它可以称为准动态语言,因为Java可以利用反射机制获得类似动态语言的特性,Java的动态性让它在编程时更加灵活。
反射机制允许程序在执行期借助于Reflection API取得任何类的内部信息,并能直接操作任意对象的内部属性以及方法等。类在被加载完之后,会在堆内存的方法区中生成一个Class类型的对象,一个类只有一个Class对象,这个对象包含了类的结构信息。我们可以通过这个对象看到类的结构。
比如我们可以通过Class clz = Class.forName("java.lang.String");
获得String类的Class对象。我们知道每个类都隐式继承Object类,Object类有个getClass()
方法也能获取Class对象。
Java反射机制提供的功能
- 在运行时判断任意一个对象所属的类
- 在运行时构造任意一个类的对象
- 在运行时判断任意一个类具有的成员变量和方法
- 在运行时获取泛型信息
- 在运行时调用任意一个对象的成员变量和方法
- 在运行时获取注解
- 生成动态代理
- ...
Java反射机制的优缺点
- 优点:实现动态创建对象和编译,有更加的灵活性。
- 缺点:对性能有影响。使用反射其实是一种解释操作,即告诉JVM我们想要做什么,然后它满足我们的要求,所以总是慢于直接执行相同的操作。
Java反射相关的主要API
- java.lang.Class:代表一个类
- java.lang.reflect.Method:代表类的方法
- java.lang.reflect.Field:代表类的成员变量
- java.lang.reflect.Constructor:代表类的构造器
我们知道在运行时通过反射可以准确获取到注解信息,其实以上类(Class,Method,Field,Constructor等)都直接或间接实现了AnnotatedElement接口,并实现了它定义的方法,AnnotatedElement接口的作用主要用于表示正在JVM中运行的程序中已使用注解的元素,通过该接口提供的方法可以获取到注解信息。
java.lang.Class 类
在Java反射中,最重要的是Class这个类了。Class本身也是一个类。当程序想要使用某个类时,如果此类还未被加载到内存中,首先会将类的class文件字节码加载到内存中,并将这些静态数据转换为方法区的运行时数据结构,然后生成一个Class类型的对象(Class对象只能由系统创建),一个类只有一个Class对象,这个对象包含了类的结构信息。我们可以通过这个对象看到类的结构。每个类的实例都会记得自己是由哪个Class实例所生成的。
通过Class对象可以知道某个类的属性,方法,构造器,注解,以及实现了哪些接口等信息。注意,只有class,interface,enum,annotation,primitive type,void,[] 等才有Class对象。
package com.nobody;
import java.lang.annotation.ElementType;
import java.util.Map;
public class TestClass {
public static void main(String[] args) {
// 类
Class<MyClass> myClassClass = MyClass.class;
// 接口
Class<Map> mapClass = Map.class;
// 枚举
Class<ElementType> elementTypeClass = ElementType.class;
// 注解
Class<Override> overrideClass = Override.class;
// 原生类型
Class<Integer> integerClass = Integer.class;
// 空类型
Class<Void> voidClass = void.class;
// 一维数组
Class<String[]> aClass = String[].class;
// 二维数组
Class<String[][]> aClass1 = String[][].class;
// Class类也有Class对象
Class<Class> classClass = Class.class;
System.out.println(myClassClass);
System.out.println(mapClass);
System.out.println(elementTypeClass);
System.out.println(overrideClass);
System.out.println(integerClass);
System.out.println(voidClass);
System.out.println(aClass);
System.out.println(aClass1);
System.out.println(classClass);
}
}
// 输出结果
class com.nobody.MyClass
interface java.util.Map
class java.lang.annotation.ElementType
interface java.lang.Override
class java.lang.Integer
void
class [Ljava.lang.String;
class [[Ljava.lang.String;
class java.lang.Class
获取Class对象的方法
- 如果知道具体的类,可通过类的class属性获取,这种方法最安全可靠并且性能最高。
Class clz = User.class;
- 通过类的实例的getClass()方法获取。
Class clz = user.getClass();
- 如果知道一个类的全限定类名,并且在类路径下,可通过Class.forName()方法获取,但是可能会抛出ClassNotFoundException。
Class clz = Class.forName("com.nobody.User");
- 内置的基本数据类型可以直接通过类名.Type获取。
Class<Integer> clz = Integer.TYPE;
- 通过类加载器ClassLoader获取
Class类的常用方法
- public static Class<?> forName(String className):创建一个指定全限定类名的Class对象
- public T newInstance():调用Class对象所代表的类的无参构造方法,创建一个实例
- public String getName():返回Class对象所代表的类的全限定名称。
- public String getSimpleName():返回Class对象所代表的类的简单名称。
- public native Class<? super T> getSuperclass():返回Class对象所代表的类的父类的Class对象,这是一个本地方法
- public Class<?>[] getInterfaces():返回Class对象的接口
- public Field[] getFields():返回Class对象所代表的实体的public属性Field对象数组
- public Field[] getDeclaredFields():返回Class对象所代表的实体的所有属性Field对象数组
- public Field getDeclaredField(String name):获取指定属性名的Field对象
- public Method[] getDeclaredMethods():返回Class对象所代表的实体的所有Method对象数组
- public Method getDeclaredMethod(String name, Class<?>... parameterTypes):返回指定名称和参数类型的Method对象
- myClassClass.getDeclaredConstructors();:返回所有Constructor对象的数组
- public ClassLoader getClassLoader():返回当前类的类加载器
在反射中经常会使用到Method的invoke方法,即public Object invoke(Object obj, Object... args)
,我们简单说明下:
- 第一个Object对应原方法的返回值,若原方法没有返回值,则返回null。
- 第二个Object对象对应调用方法的实例,若原方法为静态方法,则参数obj可为null。
- 第二个Object对应若原方法形参列表,若参数为空,则参数args为null。
- 若原方法声明为private修饰,则调用invoke方法前,需要显示调用方法对象的method.setAccessible(true)方法,才可访问private方法。
反射操作泛型
泛型是JDK 1.5的一项新特性,它的本质是参数化类型(Parameterized Type)的应用,也就是说所操作的数据类型被指定为一个参数,在用到的时候再指定具体的类型。这种参数类型可以用在类、接口和方法的创建中,分别称为泛型类、泛型接口和泛型方法。
在Java中,采用泛型擦除的机制来引入泛型,泛型能编译器使用javac时确保数据的安全性和免去强制类型转换问题,泛型提供了编译时类型安全检测机制,该机制允许程序员在编译时检测到非法的类型。并且一旦编译完成,所有和泛型有关的类型会被全部擦除。
Java新增了ParameterizedType
,GenericArrayType
,TypeVariable
和WildcardType
等几种类型,能让我们通过反射操作这些类型。
- ParameterizedType:表示一种参数化类型,比如Collection<String>
- GenericArrayType:表示种元素类型是参数化类型或者类型变量的数组类型
- TypeVariable:是各种类型变量的公共父接口
- WildcardType:代表种通配符类型表达式
package com.nobody;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.Map;
public class TestReflectGenerics {
public Map<String, Person> test(Map<String, Integer> map, Person person) {
return null;
}
public static void main(String[] args) throws NoSuchMethodException {
// 获取test方法对象
Method test = TestReflectGenerics.class.getDeclaredMethod("test", Map.class, Person.class);
// 获取方法test的参数类型
Type[] genericParameterTypes = test.getGenericParameterTypes();
for (Type genericParameterType : genericParameterTypes) {
System.out.println("方法参数类型:" + genericParameterType);
// 如果参数类型等于参数化类型
if (genericParameterType instanceof ParameterizedType) {
// 获得真实参数类型
Type[] actualTypeArguments =
((ParameterizedType) genericParameterType).getActualTypeArguments();
for (Type actualTypeArgument : actualTypeArguments) {
System.out.println(" " + actualTypeArgument);
}
}
}
// 获取方法test的返回值类型
Type genericReturnType = test.getGenericReturnType();
System.out.println("返回值类型:" + genericReturnType);
// 如果参数类型等于参数化类型
if (genericReturnType instanceof ParameterizedType) {
// 获得真实参数类型
Type[] actualTypeArguments =
((ParameterizedType) genericReturnType).getActualTypeArguments();
for (Type actualTypeArgument : actualTypeArguments) {
System.out.println(" " + actualTypeArgument);
}
}
}
}
class Person {}
// 输出结果
方法参数类型:java.util.Map<java.lang.String, java.lang.Integer>
class java.lang.String
class java.lang.Integer
方法参数类型:class com.nobody.Person
返回值类型:java.util.Map<java.lang.String, com.nobody.Person>
class java.lang.String
class com.nobody.Person
反射操作注解
在Java运行时,通过反射获取代码中的注解是比较常用的手段了,获取到了注解之后,就能知道注解的所有信息了,然后根据信息进行相应的操作。下面通过一个例子,获取类和属性的注解,解析映射为数据库中的表信息。
package com.nobody;
import java.lang.annotation.*;
public class AnalysisAnnotation {
public static void main(String[] args) throws Exception {
Class<?> aClass = Class.forName("com.nobody.Book");
// 获取类的指定注解,并且获取注解的值
Table annotation = aClass.getAnnotation(Table.class);
String value = annotation.value();
System.out.println("Book类映射的数据库表名:" + value);
java.lang.reflect.Field bookName = aClass.getDeclaredField("bookName");
TableField annotation1 = bookName.getAnnotation(TableField.class);
System.out.println("bookName属性映射的数据库字段属性 - 列名:" + annotation1.colName() + ",类型:"
+ annotation1.type() + ",长度:" + annotation1.length());
java.lang.reflect.Field price = aClass.getDeclaredField("price");
TableField annotation2 = price.getAnnotation(TableField.class);
System.out.println("price属性映射的数据库字段属性 - 列名:" + annotation2.colName() + ",类型:"
+ annotation2.type() + ",长度:" + annotation2.length());
}
}
// 作用于类的注解,用于解析表数据
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@interface Table {
// 表名
String value();
}
// 作用于字段,用于解析表列
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@interface TableField {
// 列名
String colName();
// 列类型
String type();
// 长度
int length();
}
@Table("t_book")
class Book {
@TableField(colName = "name", type = "varchar", length = 15)
String bookName;
@TableField(colName = "price", type = "int", length = 10)
int price;
}
// 输出结果
Book类映射的数据库表名:t_book
bookName属性映射的数据库字段属性 - 列名:name,类型:varchar,长度:15
price属性映射的数据库字段属性 - 列名:price,类型:int,长度:10
性能分析
前面我们说过,反射对性能有一定影响。因为反射是一种解释操作,它总是慢于直接执行相同的操作。而且Method,Field,Constructor都有setAccessible()方法,它的作用是开启或禁用访问安全检查。如果我们程序代码中用到了反射,而且此代码被频繁调用,为了提高反射效率,则最好禁用访问安全检查,即设置为true。
package com.nobody;
import java.lang.reflect.Method;
public class TestReflectSpeed {
// 10亿次
private static int times = 1000000000;
public static void main(String[] args) throws Exception {
test01();
test02();
test03();
}
public static void test01() {
Teacher t = new Teacher();
long start = System.currentTimeMillis();
for (int i = 0; i < times; i++) {
t.getName();
}
long end = System.currentTimeMillis();
System.out.println("普通方式执行10亿次消耗:" + (end - start) + "ms");
}
public static void test02() throws Exception {
Teacher teacher = new Teacher();
Class<?> aClass = Class.forName("com.nobody.Teacher");
Method getName = aClass.getDeclaredMethod("getName");
long start = System.currentTimeMillis();
for (int i = 0; i < times; i++) {
getName.invoke(teacher);
}
long end = System.currentTimeMillis();
System.out.println("反射方式执行10亿次消耗:" + (end - start) + "ms");
}
public static void test03() throws Exception {
Teacher teacher = new Teacher();
Class<?> aClass = Class.forName("com.nobody.Teacher");
Method getName = aClass.getDeclaredMethod("getName");
getName.setAccessible(true);
long start = System.currentTimeMillis();
for (int i = 0; i < times; i++) {
getName.invoke(teacher);
}
long end = System.currentTimeMillis();
System.out.println("关闭安全检查反射方式执行10亿次消耗:" + (end - start) + "ms");
}
}
class Teacher {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
//输出结果
普通方式执行10亿次消耗:13ms
反射方式执行10亿次消耗:20141ms
关闭安全检查反射方式执行10亿次消耗:8233ms
通过实验可知,反射比直接执行相同的方法慢了很多,特别是当反射的操作被频繁调用时效果更明显,当然通过关闭安全检查可以提高一些速度。所以,放射也不应该泛滥成灾的,而是适度使用才能发挥最大作用。
小白都能学会的Java注解与反射机制的更多相关文章
- Java注解和反射笔记
Java注解和反射笔记 1 注解 1.1 定义 Annotation是从JDK1.5开始引入的技术 作用 不是程序本身,可以对程序作出解释 可以被其他程序(编译器等)读取 格式 @注释名,可以添加一些 ...
- Java注解与反射
概要 本文主要是总结Java注解与反射的相关知识,加深自己对Java类动态语言的理解,同时为日后学习Spring打下基础. 注解: 什么是注解 Annotation的作用 不是程序本身,但是可以对程序 ...
- 浅说Java中的反射机制(二)
写过一篇Java中的反射机制,不算是写,应该是抄了,因为那是别人写的,这一篇也是别人写的,摘抄如下: 引自于Java基础--反射机制的知识点梳理,作者醉眼识朦胧.(()为我手记) 什么是反射? 正常编 ...
- java中的反射机制在Android开发中的用处
JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法:对于任意一个对象,都能够调用它的任意一个方法和属性:这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反 ...
- 黑马程序员——【Java高新技术】——反射机制
---------- android培训.java培训.期待与您交流! ---------- 一.概述 1.Java反射机制:是指“在运行状态中”,对于任意一个类,都能够知道这个类中的所有属性和方法: ...
- Java 中的反射机制
JAVA反射机制 JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法:对于任意一个对象,都能够调用它的任意一个方法:这种动态获取的信息以及动态调用对象的方法的功能称为ja ...
- JAVA类型信息——反射机制
JAVA类型信息——反射机制 一.反射机制概述 1.反射机制:就是java语言在运行时拥有的一项自我观察的能力,java通过这种能力彻底了解程序自身的情况,并为下一步的动作做准备. 2.反射机制的功能 ...
- 浅说Java中的反射机制(一)
在学习传智播客李勇老师的JDBC系列时,会出现反射的概念,由于又是第一次见,不免感到陌生.所以再次在博客园找到一篇文章,先记录如下: 引用自java中的反射机制,作者bingoideas.(()为我手 ...
- java学习之反射机制
java语言区别于C,C++等准静态语言的最大特点就是java的反射机制.静态语言的最直接定义就是不能在运行时改变程序结构或变量的类型.按照这样的定义,python,ruby是动态语言,C,C++,J ...
随机推荐
- TypeScript 如何编写类库声明文件 .d.ts
TypeScript 如何编写类库声明文件 .d.ts how to write a d.ts file declaration-files/ https://www.typescriptlang.o ...
- web performance optimise & css
web performance optimise & css 俄罗斯套娃 clients hints https://cloudinary.com/blog/automatic_respons ...
- 人物传记JULLIAN MURPHY:投资哪家强,区块链必然>股票+房地产
今年上半年在金融股市出现巨大波动的时候,星盟的项目审核经理JULLIAN MURPHY发现了一个有趣的现象:各种熔断和暴跌的背后,特斯拉的股票却从去年年末开始至今已经暴涨了12倍,即便中途有所回落,但 ...
- 下一代币王花落谁家?是否是BGV更胜一筹呢?
BGV作为Baccarat的平台币横空出世,通过BGV来激励拥有NGK的用户.在共识算法上,Baccarat 在共识层采用了混合机制 DPOSS共识机制,这是维护 Baccarat 生态体系良性发展的 ...
- Masterboxan INC 下半年将聚焦超高净值和家族全权委托客户
"投资是一个没有终点的奋斗.我们不能简单的预测市场,而是应对市场做出正确的反应.这需要我们不断反思.总结.提升,找到自己的投资哲学,然后用一生的时间去坚守."Masterboxan ...
- Python算法_整数反转(02)
给出一个 32 位的有符号整数,你需要将这个整数中每位上的数字进行反转. 示例 1: 输入: 123输出: 321 示例 2: 输入: -123输出: -321 示例 3: 输入: 120 输出: 2 ...
- Github Packages和Github Actions实践之CI/CD
概述 Github在被微软收购后,不忘初心,且更大力度的造福开发者们,推出了免费私有仓库等大更新.近期又开放了packages和actions两个大招,经笔者试用后感觉这两个功能配合起来简直无敌. G ...
- JQuery:JQuery基本语法,JQuery选择器,JQuery DOM,综合案例 复选框,综合案例 随机图片
知识点梳理 课堂讲义 1.JQuery快速入门 1.1.JQuery介绍 jQuery 是一个 JavaScript 库. 框架:Mybatis (jar包) 大工具 插件:PageHelper (j ...
- canal数据同步 客户端代码实现
1.引入相关依赖 <dependencies> <dependency> <groupId>org.springframework.boot</groupId ...
- 死磕Spring之IoC篇 - BeanDefinition 的解析阶段(XML 文件)
该系列文章是本人在学习 Spring 的过程中总结下来的,里面涉及到相关源码,可能对读者不太友好,请结合我的源码注释 Spring 源码分析 GitHub 地址 进行阅读 Spring 版本:5.1. ...