Spring
Introduction to Spring Framework
Spring
IOC
Inversion Of Control,一种编程模式,将依赖关系的获取和管理转移到了容器或框架中,从而实现了组件的松耦合。
ApplicationContext
就是管理bean的容器,ioc容器。
构建context&bean的方式
ClassPathXmlApplicationContext
1
2
3<beans +一堆命名空间>
<bean id="" class=""></bean>
</beans>1
2
3
4new ClassPathXmlApplicationContex("这里面放resources目录下的配置文件路径").var
Object o = context.get("beanId"); //<T> t = context.getBean("beanId",T.class);
//这里的实际类型就是bean的实际类型,只是用Object来接收它,所以直接打印调用的是重写的tostring
//默认tostring返回 getClass().getName() + "@" + Integer.toHexString(hashCode()),而且默认的hashCode是本地方法,根据对象的实际内存地址得到哈希码(与内容无关),如果没有哈希冲突,这个哈希码可以唯一标识对象。重写的hashCode大多都是根据内容得到哈希码,内容一样的两个对象得到一样的哈希码,比如两个值相同的字符串。AnnotationConfigApplicationContext
@ComponentScan("")
@Configuration
@Bean
1
new AnnotationConfigApplicationContext(T.class).var
两种可以混合使用
- 在xml中
<context:component-scan base-package="">
或者@ComponentScan("")
;然后注解创建 - 在配置类上
@ImportResource("/.../XXX.xml")
导入配置文件;
- 在xml中
@component
修饰在类上,context在初始化的时候就会实例化一个该类的对象放入context
集合的创建
1
2
3
4
5
6
7
8
9
10
11
12
13<util:list id="list" value-type="java.lang.String">
<value>El1</value>
<value>El2</value>
<value>El3</value>
</util:list>
<util:set id="set" value-type="com.wdte.bean.User">
<value>wdte</value>
<value>liys</value>
<value>publishBy</value>
</util:set>
<util:map id="map" key-type="java.lang.String" value-type="com.wdte.bean.Note">
<entry key="note-1" value="note1"/>
</util:map>这里还需要注册类型转换器把id转成实际的bean
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17import org.springframework.core.convert.converter.Converter;
public class StringToUserConverter implements Converter<String, User> {
@Override
public User convert(String source) {
// 在这里执行将字符串转换为User对象的逻辑
// 例如,根据字符串查询数据库获取User对象
// 然后返回User对象
}
}
<bean id="conversionService" class="org.springframework.context.support.ConversionServiceFactoryBean">
<property name="converters">
<set>
<bean class="com.example.StringToUserConverter"/>
</set>
</property>
</bean>
DI
Dependency Injection,一种特殊的赋值,通过外部的方式将一个对象的依赖关系注入到该对象中,而不是由该对象自己去创建或获取依赖对象。(解耦)
- 构造方法注入
<constructor-arg name="" value=""/>
- setter方法注入(通过外部文件如yaml就是这种方式)
<property name="" value=""/>
- 注解注入
@Component
,需要开启包扫描@ComponentScan()
||<context:component-scan base-package="">
- 构造方法和setter方法可以混用,因为默认空参构造
- 前两种方法的赋值如果传入的不是基本类型,value变ref,引用context中已有的bean
- 使用工厂模式的工厂方法创建的bean首先要创建工厂实例,然后用
factory-bean=“”
传入工厂实例和factory-mothod
指定工厂方法,然后嵌套<constructor-arg/>
传构造方法参数
bean标签是创建bean,里面嵌套的赋值是DI;自动装配是为了更灵活地DI
特殊的有集合bean的创建和DI,还需要引入命名空间
AutoWiring
无需显式指定依赖关系
在Spring框架中,自动装配(Autowiring)是一种通过Spring容器自动将组件之间的依赖关系建立起来的机制。通过自动装配,您可以告诉Spring框架,哪些组件需要依赖哪些其他组件,而Spring会在运行时自动将这些依赖关系满足。
自动装配可以通过注解或XML配置来实现,最常用的方式是使用注解。
要是没有自动装配,需要用户对每个bean的字段一个一个手动赋值,复杂类型的用ref属性引用已经存在的bean
有了自动装配,就不用手动赋值了。配置文件的所有自动装配都不能装配简单类型(包括String、Integer…)
自动装配的类型:
byName:在context里面找到
beanId
和字段名一样的bean并赋值 需要保证
beanId
唯一性,并且如果有多个类型不一样的bean使用同一个id会类型转换错误1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28<bean id="publishBy" class="com.wdte.bean.User">
<property name="name" value="publish User"/>
<property name="age" value="18"/>
</bean>
<bean id="content" class="java.lang.String">
<constructor-arg value="Hello World"/>
</bean>
<bean id="note1" class="com.wdte.bean.Note" autowire="byName">
<property name="id" value="1"/>
</bean>
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Note {
private Integer id;
private String content;
private User publishBy;
}
@Data
@NoArgsConstructor
@AllArgsConstructor
public class User {
private String name;
private int age;
}
这里content不会被自动装配byType:在context里面找到该类型的唯一一个实例并赋值
要么有一个该类型实例要么没有,没有或者有多个会报错
构造方法(constructor)
首先byType再byName
使用了autowire=”constructor”属性,实例化这个对象的时候就会 根据需要的 DI 在提供的构造方法中选择合适的构造方法实例化
如果 被选中的构造方法中包含一个找不到的形参(此时context中没有该类型的bean||有多个该类型的bean)就会报错
没有被构造方法参数列表包含的字段会被设为字段类型的默认值
自定义自动装配(custom):通过实现
BeanFactoryPostProcessor
接口,可以自定义自动装配逻辑。无自动装配
通过注解声明自动装配方式:
@Autowired
byType
@Resource
byName
@Autowired
+ **@Qualifier
**byType产生歧义时,@Qualifier(“”) 传入name
可以这样使用:@Service将service层的实现类放入context,自动装配用接口类型接收,接口有多个实现时,用@Qualifier(“”) 指定beanId,或者在其中一个实现类上标记@Primary,首选实现。
构造方法:如果一个类只有一个构造方法,并且该构造方法的参数在Spring容器中有对应的组件,那么Spring会自动将这些参数注入到构造方法中,即使构造方法上没有显式使用@Autowired
注解。
对象在实例化的时候就会传参引入依赖,这种做法更保险,但是还是要根据实际需求选择自动装配的方式。
自动装配简化了Spring应用程序的配置,提高了代码的可维护性和可读性。然而,在使用自动装配时,需要谨慎处理好依赖关系,避免出现不清晰的情况,以确保应用程序的正确性。
@Value()
注入简单类型 “”
、外部配置文件${}
、别的实例的属性值和spEL
#{}
bean在 DI 和 自动装配时的先后顺序
场景
如果A类中需要 DI 一个B类类型的字段 结果 A在进行DI时 B还没有注入到IOC容器
解决方案
Spring 在设计之初考虑到这个问题 会尽可能的分析依赖关系 先从最下层的依赖bean创建并放入IOC容器 然后逐级向上创建对象并进行DI 然而 这并不稳妥 Spring只是尽可能做到这个事情
Spring团队也考虑到这个问题 在bean标签上提供了 depends-on 属性 方便我们声明依赖关系 Spring 得到这个依赖关系的树状图后 会先从最底层的bean开始创建并进行DI
DI&Autowring方式对比
配置类 @Configuration | @Component |
---|---|
将一个对象注入到IOC容器 | 将一个对象注入到IOC容器 |
@Bean注解注在方法上 | @Component注解在类上 当前类的一个实例会被放入IOC |
自己new 或者 new完set | @Value + @Autowired + @Resource |
调用被@Bean注解修饰的方法 | @Autowired + @Resource 自动装配 |
往往在注入其他人或者开源团队写好的代码中的对象到IOC容器中 | 往往是注入自己的一个类的对象到IOC容器中 |
@Resource是JavaEE规范提供的,不是spring的注解,在别的框架也可以用。
@Resource主要是byName的装配,更适合字段装配;@Autowred主要是byType的装配,不太适合但是可以用在字段上。
@Value
@Value
是 Spring 框架中的一个注解,用于将值注入到 Spring 管理的 bean 中的属性中。这允许你在配置文件中指定值,然后在代码中使用这些值,从而实现了配置和代码的分离。@Value
注解可以用于字段、方法、构造函数参数和方法参数上。
在注入值时,@Value
可以用于以下不同的场景:
注入简单值: 你可以使用
@Value
注解将一个简单的值(如字符串、数字等)注入到属性中。1
2@Value("Hello, Spring!")
private String greeting;注入属性值: 你可以使用
@Value
注解来注入其他 bean 的属性值。1
2@Value("#{otherBean.property}")
private String someProperty;注入外部配置:
@Value
可以用来注入外部配置文件(如 properties 文件)中的属性值。1
2@Value("${app.url}")
private String appUrl;SpEL 表达式: 你还可以使用 Spring 表达式语言(SpEL)来计算属性值。
1
‘xxxxxxxxxx2 1@Value("#{ T(java.lang.Math).PI * 2 * radius }")2private double circumference;java
作用域
Singleton
默认的作用域
跟随context被实例化而被实例化,销毁而销毁。生命周期由context管理
1
2
3<bean id="mySingletonBean" class="com.example.MySingletonBean" scope="singleton">
<!-- Bean properties and configuration -->
</bean>Prototype
每次获取时创建一个新的bean,需要调用者手动销毁。context不负责管理它的生命周期
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21import javax.annotation.PreDestroy;
@Component
@Scope("prototype")
public class User {
private String name;
private int age;
public User(String name, int age) {
this.name = name;
this.age = age;
}
// Bean properties and methods
@PreDestroy
public void destroy() {
// Perform cleanup or resource release here
System.out.println("User Bean is being destroyed");
}
}Request
每次http请求创建一个新的实例,请求结束销毁。
Session
每个用户会话创建一个新的实例,会话结束销毁。
…
Singleton和Prototype对比
Singleton | Prototype | |
---|---|---|
创建时机 | 在应用程序上下文启动时创建 | 在每次请求或获取时创建 |
实例重用 | 是,每次请求或获取时返回同一个实例 | 否,每次请求或获取时返回新的实例 |
生命周期 | 与应用程序上下文相同 | 由调用者负责管理和销毁 |
线程安全性 | 需要保证线程安全 | 不用保证线程安全,每个实例在单独的线程中使用 |
依赖注入 | 可以被其他Bean注入依赖 | 可以被其他Bean注入依赖,但每次注入的都是新实例 |
适用场景 | 适用于状态无关、共享数据的Bean | 适用于每次请求需要新的实例或状态相关的Bean |
Spring容器管理 | 是,由Spring容器负责创建和销毁 | 否,由调用者负责管理和销毁 |
Singleton的懒加载
@Lazy
或者 lazy-init="true"
会让singleton作用域的bean懒加载,即:需要使用时再实例化。
与prototype不同的是,实例化后一直在context中,生命周期由context管理。
生命周期
由实例化、赋值、初始化、使用、销毁几个阶段组成
实例化:contructor/factory方法实例化创建bean
赋值:setter方法(
标签)注入依赖 赋值 初始化:
- 后置处理器前置初始化方法(需要BeanPostProcessor类型的实例,有一个实例所有的bean都会有这个周期)
- 初始化前回调方法 实现InitializingBean接口,实现afterPropertiesSet方法
- 自定义初始方法 init-mothod指定自定义初始方法
- 后置处理器后置初始化方法(BeanPostProcessor)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27@Bean
BeanPostProcessor beanPostProcessor(){
return new BeanPostProcessor() {
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
System.out.println("这是后置处理器前置初始化方法from "+beanName);
return bean;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
System.out.println("这是后置处理器后置初始化方法from "+beanName);
return bean;
}
};
}
这是后置处理器前置初始化方法from org.springframework.context.event.internalEventListenerProcessor
这是后置处理器后置初始化方法from org.springframework.context.event.internalEventListenerProcessor
这是后置处理器前置初始化方法from org.springframework.context.event.internalEventListenerFactory
这是后置处理器后置初始化方法from org.springframework.context.event.internalEventListenerFactory
这是后置处理器前置初始化方法from abean
Properties seted!
这是后置处理器后置初始化方法from abean
这是后置处理器前置初始化方法from bbean
这是后置处理器后置初始化方法from bbean
abeanAbean(field1=1, field2=2, field3=3)
bbeanBbean(field1=4, field2=5)使用:getBean方法获取使用bean
销毁:
- 销毁前回调方法 @Destroy 方法标记方法
- 自定义销毁方法 DisposableBean接口desdroy方法
- context销毁bean
AOP
Aspected Oriented Programming,一种编程思想;
通过预编译和运行期间动态代理实现程序功能统一维护的一种技术;
AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。AOP是OOP的延续。
可以做到在不修改原来的代码的情况下增加业务
切面Aspect
创建切面类@Aspect标记
切点Cutpoint
用表达式声明拦截哪些方法
连接点JoinPoint
向增强方法传的参数
通知Advice
- @Before
- @After
- @AfterReturning
- @AfterThrowing
Dynamic Proxying
Aop 实现原理
Transaction (TX)
Aop的应用
循环依赖
循环依赖(Circular Dependency)是指两个或多个类之间存在相互依赖关系的情况,从而形成了一个闭环。这种情况下,每个类都依赖于另一个类,导致无法在不出现问题的情况下正确地创建这些类的实例。循环依赖可能会导致程序运行时的错误、死锁、性能问题以及代码难以理解和维护。
在软件开发中,循环依赖是应该避免的,因为它们可能会引发一系列问题。下面是一些关于循环依赖的问题和解决方法:
问题:
无法正确实例化: 当两个或多个类互相依赖时,创建它们的实例可能会导致死循环或栈溢出,从而无法成功初始化这些类。
不稳定性: 循环依赖可能导致对象的状态不稳定。因为循环依赖可能会导致对象的构造和初始化过程交织在一起,使得对象的状态在不同步骤中发生变化。
难以理解和维护: 循环依赖会增加代码的复杂性,使代码难以理解和维护。代码中出现循环依赖会使得类之间的关系变得混乱,增加了调试和修改的困难。
解决方法:
设计调整: 重新考虑类之间的依赖关系,看是否可以进行逻辑上的调整,以减少循环依赖。
引入中间层: 可以考虑引入一个中间层或接口来隔离循环依赖,从而将直接依赖转变为间接依赖。
使用延迟初始化: 在 Spring 等容器中,可以使用延迟初始化(Lazy Initialization)来解决循环依赖问题。容器会在需要时才创建对象,从而避免了循环依赖导致的初始化问题。
使用构造函数注入: 在 Spring 中,使用构造函数注入可以降低循环依赖的风险。构造函数注入在对象创建时就完成了依赖的注入,避免了后续初始化过程中的问题。
总的来说,循环依赖是一种应该尽量避免的情况,因为它会引发一系列问题。在设计和编写代码时,需要注意依赖关系,以确保类之间的依赖关系清晰,避免出现循环依赖。
需要包扫描的注解
- @Component 及其衍生注解: 包括
@Component
、@Service
、@Repository
和@Controller
注解,用于定义组件类。这些注解通常被用来标记需要被Spring容器管理的类。 - @Configuration: 用于定义配置类,其中包含了Spring Bean的定义和配置信息。
- @RestController 和 @Controller: 用于定义Web控制器类。
- @Service: 用于定义业务逻辑的服务类。
- @Repository: 用于定义数据访问对象(DAO)类。
- @Autowired 和 @Inject: 用于自动装配Bean依赖。
- @Value: 用于属性注入,允许将外部属性值注入到Bean的字段或方法参数中。
- @Aspect: 用于定义切面,通常与AOP一起使用。
- 自定义注解: 如果您定义了自己的注解,并希望通过在类上使用这些注解来标记特定的类,那么您需要进行包扫描以使Spring能够发现这些标记的类
本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!