Spring 概述
Spring 框架是一个轻量级的开源应用框架,为Java平台提供了全面的基础设施支持。
Spring 的出现主要是为了解决 JavaEE 项目开发的繁琐,它在简化企业级应用开发的同时,也提高了代码的可测试性和可维护性
Spring 框架的核心特性包括 依赖注入 IoC/DI (Dependency Injection)和 面向切面编程 AOP(Aspect-Oriented Programming)
- Spring 功能:
- 可以将开发过程中使用到的 组件对象 放入到 Spring容器中,以便实现单例效果(一个类只有一个实例对象)
- Spring 可以对容器中的 bean对象进行增强,实现 AOP面向切面编程的效果
注解总结
注解名称 | 作用 |
---|---|
@Configuration | 声明这个类是一个配置类 |
@Bean | 在配置类中标注的方法会被 Spring 扫描、运行 |
@Qulifier | 需要取出同一个类型的多个实例对象 |
@ComponentScan | 设定要扫描的包目录 |
@Autowired |
IoC/DI
Spring 的核心特性之一是 IoC/DI,翻译过来就是 控制反转(依赖注入) 这是同一个功能的两种不同角度的称呼
Spring 通过 依赖注入容器 管理组件之间的依赖关系,可以通过 配置文件 或者 注解 来实现,从而实现 松耦合 的组件设计。
- 控制反转:控制 指的是 开发人员对于某个对象的创建控制权,反转 指的是 将这个权利由开发人员转交给 Spring 框架
- 依赖注入:Spring 会自动维护对象之间的依赖关系,使用这个对象时,我们只需要从 Spring 中获取即可
关于 IoC/DI ,底层原理就是: Spring 内部有一个容器(map),Spring 帮助开发人员去创建好实例对象(IoC),并且把这些对象放入到 Spring 容器中,后续使用的时候,直接从容器中取出来即可(DI)
为什么使用Spring,使用容器来维护?
降低服务器的开销成本,不需要创建多个对象,只需要交给容器去维护
便于维护对象和对象之间的依赖关系
减少耦合性
Spring 容器 在 spring 框架中是由 ApplicationContext
来充当的
实例化Bean
创建对象
构造函数
虽然 Spring 会借助于反射来创建 Bean对象,其实本质上来说依然是要借助于构造函数,绝大数情况下使用的是无参构造函数
静态工厂及实例工厂
在 xml文件中 使用静态工厂、实例工厂创建实例化Bean对象的方式了解即可,后续会使用注解、配置类的方法
FactoryBean
这个基于 FactoryBean 的方法需要重点关注
- 某个类实现了 FactoryBean 接口,那么直接利用其编号取出来的 并不是该对象本身,而是 getObject()返回值的结果
比如: - 配置:
<bean id="userfb" class="com.cskaoyan.th58.factorBean.UserFactorBean"/>
- 代码:
public class UserFactorBean implements FactoryBean<User> {
@Override
public User getObject() throws Exception {
return new User();
}
@Override
public Class<?> getObjectType() {
return User.class;
}
}
- 单元测试:
@Test
public void test2(){
ApplicationContext context = new ClassPathXmlApplicationContext("app.xml");
// 用FactoruBean方法取出来的类型是getBean()方法的返回值 --- User
User userfb = (User) context.getBean("userfb");
}
组件注册方式
放入Spring容器
xml文件
了解即可
- 在 pom.xml 文件导入依赖
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.3.30</version>
</dependency>
<dependency> <groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.2</version>
<scope>test</scope>
</dependency>
</dependencies>
- 在 resources 目录下新建 app.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">
<!--放入Spring容器中的对象称之为 bean,id指的是放入容器中的对象编号,class是其全限定类名-->
<bean id="userService" class="com.cskaoyan.th58.service.UserServiceImpl"/>
<bean id="userMapper" class="com.cskaoyan.th58.mapper.UserMapperImpl"/>
</beans>
- 编写单元测试用例
@Test
public void test1(){
// 使用Spring容器去管理对象,前提是需要有一个Spring容器
// Spring容器在 spring 框架中是由 ApplicationContext 来充当的,他是一个接口,具体的子类实现我们选择的是读取xml来实例化容器
// 我们要输入一个位于 classpath目录下 xml文件的信息
ApplicationContext context = new ClassPathXmlApplicationContext("app.xml");
// Spring会帮助我们去创建对应的实例对象,并且放入到Spring容器中
// 只需要利用容器的方法来获取位于容器中的组件对象即可
// key值是什么呢,就是刚刚注册的时候设置的id编号,编号要求是唯一的
UserService userService = (UserService) context.getBean("userService");
UserMapper userMapper = (UserMapper) context.getBean("userMapper");
System.out.println(userService);
System.out.println(userMapper);
}
配置类+@Bean注解
⭐操作步骤:
-
新建一个config包,新建一个配置类,标注
@Configuration
注解,声明其是一个 配置类 -
在配置类中去编写一个一个的方法,方法的要求如下:
- 编写一个方法,方法的修饰符要求是 public
- 方法的返回值类型 便是注册到 Spring 容器中的组件类型,一般建议 使用父接口类型来接收
- 方法的名称 便是注册到 Spring 容器中的组件的编号
- 如果需要注入依赖,那么使用@Bean注解的话非常简单:直接在方法的形参中编写你需要从容器中取出来的组件的类型,Spring容器便会自动从容器中取出对应的类型的组件。
- 如果同一个类型的实例对象有多个的话,可以使用
@Qualifier
注解,注解中写明编号即可
原理:凡是配置类中标注了
@Bean
注解的方法,那么 Spring 会依次去扫描,依次去运行,得到一个实例对象,把该实例对象放入到 Spring 容器中。
- 如果不需要维护对象和对象之间的依赖关系,那么下面的写法就ok了:
@Configuration
public class SpringConfig {
//你希望向 Spring容器 中去注册哪个组件,那么便编写哪个对象的创建语句
@Bean
public UserService userService(){
UserServiceImpl userService = new UserServiceImpl();
//userService.setUserMapper();
return userService;
}
@Bean
public OrderService orderService(){
OrderServiceImpl orderService = new OrderServiceImpl();
// orderService.setUserMapper();
return orderService;
}
@Bean
public UserMapper userMapper(){
UserMapper userMapper = new UserMapperImpl();
return userMapper;
}
}
- 但是如果需要维护对象和对象之间的关系:
//声明其是一个配置类
@Configuration
public class SpringConfig {
//你希望向 Spring容器中去注册哪个组件,那么便编写哪个对象的创建语句
@Bean
public UserService userService(UserMapper userMapper){
UserServiceImpl userService = new UserServiceImpl();
//service实现类需要提供set方法即可
userService.setUserMapper(userMapper);
return userService;
}
//Spring 处理过程:
//1.根据方法的返回值类型,得知最终注入到 Spring容器中的是 OrderService类型的对象
//2.方法的名称叫做 orderService,所以注册到 Spring容器中的对象的编号为 orderService
//3.方法的形参列表有一个叫做 UserMapper,所以 Spring会扫描容器,从容器中取出一个 UserMapper实例对象,在调用当前方法时传递进来
@Bean
// 如果某个类型的实例对象有多个的话,并且希望指定从容器中获取对应的实例对象,可以使用 @Qualifier 注解
public OrderService orderService(@Qualifier("userMapperImpl2") UserMapper userMapper){
OrderServiceImpl orderService = new OrderServiceImpl();
orderService.setUserMapper(userMapper);
return orderService;
}
@Bean
public UserMapper userMapper(){
UserMapper userMapper = new UserMapperImpl();
return userMapper;
}
}
使用场景:
主要用在整合第三方框架时,把第三方框架里面的类库放入到spring容器中
配置类+@ComponentScan
操作步骤:
-
编写一个配置类,标注
@Configuration
注解,声明这是一个配置类 -
配置类还需要标注一个注解
@ComponentScan()
,设定 扫描的包目录 -
业务代码,如果希望放入到 Spring容器 中,按照三层架构划分:
@Controller
、@Service
、@Reposiroty
,如果不是这三层架构的就添加@Component
-
如果同一类型的组件对象有多个,希望可以获取特定某个对象,可以使用
@Qualifier
注解 -
如果需要维护对象之间的关系需要添加
@Autowired
注解
- 配置
// 我们还需要将那些需要放入到 Spring容器中的类头上添加 @Component 注解
// @Component 与 @Controller @Service @Repository 等价,对应三层架构中的三个组件
@Configuration // 声明这是一个配置类
@ComponentScan("com.cskaoyan.th58") // 设定扫描的包目录(可以通过递归的方式扫描到所有的类)
public class SpringConfig {
}
- 编写代码
@Service
public class OrderServiceImpl implements OrderService {
@Autowired // 表明需要从容器中取出符合类型的数据对象
@Qualifier("userMapperImpl2")
UserMapper userMapper;
}
@Service
public class UserServiceImpl implements UserService {
@Autowired
@Qualifier("userMapperImpl")
UserMapper userMapper;
}
- 单元测试
public class SpringTest {
@Test
public void test1(){
// 首先进行容器的实例化
ApplicationContext context = new AnnotationConfigApplicationContext(SpringConfig.class);
Object userServiceImpl = context.getBean("userServiceImpl");
Object orderServiceImpl = context.getBean("orderServiceImpl");
Object userMapperImpl = context.getBean("userMapperImpl");
Object userMapperImpl2 = context.getBean("userMapperImpl2");
System.out.println(userServiceImpl);
System.out.println(orderServiceImpl);
System.out.println(userMapperImpl);
System.out.println(userMapperImpl2);
}
}
使用场景:
项目中编写的业务代码,使用的就是这种方式
![[两种组件注册方式比对.png|800]]
组件获取方式
从Spring容器获取
getBean 方法
利用容器的 getBean方法,可以从容器中获取指定类型的实例对象
public class SpringTest {
@Test
public void test1() {
// 首先进行容器的实例化
ApplicationContext context = new AnnotationConfigApplicationContext(SpringConfig.class);
Object userServiceImpl = context.getBean("userServiceImpl");
Object orderServiceImpl = context.getBean("orderServiceImpl");
Object userMapperImpl = context.getBean("userMapperImpl");
Object userMapperImpl2 = context.getBean("userMapperImpl2");
System.out.println(userServiceImpl);
System.out.println(orderServiceImpl);
System.out.println(userMapperImpl);
System.out.println(userMapperImpl2);
} }
关于 容器,我们之前提及的 ApplicationContext,是一个接口,但是他不是最顶层接口,向上还有一个 BeanFactory接口,那么该接口其实也是容器的实现
ApplicationContext 继承自 BeanFactory,BeanFactory 其实就是最初的容器实现,ApplicationContext继承自他,功能都具有,又做了一些扩展。
![[容器继承关系.png]]
FactoryBean 和 BeanFactory 没有关系
FactoryBean 强调的是 Bean,是一种创建对象的方式
BeanFactory 强调的是 Factory,指的是容器
注解
使用注解来获取容器中的组件对象
最常用的注解是 @Autowired
注解,背后的原理就是 Spring 会帮助我们在背后调用 getBean 方法,将获取到的组件对象注入到当前的引用类型变量中
除此之外还可以使用 @Resource
注解,不过需要导入一个 javax.annotation-api 依赖,这是一个 JDK内嵌的注解
Spring 整合 Junit
如果我们希望再进行 单元测试用例 的时候,可以直接使用注解的方式 来获取指定的实例对象,那么可以这么配置(也交给Spring去管理):
- 导入 spring-test 依赖
- 单元测试类需要添加
@RunWith()
注解,表示会读取 Spring 的配置信息,用于整合 junit 所必须的步骤 - 添加
@ContextConfiguration
注解,用于实例化容器 - 直接使用
@Autowired
注解来获取指定类型的实例对象即可
// Spring 整合 junit 的环境所必须要处理的步骤
@RunWith(SpringJUnit4ClassRunner.class)
// 用于进行读取配置类或者读取xml配置文件的,用于实例化容器
@ContextConfiguration(classes = SpringConfig.class)
public class SpringTest2 {
// 如果希望在某个类中可以直接通过Autowired来获取容器中的对象,那么当前类对象也必须要交给Spring管理
@Autowired
UserService userService;
@Test
public void test1(){
System.out.println(userService);
} }
组件的生命周期
组件的生命周期,就是指的是 组件从诞生到最终销毁的整个阶段。在特定的阶段,Spring容器会调用组件相对应的方法。
过程如下:
![[组件的生命周期.png]]
- 容器启动
ApplicationContext context = new ClassPathXmlApplicationContext(xml);
ApplicationContext context = new AnnotationConfigApplicationContext(配置类.class);
-
实例化Bean对象
主要借助于反射来进行对象的实例化
反射需要的信息可以从 xml 配置文件中获取,也可以从配置类中获取IoC 是在这一步体现的
-
设置对象的属性值(赋值操作)
这一步实际是在维护对象之间的依赖关系
如果需要注入的对象没有被实例化,会先将该对象进行实例化DI 主要是在这一步体现的
-
根据当前对象是否实现 aware接口,对bean对象进行类型转换,去调用接口对应的 set方法
最大的意义是可以将容器的引用传递给当前对象,可以很方便的操作容器
-
BeanPostProcessor 对 bean 对象进行处理 ⭐
如果需要这个功能,我们需要创建一个 BeanPostProcessor 进行前置处理
// 前置处理
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
System.out.println(bean + "=======" + beanName);
// 很重要 这个是AOP的基石
return bean;
}
-
init 阶段
类似于 Servlet 的 init 方法
会根据当前的 bean对象 是否实现了 InitializingBean接口,调用对应的方法,或者实现了自定义的 init 方法,调用对应的自定义 init方法,调用对应的自定义 init方法,可以去做一些初始化的业务逻辑
// 实现 InitializingBean 接口
@Override
public void afterPropertiesSet() throws Exception {
System.out.println("Initialiizing Bean...初始化的业务逻辑");
}
// 除了上面还有另外一种自定义 init方法,可以不用实现 InitializingBean 接口
// 1.导入 javax.annotation-api 依赖
// 2.添加注解 @PostConstruct
@PostConstruct
public void myinit() throws Exception {
System.out.println("Myinit");
}
-
后置处理,放入容器
init 阶段完成之后,再次经过 beanPostsProcessor后置 处理,处理过后便放入到 Spring容器 中,便是 可使用的状态 了。
// 后置处理
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
return bean;
}
-
调用销毁方法
最后根据是否实现
DisposableBean
接口,决定是否调用destroy方法
(一般框架会用该方法来完成一些善后、销毁工作,比如释放资源
或者根据当前 Bean 是否实现 自定义destroy方法,决定是否调用 自定义destroy方法