一、Spring 核心技术
1、Spring框架概述
Spring框架采用了控制反转(IoC)和面向切面编程(AOP)的设计理念,简化了企业级Java应用程序的开发过程。
2、从面试看Spring
二、Spring Ioc
1、IOC概述
IoC(Inversion of Control),即控制反转,是一种设计原则。将程序由传统的操作控制流程反转了。
在Spring中,由IoC容器创建、组装和管理的对象被称为Bean。
2、Spring Ioc示例
(1)导入依赖
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>6.0.11</version>
</dependency>
(2)新建ContextConfig类
/**
* 创建Spring的配置类
* 代表Spring Ioc容器
*/
@Configuration //声明这是Spring Ioc容器
@ComponentScan("cn.highedu.entity") //声明到哪里去找具有@Component注解的类
public class ContextConfig {
}
(3)声明cn.highedu.entity包,并在该包下创建Computer类
@Component //将该类交给Spring去管理
//Spring会负责创建该类的对象 并提供全方位的管理
public class Computer {
@Override
public String toString() {
return "Computer{}";
}
}
(4)在cn.highedu包下创建案例的主类,命名为Application
public class Application {
public static void main(String[] args) {
/**
* 获取spring的容器管理的对象
* 1、获取容器
* 2、获取容器中的对象
*/
//初始化Spring Ioc容器对象 指定管理类
ApplicationContext context =
new AnnotationConfigApplicationContext(ContextConfig.class);
//从容器中获取被管理的Bean
Computer c = context.getBean(Computer.class);
System.out.println(c);
}
}
运行结果:
Computer{}
好处:如果spring在那么对象一直就在,不用自己再去创建对象
3、IoC示例运行原理解析
- @Configuration:用于标记当前类是一个Spring框架的配置类(管理类)
- @ComponentScan("cn.highedu"):用于通知Spring框架自动扫描和加载指定包下的所有组件并注册到Spring IoC容器中
Computer是一个交给Spring管理的类。
- @Component:用于将类标识为一个受Spring管理的组件
ApplicationContext是Spring容器中最重要的核心接口之一,它扮演着Spring容器的角色,提供了一种对Spring Bean进行集中式管理的机制。
- getBean方法用于从IoC容器中获取一个符合条件的Bean对象。
AnnotationConfigApplicationContext是ApplicationContext接口的一个重要实现,它可以通过读取基于Java类的配置以及指定的Component classes来构建Spring IoC容器。
4、IoC实现解耦示例
本案例通过一个简单的耦合场景,演示Spring IoC为项目带来的好处。
定义一个接口A和它的实现类B,然后定义20个类,分别命名为X1到X20。在X1到X20中都有一个名为method的方法,在方法中创建了B类的实例,并调用了run方法。
此时,X1到X20与接口及其他实现类B存在依赖关系,即存在耦合。如下图所示:
此时,有一个程序扩展需求:新建A接口的实现类C,将程序中所有的B类的对象都替换为C类的对象。为实现这一需求,需要逐个修改X1到X20类中的代码,程序的可扩展性和可维护性较差。
如果项目中应用了Spring IoC,则可以极大的简化这一工作。
示例:
创建接口A:
public interface A {
void run();
}
创建实现类B:
@Component //对象有标识,默认是类名首字母小写
public class B implements A {
@Override
public void run() {
System.out.println("B开始工作");
}
}
创建实现类C:
@Component //对象有标识,默认是类名首字母小写
public class C implements A {
@Override
public void run() {
System.out.println("C开始工作");
}
}
启动类:
public class Application {
public static void main(String[] args) {
/**
* 获取spring的容器管理的对象
* 1、获取容器
* 2、获取容器中的对象
*/
//初始化Spring Ioc容器对象 指定管理类
ApplicationContext context =
new AnnotationConfigApplicationContext(ContextConfig.class);
// 通过类型获取对象
//A a1 = context.getBean(A.class);
//A a2 = context.getBean(A.class);
//a1.run();
//a2.run();
//通过对象的名字获取对象
A a1 = context.getBean("b", A.class);
A a2 = context.getBean("c", A.class);
a1.run(); //B开始工作
a2.run(); //C开始工作
}
}
5、Spring DI
DI是Dependency Injection的缩写,即依赖注入,是一项使一个对象接收到它所依赖的其他对象的技术。DI 的实现通常需要使用注解或 XML 配置文件来声明依赖关系:
1、注解方式
Spring框架支持使用@Autowired、@Resources等注解来实现依赖注入。
- @Autowired注解可以注入一个具体的类、接口、或者抽象类,一般用于注入当前对象依赖的其他对象
- @Resources注解与@Autowired注解功能相似,但是执行机制上略有不同,推荐使用@Autowired注解
@Component
public class Student {
//需要Computer对象
@Autowired //依赖注入 Spring会把Student类需要的依赖对象Computer加载进来
private Computer computer;
public void run() {
System.out.println(computer);
}
}
启动类:
public class Application {
public static void main(String[] args) {
/**
* 获取spring的容器管理的对象
* 1、获取容器
* 2、获取容器中的对象
*/
//初始化Spring Ioc容器对象 指定管理类
ApplicationContext context =
new AnnotationConfigApplicationContext(ContextConfig.class);
Student student = context.getBean(Student.class);
student.run();
}
}
三、Spring Ioc进阶
Spring 管理注解:
使用注解来标识Bean,并指定Bean的属性、依赖关系等信息。
1、定义Bean
在Spring IoC容器中获取Bean可以通过以下几种方式:
1、context.getBean(Class
- requiredType为要获取bean的类型
- 如果容器中有多个实现该类型的Bean,但是没有配置优先使用哪个Bean,则会抛出NoUniqueBeanDefinitionException异常
2、context.getBean(String name):根据名称获取Bean。其中:
- name为Bean在Spring IoC容器中的名称
- 默认情况下,Bean的名称是类名进行了首字母小写的处理后的结果
- 可以通过在Spring注解中设置名称来设置Bean的名称,如:@Component("myBean")
3、context.getBean(String name, Class
- 该方法相当于在getBean(String name)方法的基础上添加了Bean类型的验证
- 如果获取的Bean对象与requiredType指定的类型不同,将抛出BeanNotOfRequiredTypeException
4、context.getBeansOfType(Class
- type为要获取bean的类型
- 该方法返回Map类型,其中key为Bean在容器中的名称,value为Bean实例
/**
* @document: 管理第三方的组件
* @Author:SmallG
* @CreateTime:2023/9/14+14:19
*/
@Configuration //管理类声明
public class BeanDefineConfig {
/**
* 被Spring管理一个对象 对象的标识(id)是方法的名字(getName)
* 对象的类型是String
* 对象的内容是Tom
*
* @return
*/
@Bean
public String getName() {
return "Tom";
}
@Bean
public String[] getNames() {
String[] names = {"Tom", "Jerry", "Spike"};
return names;
}
@Bean
public ArrayList<String> names() {
ArrayList<String> names = new ArrayList<>();
names.add("张三");
names.add("李四");
names.add("王五");
return names;
}
/**
* 被Spring管理的Bean 标识是list
* 需要引入Spring IOC容器中其他的Bean
* 方法中的参数的名字就是要引入的Bean的标识
* @param names
* @return
*/
@Bean
public ArrayList<String> list(ArrayList<String> names) {
names.add("关羽");
names.add("关兴");
return names;
}
}
测试类:
public class ApplicationTest {
private ApplicationContext context;
@BeforeEach
public void createContext() {
context = new AnnotationConfigApplicationContext(BeanDefineConfig.class);
}
@Test
public void test1() {
//获取被Spring IOC容器管理的第三方组件的对象
String name = context.getBean("getName", String.class);
System.out.println(name);
}
@Test
public void test2() {
String[] names = (String[]) context.getBean("getNames");
for (String name : names) {
System.out.println(name);
}
}
@Test
public void test3(){
ArrayList<String> names= (ArrayList<String>) context.getBean("names");
names.forEach(System.out::println);
}
@Test
public void test4(){
ArrayList<String> names= (ArrayList<String>) context.getBean("list");
names.forEach(System.out::println);
}
}
2、Bean的作用域
常用的两种作用域:单例作用域、原型作用域
- 单例作用域(singleton):Bean的默认作用域,任何时候获得的Bean对象都是同一个实例
- 原型作用域(prototype):每次引到bean时都会创建新的实例
作用域示例:
在cn.highedu包下新建SingletonBean
/**
* Spring Bean单例作用域
* Spring Bean默认的作用域就是单例作用域(singleton)
* 在初始化Spring IOC容器时 同时初始化所有作用域是单例的Bean
* 在以后使用的过程中 Spring IOC容器始终管理这个Bean
*/
@Component
public class SingletonBean {
//一个类被Spring管理 类中要有默认的构造方法
public SingletonBean() {
System.out.println("SingletonBean被实例化");
}
@Override
public String toString() {
return "SingletonBean{}";
}
}
声明一个管理类:
//声明一个管理类
@Configuration
@ComponentScan("cn.highedu.spring") //扫描spring包里的所有类
public class ScopeConfig {
}
对作用域进行测试
public class ScopeTest {
private ApplicationContext context;
@BeforeEach
public void createContext() {
context = new AnnotationConfigApplicationContext(ScopeConfig.class);
}
@Test
public void test1() {
SingletonBean bean1 =
context.getBean("singletonBean", SingletonBean.class);
SingletonBean bean2 =
(SingletonBean) context.getBean("singletonBean");
System.out.println(bean1 == bean2); // true 说明两次获取的是同一个bean
}
}
声明原型作用域的类:
原型作用域在获取Bean的时候Spring IOC才会创建Bean的示例,Spring IOC不会管理原型作用域的Bean,java垃圾回收机制会回收这样的Bean
/**
* 原型作用域,非单例模式,每次创建都是新的
*/
@Component
@Scope("prototype") //声明这个bean的作用域是原型作用域
public class PrototypeBean {
public PrototypeBean() {
System.out.println("PrototypeBean被实例化");
}
}
测试作用域:
@Test
public void test2() {
PrototypeBean bean1 =
context.getBean("prototypeBean", PrototypeBean.class);
PrototypeBean bean2 =
(PrototypeBean) context.getBean("prototypeBean");
System.out.println(bean1 == bean2); // false 说明两次获取的不是同一个bean
}
3、依赖注入
Spring框架主要支持三种注入方式:字段注入、构造器注入、setter方法注入
构造方法依赖注入示例:
@Component
public class Student {
//需要Computer对象
@Autowired //依赖注入 Spring会把Student类需要的依赖对象Computer加载进来
private Computer computer;
public Student() {
System.out.println("调用了无参构造");
}
//构造方法实现依赖注入
@Autowired
public Student(Computer computer) {
System.out.println("调用了有参构造");
this.computer = computer;
}
public void run() {
System.out.println(computer);
}
}
测试类:
public class DITest {
private ApplicationContext context;
@BeforeEach
public void createContext() {
context = new AnnotationConfigApplicationContext(ContextConfig.class);
}
@Test
public void test1(){
Student student = context.getBean("student",Student.class);
student.run();
}
}
测试结果:
调用了有参构造
Computer{}
setter方法实现依赖注入(spring 官方推荐)
@Component
public class Student {
//需要Computer对象
@Autowired //依赖注入 Spring会把Student类需要的依赖对象Computer加载进来
private Computer computer;
//setter方法实现依赖注入
@Autowired
public void setComputer(Computer computer) {
this.computer = computer;
}
public Student() {
System.out.println("调用了无参构造");
}
//构造方法实现依赖注入
@Autowired
public Student(Computer computer) {
System.out.println("调用了有参构造");
this.computer = computer;
}
public void run() {
System.out.println(computer);
}
}