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 主要有两种配置元数据的方式,一种是基于 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/java
、src/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 声明周期可以让我们更轻松的初始化一些行为以及维护数据
- 暂时只要掌握这个初始化方法就好了
网友评论