1.注解简介
1.1.什么是注解
- Annotation是从JDK5.0开始引入的新技术
- Annotation的作用
- 不是程序本身,可以对程序做出解释(这一点和注释(comment)没有什么区别)
- 可以被其他程序(比如: 编译器等) 读取
- Annotation的格式
- 注解是以“@注释名”在代码中存在的,还可以添加一些参数值,例如: @SuppressWarnings(value=“unchecked”)
- Annotation在哪里使用?
- 可以附加在package,class,method,field等上面,相当于给他们添加了额外的辅助信息,我们可以通过反射机制编程实现对这些元数据的访问
1.2.内置注解
- @Override:定义在java.lang.Override中,此注释只适用于修饰方法,表示一个方法打算重写超类中的另一个方法声明
- @Deprecated: 定义在java.lang.Deprecated中,此注解用于修饰方法,属性,类,表示不鼓励程序员使用这样的元素,通常是因为它很危险或者存在更好的选择
- @SuppressWarnings:定义在java.lang.SuppressWarnings中,用来抑制编译时的警告信息
- 与前两个注解有所不同,需要添加一个参数才能正确使用,这些参数都是已经定义好了的,我们选择性的使用就好了
- @SuppressWarnings(“all”)
- @SuppressWarnings(“unchecked”)
- @SuppressWarnings(value={“unchecked”,“deprecation”})
- 等等
1.3.元注解
- 元注解的作用就是负责注解其他注解,Java定义了4个标准的meta-annotation类型,他们被用来提供对其他annotation类型作说明
- 这些类型和它们所支持的类在java.lang.annotation包中可以找到(@Target,@Retention,@Documented,@Inherited)
- @Target:用于描述注解的使用范围(即:被描述的注解可以用在什么地方)
- @Retention:表示需要在什么级别保存注释信息,用于描述注解的生命周期(Source<class<Runtime)
- @Document: 说明该注解将被包含在javaDoc中
- @Inherited:说明子类可以继承父类中的该注解
@Retention:定义注解的保留策略 @Retention(RetentionPolicy.SOURCE) //注解仅存在于源码中,在class字节码文件中不包含 @Retention(RetentionPolicy.CLASS) //默认的保留策略,注解会在class字节码文件中存在,但运行时无法获得, @Retention(RetentionPolicy.RUNTIME) //注解会在class字节码文件中存在,在运行时可以通过反射获取到 @Target:指定被修饰的Annotation可以放置的位置(被修饰的目标) @Target(ElementType.TYPE) //接口、类 @Target(ElementType.FIELD) //属性 @Target(ElementType.METHOD) //方法 @Target(ElementType.PARAMETER) //方法参数 @Target(ElementType.CONSTRUCTOR) //构造函数 @Target(ElementType.LOCAL_VARIABLE) //局部变量 @Target(ElementType.ANNOTATION_TYPE) //注解 @Target(ElementType.PACKAGE) //包 注:可以指定多个位置,例如: @Target({ElementType.METHOD, ElementType.TYPE}),也就是此注解可以在方法和类上面使用 @Inherited:指定被修饰的Annotation将具有继承性 @Documented:指定被修饰的该Annotation可以被javadoc工具提取成文档.
1.4.自定义注解
- 使用@interface自定义注解时,自动继承了java.lang.Annotation接口
- 分析
- @interface用来声明一个注解,格式:public @interface 注解名{定义内容}
- 其中的每一个方法实际上是声明了一个配置参数
- 方法的名称就是参数的名称
- 返回值的类型就是参数的类型(返回值只能是基本类型,Class,String ,enum)
- 可以通过default来声明参数的默认值
- 如果只有一个参数成员,一般参数名为value
- 注解元素必须要有值,我们定义注解元素时,经常使用空字符串,0作为默认值
2.反射机制(java.Reflection)
2.1、静态语言 VS 动态语言
动态语言
- 是一类在运行时可以改变其结构的原因:例如新的函数、对象、甚至代码可以被引进,已有的函数可以被删除或是其他结构上的变化。通俗点说就是在运行时代码可以根据某些条件改变自身结构
- 主要动态语言:Object-c、C#、JavaScript、php、Python等
静态语言
- 与动态语言相对应的,运行时结构不可变的语言就是静态语言。如Java、C、C++。
- Java不是动态语言,但Java可以称之为"准动态语言"。即Java有一定的动态性,我们可以利用反射机制获得类似语言的特性。Java的动态性让编程的时候更加灵活!
2.2.Java Reflection
- Reflection (反射) 是Java被视为动态语言的关键,反射机制允许程序在执行期借助与Reflection API取得任何动态类的内部信息,并能直接操作任意对象的内部属性及方法。
Class c = Class.forName(“java.lang.String”) - 加载完类之后,在堆内存的方法区中就产生了一个Class类型的对象(一个类只有一个Class对象),这个对象就包含了完整的类结构信息。我们可以通过这个对象看到类的机构。这个对象就像一面镜子,透过这个镜子看到类的结构,所以我们形象的称之为:反射
2.3.Java反射机制研究及应用
Java反射机制提供的功能
- 在运行时判断任意一个对象所属的类
- 在运行时构造任意一个类的对象
- 在运行时判断任意一个类所具有的成员变量和方法
- 在运行时获取泛型信息
- 在运行时调用任意一个对象的成员变量和方法
- 在运行时处理注解
- 生成动态代理
2.4.Java 反射优点和缺点
优点:可以实现动态创建对象和编译,体现出很大的灵活性
缺点:对性能有影响。使用反射基本上是一种解释操作,我们可以告诉JVM,我们希望做什么并且它满足我们的要求。这类操作总是慢于直接执行相同的操作
2.5.反射的主要API
- java.lang.Class: 代表一个类
- java.lang.reflect.Method: 代表类的方法
- java.lang.reflect.Field:代表类的成员变量
- java.lang.reflect.Constructor: 代表类的构造器
2.6.Class类
在Object类中定义了以下方法,此方法将被所有子类继承
public final Class getClass()
以上的方法返回值的类型是一个Class类,此类是Java反射的源头,实际上所谓反射从程序的运行结果来看也很好理解,即:可以通过对象反射求出类的名称
对象照镜子后可以得到的信息:某个类的属性、方法和构造器、某个类到底实现了哪些接口。对于每个类而言,JRE都为其保留一个不变的Class对象。一个Class对象包含了特点某个结构(class/interface/enum/annotation/primitive type/void/[])的有关信息。
- Class本身也是一个类
- Class对象只能由系统建立对象
- 一个加载的类在JVM中只会有一个Class实例
- 一个Class对象对于的是一个加载到JVM中的一个.class文件
- 每个类的实例都会记得自己是由那个Class实例所生成的
- 通过Class可以完整地得到一个类中的所有被加载的结构
- Class类是Reflection的根源,针对任何你想动态加载、运行的类,唯有先获得相应的Class对象
方法名 | 功能说明 |
static ClassforName(String name) | 返回指定类名name的Class对象 |
Object newInstance() | 调用缺省构造函数,返回Class对象的一个实例 |
getName() | 返回次Class对象所表示的实体(类,接口,数组类或void)的名称。 |
Class getSuperClass() | 返回当前Class对象的父类的Class对象 |
Class[] getinterfaces() | 返回当前Class对象的接口 |
ClassLoader getClassLoader() | 返回该类加载器 |
Constructor[] getConstructores() | 返回一个包含某些Constructor对象的数组 |
Method getMethod(String name,Class… T) | 返回一个Method对象,次对象的形参类型为paramType |
Field[] getDeclaredFields() | 返回Field对象的一个数组 |
2.6.1.获取Class类的实例
- 若已知具体的类,通过类的class属性获取,该方法最为安全可靠,程序性能最高。
Class clazz = Person.class; - 已知某个类的实例,调用该实例的getClass()方法获取Class对象
Class clazz = Person.getClass(); - 已知一个类的全类名,且该类在类路径下,可通过Class类的静态方法forName()获取,可能抛出ClassNotFoundException
Class clazz = Class.forName(“demo01.Student”); - 内置基本数据类型可以直接用类名.Type
- 还可以利用ClassLoader
2.2.2.哪些类型可以有Class对象
- class: 外部类,成员(成员内部类,静态内部类),局部内部类,匿名内部类
- interface: 接口
- [] 数组
- enum: 枚举
- annotation: 注解@interface
- primitive type : 基本数据类型
- void
2.7.了解类的加载过程
2.7.1.类的加载与ClassLoader的理解
- 加载:将class文件字节码内容加载到内存中,并将这些静态数据转换成方法区的运行时数据结构,然后生成一个代表这个类的java.lang.Class对象
- 链接:将Java类的二进制代码合并到JVM的运行状态之中的过程。
- 验证:确保加载的类信息符合JVM规范,没有安全方面的问题
- 准备:正式为类变量(static)分配内存并设置类变量默认初始值的阶段,这些内存都将在方法区中进行分配。
- 解析:虚拟机常量池内的符号引用(常量名)替换为直接引用(地址)的过程。
- 初始化:
- 执行类构造器()方法的过程。类构造器()方法是由编译器自动收集类中所有类变量的赋值动作和静态代码块中的语句合并产生的。(类构造器是构造类信息的,不是构造该类对象的构造器)
- 当初始化一个类的时候,如果发现其父类还没有进行初始化,则需要先触发其父类的初始化。
- 虚拟机会保证一个类的()方法在多线程环境中被正确加锁和同步。
2.7.2.什么时候会发生类初始化?
- 类的主动引用(一定会发生类的初始化)
- 当虚拟机启动,先初始化main方法所在的类
- new 一个类的对象
- 调用类的静态成员(除了final常量)和静态方法
- 使用java.lang.reflect包的方法对类进行反射调用
- 当初始化一个类,如果其父类没有被初始化,则先会初始化它的父类
- 类的被动引用(不会发生类的初始化)
- 当访问一个静态域时,只有真正声明这个域的类才会被初始化。如:当通过子类引用父类的静态变量,不会导致子类初始化
- 通过数组定义类引用,不会触发此类的初始化
- 引用常量不会触发此类的初始化(常量在链接阶段就存入调用类的常量池中了)
2.7.3.类加载器的作业
- 类加载器的作用:将class文件字节码内容记载到内存中,并将这些静态数据转换成方法区的运行时数据结构,然后再堆中生成一个代表这个类的java.lang.Class对象,作为方法区中类数据的访问入口。
- 类缓存:标准的JavaSE类加载器可以按要求查找类,但一旦某个类被加载到加载器中,它将维持加载(缓存)一段时间。不过JVM垃圾回收机制可以回收这些Class对象
2.2.4.类加载器的作用
类加载器作用是用来把类(class)装载进内存的。JVM规范定义了如下类型的类的加载器
2.8.创建运行时类的对象
2.8.1.获取运行时类的完整结构
通过反射获取运行时类的完整结构
Field、Method、Constructor、Superclass、Interface、Annotation
- 实现的全部接口
- 所继承的父类
- 全部的构造器
- 全部的方法
- 全部的Field
- 注解
2.9.小结
- 在实际的操作中,取得类的信息的操作代码,并不会经常开发
- 一定要属性java.lang.reflect包的作用,反射机制
- 如何取得属性、方法、构造器的名称、修饰符等
3.编写自定义注解
3.1.创建SpringBoot项目
PS: 后面还要使用到, 所以这里创建一个空的项目,通过创建模块的方式完成
3.1.1.点击New Project
3.1.2.创建新项目
3.1.3.创建一个新的Module
3.1.4.创建SpringBoot模块
3.1.5.项目创建完成
3.2.修改pom.xml
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.3.7.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent> <groupId>com.zjzaki</groupId> <artifactId>spboot01</artifactId> <version>0.0.1-SNAPSHOT</version> <name>spboot01</name> <description>spboot01</description> <properties> <java.version>1.8</java.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <!-- <scope>test</scope>--> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project>
3.3.案例一
3.3.1.新建一个annotation包
3.3.2.添加TranscationModel枚举类
package com.zjzaki.spboot01.annotation.demo01; public enum TranscationModel { Read, Write, ReadWrite }
3.3.3.新建MyAnnotation1注解
package com.zjzaki.spboot01.annotation.demo01; import java.lang.annotation.*; /** * MyAnnotation1注解可以用在类、接口、属性、方法上 * 注解运行期也保留 * 不可继承 */ @Target({ElementType.TYPE, ElementType.FIELD,ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface MyAnnotation1 { String name(); }
3.3.4.新建MyAnnotation2注解
package com.zjzaki.spboot01.annotation.demo01; import java.lang.annotation.*; /** * MyAnnotation2注解可以用在方法上 * 注解运行期也保留 * 不可继承 */ @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface MyAnnotation2 { TranscationModel model() default TranscationModel.ReadWrite; }
3.3.5.新建MyAnnotation3注解
package com.zjzaki.spboot01.annotation.demo01; import java.lang.annotation.*; /** * MyAnnotation3注解可以用在方法上 * 注解运行期也保留 * 可继承 */ @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) @Inherited @Documented public @interface MyAnnotation3 { TranscationModel[] models() default TranscationModel.ReadWrite; }
3.3.6.新建Demo类
package com.zjzaki.spboot01.annotation.demo01; /** * 获取类与方法上的注解值 */ @MyAnnotation1(name = "abc") public class Demo1 { @MyAnnotation1(name = "xyz") private Integer age; @MyAnnotation2(model = TranscationModel.Read) public void list() { System.out.println("list"); } @MyAnnotation3(models = {TranscationModel.Read, TranscationModel.Write}) public void edit() { System.out.println("edit"); } }
3.3.7.新建DemoTest类
package com.zjzaki.spboot01.annotation.demo01; import org.junit.Test; /** * @Author zjzaki * @Package com.zjzaki.spboot01.annotation.demo01 * @Date 2023/8/4 20:55 */ public class Demo1Test { @Test public void list() throws Exception { //获取类上的注解 MyAnnotation1 annotation1 = Demo1.class.getAnnotation(MyAnnotation1.class); System.out.println(annotation1.name());//abc // 获取方法上的注解 MyAnnotation2 myAnnotation2 = Demo1.class.getMethod("list").getAnnotation(MyAnnotation2.class); System.out.println(myAnnotation2.model());//Read // 获取属性上的注解 MyAnnotation1 myAnnotation1 = Demo1.class.getDeclaredField("age").getAnnotation(MyAnnotation1.class); System.out.println(myAnnotation1.name());// xyz } @Test public void edit() throws Exception { MyAnnotation3 myAnnotation3 = Demo1.class.getMethod("edit").getAnnotation(MyAnnotation3.class); for (TranscationModel model : myAnnotation3.models()) { System.out.println(model);//Read,Write } } }
3.7.8.运行结果
3.4. 案例二(获取类属性上的注解属性值)
3.4.1.新建demo02包
3.4.2.新建TestAnnotation注解
package com.zjzaki.spboot01.annotation.demo02; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** * @Author zjzaki * @Package com.zjzaki.spboot01.annotation.demo02 * @Date 2023/8/4 20:58 */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.FIELD) public @interface TestAnnotation { String value() default "默认value值"; String what() default "这里是默认的what属性对应的值"; }
3.4.3.新建Demo2类
package com.zjzaki.spboot01.annotation.demo02; /** * @Author zjzaki * @Package com.zjzaki.spboot01.annotation.demo02 * @Date 2023-08-04 21:07:08 */ public class Demo2 { @TestAnnotation(value = "这就是value对应的值_msg1", what = "这就是what对应的值_msg1") private static String msg1; @TestAnnotation("这就是value对应的值1") private static String msg2; @TestAnnotation(value = "这就是value对应的值2") private static String msg3; @TestAnnotation(what = "这就是what对应的值") private static String msg4; }
3.4.4.新建Demo2Test
package com.zjzaki.spboot01.annotation.demo02; import org.junit.Test; /** * @Author zjzaki * @Package com.zjzaki.spboot01.annotation.demo02 * @Date 2023-08-04 21:13:06 */ public class Demo2Test { @Test public void test1() throws Exception { TestAnnotation msg1 = Demo2.class.getDeclaredField("msg1").getAnnotation(TestAnnotation.class); System.out.println(msg1.value()); System.out.println(msg1.what()); } @Test public void test2() throws Exception { TestAnnotation msg2 = Demo2.class.getDeclaredField("msg2").getAnnotation(TestAnnotation.class); System.out.println(msg2.value()); System.out.println(msg2.what()); } @Test public void test3() throws Exception { TestAnnotation msg3 = Demo2.class.getDeclaredField("msg3").getAnnotation(TestAnnotation.class); System.out.println(msg3.value()); System.out.println(msg3.what()); } @Test public void test4() throws Exception { TestAnnotation msg4 = Demo2.class.getDeclaredField("msg4").getAnnotation(TestAnnotation.class); System.out.println(msg4.value()); System.out.println(msg4.what()); } }
运行结果
3.5.案例三(获取参数修饰注解对应的属性值)
通案例一和二,此处只贴代码
package com.zjzaki.spboot01.annotation.demo3; import java.lang.annotation.*; /** * @Author zjzaki * @Package com.zjzaki.spboot01.annotation.demo3 * @Date 2023-08-04 22:14:31 */ @Documented @Target({ElementType.PARAMETER}) @Retention(RetentionPolicy.RUNTIME) public @interface IsNotNull { boolean value() default false; }
package com.zjzaki.spboot01.annotation.demo3; /** * 获取参数修饰注解对应的属性值 * @Author zjzaki * @Package com.zjzaki.spboot01.annotation.demo3 * @Date 2023-08-04 22:14:54 */ public class Demo3 { public void hello1(@IsNotNull(true) String name) { System.out.println("hello:" + name); } public void hello2(@IsNotNull String name) { System.out.println("hello:" + name); } }
package com.zjzaki.spboot01.annotation.demo3; import org.junit.Test; import java.lang.reflect.Parameter; /** * @Author zjzaki * @Package com.zjzaki.spboot01.annotation.demo3 * @Date 2023-08-04 22:15:19 */ public class Demo3Test { @Test public void hello1() throws Exception { Demo3 demo3 = new Demo3(); for (Parameter parameter : demo3.getClass().getMethod("hello1", String.class).getParameters()) { IsNotNull annotation = parameter.getAnnotation(IsNotNull.class); if(annotation != null){ System.out.println(annotation.value());//true } } } @Test public void hello2() throws Exception { Demo3 demo3 = new Demo3(); for (Parameter parameter : demo3.getClass().getMethod("hello2", String.class).getParameters()) { IsNotNull annotation = parameter.getAnnotation(IsNotNull.class); if(annotation != null){ System.out.println(annotation.value());//false } } } }
4.Aop自定义注解的应用
4.1.在pom.xml中添加依赖
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId> </dependency>
4.2.自定义注解
package com.zjzaki.spboot01.annotation.aopDemo; 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 MyLog { String desc(); }
4.3.定义切面类
package com.zjzaki.spboot01.annotation.aopDemo; import com.sun.org.slf4j.internal.Logger; import com.sun.org.slf4j.internal.LoggerFactory; import org.aspectj.lang.JoinPoint; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; import org.aspectj.lang.annotation.Pointcut; import org.aspectj.lang.reflect.MethodSignature; import org.springframework.stereotype.Component; /** * @Author zjzaki * @Package com.zjzaki.spboot01.annotation.aopDemo * @Date 2023-08-04 22:18:57 */ @Component @Aspect public class MyLogAspect { private static final Logger logger = LoggerFactory.getLogger(MyLogAspect.class); /** * 只要用到了com.javaxl.p2.annotation.springAop.MyLog这个注解的,就是目标类 */ @Pointcut("@annotation(com.zjzaki.spboot01.annotation.aopDemo.MyLog)") private void MyValid() { } @Before("MyValid()") public void before(JoinPoint joinPoint) { MethodSignature signature = (MethodSignature) joinPoint.getSignature(); logger.debug("[" + signature.getName() + " : start.....]"); System.out.println("[" + signature.getName() + " : start.....]"); MyLog myLog = signature.getMethod().getAnnotation(MyLog.class); logger.debug("【目标对象方法被调用时候产生的日志,记录到日志表中】:"+myLog.desc()); System.out.println("【目标对象方法被调用时候产生的日志,记录到日志表中】:" + myLog.desc()); } }
4.4.新建LogController
package com.zjzaki.spboot01.annotation.aopDemo; import org.springframework.stereotype.Component; /** * @Author zjzaki * @Package com.zjzaki.spboot01.annotation.aopDemo * @Date 2023-08-04 22:20:50 */ @Component public class LogController { @MyLog(desc = "这是结合spring aop知识,讲解自定义注解应用的一个案例") public void testLogAspect(){ System.out.println("这里随便来点啥"); } }
4.4.新建LogControllerTest
package com.zjzaki.spboot01.annotation.aopDemo; import org.junit.Test; import org.springframework.beans.factory.annotation.Autowired; /** * @Author zjzaki * @Package com.zjzaki.spboot01.annotation.aopDemo * @Date 2023-08-04 22:22:01 */ public class LogControllerTest extends BaseTestCase { @Autowired private LogController logController; @Test public void testLogAspect(){ logController.testLogAspect(); } }
4.5.运行结果
4.6.MyLogAspect中添加内容
@Around("MyValid()") public Object doAround(ProceedingJoinPoint pjp) throws Throwable { long startTime = System.currentTimeMillis(); Object[] args = pjp.getArgs(); System.out.println(Arrays.toString(args)); Object ob = pjp.proceed();// ob 为方法的返回值 System.out.println("耗时 : " + (System.currentTimeMillis() - startTime) + "ms"); return ob; }
4.7.修改LogController.java
package com.zjzaki.spboot01.annotation.aopDemo; import org.springframework.stereotype.Component; /** * @Author zjzaki * @Package com.zjzaki.spboot01.annotation.aopDemo * @Date 2023-08-04 22:20:50 */ @Component public class LogController { @MyLog(desc = "这是结合spring aop知识,讲解自定义注解应用的一个案例") public void testLogAspect(String name, int age) { try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("这里随便来点啥"); } }
4.8.LogConteroller.java
package com.zjzaki.spboot01.annotation.aopDemo; import org.junit.Test; import org.springframework.beans.factory.annotation.Autowired; /** * @Author zjzaki * @Package com.zjzaki.spboot01.annotation.aopDemo * @Date 2023-08-04 22:22:01 */ public class LogControllerTest extends BaseTestCase { @Autowired private LogController logController; @Test public void testLogAspect() { logController.testLogAspect("张三", 18); } }
4.9.运行结果