IoC(Inversion of Control)
IoC即为“控制反转”,是Spring的一种核心思想,我们可以在类上添加对应的注解,将控制权转交给Spring,Spring就会在框架启动时加载该类,把对象交给Spring管理,就是一种IoC思想。
控制反转是一种思想,在我们生活中也处处体现。列如自动驾驶汽车,将汽车的控制权转交给了自动驾驶系统来控制。
IoC优点
程序的设计应该符合”高内聚低耦合“的设计原则,IoC的思想帮助我们很好的实现了各个不同模块之间的解耦合功能。
DI(Depedency Injection)
DI即”依赖注入“,指在容器运行期间,动态的为应用程序提供运行时所依赖的资源。是Spring实现IoC的一种具体实现方式。
IoC&DI
IoC是一种思想,DI是Spring对IoC的实现方式。这句话该如何理解呢?这就相当于公式与代入实际题目解题之间的关系。
IoC&DI的使用
Spring作为一个IoC容器,具备最基本的两个功能”存“和”取“。
Spring 容器管理的主要是对象, 这些对象, 我们称之为"Bean". 我们把这些对象交由Spring管理, 由 Spring来负责对象的创建和销毁. 我们程序只需要告诉Spring, 哪些需要存, 以及如何从Spring中取出对象。接下来以图书管理系统为例,介绍IoC&DI的使用。
1. Service层及Dao层的实现类,交给Spring管理,使用注解 @Component
@Component
public class BookDao {
public List<BookInfo> mockData() {
List<BookInfo> bookInfos = new ArrayList<>(15);
for (int i = 0; i < 15; i++) {
BookInfo bookInfo = new BookInfo();
bookInfo.setId(1);
bookInfo.setBookName("图书" + i);
bookInfo.setAuthor("作者" + i);
bookInfo.setCount(new Random().nextInt(200));
bookInfo.setPrice(new BigDecimal(new Random().nextInt(100)));
bookInfo.setPublish("出版社" + i);
bookInfo.setStatus(i % 5 == 0 ? 2 : 1);
bookInfos.add(bookInfo);
}
return bookInfos;
}
}
@Component
public class BookService {
@Autowired
private BookDao bookDao;
public List<BookInfo> getBookList(){
List<BookInfo> bookInfos = bookDao.mockData();
for(BookInfo bookInfo:bookInfos){
if(bookInfo.getStatus() == 1){
bookInfo.setStatusCN("可借阅");
}else{
bookInfo.setStatusCN("不可借阅");
}
}
return bookInfos;
}
}
2. 在Controller层 和Service层 注⼊运⾏时依赖的对象: 使⽤注解 @Autowired
@RestController
@RequestMapping("/user")
public class UserController {
@RequestMapping("/login")
public boolean login(String userName, String password, HttpSession session) {
if(!StringUtils.hasLength(userName) || !StringUtils.hasLength(password)){
return false;
}
if("admin".equals(userName) && "admin".equals(password)){
session.setAttribute("userName",userName);
return true;
}
return false;
}
}
@RestController
@RequestMapping("/book")
public class BookController {
@Autowired
private BookService bookService;
@RequestMapping("/getBookList")
public List<BookInfo> getBookList(){
List<BookInfo> bookInfos = bookService.getBookList();
return bookInfos;
}
}
@Component
public class BookService {
@Autowired
private BookDao bookDao;
public List<BookInfo> getBookList(){
List<BookInfo> bookInfos = bookDao.mockData();
for(BookInfo bookInfo:bookInfos){
if(bookInfo.getStatus() == 1){
bookInfo.setStatusCN("可借阅");
}else{
bookInfo.setStatusCN("不可借阅");
}
}
return bookInfos;
}
}
通过上⾯的案例, 我们已经知道了Spring IoC 和DI的基本操作, 接下来我们来系统的学习Spring IoC和DI 的操作。
IoC详解
在之前的案例中,我们通过@Component和@Autowired将多个需要用到的类对象交给了Spring管理,实现了控制反转。而Spring为了提供更好的web服务,提供了更加丰富的注解。一共可以分为以下两类。
类注解:@Controller(控制器存储)、@Service(服务存储)、@Repository(仓库存储)、@Component(组件存储)、@Configuration(配置存储)
方法注解:@Bean
由于使用方式类似,以Controller为例介绍。
@Controller(控制器存储)
@Controller
public class UController {
public void sayHi(){
System.out.println("hello controller");
}
}
此时我们已经将对象存储到了Spring当中,那么我们该如何调用呢?
@SpringBootApplication
public class SpringBootDemoApplication {
public static void main(String[] args) {
ApplicationContext applicationContext = SpringApplication.run(SpringBootDemoApplication.class, args);
UController ucontroller = applicationContext.getBean(UController.class);
ucontroller.sayHi();
}
}
因为对象都交给 Spring 管理了,所以获取对象要从 Spring 中获取,那么就得先得到 Spring 的上下⽂,通过ApplicationContext的getBean()方法获取UController对象。
以下是Spring源码中提供的获取对象的几种方式。常用的是1,2,5几种方式。
public interface BeanFactory {
Object getBean(String var1) throws BeansException;
<T> T getBean(String var1, Class<T> var2) throws BeansException;
Object getBean(String var1, Object... var2) throws BeansException;
<T> T getBean(Class<T> var1) throws BeansException;
<T> T getBean(Class<T> var1, Object... var2) throws BeansException;
}
@SpringBootApplication
public class SpringBootDemoApplication {
public static void main(String[] args) {
ApplicationContext applicationContext = SpringApplication.run(SpringBootDemoApplication.class, args);
UController ucontroller = (UController) applicationContext.getBean("UController");
UController ucontroller1 = applicationContext.getBean("UController", UController.class);
UController ucontroller2 = applicationContext.getBean(UController.class);
System.out.println(ucontroller);
System.out.println(ucontroller1);
System.out.println(ucontroller2);
}
}
根据几种不同的方式获取对象,发现地址是一样的。
类注解及其关系
为了与应用分层呼应,能够让程序员看到类注解后直接明白该类的用途,所以有了各种名称不同,功能类似的注解。
@Controller:控制层, 接收请求, 对请求进⾏处理, 并进⾏响应.
@Servie:业务逻辑层, 处理具体的业务逻辑.
@Repository:数据访问层,也称为持久层. 负责数据访问操作
@Configuration:配置层. 处理项⽬中的⼀些配置信息.
Spring三层架构
方法注解@Bean
类注解是添加到某个类上的, 但是存在两个问题:
1. 使⽤外部包⾥的类, 没办法添加类注解
2. ⼀个类, 需要多个对象, ⽐如多个数据源
这时候我们就需要使用方法注解@Bean
方法注解的的使用需要搭配类注解共同使用
@Component
public class BeanController {
@Bean
public UserInfo userInfo() {
UserInfo userInfo = new UserInfo();
userInfo.setId(2);
userInfo.setName("lisi");
userInfo.setAge(13);
return userInfo;
}
}
可以看到我们成功获取了UserInfo中存储的信息
通过Bean获取多个对象
@Component
public class BeanController {
@Bean
public UserInfo userInfo() {
UserInfo userInfo = new UserInfo();
userInfo.setId(2);
userInfo.setName("lisi");
userInfo.setAge(13);
return userInfo;
}
@Bean
public UserInfo userInfo1() {
UserInfo userInfo = new UserInfo();
userInfo.setId(23);
userInfo.setName("zhangsan");
userInfo.setAge(14);
return userInfo;
}
}
我们可以通过给方法起不同的名称,通过名称获取对应的对象.
观察结果,对象已经被成功取出
通过设置Bean的name属性获取对象
@Component
public class BeanController {
@Bean
public UserInfo userInfo() {
UserInfo userInfo = new UserInfo();
userInfo.setId(2);
userInfo.setName("lisi");
userInfo.setAge(13);
return userInfo;
}
@Bean(name = {"uf1","userInfo1"})
public UserInfo userInfo1() {
UserInfo userInfo = new UserInfo();
userInfo.setId(23);
userInfo.setName("zhangsan");
userInfo.setAge(14);
return userInfo;
}
}
这里给userInfo1设置了别名uf1
设置别名的uf1也被成功取出
DI详解
依赖注⼊是⼀个过程,是指IoC容器在创建Bean时, 去提供运⾏时所依赖的资源,⽽资源指的就是对象. 在上⾯程序案例中,我们使⽤了@Autowired这个注解,完成了依赖注⼊的操作.
简单来说, 就是把对象取出来放到某个类的属性中.
依赖注入可以分为以下三种:1.属性注入2.构造方法注入3.setter注入
属性注入
属性注⼊是使⽤ @Autowired 实现的,将 Service 类注⼊到 Controller 类中。
@Service
public class UserService {
public void sayHi(){
System.out.println("hi,userService!");
}
}
@Controller
public class UserController {
@Autowired
private UserService userService;
public void sayHi(){
System.out.println("hi,userController!");
userService.sayHi();
}
}
结果显示我们已经成功将userService类注入到userController类中
构造方法注入
构造⽅法注⼊是在类的构造⽅法中实现注⼊,如下代码所⽰:
@Controller
public class UserController {
private UserService userService;
@Autowired
public UserController(UserService userService) {
this.userService = userService;
}
public void sayHi(){
System.out.println("hi,userController!");
userService.sayHi();
}
}
同样成功获取到了userController类对象,需要注意的是,如果类只有⼀个构造⽅法,那么 @Autowired 注解可以省略;如果类中有多个构造⽅法, 那么需要添加上 @Autowired 来明确指定到底使⽤哪个构造⽅法.
Setter注入
Setter 注⼊和属性的 Setter ⽅法实现类似,只不过在设置 set ⽅法的时候需要加上 @Autowired 注 解 ,如下代码所⽰:
@Controller
public class UserController {
private UserService userService;
public void sayHi(){
System.out.println("hi,userController!");
userService.sayHi();
}
@Autowired
public void setUserService(UserService userService) {
this.userService = userService;
}
}
三种注入优缺点分析
属性注⼊
优点: 简洁,使⽤⽅便
缺点:
1. 只能⽤于 IoC 容器,如果是⾮ IoC 容器不可⽤,并且只有在使⽤的时候才会出现 NPE(空指针异常
2.不能注⼊⼀个Final修饰的属
构造函数注⼊
优点:
1. 可以注⼊final修饰的属性
2. 注⼊的对象不会被修改
3. 依赖对象在使⽤前⼀定会被完全初始化,因为依赖是在类的构造⽅法中执⾏的,⽽构造⽅ 法是在类加载阶段就会执⾏的⽅法。
4. 通⽤性好, 构造⽅法是JDK⽀持的, 所以更换任何框架,他都是适⽤的
缺点:
注⼊多个对象时, 代码会⽐较繁琐
Setter注⼊
优点:
⽅便在类实例之后, 重新对该对象进⾏配置或者注⼊
缺点:
1.不能注⼊⼀个Final修饰的属性
2,注⼊对象可能会被改变, 因为setter⽅法可能会被多次调⽤, 就有被修改的⻛险.