Spring Framework
文章目录
前言
在传统开发环境中, 需要使用某个对象时, 使用new
关键字去创建对象
Object object = new Object();
上面代码, 如果在某个方法中执行, 该变量是局部变量, 当方法运行结束, 该局部变量就会被销毁, 但实际开发, 很多地方需要对象, 长期存在于内存中, 当需要使用对象时, 通过某种方式去获取对象就行, 不需要重写创造对象
项目开发中经常遇到类中注入对象, 各个类型之间具有依赖关系, 如:
public class UserMapper { // 数据库用户表操作
public void insert{ // 向数据表中的"用户表"中插入数据 }
}
public class UserController { // 处理用户操作
public UserMapper userMapper;
public void userReg() {
userMapper.insert();
}
}
以上示例, 可以说UserController
依赖于UserMapper
, 也可以说UserMapper
为UserController
的依赖项, 传统开发中经常去new
创建依赖项
Spring框架 使对象的创建和对象的管理 可以交给框架去处理, 不用再关心对象的创建和管理
提示:以下是本篇文章正文内容,下面案例可供参考
一、创建基于Spring的工程
项目中需要使用Spring框架时, 推荐使用Maven工程, 在IDEA中创建Maven工程后, 会自动打开pom.xml
文件, 用于工程依赖导入, 在文件中的<dependencies>
中添加依赖项, 完成后刷新Maven, 依赖版本根据项目需要选择
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.3.14</version>
</dependency>
</dependencies>
二、通过Spring创建对象
1.@Bean方法
通过某个类(通常是配置类) 中的方法被@Bean注解标注 创建对象 返回值为此对象
代码如下(示例):
public class SpringBeanFactory {
@Bean
public Object object(){
return new Object();
}
}
再通过另一个类去执行:
public class SpringRunner {
public static void main(String[] args) {
// 加载 Spring, 创建spring容器, 其中定义类为Spring解释的类中的方法 会根据@Bean注解创建对象
AnnotationConfigApplicationContext acac = new AnnotationConfigApplicationContext(SpringBeanFactory.class);
// 从Spring 获取通过@Bean自动创建的对象, 通过getBean(被@Bean标注的方法名)方法返回Object类型对象 如果指定类对象则返回@Bean标注方法返回值类型的参数 getBean("方法名",)
Object object = acac.getBean("object");
// Object object =
// 关闭spring容器
acac.close();
}
}
小结:
- 在
AnnotationConfigApplicationContext
中, 如果不把有@Bean注解的类对象通过参数传入, Spring就不会加载此类的内容 - getBean()方法中传入的字符串参数, 是指定被Spring加载类
SpringBeanFactory
中方法的名称 - getBean()方法必须传入有效的参数, 否则会抛出异常
NoSuchBeanDefinitionException
, - getBean()方法参数还可以是
Object
的类对象(返回值类型的类对象), T getBean(class beanClass), 如果这样传入类型必须在Spring中必须有且仅有一个此类对象, 如果没有对象导致NoSuchBeanDefinitionException
如有两个对象(两个方法返回结果为新建此类型对象)会抛出NoUniqueBeanDefinitionException
- @Bean()注解可以传入字符串参数, 如果这样getBean() 传入字符串可以是给@Bean()中传入的参数, 因此T getBean(“beanName”,class beanClass) 也可以用
@Bean("beanName")
public Random random(){
return new Random();
}
//此时下面三种形式返回是同一对象
Random r1 = (Random)acac.getBean("random");
Random r2 = acac.getBean(Random.class);
Random r3 = acac.getBean("beanName",Random.class);
- 被Spring加载的
SpringBeanFactory
类中的方法通过@Bean标注, 在Spring加载时被执行, Spring会自动调用此方法, 并管理此方法的返回结果(创建的对象)
2.组件扫描
- 关于组件: 在Spring框架中, 可用的组件注解有 5 种
@Component
: 部件, 通用组件注解@Controller
: 控制器, 应该添加在"控制器类"上@Service
: 服务, 应该添加在"业务类"上@Repository
: 仓库, 应该添加在"数据存取类"上@Configuration
: 配置, 应该添加在"配置类"上, 这是一种特殊的组件, 被此注解标注的类也会被创建对象
注: 五种注解功能相同, 通过组件扫描创建对象, 功能上可以混用, 在实际中不能混用, 还是要区分开
例子: 通过组件扫描Spring自动创建对象
//组件扫描类
package cn.qingtian.spring
@Component
public class UserMapper {}
//启动类
package cn.qingtian.spring
public class SpringTest {
public static void main(String[] args) {
// 加载Spring, 参数传入要被扫描的包名,包中被Spring解释的类会被Spring自动创建对象并管理
AnnotationConfigApplicationContext acac = new AnnotationConfigApplicationContext("cn.qingtian.spring");
// Spring容器对象 调用getBean方法获取对象
UserMapper userMapper = getBean("userMapper",UserMapper.class);
// 关闭spring容器
acac.close();
}
}
关于以上代码:
- 在创建
AnnotationConfigApplicationContext
spring容器时传入的参数是一个basePackages
, 可以是多个"根包", 他的构造方法涉及的是"String…"类型的可变长参数, 需要传入多个包名时, 用逗号隔开即可, 它会使得spring框架扫描传入包及其子孙包中的所有类, 并尝试创建这些包中的组件对象 AnnotationConfigApplicationContext
通过组件扫描传入包名时, 不要扫描到非自定义的包即可, 如项目依赖包- 组件扫描前提是: 1. 在被扫描的包中 2. 类被组件扫描注解标注(被注解标注才会被spring视为"组件",spring才会创建对应的类对象)
getBean
方法获取对象时, 被spring创建的组件类的对象, 默认的名称都是将之前类名首字母改为小写以上规则仅适用于: 类中首字母是大写, 且第2个字母是小写的情况, 如果类名不符合这种情况, 组件类对象名称于原类名相同
- 可以在组件注解(不包括
@Configuration
)中配置字符串参数, 显示指定Bean的名称 - 可以使用一个配置类, 在配置类上通过
@ComponentScan
来指定扫描的组件包, 并在加载Spring时, 传入此配置类即可, 例如:
package cn.qingtian.spring.util
// 自动创建对象的目标类
@Componnet("target")// 在组件注解中, 传入字符串参数, 显示指定Bean的名称
public class SpringBean {}
package cn.qingtian.spring.config
@Configuration// 此类用于加载的配置类
@ComponentScan("cn.qingtian.spring.util")
public class SpringConfig {}
package cn.qingtian.spring
public class SpringRun {// 此类创建spring容器
public static void main(String[] args) {
// 加载spring 创建容器, 传入配置类作为参数, 此时会根据配置类中的@ComponentScan注解扫描cn.qingtian.spring.util包下的所有类
AnnotationConfigApplicationContext acac = new AnnotationConfigApplicationContext(SpringConfig.class);
// 获取目标类对象
SpringBean springBean = acac.getBean("target",SpringBean.class);
// 关闭spring容器
acac.close();
}
}
在使用ComponentScan
注解时, 可以传入多个要扫描的包名, 例如:
@ComponentScan({"cn.qingtian.spring.util","cn.qingtian.spring.config"})
spring创建对象小结:
以上分别是使用Bean
方法和使用组件扫描的方式使得Spring创建对象的做法, 在实际应用中:
Bean
方法可以使用在所有的场景, 但是使用过程相对繁琐- 使用组件扫描的做法只适用于自定义类型, 使用过程便捷
当需要被spring创建对象的类型是自定义的, 应该使用组件扫描的做法, 如果不是自定义的, 只能使用@Bean
方法, 这两种方法在工程中都会用到
三、Spring管理对象的作用域
由spring管理的对象的作用域默认是单例的(并不是单例模式), 对于同一个Bean, 无论多少次获取, 得到的都是同一个对象, 如果希望某个被spring管理的对象不是单例的, 可以在类上添加@Scope("prototype")
注解
例子: 使一个类对象在spring中不是单例的
package cn.qingtian.spring.controller
// 目标类 用户控制类
@Controller("target")
@Scope("prototype")
public class UserController {}
package cn.qingtian.spring.config
// 配置类 配置spring组件扫描包
@Configuration
@ComponentScan("cn.qingtian.spring.controller")
public class SpringConfig {}
package cn.qingtian.spring
// spring启动类 加载spring 创建spring容器对象
public class SpringRun {
public static void main(String[] args) {
// 加载spring 载入spring扫描范围 并 创建spring容器对象
AnnotationConfigApplicationContext acac = new AnnotationConfigApplicationContext(SpringConfig.class);
// 获取目标类对象
UserControllrt target1 = acac.getBean(UserController.class);
UserController target2 = acac.getBean("target",UserController.class);
// 因为组件注解传入了target字符串参数致使原Bean名改变为target
Object target3 = acac.getBean("target");
System.out.println(target1);
System.out.println(target2);
System.out.println(target3);
// 关闭spring容器
acac.close();
}
}
// 不是单例下, 控制台输出结果为:
cn.qingtian.spring.controller.UserController@157632c9
cn.qingtian.spring.controller.UserController@6ee12bac
cn.qingtian.spring.controller.UserController@55040f2f
如果不加@Scope("prototype")
注解, UserController为单例, 使用是同一对象, 所以得出测试结果为:
// 单例下控制台输出结果:
cn.qingtian.spring.controller.UserController@145eaa29
cn.qingtian.spring.controller.UserController@145eaa29
cn.qingtian.spring.controller.UserController@145eaa29
- 并且, 在单例模式下, 默认不是懒加载的, 还可以通
@Lazy
注解控制它是否为懒加载模式
懒加载: 简单说是用到此对象, spring在用到时在创建对象, spring默认在spring加载时, spring容器创建过程中所有对象都会被实例化的
例子: 通过打桩输入判断spring自动创建的过程
package cn.qingtian.spring.service
@Service // 懒加载类
@Lazy
public class UserService { public UserService() { System.out.println("懒加载类 UserService 被创建对象了"); }}
package cn.qingtian.spring.mapper
@Repositroy // 对照类 此类在spring加载时创建对象
public class UserMapper { public UserMapper() { System.out.println("数据存储类 UserMapper 被创建对象了");} }
package cn.qingtian.spring.config
@Configuration
@ComponentScan({"cn.qingtian.spring.mapper","cn.qingtian.spring.service"})
public class SpringConfig { public SpringConfig() { System.out.println("配置类 SpringConfig 被创建对象了"); } }
package cn.qingtian.spring
// spring容器创建
public class SpringRun {
public static void main(String[] args) {
System.out.println("spring开始加载");
AnnotationConfigApplicationContext acac = new AnnotationConfigApplicationContext(SpringConfig.class);
System.out.println("spring加载结束");
System.out.println("开始获取UserMapper对象");
UserMapper userMapper = acac.getBean(UserMapper.class);
System.out.println("已获取UserMapper对象: " + userMapper);
System.out.println("开始获取UserService对象");
UserService userService = acac.getBean(UserService.class);
System.out.println("已获取UserService对象: " + userService );
// 关闭spring容器
acac.close();
}
}
// 控制台信息:
spring开始加载
配置类 SpringConfig 被创建对象了
数据存储类 UserMapper 被创建了
spring加载结束
开始获取UserMapper对象
已获取UserMapper对象: cn.qingtian.spring.mapper.UserMapper@667a738
开始获取UserService对象
懒加载类 UserService 被创建对象了
已获取UserService对象: cn.qingtian.spring.service.UserService@36f0f1be
// 可以看到 在spring容器创建时, 配置类最先被创建, 其次默认类UserMapper, 懒加载类在向spring容器获取对象时创建
四、Spring管理对象的生命周期
由spring创建并管理对象, 则开发人员就没有了对象的控制权, 无法对此对象的历程进行干预,
而spring允许在类中自定义最多2个方法, 分别表示初始化方法和销毁方法, 并且 spring 会在对象创建之后就执行初始化方法, 在销毁之前执行销毁方法
对于这俩个方法, 按需添加, 如需要干预销毁方法, 可以只定义销毁的方法, 不需要定义初始化方法
关于这两个方法的声明:
- 访问权限: 推荐使用
public
- 返回值类型: 推荐使用
void
- 方法名称: 自定义
- 参数列表: 推荐为空
然后, 需要在初始化方法上添加@PostConstruct
注解, 在销毁方法上添加@PreDestroy
注解
例子: 观察目标类创建和销毁过程中初始化方法和销毁方法的执行过程
package cn.qingtian.spring.component
@Component("target")
public class UserComponent {
public UserComponent() {
System.out.println("UserComponent构造方法执行");
}
@PostConstruct
public void init() {
System.out.println("init方法执行");
}
@PreDestroy
public void destroy() {
System.out.println("destroy方法执行");
}
}
package cn.qingtian.spring
public class SpringRun{
public static void main(String[] args){
System.out.println("spring开始加载");
AnnotationConfigApplicationContext acac = new AnnotationConfigApplicationContext("cn.qingtian.spring.component");
System.out.println("spring加载结束");
System.out.println("开始获取目标类对象");
UserComponent userComponent = acac.getBean("target",UserComponent.class);
System.out.println("已获取目标类对象");
System.out.println("开始关闭spring");
acac.close();
System.out.println("已关闭spring");
}
}
// 在控制台上, 可以看到:
spring开始加载
UserComponent构造方法执行
init方法执行
spring加载结束
开始获取目标类对象
已获取目标类对象
开始关闭spring
destroy方法执行
已关闭spring
// 目标构造方法执行后, 被@PostConstruct标注的init方法马上执行, 在spring关闭开始时, 被@PreDesctroy标注的destroy方法被调用
注: 仅当类的对象被spring管理且是单例的, 才有讨论声明周期的价值, 否则, 不讨论声明周期
如果某个类的对象是通过@Bean
方法被spring管理的, 并且这个类不是自定义的, 可以在@Bean
注解中配置initMethod
和destroyMethod
这两个属性, 值为方法名称, 如: destroyMethod="close"
将这个类的方法指定为声明周期方法
五、Spring的自动装配机制
spring自动装配机制表现为: 当你需要某个对象时, 可以使用特定的语法, 而spring就会尝试从容器找到合适的值, 并赋值到对应位置
如: 在类的属性上添加@Autowired
注解, spring就会尝试从容器中找到合适的值为这个属性赋值
例子: 使用@Autowired
自动给属性赋值
package cn.qingtian.spring.mapper
@Repository
public class UserMapper{
public void insert(){
System.out.println("UserMapper.insert 将用户数据写入数据库中");
}
}
package cn.qingtian.spring.controller
@Controller
public class UserController{
@AutoWired
UserMapper userMapper;
public void reg(){
System.out.println("正在注册");
userMapper.insert();
}
}
package cn.qingtian.spring
public class SpringRun{
public static void main(String[] args){
AnnotationConfigApplicationContext acac = new AnnotationConfigApplicationContext("cn.qingtian.spring");
UserController userController = acac.getBean(UserController.class);
userController.reg();
acac.close();
}
}
// 控制台结果:
正在注册
UserMapper.insert 将用户数据写入数据库中
关于@Autowired自动装配机制:
首先, 会根据需要装配数据的类型在spring容器中查找匹配的Bean(对象)的数量, 当数量为:
- 0个: 判断
@Autowired
注解的required
属性的值, 当required=true
时: 装配失败, 报告异常; 当required=false
时, 放弃装配, 不会报出异常, 当后续使用到此属性时, 会出现空指针异常 - 1个: 直接装配, 且装配成功
- 多个: 自动按照名称实现装配(属性的名称与Spring Bean的名称), 存在与属性名匹配的Spring Bean: 装配成功; 不存在属性名匹配的Spring Bean: 装配失败, 抛出异常
使用@Resource
注解也可以实现自动装配(此注解是Javax
包中的), 其装配机制先尝试根据名称装配, 如果不匹配, 再根据类型装配
spring自动装配除了表现在属性装配上, 还可以表现在方法参数上, 如果某个方法由spring框架自动调用(通常是构造方法. 或@Bean
方法), 当这个方法被声明了参数时, spring框架也会自动尝试从容器找到匹配的对象赋值给参数, 用于调用此方法
六、读取properties配置文件中的信息
properties
文件格式是属性=值的方式存在, 在src/main/resources
文件夹下创建jdbc.properties
, 内容为:
spring.jdbc.url=jdbc:mysql://localhost:3306/
spring.jdbc.username=root
spring.jdbc.password=root
注意: 自定义的属性名称建议添加一些前缀, 避免与系统属性和Java属性冲突, 如: username会默认读取电脑用户名, 不会读取文件的值
1.@Value注解从Environment中获取数据
格式: 在属性上添加@Value("${配置文件中的属性}")
例如:
在src/main/java
下创建配置类, 使用@PropertySource
注解读取配置文件中的信息
package cn.qingtian.spring.config
@Configuration
@ComponentScan("cn.qingtian.spring")
@PropertySource("classpath:jdbc.properties")
public class SpringConfig {}
当spring框架读取了配置文件中的信息后, 会将这些读取到的数据封装在内置的Environment
对象中, 如需要某些配置信息可以从Environment
中读取到配置的数据
创建类, 用于从Environment
中读取配置的数据, 例如创建JdbcConfig
类, 在属性上添加@Value
注解将Environment
中的配置数据注入到属性中
package cn.qingtian.spring
@Component
public class JdbcConfig {
@Value("${spring.jdbc.url}")
private String url;
@Value("${spring.jdbc.username}")
private String username;
@Value("${spring.jdbc.password}")
private String password;
// 省略自动生成的 getter方法
}
package cn.qingtian.spring
public class SpringRun{
public static void main(String[] args){
AnnotationConfigApplicationContext acac = new AnnotationConfigApplicationContext(SpringConfig.class);
JdbcConfig jdbcConfig = acac.getBean(JdbcConfig.class);
System.out.println(jdbcConfig.getUrl());
System.out.println(jdbcConfig.getUsername());
System.out.println(jdbcConfig.getPassword());
}
}
// 控制台输出 属性已经能读取了
jdbc:mysql://localhost:3306/
root
root
2.从Environment中获取数据
格式:Environment 对象中的 getProperty("属性")
获取配置值
除了利用@Value("${属性}")
注解获取值赋值给属性, 还可以直接装配一个Environment
对象, 并在需要的时候通过Environment
对象读取配置的数据, 例如:
package cn.qingtian.spring
@Component //获取Environment对象
public class EnvironmentData{
@Autowired
// Environment 是 spirng框架下的类
private Environment environment;
public Environment getEnvironment(){
return envirment;
}
}
package cn.qingtian.spring.config
@Configuration
@ComponentScan("cn.qingtian.spring")
@PropertySource("classpath:jdbc.properties")
public class SpringConfig{}
package cn.qingtian.spring
public class SpringRun{
public static void main(String[] args){
AnnotationConfigApplicationContext acac = new AnnotationConfigApplicationContext(SpringConfig.class);
EnvironmentData environmentData = acac.getBean(EnvironmentData.class);
Environment env = environmentData.getEnvironment();
System.out.println(env.getProperty("spring.jdbc.url"));
System.out.println(env.getProperty("spring.jdbc.username"));
System.out.println(env.getProperty("spring.jdbc.password"));
}
}
// 控制台打印信息 配置数据正常获取
jdbc:mysql://localhost:3306/
root
root
七、其他
IoC
(Inversion of Controll) 控制反转
, 简单理解 Spring框架自动创建并管理对象, 是无法干预的, 相当于把控制权交给Spirng框架, 作用: 降低耦合 由Spirng管理依赖关系
DI
(Dependency Injection) 依赖注入
, 简单说 不用自己创建对象, 但需要通过注解描述, Spirng容器会自动装配
AOP
(Aspect Oriented Programming) 面向切面编程
总结
- 在Maven项目中的pom.xml文件中导入Spring框架依赖, 并刷新Maven
- 加载Spring框架, 通过
AnnotationConfigApplicationContext
创建Spring容器, 通过@Bean
或组件扫描方式使Spring自动创建对象 - 组件注解 有5种,
@Component通用组件
@Service业务类组件
@Controller控制器类组件
@Repository数据存储类组件
Configuration配置类组件(不可以配置字符串参数,显示指定Bean名称)
- 在
@Configuration
下配置@ComponentScan
可以指定Spring扫描范围, 参数是包名, 可以是多个包名{"cn.qingtian.mapper","cn.qingtian.controller"}
- 通过
@Lazy
使此类对象是懒加载 - 通过
@Scope("prototype")
让此类对象不在是单例 - 对象创建和销毁是由Spring框架管理, 程序员不能干预其过程, 但是Spring框架提供了
@PostConstruct
和@PerDestroy
注解可以在类对象初始化之后和销毁之前执行 - 如果对象是通过
@Bean
被Spring管理的, 并且类不是自定义的, 可以在@Bean
中配置initMethod和destroyMethod属性(它的值为方法的名称)指定为生命周期方法 - 自动装配: Spring提供了
@Autowired
和javax包中@Resource
两种装配方式 - 读取Properties配置文件, 在
src/main/resources
文件夹下创建jdbc.properties
文件,通过@Value
在Environment种获取并注入属性值: 在@Configuration
下配置@PropertyResource("classpath:jdbc.properties")
, 并在自定义属性类上加@Component
, 在类中属性上添加@Value("${属性名称}")
, 通过Environment对象的getProperty(“属性名称”)方法获取值, Environment对象通过自动装配获取