当前位置:   article > 正文

自己写一个@Component注解_自定义component注解

自定义component注解

引子

Java里的注解

注解是什么

注解是Java SE5中引入的总要特性,注解的出现使得我们能够以将有编译器来测试和验证的格式,存储有关程序的额外信息.

元注解

Java 中的元注解主要有以下四个

  • @Target      用来定义你的注解将应用于什么地方,例如类,方法,字段
  • @Retention 用来定义该注解在哪个级别可用 Source,Class Runtime.
  • @Document 将此注解包含在Javadoc中
  • @Inherited    允许子类继承父类中的注解

注解的保存策略

上面提到@Retention注解的取值有Source,Class,Runtime这三个,这三个值的意思如下

  • Source 注解只保留在源码中,被编译器丢弃,实际开发中几乎没有用到
  • Class    注解保留在Class文件中,但不在运行时由虚拟机保留
  • Runtime 注解保留在Class文件中,并且在运行时由虚拟机保留,我们知道反射是运行时动态修改类信息的技术,如果我们自定义的注解保存策略声明为Runtime,那么我们就能通过反射在运行时动态获取注解,然后通过注解处理器来实现对应的逻辑。

自定义注解

有了Java中提供的元注解,我们就可以依赖元注解来编写自定义注解了,我们只需要指定两个关键信息:注解在哪个级别可用,以及注解作用的位置就可以了,即给我们的注解打上@Target和@Retention注解。我们来看一个例子,下面的代码声明了一个定义注解MyAnnotion

  1. package com.nightcat.annotaion;
  2. import java.lang.annotation.ElementType;
  3. import java.lang.annotation.Retention;
  4. import java.lang.annotation.RetentionPolicy;
  5. import java.lang.annotation.Target;
  6. @Target(ElementType.TYPE)
  7. @Retention(RetentionPolicy.RUNTIME)
  8. public @interface MyAnnotation {
  9. }

可以看到@Target制定了我们自定义的注解可以使用在类上,@Rentention中的保存策略指定了我们的注解将在运行期保留(这很重要)。

注解元素

首先我们为自定义注解添加一些属性:

  1. @Target(ElementType.TYPE)
  2. @Retention(RetentionPolicy.RUNTIME)
  3. public @interface MyAnnotation {
  4. String title() default "";
  5. int type() default 0;
  6. }

我们向自定义注解中添加了一个String类型的title属性和一个int类型的type属性。

那么自定义注解中可以使用的元素类型有哪些呢?

  • 所有基本类型
  • String
  • Class
  • enum
  • Annotation
  • 以上所有类型的数组

注解处理器

我们定义好了注解,它能做些什么?这就必须提到注解处理器,因为如果没有用来处理注解的工具,那注解也不会有什么用处,那么什么是注解处理器呢?其实就是由我们自己编写的处理自定义注解的程序。下面我们就来编写一个注解处理器,用来处理我们的自定义注解。

由于我们的注解保存策略指定为Runtime,那么我们就能通过反射技术来获取注解。我们先来看一段代码

  1. package com.nightcat.bean;
  2. import com.nightcat.annotaion.MyAnnotation;
  3. @MyAnnotation(title = "《好好学习》", type = 1)
  4. public class Article {
  5. }

我们声明了一个Article类,并且给它打上了我们自定义的注解,下面我们通过自定义的注解处理器来获取注解中属性的值。

  1. package com.nightcat.test;
  2. import com.nightcat.annotaion.MyAnnotation;
  3. import com.nightcat.bean.Article;
  4. public class AnnotationTest {
  5. public static void main(String[] args) {
  6. Class<Article> articleClass = Article.class;
  7. MyAnnotation annotation = articleClass.getAnnotation(MyAnnotation.class);
  8. System.out.println("annotation.title() = " + annotation.title()); //《好好学习》
  9. System.out.println("annotation.type() = " + annotation.type());// 1
  10. }
  11. }

你会发现,我们用了反射技术获取到了定义注解的属性值,这就是自定义注解的使用方法:

  1. 将保存策略声明为Runtime.
  2. 通过反射获取注解信息,执行对应的业务逻辑.

注解是什么

学习了上面的知识,我们对注解有了一个基本认识,那么注解究竟是什么呢?

其实注解就是继承了Annotion接口的一个接口而已,拿我们自定义的MyAnnotation举例子:

我们先来看一下MyAnnotation.class 文件

  1. //
  2. // Source code recreated from a .class file by IntelliJ IDEA
  3. // (powered by Fernflower decompiler)
  4. //
  5. package com.nightcat.annotaion;
  6. import java.lang.annotation.ElementType;
  7. import java.lang.annotation.Retention;
  8. import java.lang.annotation.RetentionPolicy;
  9. import java.lang.annotation.Target;
  10. @Target({ElementType.TYPE})
  11. @Retention(RetentionPolicy.RUNTIME)
  12. public @interface MyAnnotation {
  13. String title() default "";
  14. int type() default 0;
  15. }

这是IDEA 替我们处理好的,接着我们用 javap -c 命令反编译一下看看

 通过反编译我们发现注解其实就是一个接口。

自己写一个@Component注解

准备工作

有了上面的基础我们终于要开始写自己的@Component注解了。我们知道Spring Framework中常用的注解有 @Component @Controller @Service @Bean @Repository  等等,这些注解有一个基础的功能,将注解标记的类加到Spring 容器中,交给Spring管理。那么这就涉及到两个问题:

  1. 扫描指定路径下被这些注解标记的类
  2. 把这些类加入到IOC容器中

好了,接下来我们就开始写代码了

代码实现

准备@MyComponent 注解

  1. package com.nightcat.annotaion;
  2. import java.lang.annotation.ElementType;
  3. import java.lang.annotation.Retention;
  4. import java.lang.annotation.RetentionPolicy;
  5. import java.lang.annotation.Target;
  6. @Retention(RetentionPolicy.RUNTIME)
  7. @Target(ElementType.TYPE)
  8. public @interface MyComponent {
  9. }

你会发现我们的这个注解的保存策略是Runtime,原因不再赘述。

准备一个自定义类

  1. package com.nightcat.bean;
  2. import com.nightcat.annotaion.MyComponent;
  3. @MyComponent
  4. public class Student {
  5. private Integer age;
  6. private String stuName;
  7. public Integer getAge() {
  8. return age;
  9. }
  10. public void setAge(Integer age) {
  11. this.age = age;
  12. }
  13. public String getStuName() {
  14. return stuName;
  15. }
  16. public void setStuName(String stuName) {
  17. this.stuName = stuName;
  18. }
  19. @Override
  20. public String toString() {
  21. return "Student{" +
  22. "age=" + age +
  23. ", stuName='" + stuName + '\'' +
  24. '}';
  25. }
  26. }

注意这个类上打上了我们自定义的@MyComponent注解,方便检测哪些类需要加入到ioc容器,哪些不需要。

准备一个简易的ApplicationContext

我们在使用ioc容器时,主要使用的是 ClassPathXmlApplicationContext 或者AnnotationConfigApplicationContext,通过这两个类的构造得到ioc容器,然后通过getBean方法获取指定的bean,这个过程涉及到BeanFactory,需要判断以下内容

  • Bean的scope是单例的还是多例的
  • 是不是IOC容器中已经有这个Bean了
等等。

我们这里不这么麻烦,直接通过一个线程安全的HashMap来模拟oc容器

主要实现思路如下

  • 用ConcurrentHashMap来模拟容器
  • 扫描传入的包路径及其子路径
  • 如果该路径下存在我们自定义注解@MyComponent标记的类,就获取该类的全类名,然后通过反射创建对象,并将其放入我们自定义的ioc容器
  • 测试我们自定义的注解是否起作用

实现代码

  1. package com.nightcat;
  2. import com.nightcat.annotaion.MyComponent;
  3. import java.io.File;
  4. import java.net.URL;
  5. import java.util.Map;
  6. import java.util.concurrent.ConcurrentHashMap;
  7. import java.util.logging.Logger;
  8. public class ApplicationContext {
  9. private Logger logger = Logger.getLogger(ApplicationContext.class.toString());
  10. private Map<String, Object> ioc = new ConcurrentHashMap();
  11. public Object getBean(String beanName) {
  12. return ioc.get(beanName);
  13. }
  14. public ApplicationContext(String packagePath) {
  15. scanPackagePath(packagePath);
  16. }
  17. private void scanPackagePath(String packagePath) {
  18. File file = getFile(packagePath);
  19. File[] allFile = getAllFile(packagePath, file);
  20. handleClassFile(packagePath, allFile);
  21. }
  22. private void handleClassFile(String packagePath, File[] allClassFile) {
  23. for (File file : allClassFile) {
  24. String classFileName = file.getName();
  25. String fileName = classFileName.substring(0, classFileName.lastIndexOf("."));
  26. String beanName = String.valueOf(fileName.charAt(0)).toLowerCase() + fileName.substring(1);
  27. String fullClassName = packagePath + "." + fileName;
  28. try {
  29. Class<?> aClass = Class.forName(fullClassName);
  30. if (aClass.isAnnotationPresent(MyComponent.class)) {
  31. Object o = aClass.newInstance();
  32. ioc.put(beanName, o);
  33. }
  34. } catch (Exception e) {
  35. logger.warning("实例化异常");
  36. }
  37. }
  38. }
  39. private File[] getAllFile(String packagePath, File file) {
  40. File[] files = file.listFiles(file1 -> {
  41. if (file1.isDirectory()) {
  42. scanPackagePath(packagePath + "." + file1.getName());
  43. } else {
  44. String name = file1.getName();
  45. if (name.endsWith(".class")) return true;
  46. else return false;
  47. }
  48. return false;
  49. });
  50. return files;
  51. }
  52. private File getFile(String packagePath) {
  53. String dirPath = packagePath.replaceAll("\\.", "/");
  54. URL url = this.getClass().getClassLoader().getResource(dirPath);
  55. String fileName = url.getFile();
  56. return new File(fileName);
  57. }
  58. }

通过上面的代码,我们会发现,我们传入的basePackage 也就是包名,是以点号分割的,而我们磁盘上的文件是以斜线分割的,所以我们要将点号替换成斜线

再接着我们获取packagePath 对应的url资源,通过它获取文件的全路径名,有些小伙伴可能会说,哪里用这么麻烦,我直接用packagePath new File(String path)的形式创建文件不行么,干嘛用url啊,这是因为你传入的类似com.nightcat 这种包名,你去直接new File ,是不是发现少了一些东西? 没错,就是你的磁盘路径,发现了么,而Java提供的Url类可以解决这个问题。

剩下的工作就简单了,我们去扫描传入的包路径和自路径,并且检测类上是否有我们自定义的注解,如果有,我们就用创建对象,同时将类名首字母小写,作为ConcurrentHashMap的key,创建的对象作为Value就可以了。

有些小伙伴可能会问,你这个Bean无法保证单例呀,也很简单,你写代码的时候判断一下容器里有没有,有了就不添加了。至于其他的知识,不在这篇文章的范畴,可以自行研究。

测试自定义注解和容器

  1. package com.nightcat.test;
  2. import com.nightcat.ApplicationContext;
  3. import com.nightcat.bean.Student;
  4. public class CodeTest {
  5. public static void main(String[] args) {
  6. ApplicationContext context = new ApplicationContext("com.nightcat");
  7. Student student = (Student) context.getBean("student");
  8. System.out.println(student);
  9. }
  10. }

 

 通过测试,发现我们自定义的@MyComponet 注解生效了。

  想学习更多关于Spring的内容,欢迎一键三连。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/爱喝兽奶帝天荒/article/detail/985547
推荐阅读
相关标签
  

闽ICP备14008679号