文章目录
- 什么是Spring
- Spring项目的创建
- 存储Bean对象
- 读取Bean对象
- getBean()方法
- 更简单的读取和存储对象的方式
- 路径配置
- 使用类注解存储Bean对象
- 关于五大类注解
- 使用方法注解@Bean存储对象
- @Bean重命名
- Bean对象的读取
- 使用@Resource注入对象
- @Resource VS @Autowired
- 同一类型多个bean对象的读取问题
什么是Spring
Spring一般是指Spring Framework,即Spring框架。它是一个强大的java开发框架,可以支持多种应用场景;通过使用Spring框架,可以极大程度地简化开发流程,提高开发效率。
常见的对Spring的概括是:Spring是包含了众多工具方法的Ioc容器;
包含了“众多工具方法”不难理解,那么什么是所谓的Ioc容器呢?
Ioc实际上就是Inversion Control,即控制反转。在传统的开发模式中,对于在A类中使用B类这样一个场景,就需要在A类中创建B类的对象。这样,关于这个被创建的B类的对象,A就可以控制这个对象的所有行为,包括创建、使用、销毁。而如果是使用控制反转的开发模式,就是将这个对B类对象的控制权交出去,交给Spring去控制。也就是说,控制反转实际就是控制权的反转。
很明显,传统的开发模式存在一定的问题,即当代码或程序之间的调用关系过于复杂,就会存在修改了一个程序的代码之后,可能就需要修改对应的调用链上的一系列代码。但控制反转的开发模式则是可以很好地解决这个问题,实现代码之间的解耦。
可以使用代码来进行理解:
既然Ioc就是控制权反转的意思,那么容器又是指什么呢?在日常生活中的容器,就是用来容纳某种东西的一个装置,容器最大的作用也就是存和取。因此对于这样一个Ioc容器而言,两个最基础的功能应该也就是:存入对象到容器、从容器取出对象;
而对于Spring而言,将对象存储到Spring中,再根据需要从Spring中获取对象的过程其实也就是它最核心的功能或步骤;
Spring项目的创建
- 创建一个Maven项目;
- 添加需要的Spring框架依赖到pom.xml,包括Spring上下文和spring对象;
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.3.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
<version>5.2.3.RELEASE</version>
</dependency>
</dependencies>
添加完成以后一定要进行刷新,确保依赖已经下载成功;
如上,表示下载成功;
- 在java文件夹下创建一个启动类(包含main方法即可);
至此,一个spring文件就创建成功了!
存储Bean对象
首先就是spring的第一个关键功能,存储对象到spring中,具体操作如下:
- 创建一个Bean对象;
Bean对象实际也只是java中的一个普通对象,创建在java文件夹下即可:
- 将bean对象注册到spring中;
在resources文件夹下创建一个xml文件,加入下面的内容:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
</beans>
(上面的格式是固定内容,不需要记忆)
将之前创建的bean对象注册到spring中:
id是对象的标识,在之后取对象时会用到;class是指明了bean对象的位置(包名+类名);
读取Bean对象
- 得到Spring上下文;
- 从spring上下文获取bean对象;
- 根据需要使用bean对象;
package com.yun;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class Start {
public static void main(String[] args) {
//1.得到spring上下文对象
ApplicationContext context=
new ClassPathXmlApplicationContext("spring-config.xml");
//2.从spring上下文中取出bean对象
User user=(User) context.getBean("user");
//3.使用bean对象
user.fun();
}
}
在得到spring上下文对象时,需要指明对应的spring配置文件;
从spring上下文获取bean对象时,括号中的内容需要与配置文件中id一一对应;
getBean()方法
关于读取Bean对象的getBean()方法,其实还有多种重载方法可以来获取Bean对象:
- 直接根据对象的id(名称)来获取bean对象;
- 根据类型来获取bean对象;
- 使用名称+类型的方式来获取bean对象;
使用代码进行演示:
三种方式各有优劣,一般来说,如果使用名称获取的方式,必须保证在配置文件中的id是唯一的;如果使用类型的方式获取,当出现同一个类型被多次注册到配置文件中时(即一下面所示的情况),程序就会出错;
更简单的读取和存储对象的方式
关于前面整个Bean对象的存储和读取的过程,实际还是较为繁琐的;为了简化其流程,我们使用注解来完成一种更加简单的存储和读取过程;
路径配置
在进行更简单的方式之前,我们首先需要完成Bean对象扫描路径的配置工作:
即在spring-config.xml中添加下面的代码片段:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:content="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd">
<content:component-scan base-package="com.yun">
</content:component-scan>
</beans>
需要根据自己的代码的具体路径来修改上面图片红色方框中的路径,这实际就是Spring的扫描路径,只有在该路径下的Bean对象才会被存储到Spring中;
使用类注解存储Bean对象
在Spring中,提供了五大类注解可以实现将对象存储到Spring,分别是@Controller @Service @Repository @Component @Configuration ;
- @Controller
该注解主要是负责控制器存储,使用代码演示:
package com.yun.controller;
import org.springframework.stereotype.Controller;
@Controller
public class UserController {
public void sayHello(){
System.out.println("Hello~Controller");
}
}
此时就可以读取到这里存储的UserController对象:
public class App {
public static void main(String[] args) {
//1.获取到Spring上下文对象
ApplicationContext context=new ClassPathXmlApplicationContext("spring-config.xml");
//2.从Spring上下文取出bean 对象
// 使用注解默认的命名规则是小驼峰
UserController userController=context.getBean("userController",UserController.class);
//3.使用bean对象
userController.sayHello();
}
}
@Controlller注解使用中文翻译就是控制器的意思,主要是负责验证前端传递过来的参数,起到一个“安全检查”的作用;
- @Service
该注解是负责服务存储;
package com.yun.service;
import org.springframework.stereotype.Service;
@Service
public class UserService {
public void helloSer(){
System.out.println("Hello~Service");
}
}
@Service注解主要是负责了服务调用的编排和汇总;
- @Repository
该注解负责仓库存储;
package com.yun.repository;
import org.springframework.stereotype.Repository;
@Repository
public class UserRepository {
public void doRepository(){
System.out.println("Hello~Repository");
}
}
@Repository该注解的中文意思为仓库,使用该注解可以直接操作数据库;
- @Component
该注解负责组件存储;
package com.yun.component;
import org.springframework.stereotype.Component;
@Component
public class UserComponent {
public void doComponent(){
System.out.println("Hello~Component");
}
}
@Component 该注解表示了组件的意思,主要是负责一些通用化的工具类;
- @Configuration
该注解负责配置存储;
package com.yun.configuration;
import org.springframework.context.annotation.Configuration;
@Configuration
public class UserConfiguration {
public void doConfiguration(){
System.out.println("Hello~Configuration");
}
}
@Configuration该注解表示配置,负责了项目所需要的相关的所有配置;
使用五大类注解存储Bean对象的方法如上,而关于使用相关注解的Bean对象的读取,也是首先获取Spring的上下文,再通过上下文得到bean对象,最后使用bean对象;
关于五大类注解
- 首先,为什么需要如此多的类注解呢?
如果是单从上面使用类注解来存储Bean对象的操作来看,似乎每一个类注解起到的作用都是相同的。但在实际的业务开发中,使用不同的类注解可以清晰的表明当前类的用途,这也就是前面说到不同的类注解负责的业务或模块是不同的;
- 五大类注解之间的关系?
如果我们尝试去溯源五大注解的源码,就会发现:
可以看到, @Controller / @Service / @Repository / @Configuration四个注解的实现实际上都借助了@Component注解来实现,所有它们之间的关系也就显而易见,即可以简单地理解为前面四种注解是@Component的子类;
- 使用五大类注解时Bean对象的命名
在上面的代码中,我们关于类名都是使用了大驼峰的方式进行标准的命名,在读取bean时则是默认使用了首字母小写的方式,最后也如愿读取成功了,关于这样使用的原因,我们同样可以溯源到相关的源码:
bean的命名方式在默认情况下使用类名首字母小写的方式进行;
特殊情况下,当类名的前两个字母均为大写的情况下,bean的命名直接使用原类名即可;
使用方法注解@Bean存储对象
方法注解@Bean,顾名思义就是使用在方法上的;方法注解正常使用的前提是:搭配类注解一起使用;
使用代码进行演示:
package com.yun.controller;
import com.yun.model.User;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;
@Component
public class UserBeans {
@Bean
public User user(){
User user=new User();
user.setId(1);
user.setName("张三");
user.setAge(18);
return user;
}
}
使用方法注解存储的Bean对象,在后续使用Bean对象时,是直接使用方法名来命名Bean对象;
另外,关于方法注解,只能使用在无参的方法上,因为Spring在初始化存储时,无法提供相应的参数;
@Bean重命名
当然,除了直接使用方法名,在Spring中关于方法注解的使用,还可以通过为Bean对象设置name属性来达到重命名的目的;
重命名的设置方法有三种方式:
- 显示使用name属性重命名
- 直接使用双引号重命名
- 显式使用name属性进行多个重命名;
当然,在对Bean进行了重命名以后,就不能再使用原来的方法名获取Bean对象了;
Bean对象的读取
Bean对象的读取即,将对象读到以后放到某个类中,也称为对象装配或对象注入;
对象注入的方式有下面3种:
- 属性注入
借助@Autowired注解实现,使用代码演示:
service部分的原始代码:
package com.yun.service;
import org.springframework.stereotype.Service;
@Service
public class UserService {
public boolean helloSer(){
System.out.println("Hello~Service");
return true;
}
}
将UserService注入到UserController类中:
package com.yun.controller;
import com.yun.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.stereotype.Repository;
@Controller
public class UserController {
//将service中的UserService注入到了该类中
@Autowired
private UserService userService;
public void sayHello(){
System.out.println(userService.helloSer());
}
}
使用bean对象进行验证注入是否成功;
import com.yun.controller.UserController;
import com.yun.model.User;
import com.yun.repository.UserRepository;
import com.yun.service.UserService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class App {
public static void main(String[] args) {
//1.获取到Spring上下文对象
ApplicationContext context=new ClassPathXmlApplicationContext("spring-config.xml");
//2.从Spring上下文取出bean 对象
UserController userController=context.getBean("userController",UserController.class);
//使用bean对象
userController.sayHello();
}
}
运行结果:
- Setter注入
需要在属性的set方法上加上@Autowired注解来实现;
service中的代码基本保持不变;
package com.yun.service;
import org.springframework.stereotype.Service;
@Service
public class UserService {
public boolean helloSer(){
System.out.println("setter~Service");
return true;
}
}
在controller中改变注入对象的方式:
package com.yun.controller;
import com.yun.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.stereotype.Repository;
@Controller
public class UserController {
//将service中的UserService注入到了该类中
private UserService userService;
@Autowired
public void setUserService(UserService userService){
this.userService=userService;
}
public void sayHello(){
System.out.println(userService.helloSer());
}
}
最后进行验证的代码与前面相同,下面是具体的运行结果:
- 构造方法注入
构造方法注入是在当前类的构造方法中实现注入,同样使用到了@Autowired注解;
package com.yun.controller;
import com.yun.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.stereotype.Repository;
@Controller
public class UserController {
//将service中的UserService注入到了该类中
private UserService userService;
@Autowired
public UserController(UserService userService){
this.userService=userService;
}
public void sayHello(){
System.out.println(userService.helloSer());
}
}
其余部分的代码与前面基本相同;
三种对象注入的方式各有特点,下面是对其各自优缺点的分析:
- 属性注入
优点:
- 代码简洁,使用方便;
缺点:
- 只适用于IOC容器,代码的可移植性不强;
- 无法注入不可变的对象(final修饰的对象);
- 容易违反单一设计原则;
使用属性注入的方式,代码量少,使用方便又简单;但也是由于这一点,违反单一设计原则、代码滥用的概率也相应增加;同时由于spring 是基于java环境实现,也必须遵守final关键字的使用规范,即不可以使用属性注入一个final修饰的对象。
- setter注入;
优点:
- 符合单一设计原则;
缺点:
- 无法注入一个不可变的对象;
- 注入的对象存在被修改的概率;
在spring 4.2之前,这是官方推荐使用的注入方式,它遵循了单一设计原则;但由于set方法在代码中可能被多次调用,也相应地被修改的概率要更大。
- 构造方法注入;
优点:
- 可以注入final修饰的对象;
- 注入的对象没有被修改的概率;
- 所依赖的对象在使用之前就会被完全初始化;
- 代码的通用性更强;
缺点:
- 当有多个注入时,代码略显臃肿;
在spring 4.2 之后,构造方法注入成为了官方推荐的注入方式。由于构造方法是会在类创建之初执行一次,因此使用这种方式注入的对象不会被修改,同时对象在使用之前就进行了初始化;另外因为构造方法是由JDK支持实现,因此使用这种方式注入的代码的通用性要更强。
进行对象的注入,除了使用前面提到的@Autowired注解,实际还有一个注解同样可以实现对象的注入;
使用@Resource注入对象
同样使用代码来演示@Resource的对象注入方式;
- 属性注入;
创建一个UserService2类;
package com.yun.service;
import org.springframework.stereotype.Service;
@Service
public class UserService2 {
public void doService(){
System.out.println("Do Service!");
}
}
将上面创建的对象使用@Resource注入到UserController2中;
package com.yun.controller;
import com.yun.service.UserService2;
import org.springframework.stereotype.Controller;
import javax.annotation.Resource;
import java.nio.file.attribute.UserPrincipalLookupService;
@Controller
public class UserController2 {
@Resource
public UserService2 userService2;
public void doController(){
userService2.doService();
}
}
- setter注入;
package com.yun.controller;
import com.yun.service.UserService2;
import org.springframework.stereotype.Controller;
import javax.annotation.Resource;
import java.nio.file.attribute.UserPrincipalLookupService;
@Controller
public class UserController2 {
private UserService2 userService2;
@Resource
public void setUserService2(UserService2 userService2) {
this.userService2 = userService2;
}
public void doController(){
userService2.doService();
}
}
直接看运行结果:
- 构造方法注入
可以看到@Resource注解不能使用在构造方法注入的实现上;
@Resource VS @Autowired
既然两种注解都可以实现对象的注入,那么它们又具体有什么区别呢?
- 方法数量上;
首先从java为两种注解提供的方法就可以发现,@Resource注解是包含了有众多的方法,而@Autowired则只有一个方法;
可以看到@Resource相对于@Autowired而言,支持更多的参数设置
- 匹配对象的顺序;
@Autowired在从spring中查找相应的bean对象时,首先会根据对象的类的类型进行匹配,在未匹配成功的情况下,继续使用bean的名称来匹配;
@Resource则是首先进行bean名称的匹配查找,后进行类型的查找;
- 出身来源不同;
@Autowired是由spring提供的,而@Resource则是属于JDK的注解;
同一类型多个bean对象的读取问题
在一些特定的场景中,可以需要将同一类型的多个bean对象存储到spring中,在这种情况下,对于bean对象的读取必然会出现下面的错误:
首先创建一个类,这个类中包含了多个User类型的对象,再将User类型的对象使用方法注解存储到spring中;
package com.yun.controller;
import com.yun.model.User;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;
@Component
public class UserBeans {
@Bean
public User user1(){
User user=new User();
user.setId(1);
user.setName("张三");
user.setAge(18);
return user;
}
@Bean
public User user2(){
User user=new User();
user.setId(2);
user.setName("李四");
user.setAge(19);
return user;
}
@Bean
public User user3(){
User user=new User();
user.setId(3);
user.setName("王五");
user.setAge(20);
return user;
}
}
将User类注入注入到该类中:
package com.yun.controller;
import com.yun.model.User;
import org.springframework.stereotype.Controller;
import javax.annotation.Resource;
@Controller
public class UserController3 {
@Resource
private User user;
public void doCon(){
System.out.println(user.getName()+" "+user.getAge());
}
}
在读取时发现出现了下面的报错信息:
通过报错信息,我们找到产生问题的原因就是:我们此处的bean对象不是唯一的,在同一类型下找到了多个匹配的bean对象,下面是相关的解决办法:
-
使用@Resource注解注入,通过设置参数指定bean对象;
-
使用@Autowired注解注入,搭配@Qualifier 注解一起使用;
使用上面两种方式修改代码,即可得到正确的运行结果;
至此,关于Spring的创建以及将对象如何存储到Spring中,再从Spring中读取的全过程就介绍完毕啦~