了解Spring框架

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, 也可以说UserMapperUserController的依赖项, 传统开发中经常去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 种
  1. @Component: 部件, 通用组件注解
  2. @Controller: 控制器, 应该添加在"控制器类"上
  3. @Service: 服务, 应该添加在"业务类"上
  4. @Repository: 仓库, 应该添加在"数据存取类"上
  5. @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();
	}
}

关于以上代码:

  • 在创建AnnotationConfigApplicationContextspring容器时传入的参数是一个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注解中配置initMethoddestroyMethod这两个属性, 值为方法名称, 如: 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) 面向切面编程


总结

  1. 在Maven项目中的pom.xml文件中导入Spring框架依赖, 并刷新Maven
  2. 加载Spring框架, 通过AnnotationConfigApplicationContext创建Spring容器, 通过@Bean或组件扫描方式使Spring自动创建对象
  3. 组件注解 有5种, @Component通用组件 @Service业务类组件 @Controller控制器类组件 @Repository数据存储类组件 Configuration配置类组件(不可以配置字符串参数,显示指定Bean名称)
  4. @Configuration下配置@ComponentScan可以指定Spring扫描范围, 参数是包名, 可以是多个包名{"cn.qingtian.mapper","cn.qingtian.controller"}
  5. 通过@Lazy使此类对象是懒加载
  6. 通过@Scope("prototype")让此类对象不在是单例
  7. 对象创建和销毁是由Spring框架管理, 程序员不能干预其过程, 但是Spring框架提供了@PostConstruct@PerDestroy注解可以在类对象初始化之后和销毁之前执行
  8. 如果对象是通过@Bean被Spring管理的, 并且类不是自定义的, 可以在@Bean中配置initMethod和destroyMethod属性(它的值为方法的名称)指定为生命周期方法
  9. 自动装配: Spring提供了@Autowired和javax包中@Resource两种装配方式
  10. 读取Properties配置文件, 在src/main/resources文件夹下创建jdbc.properties文件,通过@Value在Environment种获取并注入属性值: 在@Configuration下配置@PropertyResource("classpath:jdbc.properties"), 并在自定义属性类上加@Component, 在类中属性上添加@Value("${属性名称}"), 通过Environment对象的getProperty(“属性名称”)方法获取值, Environment对象通过自动装配获取
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值