Spring Framework
Spring的两个核心
IOC控制反转
-
Inverse of Control的简写,为 控制反转,指是将对象的创建和管理交由Spring框架来完成,而不是由开发人员手动创建和管理
-
即:反转资源获取方向,把自己创建资源、向环境索取资源的方式变为环境自动将资源准备好,我们享受资源注入
IOC容器
-
IoC容器是用来实现IoC思想的一个工具或者说技术手段
-
它能够自动扫描应用程序中的对象,将它们实例化,并自动注入它们所需要的依赖对象,使应用程序的开发人员能够更加专注于业务逻辑的实现,而不用关心对象的创建和管理。Spring通过IoC容器来管理所有的Java对象的实例化和初始化,控制着对象与对象之间的依赖关系
依赖注入DI
- DI (Dependency Injection):依赖注入,依赖注入实现了控制反转的思想,是指Spring创建对象的过程中,将对象依赖属性通过配置进行注入
- 所以 IoC 是一种控制反转的思想,而依赖注入 DI 是对 IoC 的一种具体实现
- Bean管理:指Bean对象的创建,以及Bean对象中属性的赋值
IOC容器实现
Spring中的IoC容器就是IoC思想的一个落地产品实现。IoC容器中管理的组件也叫做bean。在创建bean之前,首先需要创建IoC容器,Spring提供了IoC容器的两种实现方式
-
BeanFactory
这是IoC容器的基本实现,是Spring内部使用的接口,面向Spring本身,不提供给开发人员使用。 -
ApplicationContext
BeanFactory的子接口,提供了更多高级特性,面向Spring的使用者,几乎所有场合都使用 ApplicationContext,而不使用底层的BeanFactory。
ApplicationContext的主要实现类
类型 | 说明 |
---|---|
AnnotationConfigApplicationContext | 使用注解方式构建IoC容器 |
ClassPathXmlApplicationContext | 使用XML配置文件方式构建Spring IoC容器 |
注解管理Bean
Bean对象定义
在Spring框架规范中,所有由spring管理的对象都称之为Bean对象。
Spring提供了以下多个注解,这些注解可以直接标注在java类上,将它们定义成Spring Bean。
注解 | 说明 |
---|---|
@Component | 该注解用于描述Spring中的Bean,它是一个泛化的概念,仅仅标识容器中的一个组件(Bean),并且可以作用在任何层次,例如Service层、Dao层等,使用时只需将该注解标注在相应的类上即可。 |
@Repository | 该注解用于数据访问层(Dao层)的类标识为Spring中的Bean,功能与@Component相同。 |
@Service | 该注解通常作用在业务层(Service层),用于将业务层的类标识为Spring中的Bean,其功能与@Component相同。 |
@Controller | 该注解通常作用在控制层(如SpringMVC的Controller),用于将控制层的类标识为Spring中的Bean,其功能与@Component相同。 |
Bean对象获取
ApplicationContext context = new AnnotationConfigApplicationContext("包扫描路径");
User user = context.getBean(类名.class);
Bean对象作用域分析@Scope
在Spring框架中,Bean是按照作用域来创建的
常见的作用域有两种:Singleton和Prototype
Singleton (单例)
- 是指整个应用中只有一个实例,并在第一次请求时创建实例
- 单实例(Singleton)是指某个类只能创建唯一的一个实例对象,并且该类提供一个全局的访问点(静态方法)来让外界获取这个实例,常常用在那些只需要一个实例来处理所有任务的场景下,例如数据库连接。
Prototype(多例)
- 是指每次请求都会创建一个新的实例并返回,每个实例之间是相互独立的
- 多实例(Multiple Instance)则是指可以在同一个类的定义下,创建多个实例对象。每个对象都是相互独立的,有自己的状态和行为;常常用于需要同时处理多个任务的场景。
在Spring中可以通过 @Scope 注解来指定bean的作用域范围,具体如下
取值 | 含义 |
---|---|
@Scope(“singleton”) | 在IoC容器中,这个bean的对象为单实例 |
@Scope(“prototype”) | 这个bean在IoC容器中有多个实例 |
Bean对象生命周期管理
- 程序中的每个对象都有生命周期,对象的创建、初始化、应用、销毁的整个过程称之为对象的生命周期;
- 在对象创建以后需要初始化,应用完成以后需要销毁时执行的一些方法,可以称之为是生命周期方法;
在spring中,可以通过 @PostConstruct 和 @PreDestroy 注解实现bean对象生命周期的初始化和销毁时的方法。
引用外部属性文件
-
实际开发中,很多情况下我们需要对一些变量或属性进行动态配置,而这些配置可能不应该硬编码到我们的代码中,因为这样会降低代码的可读性和可维护性。
-
我们可以将这些配置放到外部属性文件中,比如database.properties文件,然后在代码中引用这些属性值,例如jdbc.url和jdbc.username等。这样,我们在需要修改这些属性值时,只需要修改属性文件,而不需要修改代码,这样修改起来更加方便和安全。
-
通过将应用程序特定的属性值放在属性文件中,我们还可以将应用程序的配置和代码逻辑进行分离,这可以使得我们的代码更加通用、灵活。
自动扫描配置
-
自动扫描配置是 Spring 框架提供的一种基于注解(Annotation)的配置方式,用于自动发现和注册 Spring 容器中的组件。当我们使用自动扫描配置的时候,只需要在需要被 Spring 管理的组件(比如 Service、Controller、Repository 等)上添加对应的注解,Spring 就会自动地将这些组件注册到容器中,从而可以在其它组件中使用它们。
-
在 Spring 中,通过 @ComponentScan 注解来实现自动扫描配置。
@ComponentScan 注解用于指定要扫描的包或类。
Spring 会在指定的包及其子包下扫描所有添加 @Component(或 @Service、@Controller、@Repository 等)注解的类,把这些类注册为 Spring Bean,并纳入 Spring 容器进行管理。
AOP面向切面编程
Aspect Oriented Programming 的简写,为 面向切面编程。AOP用来封装多个类的公共行为,将那些与业务无关,却为业务模块共同调用的逻辑封装起来,减少系统的重复代码。
添加依赖
<dependencies>
<!-- Maven坐标:
https://mvnrepository.com/artifact/org.springframework/spring-context -->
<!-- 引入spring context依赖-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.3.24</version>
</dependency>
</dependencies>
入门案例
注解
- @Component
注解描述的类,表示此类交给Spring框架管理
package cn.tedu.spring.example;
import org.springframework.stereotype.Component;
@Component
public class User {
public void userRun(){
System.out.println("User is do something~~");
}
}
通过Spring创建Java bean对象
- AnnotationConfigApplicationContext扫描这个包中所有带有@Component注解的类,并根据这些类创建相应的Spring组件
package cn.tedu.spring.example;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class TestUser {
public static void main(String[] args) {
ApplicationContext context = new AnnotationConfigApplicationContext("cn.tedu.spring.example");
User user1 = context.getBean(User.class);
System.out.println("user1 = " + user1);
user1.userRun();
}
}
xml管理Bean案例
- 在Spring框架中,Bean的配置可以通过 XML 文件来完成。这个文件通常被称为 Spring 配置文件或 Spring XML 文件。
package cn.tedu.spring.example;
public class UserXml {
private String username;
private String password;
public void run(){
System.out.println("今天天气不错挺风和日丽的~");
}
}
main下创建bean.XMl文件
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="userXml" class="cn.tedu.spring.example.UserXml"></bean>
</beans>
package cn.tedu.spring.example;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class TestUserXml {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("bean.xml");
UserXml userXml = (UserXml)context.getBean("userXml");
userXml.run();
}
}
DI依赖注入案例
package cn.tedu.spring.dibase;
public class Book {
private String bookName;
private String bookAuthor;
public void setBookName(String bookName) {
this.bookName = bookName;
}
public void setBookAuthor(String bookAuthor) {
this.bookAuthor = bookAuthor;
}
@Override
public String toString() {
return "Book{" +
"bookName='" + bookName + '\'' +
", bookAuthor='" + bookAuthor + '\'' +
'}';
}
}
创建Spring配置文件 bean-di.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="book" class="cn.tedu.spring.dibase.Book">
<property name="bookName" value="倚天屠龙记" />
<property name="bookAuthor" value="金庸"/>
</bean>
</beans>
package cn.tedu.spring.dibase;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class TestBook {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("bean-di.xml");
Book book = context.getBean(Book.class);
System.out.println("book = " +book);
}
}
注解管理Bean案例
@Value
@Value注入是将属性值直接注入到bean中,主要用于注入一些简单类型的属性(如字符串、基本类型等);
使用时需要注意属性的类型和格式,否则会导致注入失败。
@Autowired
@Autowired注入是将对象注入到bean中,并且在注入对象时会根据依赖注入容器中 bean的类型 进行匹配。
如果容器中有多个类型匹配的bean存在,则会抛出异常。因此,@Autowired注入常用于注入复杂对象、接口类型的属性或其他bean实例。
package cn.tedu.spring.bean;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Repository;
@Repository
public class UserDao {
@Value("jdbc:mysql://localhost:3306/tedu")
private String dbUrl;
@Value("root")
private String username;
private String password;
@Value("qwertyuiop")
public void setPassword(String password) {
this.password = password;
}
@Override
public String toString() {
return "UserDao{" +
"dbUrl='" + dbUrl + '\'' +
", username='" + username + '\'' +
", password='" + password + '\'' +
'}';
}
}
package cn.tedu.spring.bean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
@Service
public class UserService {
@Value("注册业务")
private String serveName;
@Autowired
private UserDao userDao;
@Override
public String toString() {
return "UserService{" +
"serveName='" + serveName + '\'' +
", userDao=" + userDao +
'}';
}
}
package cn.tedu.spring.bean;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class TestUserDao {
@Test
public void testBean(){
ApplicationContext context = new AnnotationConfigApplicationContext("cn.tedu.spring.bean");
UserDao userDao = context.getBean(UserDao.class);
System.out.println("userDao = " + userDao);
}
public static void main(String[] args) {
ApplicationContext context = new AnnotationConfigApplicationContext("cn.tedu.spring.bean");
UserService userService = context.getBean(UserService.class);
System.out.println("userService:"+ userService);
}
}
根据接口类型注入
当一个接口有一个唯一的实现类时,Spring框架会通过接口找到该接口对应的实现类,并进行bean对象的创建以及DI注入操作
package cn.tedu.spring.auto;
public interface Cache {
}
package cn.tedu.spring.auto;
import org.springframework.stereotype.Component;
@Component
public class CacheImpl1 implements Cache{
}
package cn.tedu.spring.auto;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;
@Repository
public class UserCache {
@Autowired
private Cache cache;
}
package cn.tedu.spring.auto;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class TestUserCache {
public static void main(String[] args) {
ApplicationContext context = new AnnotationConfigApplicationContext("cn.tedu.spring.auto");
UserCache userCache = context.getBean(UserCache.class);
System.out.println("userCache:" +userCache);
}
}
当一个接口有多个实现类时,Spring无法确定注入哪个实现类对象,因此会报错:
No qualifying bean of type ‘cn.tedu.spring.auto.Cache’ available: expected single matching bean but found 2: cacheImpl1,cacheImpl2
可以结合 @Qualifier注解 来解决这个问题
package cn.tedu.spring.auto;
import org.springframework.stereotype.Component;
@Component
public class CacheImpl2 implements Cache{
}
@Qualifier
@Qualifier注解是用于限定一个接口有多个实现类时,根据指定的限定条件来选择具体的实现类的注解;
当Spring容器中存在多个实现同一接口的bean时,在注入时,由于不能确定注入哪一个实现类,就需要通过@Qualifier注解来明确指定要注入的bean的名称。
package cn.tedu.spring.auto;
import org.springframework.stereotype.Component;
@Component(value = "AAAA")
public class CacheImpl1 implements Cache{
}
package cn.tedu.spring.auto;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Repository;
@Repository
public class UserCache {
@Autowired
@Qualifier(value = "AAAA")
private Cache cache;
}
@Resource
注解是JavaEE提供的注解之一,也支持在Spring Framework中使用。在Spring中,它可以用来注入Bean实例,与@Autowired注解的作用类似,但其也有自己的一些特点。
- @Resource注解是JDK扩展包中的,也就是说属于JDK的一部分。所以该解释是标准注解 @Resource 注解默认根据名称装配byName;
当未指定 name 时,则使用属性名作为 name 进行装配;
如果通过name也未找到,则会自动启动通过类型byType装配。 - @Autowired注解是Spring框架自己的,
@Autowired注解默认根据类型装配byType,如果想根据名称匹配,需要配合@Qualifier注解一起使用。
package cn.tedu.spring.resource;
public interface ResMapper {
}
package cn.tedu.spring.resource;
import org.springframework.stereotype.Component;
@Component
public class ResMapperImpl1 implements ResMapper {
}
package cn.tedu.spring.resource;
import org.springframework.stereotype.Component;
@Component
public class ResMapperImpl2 implements ResMapper{
}
package cn.tedu.spring.resource;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
@Component
public class WeiboMapper {
@Resource(name = "resMapperImpl1")
private ResMapper resMapper;
}
package cn.tedu.spring.resource;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class TestWeiboMapper {
public static void main(String[] args) {
ApplicationContext context = new
AnnotationConfigApplicationContext("cn.tedu.spring.resource");
WeiboMapper weiboMapper = context.getBean(WeiboMapper.class);
System.out.println("weiboMapper:" + weiboMapper);
}
}
总结
- 指定@Resource中的name,则根据名称装配
- 未指定name时,则根据属性名装配
- 未指定name,属性名也不一致,则根据类型装配
Bean对象作用域分析@Scope
package cn.tedu.spring.scope;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;
@Scope(value = "singleton")
//@Scope(value = "protoatype")
@Component
public class Order {
private String status;
}
package cn.tedu.spring.scope;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class TestOrder {
public static void main(String[] args) {
ApplicationContext context = new
AnnotationConfigApplicationContext("cn.tedu.spring.scope");
Order order1 = context.getBean(Order.class);
System.out.println(order1);
Order order2 = context.getBean(Order.class);
System.out.println(order2);
}
}
总结
- 当为单例模式 singleton 时,多次获取bean实例的地址是相同的
单例模式适用于需要共享数据并且需要避免重复创建实例的情况 - 当为多例模式 prototype 时,多次获取bean实例的地址是不同的
多例模式适用于需要动态地创建对象并提供独立实例的情况
Bean对象生命周期管理
-
@PostConstruct 注解
生命周期初始化方法,在对象构建以后执行。 -
@PreDestroy 注解
生命周期销毁方法,比如此对象存储到了spring容器,那这个对象在spring容器移除之前会先执行这个生命周期的销毁方法(注:prototype作用域对象不执行此方法)。
package cn.tedu.spring.life;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
@Component
public class Life {
private String lifeTime;
public Life() {
System.out.println("1.实例化(构造方法)");
}
@Value("高科技你咬我")
public void setLifeTime(String lifeTime) {
this.lifeTime = lifeTime;
System.out.println("2.属性赋值(set方法)");
}
@PostConstruct
public void init(){
System.out.println("3.初始化方法执行");
}
@PreDestroy
public void destroy(){
System.out.println("5.销毁阶段:销毁之前需要执行的操作");
}
}
package cn.tedu.spring.life;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class TestLife {
public static void main(String[] args) {
AnnotationConfigApplicationContext context =
new AnnotationConfigApplicationContext("cn.tedu.spring.life");
Life life = context.getBean(Life.class);
System.out.println("4.使用阶段:开发者");
context.close();
}
}
总结
-
实例化阶段(bean对象创建)
在这个阶段中,IoC容器会创建一个Bean的实例,并为其分配空间。这个过程可以通过构造方法完成。 -
属性赋值阶段
在实例化完Bean之后,容器会把Bean中的属性值注入到Bean中,这个过程可以通过set方法完成。 -
初始化阶段(bean对象初始化)
在属性注入完成后,容器会对Bean进行一些初始化操作; -
使用阶段
初始化完成后,Bean就可以被容器使用了 -
销毁阶段
容器在关闭时会对所有的Bean进行销毁操作,释放资源。
引用外部属性文件
在 resources 目录下创建文件 :application.properties
databaseUrl=jdbc:mysql://localhost:3306/tedu
username=root
password=root
package cn.tedu.spring.file;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.PropertySource;
import org.springframework.stereotype.Repository;
@PropertySource(value = "classpath:application.properties")
@Repository
public class Database {
@Value("${databaseUrl}")
private String url;
@Value("${username}")
private String uname;
@Value("${password}")
private String pwd;
@Override
public String toString() {
return "Database{" +
"url='" + url + '\'' +
", uname='" + uname + '\'' +
", pwd='" + pwd + '\'' +
'}';
}
}
package cn.tedu.spring.file;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class TestDatabase {
public static void main(String[] args) {
ApplicationContext context = new
AnnotationConfigApplicationContext("cn.tedu.spring.file");
Database database = context.getBean(Database.class);
System.out.println(database);
}
}
自动扫描配置
- @Configuration注解:
标识此类为Spring的配置类,Spring在启动时会自动加载此类; - @ComponentScan注解:
自动扫描注解;
指定包路径为:cn.tedu.spring, 扫描该包及子孙包中所有的类,把所有添加相关注解的类注册为Spring Bean;
未指定包路径,则扫描该配置文件[SpringConfig.java]所在包以及子孙包中的类;
package cn.tedu.spring.config;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
@Configuration
@ComponentScan(value = "cn.tedu.spring")
public class SpringConfig {
}
package cn.tedu.spring.config;
import cn.tedu.spring.bean.UserService;
import cn.tedu.spring.file.Database;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class TestSpringConfig {
public static void main(String[] args) {
ApplicationContext context = new
AnnotationConfigApplicationContext(SpringConfig.class);
UserService userService = context.getBean(UserService.class);
System.out.println(userService);
Database database = context.getBean(Database.class);
System.out.println(database);
}
}