Spring Bean 篇
上
The bean is an instance of a class managed by the Spring container
loC(lnversion of Control,控制反转) 容器是 Spring 框架最最核心的组件,没有 loC 容器就没有 Spring 框架。
loC(lnversion of Control,控制反转)是面向对象编程中的一种设计原则,可以用来减低计算机代码之间的耦合度
在 Spring 框架当中,主要通过依赖注入(Dependency Injection简称DI)来实现 loC。
在 Spring 的世界中,所有的 Java 对象都会通过 loC 容器转变为Bean (Spring 对象的一种称呼,以后我们都用 Bean 来表示 Java 对象),构成应用程序主干和由 Spring loC 容器管理的对象称为beans,beans 和它们之间的依赖关系反映在容器使用的配置元数据中。基本上所有的 Bean 都是由接口+实现类完成的,用户想要获取Bean 的实例直接从 loC 容器获取就可以了,不需要关心实现类
Spring 主要有两种配置元数据的方式,一种是基于 XML、一种是基于 Annotation 方案的,目前主流的方案是基于 Annotation 的,所以我们这里也是以 Annotation 为基础方案来讲解,
org.springframework.context.Applicationcontext
接类定义容器的对外服务,通过这个接口,我们可以轻松的从 loC 容器中得到Bean 对象。我们在启动 Java 程序的时候必须要先启动 loC 容器
Annotation 类型的 loC 容器对应的类是
org.springframework.context.annotation.AnnotationConfigApplicationContext
在启动类里启动IoC容器
ApplicationContext context = new AnnotationConfigApplicationContext("fm.douban");
这段代码的含义就是启动 loC 容器, 并且会自动加载包 fm.douban
下的 Bean,哪些 Bean 会被加载呢? 只要引用了 Spring 注解的类都可以被加载(前提是在这个包下哦)
AnnotationconfigApplicationcontext
这个类的构造函数有两种
-
AnnotationConfigApplicationContext(String …basePackages)根据包名实例化
-
AnnotationConfigApplicationContext(Class clazz)根据自定义包扫描行为实例化
我们的例子就是第一种,两者根据情况做选择,开始的时候一般用第一种方案
Spring 官方声明为 Spring Bean 的注解有如下几种
Spring Bean 的官方注解:
-
org.springframework.stereotype.Service
-
org.springframework.stereotype.Component
-
org.springframework.stereotype.Controller
-
org.springframework.stereotype.Repository
-
@Component
注解是通用的 Bean 注解,其余三个注解都是扩展自Component
-
@Service
正如这个名称一样,代表的是 Service Bean -
@Controller
作用于 Web Bean -
@Repository
作用于持久化相关 Bean
实际上这四个注解都可以被 IoC 容器加载,一般情况下,我们使用@Service
;如果是Web服务就使用@Controller
下
loC 容器就像一个大型的工厂一样,我们不关心工厂如何生产,只
需使用工厂生产的产品。
依赖注入的第一步是完成容器的启动,第二步就是真正的完成依赖注入行为了
依赖注入这个词也是一种编程思想,他简单的来说就是一种获取其他实
例的规范
我们还是以豆瓣为例,来学习和理解依赖注入思想
- 我们在前面的作业完成了豆瓣歌曲服务的定义,听过歌的同学应该都知道,有歌曲就有专辑,我们可以通过一个专辑获取这专辑单包含的歌曲,看一下 UML图
- 我们新增了一个 接口
SubjectService
和它的实现类subjectserviceImpl
,用来完成获取专辑的服务,在这个接口里我们定义了一个 get 方法,传入参数为 subjectld,我们期望得到专辑的信息(包括专辑包含的歌曲信息)
- 我们仔细看一下我们的
song
、subject
这两个 POJO 类,从这个模型上来看,我们如果去带着 Subject 的 id 去循环遍历所有的歌曲,筛选出来的歌曲应该就是专辑包含的歌曲。所以我们在SongService
里又新增了一个 list 方法用于查询专辑歌曲 - 回到
SubjectServiceImpl
类,如果我们想获取完整的专辑信息,就得引入 songservice 的实例,调用歌曲,对不? 我们来模拟一下这个SujectServiceImpl
ectServiceImpl 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 方法传入进来?相当麻烦
使用依赖注入 DI :
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;
}
}
改动前的问题
在任何需要SubjectService
的地方,都需要编码实例化对象:
SubjectService subjectService = new SujectServiceImpl();
SongService songService = new SongServiceImpl();
subjectService.setSongService(songService);
如果SubjectService依赖的xxxService太多,就需要new很多服务实例,代码会很长
如何解决问题(改动后)
加注解的作用,是让 Spring 系统 自动 管理 各种实例。
所谓 管理,就是用 @service
注解把``SubjectServiceImpl 和
SongServiceImpl 等等所有服务实现,都标记成 *Spring Bean* ;然后,在任何需要使用服务的地方,用@
Autowired` 注解标记,告诉Spring 这里需要注入实现类的实例。
项目启动过程中,Spring 会 自动 实例化服务实现类,然后 自动 注入到变量中,不需要开发者大量的写 new
代码了,就解决了上述的开发者需要大量写代码而导致容易出错的问题。
@service
和 @Autowired
是相辅相成的:如果 SongServicelmpl 没有加@Service
,就意味着没有标记成 Spring Bean ,那么即使加 @Autowired
也无法注入实例; 而private songservice songservice
;属性忘记加 @Autowired
Spring Bean 亦无法注入实例。二者缺一不可
每个 Annotation (注解)都有各自特定的功能,Spring 检查到代码中有注解,就自动完成特定功能,减少代码量、降低系统复杂度。
初学 Spring ,需要 理解 并 牢记 常见的这些注解的功能和作用。
依赖注入小结
OK,上面的例子其实已经是解释了依赖注入的工作模式,我们整理下,你会发现依赖注入让我们得到其他 Bean 的实例相当简单,你只5需要在属性上添加注解,就像下面的代码
@Autowired
private SongService songService;
Autowired 完整的类路径是
org.springframework.beans.factory.annotation.Autowired
当然你还有一个前提条件,那就是当前的类是 Spring Bean 哦,比如这里我们添加了 @service
到目前为止,我们掌握了 Spring Bean 的知识,可以改进一些代码啦,让我们继续实战下去,你会发现 Spring 的道理比较复杂,但是运用起来其实很简单,因为这是各种设计模式综合运用的结果。以后我们也会逐步的了解设计模式的,到时候再精进 Spring 会更好的理解,现在还是让我们熟练的使用它