作者:~小明学编程
文章专栏:Spring框架
格言:热爱编程的,终将被编程所厚爱。
目录
Spring更加高效的读取和存储对象
存储bean对象
前面我们所介绍的方法过于的繁琐,我们可以通过注解来快速实现上述的功能。
<?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.beans"></content:component-scan>
</beans>
首先是我们所依赖的资源,其中com.beans是我们所要扫描的类的包。
五大注解
- Controller 控制器
- Service 服务
- Repository 仓库
- Configuration 配置
- Component 组件
利用上面这五大注解都可以完成我们对象的注入。
@Service
public class UserService {
public void sayHi() {
System.out.println("hi Service");
}
}
@Repository
public class UserRepository {
public void sayHi() {
System.out.println("Hi Res");
}
}
@Controller
public class UserController {
public void sayHi() {
System.out.println("hello");
}
}
@Configuration
public class UserConfig {
public void sayHi() {
System.out.println("Hi Con");
}
}
@Component
public class UserComponent {
public void sayHi() {
System.out.println("Hi Com");
}
}
获取:
public static void main1(String[] args) {
//1.获取上下文
ApplicationContext context = new ClassPathXmlApplicationContext("spring-config.xml");
//2.得到bean
UserController userController = context.getBean("userController",UserController.class);
userController.sayHi();
UserService userService = context.getBean("userService",UserService.class);
userService.sayHi();
UserRepository userRepository = context.getBean("userRepository",UserRepository.class);
userRepository.sayHi();
UserConfig userConfig = context.getBean("userConfig",UserConfig.class);
userConfig.sayHi();
UserComponent userComponent = context.getBean("userComponent",UserComponent.class);
userComponent.sayHi();
}
获取的时候我们需要注意这个beanname,这个beanname是根据如下的逻辑。
关于五大类注解
既然我们五种注解的随便一种都能完成我们的对象的注入,那么我们为什么还是需要五种注解呢
因为我们一个软件是分为多个层面的,分为多层有助于我们的管理和控制。
Configuration 就是配置层:关于当前项目中的所有配置,都会写在这个文件夹里。
Controller 就是控制层:就是前端参数校验,主要就是校验前端数据的合理性和正确性。
Service 就是服务层,负责数据的组装和接口调用。更直白的说,服务器九四 “牵线搭桥” 让程序去调用对应的功能/接口/服务。
Repository 叫做仓库,但其实是 数据持久层,也叫做 Dao 层,就是对数据进行持久化操作(对数据库进行增删改查操作)。
我们使用不同的注解便于我们了解不同的代码的功能同时也方便我们对代码进行一个管理。
当然这五大类注解之间是有一定的关系的:
可以看到我们的configuration注解是调用了我们的component注解的,其它几个注解的实现方法也是如此,可以说我们的component注解是我们其它四大注解的一个父类。
对象的注入
前面我们了解了怎么样将对象给存储起来,也就是使用五大注解可以高效方便的将我们的对象给放在spring中,但是当我们想要使用的时候有没有什么方式可以比较高效的来使用呢,下面就介绍三种方式来实现我们对象的一个注入。
属性注入
通过 @Autowired 注入:
@Controller
public class UserController {
@Autowired
private UserService userService;
public void sayHi() {
userService.sayHi();
}
}
这里我们就通过关键字Autoeired的关键字来直接实现我们对象的一个注入,然在下面的方法之中我们就可以直接调用我们注入好的对象的方法了。
public static void main(String[] args) {
//1.获取上下文
ApplicationContext context = new ClassPathXmlApplicationContext("spring-config.xml");
UserController userController = context.getBean(UserController.class);
userController.sayHi();//hi Service
}
构造方法注入
@Controller
public class UserController {
// @Autowired
private UserService userService;
public UserController(UserService userService) {
this.userService = userService;
}
public void sayHi() {
userService.sayHi();
}
}
这里我们可以直接通过构造方法的方式来进行对象的一个注入,但是值得我们注意的是,当我们的构造方法不止一个的时候就必须要使用Autowired关键字了,不然会报错。
@Controller
public class UserController {
// @Autowired
private UserService userService;
@Autowired
public UserController(UserService userService) {
this.userService = userService;
}
public UserController(UserService userService,int num) {
this.userService = userService;
}
public void sayHi() {
userService.sayHi();
}
}
Setter注入
@Controller
public class UserController {
private UserService userService;
@Autowired
public void setUserService(UserService userService) {
this.userService = userService;
}
public void sayHi() {
userService.sayHi();
}
}
同样我们也可以使用Setter这样一个方法来进行一个注入。
三种注入方式的区别
- 属性注入特点是写法简单,但是通用性不好,只能运行在 IoC 容器下,如果是 非 IoC 容器的话,就会出现问题。
- 构造方法注入,通用性更好。因为构造方法的优先级是最高的。确保使用注入对象之前,对象一定初始化过了。当构造方法注入参数太多时,就需要检查自己的代码是否符合单一设计原则的规范了。构造方法注入,也是 Spring 推荐的注入方法。
- Setter 注入是早期 Spring 版本推荐的写法,但是通用性没有构造方法注入通用,因为只有一个参数。
@Resource 和 @Autowired 的区别
相同点:都可以完成对象的注入。
不同点:
- 出身不同,@Resource 来自于 JDK,@Autowired 是由 Spring 框架提供的。
- @Autowired 支持属性注入,构造方法注入 和 Setter 注入,而 @Resource 不支持构造方法注入。
- 支持的参数不同:@Resource 支持更多的参数设置,比如 name、type 设置。而 @Autowired 只支持 required 参数设置。
Bean的作用域和生命周期
在我们使用bean的时候其实默认是用的同一个bean也就是说如果我们对其中的bean进行一个更改的话,那么当我们再次去使用这个bean此时依然是更改之后的bean。
@Component
public class UserBean {
@Bean(name = {"user","user1"})
// @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public User user2() {
User user = new User(15,"lisi");
return user;
}
@Bean(name = {"user2"})
public User user1() {
User user = new User(12,"zhangsan");
return user;
}
}
@Component
public class BeanScope1 {
@Autowired
private User user2;
public User getUser() {
user2.setStr("aaa");
return user2;
}
}
@Component
public class BeanScope2 {
@Autowired
private User user2;//user2必须事被注入过的
public User getUser() {
return user2;
}
}
这个时候我们注入了两次bean对象,此时查看输入的结果:
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("spring-config.xml");
BeanScope1 beanScope1 = context.getBean(BeanScope1.class);
System.out.println(beanScope1.getUser());
BeanScope2 beanScope2 = context.getBean(BeanScope2.class);
System.out.println(beanScope2.getUser());
}
可以看到我们在对其进行更改之后,再次注入仍然是更改之后的User。
产生这样的原因是因为 Bean 在 Spring 中,默认情况下是单例状态,也就是所有人的使用都是同一个对像。这样可以很好的节约资源,避免资源的浪费。
Bean 的六种作用域
singleton:单例作用域(默认)在这种模式下的 Bean 在 IoC 容器里面,只存在一个实例。
prototype:原型作用域(多例模式)每次对作用域下的 Bean 请求,都会创建新的实例,然后再去获取 Bean。
request:请求作用域(Spring MVC)每次 Http 请求会创建新的 Bean 实例,类似于 prototype。
session:会话作用域(Spring MVC)在一个 http session 中,定义一个 Bean 实例。记录用户的登录信息。
application:全局作用域(Spring MVC)更多人使用的时候,就用 application。也是单例模式,应用在 Web 应用的上下文信息。
websocket:Http WebSocket 作用域(Spring WebSocket)在 WebSocket 会话中使用。
当我们想要改变其作用域的时候可以通过@Scope 的注解来进行改变
可以看到此时我们 所注入的对象就都是新的对象了。
设置作用域的时候,只需要通过 @Scope 注解就可以了:
- 直接设置值:@Scope(“prototype”)
- 使用枚举设置:@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
Spring的执行流程
1.我们首先是要启动我们的容器,去加载我们的配置文件。
也就是我们上面的这段代码。
2.接着就是根据我们的配置来完成我们的bean初始化。
通过扫描包中的spring注解。
3.注册 Bean 对象到容器中,只有在包扫描的路径上的类,且使用 Spring 的注解才可以被注册到容器当中。
4.装配 Bean 属性,也就是把 Bean 注册到其他类当中:
Bean的生命周期
Bean的生命周期就是Baen从诞生到销毁的一个过程 。
- 实例化 Bean(为 Bean 分配内存空间)
- 设置属性(Bean 注入和装配)
- Bean 初始化 a)执行各种通知(各种Aware)如:BeanNameAware,BeanFactoryAware,ApplicationContextAware 的接口方法。
b)执行 BeanPostProcessor 初始化前置方法。
c)执行 @PostConstruct 初始化方法,依赖注入操作之后被执行。
d)执行自己指定的 init-method 方法(如果有指定的话
e)执行 BeanPostProcessor 初始化后置方法。 - 使用 Bean
- 销毁 Bean,通过方法来销毁容器:如 @PreDestroy、DisposableBean、destroy-method
其大概的流程图就是这样的。
<?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.beans"></content:component-scan>
<bean id="beanLife"
class="com.beans.BeanLife" init-method="init"></bean>
</beans>
package com.beans;
import org.springframework.beans.factory.BeanNameAware;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
/**
* Created with IntelliJ IDEA.
* Description:
* User: 86184
* Date: 2023-03-06
* Time: 22:07
*/
@Component
public class BeanLife implements BeanNameAware {
@PostConstruct
public void Post() {
System.out.println("PostConstruct");
}
@PreDestroy
public void Pre() {
System.out.println("pre");
}
public void use() {
System.out.println("use");
}
public void init() {
System.out.println("init");
}
public void setBeanName(String s) {
System.out.println("通知");
}
}
执行流程如上。