1. 什么是Spring
前面介绍了Spring Boot,Spring MVC,那么Spring和他们之间有什么关系呢?
Spring简单一句话总结就是:它是一个包含众多工具方法的IOC容器。前面我们也接触过容器,比如List/Map,他俩是数据存储容器。
2. IOC
前面介绍了@RestController和@Controller。这两个注解修饰的类会交给Spring来管理,Sping启动的时候这些类(对象)就会被加载,把对象交给Spring来管理就是IOC。
IOC的全称:Inversion of Control (控制反转), 也就是说 Spring 是⼀个"控制反转"的容器。这个控制反转其实应该是控制权反转。那么是什么控制权反转呢?以前创建对象需要自己new,现在把控制权交给Spring,Spring来帮助我们创建对象,我们想使用对象时直接DI (依赖注⼊Dependency Injection)。由此创建对象和使用对象都只需要跟Spring容器交互,由此降低了他们之间的耦合程度。
2.1. IOC容器的优点
-
资源集中管理:资源不由使用资源的两方管理,而是由不使用资源的第三方管理,要使用资源只需要去IOC容器当中获取就可以。
-
降低耦合程度:创建实例的时候不需要了解其中的细节,降低了使用资源双方的依赖程度,也就是降低了耦合程度。
下面通过一个项目来综合练习一下
2.2. Bean的存储
存储Bean共有两类注解:
- 类注解:@Controller、@Service、@Repository、@Component、@Configuration.
- 方法 注解:@Bean.
在编写代码的过程中我们会把代码分层,分为三层,控制层,业务逻辑层,数据访问层 - Controller:控制层,负责接收前端发送的数据,对请求进行处理,返回响应。
- Service:业务逻辑层,负责业务逻辑处理,比如处理用户发来的请求。
- Dao:数据访问层,也称为持久层,负责执行CRUD语句
Service和Dao层的实现类交给Spring来管理,使用:@Component;
Service和Controller层注入运行时依赖的对象,使用:@Autowired。
2.3. @Controller(控制器存储)
ok,现在已经把这个对象存储到Spring当中了,前面说到Spring是一个容器,如何看这个对象是否真的被存进去了呢?
java
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
//获取Spring上下⽂对象
ApplicationContext context = SpringApplication.run(DemoApplicatio)
//从Spring上下⽂中获取对象
MyController controller = context.getBean(MyController.class);
//使⽤对象
controller.sayHi();
}
}
运行后可以看到结果
上面使用的是按类型查找对象,那么如果一个类型有多个Bean该怎么获取呢?
java
public interface BeanFactory {
// 1. 根据bean名称获取bean
Object getBean(String var1) throws BeansException;
// 2. 根据bean名称和类型获取bean
<T> T getBean(String var1, Class<T> var2) throws BeansException;
// 3. 按bean名称和构造函数参数动态创建bean,只适⽤于具有原型(prototype)作⽤域的bean
Object getBean(String var1, Object... var2) throws BeansException;
// 4. 根据类型获取bean
<T> T getBean(Class<T> var1) throws BeansException;
// 5. 按bean类型和构造函数参数动态创建bean, 只适⽤于具有原型(prototype)作⽤域的bean
<T> T getBean(Class<T> var1, Object... var2) throws BeansException;
}
常用的是1,2,4 这三种方法获取到的bean是一样的。1和2都涉及到bean的名称。那么这bean名称是随便命名的吗?
2.4. Bean的命名方式
Spring bean是Spring框架在运行时管理的对象,Spring会给bean起一个名字。就像学生在学校都会有学号,学校只需要查找学号就能知道学生的身份信息。Spring也会给这些bean取一个对象名(beanId),通过beanId就能找到bean。我们开发人员不必自己进行命名,Spring会自动进行命名:比如存入的bean的名字:MyController,Spring命名会变成myController,也就是首字母小写的驼峰式大小写;特殊情况,如果首字母和第二个字母都是大写,那么Spring会直接使用bean的名字例子:UController,命名后:UController。
2.5. 获取bean
java
//!!注意:如果遇到contex.getBean,点不出来的的时候要看看导的包对不对!!
import com.example.demo.demos.web.Controller.MyController;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ApplicationContext;
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
ApplicationContext context =
SpringApplication.run(DemoApplication.class, args);
//根据类型获取bean
MyController myController1 = context.getBean(MyController.class);
//根据名称获取bean
MyController myController2 = (MyController)context.getBean("myController");
//根据类型和名称获取bean
MyController mycontroller3 = context.getBean("myController",MyController.class);
System.out.println("根据类型获取bean:" + myController1);
System.out.println("根据名称获取bean:" + mycontroller3);
System.out.println("根据名称和类型获取bean:" + myController2);
}
}
2.6. ApplicationContext和BeanFactory的区别?
-
从继承和功能上来看:Spring容器有两个顶级的接口:ApplicationContext和BeanFactroy。BeanFactory提供了最基本的访问容器的能力,而ApplicationContext继承了BeanFactory,所以包含BeanFactory的所有功能,但是增加了国家化支持,资源访问支持等。
-
从性能方面来说:ApplicationContext会初始化并加载所有bean,BeanFactory是需要哪个bean才会加载哪个,所以BeanFactory的性能更高(空间换时间)。
2.7. @Service(服务存储)
java
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
ApplicationContext context =
SpringApplication.run(DemoApplication.class, args);
//按名称获取
UserService service1 = (UserService)context.getBean("userService");
//按类型获取
UserService service2 = context.getBean(UserService.class);
//按类型和名称获取
UserService service3 = context.getBean("userService",UserService.class);
System.out.println("1:"+service1);
service1.sayHi();
System.out.println("2:"+service2);
System.out.println("3:"+service3);
}
}
还有:@Repositary (仓库存储),@Configuration(配置存储),@Component(组件存储),代码上都是一样的,不再一 一展示。
2.8. 这五个类注解的作用是什么?
@Controller:控制层,负责与前端进行交互(接收前端发送的数据,把数据传回给前端)。
@Service:业务逻辑层,负责处理前端发送来的数据或者请求。
@Repository:持久层,负责与数据库进行交互。
@Configuration:配置层,包含项目中的一些配置信息。
这四个注解让人很直观的能看出来是做什么用的。
那还有一个注解:@Component呢?
先来看一下@Controller注解的底层代码,可以发现包含@Component,上面剩下三个注解底层也都包含这个注解,不一 一展示了。说面这四个注解是@Component的 "子类",@Component属于元注解,也就是说它可以注解其他类注解,剩下四个注解属于它的衍生注解。
举个例子:@Component类似于杯子,而喝水时我们通常使用水杯,刷牙时通常使用牙杯。水杯和牙杯就是@Controller等注解。
2.9. 方法注解@Bean
类注解有两个问题:
- 想使用不同包下的类无法使用类注解。
2.一个类需要多个对象,需要多个数据源无法使用类注解。
下面来演示一下,注意@Bean需要搭配方法注解使用!!
2.10. 定义多个对象
所以,当有多个相同类型的对象时不能使用按类型查找,要使用按类型和名称查找和按名称查找,演示用的是按类型和名称。
2.11. 给bean重命名
此时使用u1和user1都可以获取到。
只有一个名称的时候大括号也可以省略不写
3. Di
上面介绍了控制反转,也就是IOC,下面介绍一下DI,DI是依赖注入,简单来说就是把对象取出来放到某个类的属性当中。
依赖注入有三种方式:
-
属性注入;
-
构造方法注入;
-
Setter方法注入。
3.1 属性注入
3.2 构造方法注入
3.3 setter方法注入
3.4 三种注入方法的优缺点是什么?
1. 属性注入
优点:代码简单
缺点:
-
只能用于IOC容器,只有在使用过程中才会出现NPE(空指针异常)
-
不能注入一个final修饰的属性
2. 构造方法注入(Spring 4.X时推荐)
优点:
-
可以注入一个final修饰的属性。
-
注入的依赖对象不能被改变
-
依赖对象在使用前一定会被完全初始化,因为依赖是在类的构造方法中执行的,而构造方法是类在加载阶段就会完成的方法。
-
通用性好,因为构造方法是JDK所支持的,其他任何框架都能适用。
缺点:多个对象注入的时候代码繁琐。
3. Setter方法注入(Spring 3.X时推荐)
优点:方便在类实例化之后,重新对该对象进行配置或注入。
缺点:
-
不能注入一个final修饰的属性。
-
注入对象可能会被修改,因为setter方法可能会被多次调用。
3.5 @Autowired存在的问题
可以看到,当有多个相同类型的对象时再使用@Autowried注解就不好用了。
为了解决这个问题,下面再引入三个注解:@Primary,@Qualifier,@Resource
@Primary注解放到某个@Bean修饰的注解上时意味着默认注入该对象。
@Qualifier注解不能单独使用,必须搭配@Autowired使用,可以指定bean的名称注入
@Resource注解支持很多,也可以指定bean名称注入,但是不用必须搭配@Autowired。
3.6 @Autowired和@Resource的区别
-
@Autowired是Spring提供的注解,@Resource是JDK提供的注解。
-
@Autowired按类型注入,@Resource按名称注入,并且@Resource支持很多种注入方式。
4.图书管理项目
根据前面的介绍,做一个综合练习。
可以看到Controller层创建了两个class文件。还创建了一堆html,css,JS(JavaScript)文件。这些都是前端文件,前端文件统一放在static包下,html文件中主要在写一个网页页面有哪些内容,以及业务逻辑,css是对html中声明的内容进行美化,比如html中编写了"你好"这两个字,css可以改变你好这两个字的大小,颜色等等。
如果需要这些源文件的可以在评论区留言,如果没人看这篇博客笔者也没必要花时间弄链接,笔者写博客是为了归纳总结已学习过的知识。
下面是后端代码
java
//BookController代码:
import com.example.demo.demos.web.BookInfo;
import com.example.demo.demos.web.Service.BookService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
@RequestMapping("/book")
@RestController
public class bookController {
@Autowired
private BookService bookService;
@RequestMapping("/getList")
public List<BookInfo> getList(){
List<BookInfo> books = bookService.getBook();
return books;
}
}
java
//UserController代码:负责登录验证
@RequestMapping("/user")
@RestController
public class UserController {
@RequestMapping("/login")
public Boolean login(String name, String password, HttpSession session){
if(!StringUtils.hasLength(name) || !StringUtils.hasLength(password)){
return false;
}
if("1".equals(name) && "1".equals(password)){
session.setAttribute("userName",name);
return true;
}
return false;
}
}
java
import com.example.demo.demos.web.BookInfo;
import org.springframework.stereotype.Repository;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
@Repository
public class BookDao {
public List<BookInfo> mockData() {
List<BookInfo> bookInfos = new ArrayList<>();
for (int i = 0; i < 5; i++) {
BookInfo book = new BookInfo();
book.setId(i);
book.setBookName("书籍" + i);
book.setAuthor("作者" + i);
book.setCount(i * 5 + 3);
book.setPrice(new BigDecimal(new Random().nextInt(100)));
book.setPublish("出版社" + i);
book.setStatus(1);
bookInfos.add(book);
}
return bookInfos;
}
}
java
import com.example.demo.demos.web.BookInfo;
import com.example.demo.demos.web.Dao.BookDao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
public class BookService {
@Autowired
private BookDao bookDao;
public List<BookInfo> getBook(){
List<BookInfo> books = bookDao.mockData();
for (BookInfo bookInfo:books) {
if(bookInfo.getStatus() == 1){
bookInfo.setStatusCN("可借阅");
}else {
bookInfo.setStatusCN("不可借阅");
}
}
return books;
}
}
效果展示:
可以看到这个项目还是很简陋的,正常验证用户密码和用户名是否匹配和书籍信息都是应该要去数据库中查找的,但是这些内容现在的知识是没办法实现的,后面随着笔者介绍MyBatis框架之后就可以把数据库相关操作加入进来。