关于Spring
Spring框架主要解决了创建对象 管理对象的问题
在传统的开发中,当需要某个对象时,使用new关键字及类型的构造方法即可创建对象,例如:
Random random =new Random();
如果以上代码存在于某个方法中,则random就只是个局部变量,当方法运行结束,此变量就会被销毁
在实际项目开发,许多对象被创建出来之后,应该长期存在于内存中,而不应该销毁,当需要使用这些
对象时,通过某种方式获取对象即可,而不应该重新创建对象!
除了对象存在的时间(时长)以为,在实际项目开发中,还需要关注各个类型之间的依赖关系!
例如:
public class UserMapper{
public void insert(){
//向数据表中的"用户表"中插入数据
}
}
public class UserCoontroller{
public UserMapper userMapper;
public void reg(){
userMapper.insert();
}
}
在以上示例代码中,可视为"UserController是依赖于UserMapper的",也可以把UserMapper称之为
"UserController的依赖项"
当需要创建以上类型时,如果只是单纯的把UserController创建出来了,却没有关注其内部
userMapper属性的值,甚至该属性没有值,则是错误的做法!
如果要使得UserController中的UserMapper属性是有值的,也非常简单,
例如:
public class UserController{
public UserMapper userMapper = new UserMapper();
public void reg(){
userMapper.insert();
}
}
但是, 在实际项目中,除了UserController以外,还会有其它的组件也可能需要使用到UserMapper
如果每个组件中都使用new UserMapper() 的语法来创建对象 就不能保证UserMapper对象的唯一
性,就违背了设计初衷 为了更好的创建和管理对象 应该使用Spring框架!
2 创建基于Spring的工程
当某个项目需要使用Spring框架时,推荐使用Maven工程
创建名为Spring01的Maven工程 创建完成后 会自动打开pom.xml 文件 首先应该此文件中添加
<dependencies> 节点 此节点用于添加依赖项的:
<dependencies>
</dependencies>
然后,Spring框架的依赖项的代码需要编写在以上节点的子级,而依赖项的代码推荐从
https://mvnrepository.com/网站查询的到,Spring的依赖项名称是spring-context,则此网站搜索
该名称,以找到依赖项的代码,代码示例:
<!-- https://mvnrepository.com/artifact/org.springframework/spring-context -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.3.14</version>
</dependency>
将以上依赖项的代码复制到<dependencies>节点之下即可
然后,点IDEA中悬浮的刷新Maven的图标,或展开有侧的Maven面板点击刷新按钮,即可自动下载所
需要的jar包文件(这些文件会被下载到本机的Maven仓库中,同一个依赖项的同一个版本只会下载
一次)
3 通过Spring创建对象--通过@Bean的方法
spring01
创建cn.tedu.spring.SringBeanFactory类
在类中添加方法,方法的返回值类型就是你希望Spring创建并管理的对象类型,并在此方法中自行
遍写返回有效对象的代码
在此类上添加@Configuration 注解
在此方法上添加@Bean注解
以上步骤的示例代码:
package cn.tedu.spring;
import org.springframework.context.annotation.Bean;
import org.springFramework.context.annotation.Configuration;
import java.util.Random;
@Configuration
public class SpringBeanFactory{
@Bean
public Random random(){
return new Random();
}
}
接下来 创建某个类用于执行:
public class SpringRunner{
public static void main(String[] args){
// 1. 加载Spring
AnnotationConfigApplicationContext ac
= new AnnotationConfigApplicationContext(SpringBeanFactory.class);
// 2. 从Spring中获取对象
Random random = (Random) ac.getBean("random");
// 3. 测试使用对象,以便于观察是否获取到了有效的对象
System.out.println("random > " + random);
System.out.println("random.nextInt() > " + random.nextInt());
// 4. 关闭
ac.close();
}
}
运行SpringRunner类的main()方法即可看到执行效果
以上代码注解:
在AnnotationConfigApplicationContext的构造方法中,应该将SpringBeanFactory.class作为参数
传入,否则就不会加载SpringBeanFacty类中内容
其实在以上案例中SpringBeanFactory类上的@Configuration注解并不是必须
在getBean()时,传入的字符串参数"random"是SpringBeanFactory类中的方法名称
在SpringBeanFactory类中的方法必须添加@Bean注解,其作用是使得Spring框架自动调用此方法
并管理此方法返回结果
关于getBean()方法,此方法被重载了多次 ,典型的有:
Object getBean(String beanName)
通过此方法,传入beanName必须是有效的,否则将导致NoSuchBeanDefinitonException
T getBean(Class<T> beanClass);
使用此方法时,传入的类型在Spring中必须有且仅有1个对象,如果没有匹配类型的对象,将导致
NoSuchBeanDefinitionException,如果有2个 将导致NoUniqueBeanDefinitionException
T getBean(String beanName,Class<T> beanClass)
此方法乃是根据传入的beanName获取对象,并且根据传入的beanClass进行类型转换
使用的@Bean注解可以传入String类型的参数,如果传入.则此注解对应的方法的返回结果的
beanName就是@Bean 注解中传入的String参数值
通过Spring创建对象--组件扫描
spring02
-
在
pom.xml
中添加spring-context
的依赖项 -
自行创建某个类,例如创建
cn.tedu.spring.UserMapper
类,并在类的声明之前添加@Component
注解 -
与前次案例相似,创建可执行的类,与前次案例的区别在于:
-
在AnnotationConfigApplicationContext 的构造方法中传入的是UserMappere类的包名 即:
-
AnnotationConfigApplicationContext ac=new AnnotationConfigApplicationContext("cn.tedu.spring");
-
调用getBean()时,传入的名称是将UserMapper类的名称的首字母改为小写, 即:
UserMapper userMapper = ac.getBean("userMapper",UserMapper.class);
以上代码注解:
在创建AnnotationConfigApplicationContext时传入的参数是一个basePackages,即多个"根包",它会
使得Spring框架扫描这个包及其子孙包中的所有类,并尝试创建这些包中的组件的对象
AnnotationConfigApplicationContext的构造方法设计的是String...类型的参数,即可变参数,当需要输
入多包名时,各包名使用逗号隔开即可
推荐传入的包名是更加具体的,但不需要特别精准,只需要保证不会扫描到非自定义的包即可,列如包
名肯定不会包含项目的依赖项的包
即使有了组件扫描,Spring也不会直接创建包下所有类的对象,仅当类上添加了组件注解,才会被
Spring视为"组件",Spring才会创建对应类的对象
当getBean()时,由Spring创建的组件类的对象 默认的名称都是将首字母该为小写
以上规则仅适用于: 类名中的第1个字母是大写,且第2个字母是小写的情况, 如果类名不符合这种
情况则getBean()时传入的名称就是类名(与类名完全相同的字符串)
关于组件:
在Spring框架中,可用的组件注解有:
@Component : 通用组件组件
@Controller : 应该添加在"控制器类"上
@Service: 应该添加在"业务类"上
@Repository: 应该添加在"数据存取类"上
另外,@Configuration 是一种特殊组件,应该添加在"配置类"上,当执行组件扫描时,添加了
@Configuration 注解的类的也会被创建对象
可以在@Componentden 等组件注解(不包含@Configuration)中配置字符串参数,以显示的指定
Bean的名称
可以使用一个配置类,在配置类上通过@ComponentScan来指定组件扫描的包,并在加载Spring时
传入此配置类即可 如下
package cn.tedu.spring;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
@Configuration
@ComponentScan("cn.tedu.spring")
public class SpringConfig {
}
// 以下是加载Spring的代码片段
AnnotationConfigApplicationContext ac
= new AnnotationConfigApplicationContext(SpringConfig.class);
在使用@ComponentScan时,也可以传入多个包名,例如:
@ComponentScan({"cn.tedu.spring.controller", "cn.tedu.spring.service"})
5 关于2种通过Spring创建对象的做法
以上分别介绍了使用@Bean方法和使用组件扫描的方式使得Spring创建对象的做法,在实际应用:
使用@Bean方法可用在所有场景,但是使用过程相对繁琐
使用组件扫描的做法只适用于自定义的类型(这些类是自己定义编写出来的) 使用过程非常便捷
所以,当需要被Spring创建对象的类型是自定义的,应该使用组件扫描的做法,如果不是自定义的,
只能使用@Bean方法 这2种做法在实际的项目开发中都会被使用到 !
6 Spring管理的对象的作用域
由Spring管理的对象作用域默认是单例的(并不是单例模式), 对于同一个Bean, 无论获取多少次,得
到的都是同一个对象! 如果希望某个被Spring管理的对象不是单例的,可以在类上添加
@Scope("prototype") 注解
并且,在单例的情况下,默认不是懒加载的,还可以通过@Lazy注解控制它是否为懒加载模式!所谓的
懒加载就是:"不要逼不得已不创建对象"
7 Spring管理的对象的生命周期
由Spring创建并管理对象,则开发人员就没有了对象的控制权,无法对此对象的历程进行干预,
而Spring允许在类中自定义最多2个方法,分别表示初始化方法和销毁方法,并且,Spring会在创建
对象之后就执行初始化方法, 在销毁对象之前执行销毁方法
关于这2个方法,你可以按需添加,例如,当你只需要干预销毁过程时,你可以只定义销毁的方法,不需要
定义初始化方法
关于这2个方法的声明:
访问权限: 推荐使用public
返回值类型:推荐使用void
方法名称:自定义
参数列表:推荐为空
然后,需要在初始化方法的声明之前添加@PostContruct注解,在销毁方法的声明之前添加
@PreDestroy注解 如下:
package cn.tedu.spring;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Lazy;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Controller;
import org.springframework.stereotype.Repository;
import org.springframework.stereotype.Service;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
@Component
public class UserMapper {
public UserMapper() {
System.out.println("\tUserMapper.UserMapper()");
}
@PostConstruct
public void init() {
System.out.println("\tUserMapper.init()");
}
@PreDestroy
public void destroy() {
System.out.println("\tUserMapper.destroy()");
}
}
注意:仅当类的对象被Spring管理且是单例的,才有讨论生命周期的价值,否则,不讨论生命周期
如果某个类的的对象是通过@Bean方法被Spring管理的, 并且这个类不是自定义的,可以在@Bena
注解中配置initMethod和destroyMethod这2个属性(他们的值就是方法名称,
例如destoryMethod="close"),将这个类中的方法指定为生命周期方法
8 Spring的自动装配机制
Spring的自动装配机制表现为:当你需要某个对象时,可以使用特定的语法,而Spring就尝试从容器
找到合适的值 并赋值到对应的位置!
最典型的表现就是在类的属性上添加@Autowired注解,Spring就会尝试从容器中找到合适的值为
这个属性赋值 !
如下代码
SpringConfig.java
package cn.tedu.spring;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
@Configuration
@ComponentScan("cn.tedu.spring")
public class SpringConfig {
}
UserMapper.java
package cn.tedu.spring;
import org.springframework.stereotype.Repository;
@Repository
public class UserMapper {
public void insert() {
System.out.println("UserMapper.insert() >> 将用户数据写入到数据库中……");
}
}
UserController.java
package cn.tedu.spring;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
@Controller
public class UserController {
@Autowired // 注意:此处使用了自动装配的注解
private UserMapper userMapper;
public void reg() {
System.out.println("UserController.reg() >> 控制器即将执行用户注册……");
userMapper.insert();
}
}
SpringRunner.java
package cn.tedu.spring;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class SpringRunner {
public static void main(String[] args) {
// 1. 加载Spring
AnnotationConfigApplicationContext ac
= new AnnotationConfigApplicationContext(SpringConfig.class);
// 2. 从Spring中获取对象
UserController userController
= ac.getBean("userController", UserController.class);
// 3. 测试使用对象,以便于观察是否获取到了有效的对象
userController.reg();
// 4. 关闭
ac.close();
}
}
关于@Autowired的装配机制:
首先,会根据需要装配的数据类型在Spring容器中查找匹配的Bean(对象)的数量,当数量为:
0个:判断@Autowired注解的required属性的值
当required=true时: 装配失败,启动项目时即报告异常
当required=false时:放弃自动装配,不会报告异常
后续当使用到此属性时,会出现NullPointerException
1个:直接装配,且装配成功
多个:自动尝试按照名称实现装配(属性的名称与SpringBean的名称)
存在与属性名称匹配的SpringBean:装配成功
不存在与属性名称匹配的Spring Bean : 装配失败,启动项目时即报告异常
另外,使用@Resource注解也可以实现自动装配(此注解是javax包中的), 其装配机制是先尝试根据
名称来装配,如果失败,在尝试根据类型装配!
除了对属性装配以外,Spring的自动装配机制还可以表现出:如果某个方法是由Spring框架自动调用
的(通常是构造方法,或@Bean方法,其它方法中,如果参数有限制则专门说明),当这个方法被声明了
参数时,Spring框架也会自动的尝试从容器找到匹配的对象 用于调用此方法!
9 读取properties配置文件中的信息
创建新的工程spring04,创建步骤参考前序案例
在src/main/resources 文件夹下创建jdbc.properties,内容为:
jdbc.url=jdbc:mysql://localhost:3306/tedu
jdbc.driver=com.mysql.jdbc.Driver
jdbc.username=root
jdbc.password=1234
jdbc.init-size=5
jdbc.max-active=20
注意:自定义的属性名称建议添加一些前缀,避免与系统属性和Java属性冲突
在src/main/java下创建Java类,使用@PropertySource注解读取以上配置文件中的信息,则创建
cn.tedu.spring.SpringConfig 类:
package cn.tedu.spring;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
@Configuration
@ComponentScan("cn,tedu.spring")
@PropertySource("classpath:jdbc.properties")
public class SpringConfig{
}
提示:当Sring框架读取了配置文件中的信息后,会将这些读取到的数据封装在内置的Environment对
象中,后续,任何需要这些配置信息的组件都可以从Environment中读取到配置的数据
接下来 可以创建某个Java类,从Environment 中读取到配置的数据,例如创建JdbcConfig类:
package cn.tedu.spring;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
@Component
public class JdbcConfig {
@Value("${spring.jdbc.url}")
private String url;
@Value("${spring.jdbc.driver}")
private String driver;
@Value("${spring.jdbc.username}")
private String username;
@Value("${spring.jdbc.password}")
private String password;
@Value("${spring.jdbc.init-size}")
private int initSize;
@Value("${spring.jdbc.max-active}")
private int maxActive;
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
public String getDriver() {
return driver;
}
public void setDriver(String driver) {
this.driver = driver;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public int getInitSize() {
return initSize;
}
public void setInitSize(int initSize) {
this.initSize = initSize;
}
public int getMaxActive() {
return maxActive;
}
public void setMaxActive(int maxActive) {
this.maxActive = maxActive;
}
}
-
提示:前序的操作中,在
SpringConfig
中已经配置了组件扫描,这个JdbcConfig
类必须在组件扫描的范围内,并添加组件注解,这样Spring框架才会创建JdbcConfig
类的对象,进而根据各@Value
注解将Environment
中的配置数据注入到属性中 -
最后,可以执行本案例:
package cn.tedu.spring; import org.springframework.context.annotation.AnnotationConfigApplicationContext; public class SpringRunner { public static void main(String[] args) { // 1. 加载Spring System.out.println("1. 加载Spring,开始……"); AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(SpringConfig.class); System.out.println("1. 加载Spring,完成!"); System.out.println(); // 2. 从Spring中获取对象 System.out.println("2. 从Spring中获取对象,开始……"); JdbcConfig jdbcConfig = ac.getBean("jdbcConfig", JdbcConfig.class); System.out.println("2. 从Spring中获取对象,完成!"); System.out.println(); // 3. 测试使用对象,以便于观察是否获取到了有效的对象 System.out.println("3. 测试使用对象,开始……"); System.out.println("\turl >> " + jdbcConfig.getUrl()); System.out.println("\tdriver >> " + jdbcConfig.getDriver()); System.out.println("\tusername >> " + jdbcConfig.getUsername()); System.out.println("\tpassword >> " + jdbcConfig.getPassword()); System.out.println("\tinit-size >> " + jdbcConfig.getInitSize()); System.out.println("\tmax-active >> " + jdbcConfig.getMaxActive()); System.out.println("3. 测试使用对象,完成!"); System.out.println(); // 4. 关闭 System.out.println("4. 关闭,开始……"); ac.close(); System.out.println("4. 关闭,完成!"); } }
另外,还可以直接装配一个
Environment
对象,并在需要的时候通过Environment
对象读取配置的数据,例如: -
package cn.tedu.spring; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.core.env.Environment; import org.springframework.stereotype.Component; @Component public class EnvironmentData { @Autowired private Environment environment; public Environment getEnvironment() { return environment; } }
package cn.tedu.spring; import org.springframework.context.annotation.AnnotationConfigApplicationContext; import org.springframework.core.env.Environment; public class SpringRunner { public static void main(String[] args) { // 1. 加载Spring System.out.println("1. 加载Spring,开始……"); AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(SpringConfig.class); System.out.println("1. 加载Spring,完成!"); System.out.println(); // 2. 从Spring中获取对象 System.out.println("2. 从Spring中获取对象,开始……"); EnvironmentData environmentData = ac.getBean("environmentData", EnvironmentData.class); System.out.println("2. 从Spring中获取对象,完成!"); System.out.println(); // 3. 测试使用对象,以便于观察是否获取到了有效的对象 System.out.println("3. 测试使用对象,开始……"); System.out.println("---------------------------------------"); System.out.println("通过自动装配Environment对象获取的值:"); Environment env = environmentData.getEnvironment(); System.out.println("\tEnvironment >> " + env); System.out.println("\turl >> " + env.getProperty("spring.jdbc.url")); System.out.println("\tdriver >> " + env.getProperty("spring.jdbc.driver")); System.out.println("\tusername >> " + env.getProperty("spring.jdbc.username")); System.out.println("\tpassword >> " + env.getProperty("spring.jdbc.password")); System.out.println("\tinit-size >> " + env.getProperty("spring.jdbc.init-size")); System.out.println("\tmax-active >> " + env.getProperty("spring.jdbc.max-active")); System.out.println("3. 测试使用对象,完成!"); System.out.println(); // 4. 关闭 System.out.println("4. 关闭,开始……"); ac.close(); System.out.println("4. 关闭,完成!"); } }
10. 关于Spring框架的小结
关于Spring框架:
- 了解Spring框架的作用:创建对象,管理对象
- 掌握通过Spring创建对象的2种方式:
- 在配置类(带
@Configuration
注解的类)中使用@Bean
方法 - 使用组件扫描,并在类上添加组件注解
- 组件注解有:
@Component
、@Controller
、@Service
、@Repository
- 组件注解有:
- 如果是自定义的类,应该使用组件扫描+组件注解的方式,如果不是自定义的类,必须使用配置类中的
@Bean
方法
- 在配置类(带
- 了解Spring Bean的作用域与生命周期
- 掌握
@Autowired
自动装配,理解其装配机制- 建议背下来:
@Autowired
与@Resource
的区别
- 建议背下来:
- 掌握读取
.properties
配置文件中的数据- 先使用
@PropertySource
注解指定需要读取的文件 - 读取配置的数据时,可以:
- 使用
@Value
注解将值注入到属性中 - 自动装配
Environment
对象,并调用此对象的getProperty()
方法以获取配置值
- 使用
- 先使用
- 了解Spring的IoC(Inversion of Controll:控制反转)和DI(Dependency Injection:依赖注入)
- Spring框架基于DI实现了IoC,DI是一种实现手段,IoC是最终实现的目标/效果