目录
Spring是什么?
在javaEE当中,我们不得不提到Spring框架这个东西,
那么Spring框架究竟指的是什么东西呢?
在Spring的官网当中,我们可以看到一个醒目的标题:
“Spring makes Java Simple.”
正如他的标题所描述的,在Java的编程中,使用Spring能够让我们更快速,更容易,并且更安全的完成开发。Spring专注于速度,便捷与开发效率,正式因为这一点,也让Spring成为了全世界最流行的Java框架。
Spring Framework
Spring当下有许多不同的框架但是其中最核心最基础的部分就是Spring Framework。Spring当中其他的大部分的框架都依赖于Spring Framework。
以下是Spring Framework的运行时的模块框架:
在官方介绍中也可以查看对于各个模块的一个详细的介绍!
对于Spring Framework来说,他是我们学习、和使用整个Spring生态项目(Spring Boot、Spring Cloud等)的一个基石,也就是说,我们想要引入其他的Spring项目作为我们的依赖框架时,也会使用Spring Framework。
对于上面的运行时模块中,本文将围绕其中最重要的三个部分进行介绍:Core Container、AOP 、WebMVC。
Core Container(核心容器)
通过上图可以看到,Core Container模块总共由四部分:spring-beans、spring-core、spring-context、spring-spEL(spring expression language)四个模块。
我们学习spring-core和spring-beans模块,这两个模块提供了Spring框架最基础的设施,IoC(Inversion of Control,控制反转) 和 DI(Dependency Injection,依赖注入)。
这两部分功能相当于我们所有Spring框架的一个基础。
IOC容器
什么是IOC?
IOC/DI容器,是Inversion of Control的缩写,中文意译“控制反转”,
在我们传统的项目开发中,我们需要手动的去new对象,然后由对象的作用域来决定对象的生存周期,使用Spring之后,由框架提供了统一的容器来进行实例化,管理这些对象,并自动的组织对象与对象之间的关系。 这个容器,就是IOC容器,有些地方也叫做Spring Bean容器,Spring容器。
IOC的具体功能是取代我们的程序来控制和管理我们对象得生命周期、依赖关系等。所以叫做“控制反转”。控制权的转换。
什么是DI?
DI(Dependency Injection,依赖注入)是实现IOC的方法之一,所谓的依赖注入,就是在运行期间,通过IOC容器,动态的将某种依赖关系注入到对象当中。
所以,依赖注入(DI)和控制反转(IOC)是在不同的角度描述的同一件事情,就是通过引入IOC容器,利用依赖注入的方式,实现对象和对象之间的“解耦”。
什么是“解耦”?
如果不使用IOC,利用传统的方式来管理对象,那么对象之间的关系会非常复杂,难以管理。
利用IOC容器进行对象的管理,对象和对象之间没有直接的建立关系,而是通过一个中间容器,IOC容器有时候也被人们称之为“粘合器”。
注册Bean
在使用Spring IOC容器之前,需要先进行Bean的注册,也就是说,将我们所需要使用到的对象注册到我们的容器当中。才能在容器当中进行管理。
在Spring当中提供了三种注册Bean的方式:
注册Bean方式一:类注解
通过在类上使用以下的四个注解:
@Controller,@Service,@Repository,@Component,
在类上使用了这四个注解之后,该类就会被Spring扫描到,这种通过类注解的方式会默认的注册一个名称为类名首字母小写的Bean对象到容器中。
如下:
注册好后,我们在APP入口类胆总管红,就可以通过ApplicationContext对象获取到Bean,那么对于类注解的对象获取的方式有两种:
1、通过类型获取:这种获取方式要求,容器中该类型的Bean对象只能有一个。
2、通过名称获取:同一个类型的Bean可以有多个,通过对象的名称来获取。
//根据Spring配置文件路径创建容器:应用上下文对象
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
//在入口APP中获取刚刚定义好的并注册到容器中的bean对象,
//方法一:通过id/名称获取,这样的同一个类型的bean可以有多个,返回值为object类型,需要强制转换
LoginRepository loginRepository1 = (LoginRepository) context.getBean("loginRepository");
//方法二:通过类型获取,这种获取方式要求该类型的bean只能有一个。通过.class的方式。
LoginRepository loginRepository2 = context.getBean(LoginRepository.class);
//打印出来的是这两个对象的地址,打印结果会是相同的,因为这两个东西,获取到的是同一个对象。
System.out.println(loginRepository1);
System.out.println(loginRepository2);
获取到的两个对象是同一个。
注册Bean的方式二:@Bean
当当前的类被Spring扫描到时,可以方法上使用@Bean注解,通过方法的返回值类型,将该类型的对象,注册到Bean容器中,在使用该方法时,默认使用方法名作为Bean的名称。
首先定义一个实体类,注意,该类并没有使用类注解,所以不会被Spring扫描到。
import lombok.Setter;
import lombok.ToString;
/**
* @Name: Duck
* @Description: 创建一个实体类Duck
* @Author: panlai
* @Date: 2021/8/20 21:40
*/
@Getter
@Setter
@ToString
public class Duck {
public String name;
}
然后我们在一个使用了类注解的类当中,在他的方法上使用@Bean注解,方法的返回值会作为Bean对象注册到容器中。
例如,在LoginController中,获取到Duck对象,那么返回的d1对象就会被注册到容器中。
package org.example.controller;
import org.example.model.Duck;
import org.example.service.LoginService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Controller;
/**
* @Name: LoginController
* @Description:
* @Author: panlai
* @Date: 2021/8/20 21:34
*/
@Controller
public class LoginController {
//注册对象到容器中的放法二:
//通过在方法上使用@Bean注解,那么方法的返回值就是要注册到容器中的对象,
//只要使用Spring注解,且能够被扫描到的类,都可以使用本方法
//在@Bean注解的方法上,也会将容器中的Bean注入到方法参数上。
@Bean
public Duck d1(LoginService service){
System.out.println("loginController d1(): " + service);
Duck duck = new Duck();
duck.setName("LadyGaga"); //母鸭子
return duck;
}
@Bean
public Duck d2(){
Duck duck= new Duck();
duck.setName("femaleDuck"); //真母鸭子
return duck;
}
}
然后我们再在入口APP中尝试获取这两个对象:
注意,此时获取对象只能通过名称来获取,因为通过类型来获取的话,我们系统会得到多个对象d1,d2,他不知道究竟该获取哪个,所以就会报错。
//同一个类型注册了多个对象,只能通过名称获取,
Duck d1 = (Duck) context.getBean("d1");
Duck d2 = (Duck) context.getBean("d2");
System.out.println("d1:" + d1 +" d2:" + d2);
//通过类型获取会报错。此处会报错:NoUniqueBeanDefinitionException,Bean的定义不唯一,期望只有1个Bean但是却找到了2个.
// Duck duck3 = context.getBean(Duck.class);
// System.out.println("duck3:" +duck3);
输出结果:
注册Bean的方式三:@Configuration
通过@Configuration注解,可以注册一个配置类到容器中,配置类一般是用来自定义配置某些资源,之后会在SpringMVC中用到。
package org.example.config;
import org.springframework.context.annotation.Configuration;
/**
* @Name: AppConfig
* @Description: 注册bean的方式三@Configuration
* 使用@Configuration注解,可以注册一个配置类到容器中,
* 配置类一般用来自定义配置某些资源,之后会在 SpringMVC中用到。
* @Author: panlai
* @Date: 2021/8/22 17:43
*/
@Configuration
public class AppConfig {
}
以上,就是Spring中注册Bean对象到容器中的方式,那么,我们将Bean对象注册到容器中后,如何通过容器来管理我们对象之间的依赖呢?这就需要介绍下面的依赖注入。
依赖注入
依赖注入是为了给我们容器当中的对象之间设定一定的依赖关系为目的的。
比如说,在容器中,一个对象是另一个对象的属性,这两个对象之间就满足了“依赖”的关系,那么我们为了实现这样的关系,需要进行依赖注入。
依赖注入的方式一:属性注入
当类被spring扫描到时,可以在属性上使用@Autowired注解,会将容器中的Bean对象装配给这个类。
例如:我们给LoginService这个类的对象注入一个LoginRepository这样的一个属性,直接在类中添加一个属性,然后再属性的前面加上@Autowired注解即可。
package org.example.service;
import org.example.dao.LoginRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class LoginService {
@Autowired
private LoginRepository loginRepository;
}
属性注入中还有另外一种方式,我们知道java当中为了保证封装,一般类的属性都会设置为私有的,然后通过getter和setter方法来获取和设置类的属性,那么:
方法注入的方式,就是通过在setter方法上使用@Autowired注解:
package org.example.service;
import org.example.dao.LoginRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class LoginServiceBySetter {
private LoginRepository loginRepository;
public LoginRepository getLoginRepository() {
return loginRepository;
}
@Autowired
public void setLoginRepository(LoginRepository loginRepository) {
System.out.printf("LoginServiceBySetter: loginRepository=%s%n",
loginRepository);
this.loginRepository = loginRepository;
}
}
依然能够实现依赖关系的注入,将容器中的Bean对象注入方法的参数当中,通过给set方法设置参数,然后设置依赖关系,其实本质上也是属于对于属性的设置,所以这样的方式也属于属性注入。
依赖注入的方式二:构造方法注入
当当前类被Spring扫描到时,可以再构造方法上使用@Autowired注解,作用其实与setter方法类似,会将容器中的Bean对象注入方法参数。
package org.example.service;
import org.example.dao.LoginRepository;
import org.example.model.Duck;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Service;
/**
* @Name: LoginService
* @Description:
* @Author: panlai
* @Date: 2021/8/20 21:34
*/
@Service
public class LoginService {
// @Autowired //通过@Autowired或者@Resource注解来给对象注入属性
// private LoginRepository loginRepository;
private LoginRepository loginRepository;
@Autowired //在构造方法中也能够完成属性的注入
public LoginService(LoginRepository loginRepository) {
System.out.println("LoginServiceByConstructor : "+loginRepository);
this.loginRepository = loginRepository;
}
}
此外,使用方法注入依赖的过程中要注意:
当我们需要注入的Bean对象有多个时,单单指出对象的类型是不够的,比如:
package org.example.service;
import org.example.model.Duck;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Service;
/**
* @Name: LoginService
* @Description:
* @Author: panlai
* @Date: 2021/8/20 21:34
*/
@Service
public class LoginService {
@Autowired //容器中有多个Duck类型的对象,d1,d2 luYa 要注入哪个呢?不知道
public Duck duck;
}
由于我们刚刚在LoginController中注册了两个Duck类型的对象到我们的容器中,所以此时单单的使用类型无法判断我们要进行注入的对象具体是哪个对象。
我们在入口中尝试去获取到这个对象试试:
就会提示我们当前该类型的对象注入时期望之后一个但是却找到了两个,这个错误类似于我们通过类型来获取这个Bean对象一样,我们只需要获取到一个,但是却找到了两个我们不知道具体应该是注入哪个对象。
那么如何解决这样的问题呢?
注入指定的Bean:@Qualifier
我们只需要在需要输入的对象前,使用@Qualifier在加上一个该Bean对象的名称即可,比如,我们刚刚容器中已经注册了两个对象d1和d2,那我们如果要对d2这个对象进行依赖注入的话,直接在后面使用@Qualifier注解,并注明需要进行那来进行依赖注入的对象名称即可,如此:
package org.example.service;
import org.example.model.Duck;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Service;
/**
* @Name: LoginService
* @Description:
* @Author: panlai
* @Date: 2021/8/20 21:34
*/
@Service
public class LoginService {
@Autowired //容器中有多个Duck类型的对象,d1,d2 luYa 要注入哪个呢?不知道
@Qualifier("d2") //可以使用@Qualifier注解指明要注入的是哪个对象
public Duck duck;
}
如此以来,Spring就能明确找到我们需要进行依赖注入的对象具体是容器中的哪个对象了。该方法适用于容易中有多个同种类型的对象时使用。