美文网首页
Spring 依赖注入(注解、Bean、Resource)

Spring 依赖注入(注解、Bean、Resource)

作者: laochonger | 来源:发表于2021-01-11 21:30 被阅读0次

    Annotation(注解)

    @Service
    public class MessageServiceImpl implements MessageService{
    
        public String getMessage() {
             return "Hello World!";
        }
    
    }
    

    观察上面代码,发现多出了一个语法@service

    • 注解是Java推出的一种注释机制,与普通的注释不同,Annotation可以在编译、运行阶段读取的。我们使用他来完成一些增强功能,在Spring中就重度使用了Annotation。
    • 从另一个角度看,Annotation也是一个Java类,只是过于特殊 image.png

      掌握这5个小点,就能大致理解Annotation的工作机制了。

    1. Target

    java.lang.annotation.Target自身也是一个注解,它只有一个数组属性,用于设定该注解的目标范围,比如说可以作用于类或方法等。因为是数组,所以可以同时设定多个范围。

    具体可以作用的类型配置在java.lang.annotation.ElementType枚举类中,几个常用的

    • ElementType.TYPE
      可以作用于 类、接口类、枚举类上
    • ElementType.FIFLD
      可以作用于 类的属性
    • ElementType.METHOD
      可以作用于 类的方法
    • ElementType.PARAMETER
      可以作用于 类的参数

    如果想同时作用于类和方法上,那么可以直接@Target({ElementType.TYPE,ElementType.METHOD})

    2. Retention

    java.lang.annotation.Retention自身也是一个注解,它用于声明该注解的生命周期,简单来说就是在Java编译、运行的哪个环节有效,它的值定义在java.lang.annotation.RetentionPolicy枚举类中,有三个值可以选择

    • SOURCE:纯注释
    • CLASS:编译阶段有效
    • RUNTIME:运行时有效
      一般来说,自己定义的Annotation设置成运行时有效

    3. Document

    java.lang.annotation.Documented自身也是一个注解,它的作用是将注解中的元素包含到JavaDoc文档中,一般情况下,都会添加这个注解的。

    4. @interface

    @interface就是声明当前的Java类型是Annotation,固定语法

    5. Annotation 属性

    String value() default "";
    Annotation的属性有点像类的属性一样,它约定了属性的类型(这个类型是基础类型:String、boolean、int、long)和属性名称(默认名称是value,在引用的时候可以省略),default代表的是默认值。

    • Annotation 属性是可以有多个的,比如下面的一个注解类
    @Target(ElementType.PARAMETER)
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    public @interface RequestParam {
    
        @AliasFor("name")
        String value() default "";
    
    
        @AliasFor("value")
        String name() default "";
    
    
        boolean required() default true;
    
        String defaultValue() default ValueConstants.DEFAULT_NONE;
    }
    

    @AliasFor("name")是别名的意思

    Spring Bean

    IoC(Inversion of Control,控制反转)容器最最核心的组件,没有IoC容器就没有Spring框架

    • IoC(Inversion of Control,控制反转)是面向对象编程中的一种设计原则,可以用来减低计算机代码之间的耦合度
      在Spring框架中,主要通过依赖注入来实现IoC
    在Spring中,所有的Java对象都会通过IoC容器转变为Bean(Spring对象的一种称呼),构成应用程序主干和由Spring IoC容器管理的对象称为beans,beans和他们之间的依赖关系反映在容器使用的配置元数据中。基本上所有的Bean都是由接口+实现类完成的,用户想要获取Bean的实例直接从IoC容器获取就可用了,不需要关心实现类 image.png

    Spring 主要有两种配置元数据的方式,一种是基于 XML、一种是基于 Annotation 方案的,目前主流的方案是基于 Annotation 的,这里也是以 Annotation 为基础方案来讲解

    org.springframework.context.ApplicationContext接口类定义容器的对外服务,通过这个接口,我们可以轻松地从IoC容器中得到Bean对象。我们在启动Java程序的时候必须先要启动IoC容器

    Annotation类型的IoC容器对应的类是org.springframework.context.annotation.AnnotationConfigApplicationContext
    我们如果要启动 IoC 容器,可以运行下面的代码
    ApplicationContext context = new AnnotationConfigApplicationContext("fm.douban");
    这段代码的含义就是启动 IoC 容器,并且会自动加载包 fm.douban 下的 Bean,哪些 Bean 会被加载呢?只要引用了 Spring 注解的类都可以被加载(前提是在这个包下)

    AnnotationConfigApplicationContext 这个类的构造函数有两种

    • AnnotationConfigApplicationContext(String ... basePackages) 根据包名实例化
    • AnnotationConfigApplicationContext(Class clazz) 根据自定义包扫描行为实例化

    我们的例子就是第一种,两者根据情况做选择,开始的时候一般用第一种方案

    Spring 官方声明为 Spring Bean 的注解有如下几种:

    • org.springframework.stereotype.Service
    • org.springframework.stereotype.Component
    • org.springframework.stereotype.Controller
    • org.springframework.stereotype.Repository

    只要我们在类上引用这类注解,那么都可以被 IoC 容器加载

    • @Component注解是通用的 Bean 注解,其余三个注解都是扩展自Component
    • @Service正如这个名称一样,代表的是 Service Bean
    • @Controller作用于 Web Bean
    • @Repository作用于持久化相关 Bean

    实际上这四个注解都可以被 IoC 容器加载,一般情况下,我们使用@Service;如果是 Web 服务就使用@Controller

    • 代码演示
    package fm.douban;
    
    import org.springframework.context.ApplicationContext;
    import org.springframework.context.annotation.AnnotationConfigApplicationContext;
    import fm.douban.service.SongService;
    import fm.douban.model.Song;
    
    /**
     * Application
     */
    public class Application {
    
      public static void main(String[] args) {
    
        ApplicationContext context = new AnnotationConfigApplicationContext("fm.douban");
        // 从容器中获取歌曲服务。
        SongService songService = context.getBean(SongService.class);
        // 获取歌曲
        Song song = songService.get("001");
        System.out.println("得到歌曲:" + song.getName());
    
      }
    }
    

    依赖注入的第一步是完成容器的启动,第二步就是真正地完成依赖注入行为了

    • 依赖注入这个词也是一种编程思想,简单来说就是一种获取其他实例的规范

    我们定义一个接口的实现类SujectServiceImpl,如果我们想获取完整的专辑信息,就得引入 SongService 的实例,调用歌曲

    public class SubjectServiceImpl implements SubjectService {
    
       private SongService songService;
    
       //缓存所有专辑数据
       private static Map<String, Subject> subjectMap = new HashMap<>();
       static {
           Subject subject = new Subject();
           //... 省略初始化数据的过程
           subjectMap.put(subject.getId(), subject);
       }
    
       @Override
       public Subject get(String subjectId) {
           Subject subject = subjectMap.get(subjectId);
           //调用 SongService 获取专辑歌曲
           List<Song> songs = songService.list(subjectId);
           subject.setSongs(songs);
           return subject;
       }
    
       public void setSongService(SongService songService) {
           this.songService = songService;
       } 
    }
    
    • 那么,如何获取SongService的实例呢?

    按照之前,我们需要一个外部工厂进行传递,调用setSongService方法传入进来。
    现在,我们使用依赖注入

    import fm.douban.model.Song;
    import fm.douban.model.Subject;
    import fm.douban.service.SongService;
    import fm.douban.service.SubjectService;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Service;
    
    import java.util.HashMap;
    import java.util.List;
    import java.util.Map;
    
    @Service
    public class SubjectServiceImpl implements SubjectService {
    
        @Autowired
        private SongService songService;
    
        //缓存所有专辑数据
        private static Map<String, Subject> subjectMap = new HashMap<>();
    
        static {
            Subject subject = new Subject();
            subject.setId("s001");
            //... 省略初始化数据的过程
            subjectMap.put(subject.getId(), subject);
        }
    
        @Override
        public Subject get(String subjectId) {
            Subject subject = subjectMap.get(subjectId);
            //调用 SongService 获取专辑歌曲
            List<Song> songs = songService.list(subjectId);
            subject.setSongs(songs);
            return subject;
        }
    }
    

    做了三处改动:

    第一处
    第二处
    第三处

    在执行类中:

    package fm.douban;
    
    import fm.douban.model.Subject;
    import fm.douban.service.SongService;
    import fm.douban.service.SubjectService;
    import org.springframework.context.ApplicationContext;
    import org.springframework.context.annotation.AnnotationConfigApplicationContext;
    
    /**
     * Application
     */
    public class Application {
    
        public static void main(String[] args) {
    
            ApplicationContext context = new AnnotationConfigApplicationContext("fm.douban");
    
            SubjectService subjectService = context.getBean(SubjectService.class);
    
            Subject subject = subjectService.get("s001");
    
            System.out.println("该专辑共有:"+subject.getSongs().size()+" 首歌");
    
        }
    }
    

    在这个例子中,当我们要用到其他接口的方法时,我们可以使用依赖注入来简化过程,不需要像之前一样再在外部工厂进行传递,调用setSongService方法传入进来
    上面的例子其实已经是解释了依赖注入的工作模式,我们整理一下,你会发现依赖注入让我们得到其他 Bean 的实例相当简单,你只需要在属性上添加注解,就像下面的代码

    @Autowired
    private SongService songService;
    
    • Autowired 完整的类路径是 org.springframework.beans.factory.annotation.Autowired

    当然还有一个前提条件,那就是当前的类是 Spring Bean 哦,比如这里我们添加了 @Service

    • 到目前为止,我们掌握了 Spring Bean 的知识

    Spring Resource

    文件系统是编程不可避开的领域,Spring Framework作为完整的Java企业级解决方案,自然也有文件处理方案,那就是Spring Resource

    在正式学习Spring Resource之前,还需要了解一下在Java工程中文件的几种情况

    • 文件在电脑某个位置,d:/mywork/a.doc
    • 文件在工程目录下,比如mywork/toutiao.png
    • 文件在工程的src/main/resources 目录下,这是 Maven 工程存放文件的地方

    第一种和第二种情况都是使用 File 对象就可以读写啦,第三种情况比较特殊,因为 Maven 执行 package 的时候,会把resources目录下的文件一起打包进 jar 包里(之前提到过 jar 是 Java 的压缩文件)。

    显然在第三种情况,用 File 对象是读取不到的,因为文件已经在 jar 里了


    image.png

    Java 文件系统和计算机文件系统的差异,工程目录最后是要编译成 jar 文件的,jar文件是从包路径开始的,Maven 工程编译后,会启动去掉了 src/main/javasrc/main/resources 目录

    那么现在的如何读取 jar 内部的文件呢?

    src/main/resources目录的,会自动打包到 jar 文件内

    classpath

    在 Java 内部当中,我们一般把文件路径称为 classpath,所以读取内部的文件就是从 classpath 内读取,classpath 指定的文件不能解析成 File 对象,但是可以解析成 InputStream,我们借助 Java IO 就可以读取出来了

    classpath 类似虚拟目录,它的根目录是从 / 开始代表的是 src/main/java或者src/main/resources目录

    我们来看一下如何使用 classpath 读取文件,这次我们在 resources 目录下存放一个 data.json 文件。

    • Java 拥有很多丰富的第三方类库给我们使用,读取文件,我们可以使用 commons-io 这个库来,需要我们在 pom.xml 下添加依赖
    <dependency>
      <groupId>commons-io</groupId>
      <artifactId>commons-io</artifactId>
      <version>2.6</version>
    </dependency>
    

    测试代码如下:

    public class Test {
    
      public static void main(String[] args) {
        // 读取 classpath 的内容
        InputStream in = Test.class.getClassLoader().getResourceAsStream("data.json");
        // 使用 commons-io 库读取文本
        try {
          String content = IOUtils.toString(in, "utf-8");
          System.out.println(content);
        } catch (IOException e) {
          // IOUtils.toString 有可能会抛出异常,需要我们捕获一下
          e.printStackTrace();
        }
      }
    
    }
    

    再来认识一下这段代码

    InputStream in = Test.class.getClassLoader().getResourceAsStream("data.json");
    

    这段代码的含义就是从 Java 运行的类加载器(ClassLoader)实例中查找文件,Test.class指的当前的 Test.java 编译后的 Java class 文件

    那么,Spring Resource可以做什么呢?
    在Spring中定义了一个org.springframework.core.io.Resource来封装文件,这个类的优势在于可以支持普通的 File 也可以支持 classpath 文件。

    并且在 Spring 中通过 org.springframework.core.io.ResourceLoader 服务来提供任意文件的读写,你可以在任意的 Spring Bean 中引入 ResourceLoader

    @Autowired
    private ResourceLoader loader;
    

    现在让我们来看一下在 Spring 当中如何读取文件,我们创建一个自己的 FileService

    public interface FileService {
    
        String getContent(String name);
    
    }
    

    实现类

    import fm.douban.service.FileService;
    import org.apache.commons.io.IOUtils;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.core.io.Resource;
    import org.springframework.core.io.ResourceLoader;
    import org.springframework.stereotype.Service;
    
    import java.io.IOException;
    import java.io.InputStream;
    
    @Service
    public class FileServiceImpl implements FileService {
    
        @Autowired
        private ResourceLoader loader;
    
        @Override
        public String getContent(String name) {
            try {
                InputStream in = loader.getResource(name).getInputStream();
                return IOUtils.toString(in,"utf-8");
            } catch (IOException e) {
               return null;
            }
        }
    }
    

    看一下这个服务的调用

      FileService fileService = context.getBean(FileService.class);
      String content = fileService.getContent("classpath:data/urls.txt");
      System.out.println(content);
    

    我们可以继续复用之前的代码,只是修改一下调用的文件目录

    String content2 = fileService.getContent("file:mywork/readme.md");
    System.out.println(content2);
    

    这个file:mywork/readme.md代表的就是读取工程目录下文件mywork/readme.md

    Resource 还可以加载远程文件,比如说

    String content2 = fileService.getContent("https://www.zhihu.com/question/34786516/answer/822686390");
    System.out.println(content2);
    

    总结一下Spring Resource

    在 Spring Resource 当中,把本地文件、classpath文件、远程文件都封装成 Resource 对象来统一加载,这就是它的强悍的地方

    Spring Bean的生命周期

    为了更好的管理 Bean,Spring Bean 提供了生命周期管理能力,这将极大的提高了工程化的能力

    • 什么叫生命周期呢?任何生命都有开始和结束的时候,生命从开始到结束的整个流程、状态就是生命周期。很多编程框架都提供生命周期管理,提供类似实例的开始=》结束的状态管理

      大部分时候,我们只需要掌握 init 方法即可,注意这个 init 方法名称可以是任意名称的,因为我们是通过注解来声明 init 的,我们以 SubjectServiceImpl 为例:
    import javax.annotation.PostConstruct;
    
    @Service
    public class SubjectServiceImpl implements SubjectService {
    
      @PostConstruct
      public void init(){
          System.out.println("启动啦");
      }
    
    }  
    

    我们只要在方法上添加@PostConstruct注解,就代表该方法在 Spring Bean 启动后会自动执行

    注意这个 PostConstruct 的完整包路径是javax.annotation.PostConstruct

    有了 init 方法之后,我们就可以把之前 static 代码块的内容移到 init 里啦。所以代码就变成如下

    @Service
    public class SubjectServiceImpl implements SubjectService {
    
      @PostConstruct
      public void init(){
          Subject subject = new Subject();
          subject.setId("s001");
          subject.setName("成都");
          subject.setMusician("赵雷");
    
          subjectMap.put(subject.getId(), subject);
      }
    
    }  
    
    • Spring 声明周期可以让我们更轻松的初始化一些行为以及维护数据
    • 暂时只要掌握这个初始化方法就好了

    相关文章

      网友评论

          本文标题:Spring 依赖注入(注解、Bean、Resource)

          本文链接:https://www.haomeiwen.com/subject/coqjaktx.html