SpringFramework-01
前言
这系列的博文介绍spring的学习,后期可能会涉及到spring源码的手写,大家敬请期待
一、Spring IoC容器介绍
首先我们先来认识一下什么叫依赖
假设我们现在有两个类
A
package com.darkForest.tomcat.test;
import lombok.extern.slf4j.Slf4j;
@Slf4j(topic = "e")
public class A {
public void show(){
//debug打印日志,表示show方法执行
log.debug("showMethod action");
};
}
B
package com.darkForest.tomcat.test;
import lombok.extern.slf4j.Slf4j;
import lombok.val;
@Slf4j(topic = "e")
public class B {
A a;
public void setA(A a) {
this.a = a;
}
public void into(){
//debug打印日志,表示into方法执行
log.debug("intoMethod action");
}
}
如果我们直接new B ,然后调用B的into方法,我们来看看结果
public static void main(String[] args) {
B b=new B();
b.into();
}
显而易见,b对象调用了into方法,但是,我们并没有产生任何与A类相关的操作,那么我们写的这段代码就没有意义了(还不如不写 哈哈哈哈哈)
A a;
public void setA(A a) {
this.a = a;
}
但是,在实际的开发中,我们是需要像上面这么写的,这就是依赖
的概念。
依赖
简单来说就是java中的某个类的业务或者功能必须在该类中的自定义数据类型(比如我们自己定义的类)其业务或者功能已经实现的基础上才能实现。
强制依赖
:我们可以使用构造方法来实现
非强制依赖
:(强制依赖理解了,非强制依赖就不用说了哈)
既然需要实现该类中的自定义数据类型的业务或功能,那么我们直接实例化A,然后再把a对象通过setA()方法让B类有意义不就行了呗
public static void main(String[] args) {
A a=new A();
B b=new B();
b.setA(a);
b.into();
}
结果也是非常可以的,但是,我们这里仅仅是两个类的依赖,如果是几百上千个类,那这种我们自己去实例化的方式显然是不合适的,首先代码会显得非常臃肿,其次,没有人愿意这么干吧?
那么这个工作就需要springFramework来帮我们做了
Spring IoC容器
首先我们先来理解控制反转
和依赖注入
这两个概念,其实这两个是同一个东西,简单来说就是一个过程:把一个类当中的依赖
填充上的过程。
比如我们上面提到的A类和B类,B类当中有A这个依赖,依赖注入就是把A这个依赖注入到B类当中。
而Spring IoC容器就是完成这一过程的容器(也可以理解成工厂或者集合,反正就是在这里面完成依赖注入这个过程)
二、第一个spring例子
我们在maven项目里写,我们首先要引入springframework依赖
在pom.xml里面写上然后刷新一下
<!--springframework依赖 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.3.18</version>
</dependency>
我们要使用springframework的容器,则必须要给他两个东西
1、你想要给他的类(Business Object)
2、配置元数据(配置元数据有多种方式)
2.1 注解(Annotation):表示使用注解进行配置的元数据。Spring 中的注解包括 @Autowired、@Component、@Value 等,通过注解可以配置类、字段、方法等。
2.2 XML 元素(XML Element):表示使用 XML 进行配置的元数据。XML 元素形式的配置是传统的 Spring 配置方式,通过 XML 文件中的元素来定义各种配置项。
2.3 方法(Method):表示类中的方法的配置信息。可以指定方法的名称、参数、返回值以及该方法需要执行的逻辑。
2.4 类(Class):表示具体的类或抽象类的配置信息。可以指定类的名称、作用域、构造函数参数、初始化方法、销毁方法等。
2.5 包(Package):表示扫描的包名或特定包的配置信息。通过包配置元数据,可以指定要扫描的组件或配置项所在的包路径。
2.6 属性(Properties):表示配置选项的名称和值。属性可以是简单值(如字符串、整数等)或复杂值(如集合、嵌套属性等)。
这里的例子我们用xml配置元数据
首先创建一个spring.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
https://www.springframework.org/schema/beans/spring-beans.xsd">
<!--spring.xml文件用来告诉spring容器你的哪些类需要spring容器来管理
告诉spring容器类的依赖关系-->
<!--bean:就是交给spring管理的对象-->
<!--看到id是不是有点熟悉,id就是bean的名字(key),class就是bean的‘value’ 底层就是用map存储的-->
<bean id="A" class="com.darkForest.tomcat.test.A">
</bean>
<bean id="B" class="com.darkForest.tomcat.test.B">
</bean>
<bean id="C" class="com.darkForest.tomcat.test.C">
<!--依赖关系-->
<!--name的信息和当前配置文件无关,和类的“属性名”有关,比如C类的setA
ref的信息就是当前配置文件中的bean的id-->
<property name="a" ref="A">
</property>
<property name="b" ref="B">
</property>
</bean>
</beans>
同时我们创建三个类A、B、C来测试
(A和B是C的依赖)
A类
package com.darkForest.tomcat.test;
import lombok.extern.slf4j.Slf4j;
@Slf4j(topic = "e")
public class A {
public void show(){
//debug打印日志,表示show方法执行
log.debug("show-{}","A create");
};
}
B类
package com.darkForest.tomcat.test;
import lombok.extern.slf4j.Slf4j;
import lombok.val;
@Slf4j(topic = "e")
public class B {
public void into(){
//debug打印日志,表示into方法执行
log.debug("into-{}","B create");
}
}
C类
package com.darkForest.tomcat.test;
import lombok.extern.slf4j.Slf4j;
@Slf4j(topic = "e")
public class C {
private A a;
private B b;
public void setA(A a) {
this.a = a;
}
public void setB(B b) {
this.b = b;
}
public A getA() {
return a;
}
public B getB() {
return b;
}
public void join(){
a.show();
b.into();
log.debug("join-{}","C create");
}
}
测试代码
public static void main(String[] args) {
//创建一个spring容器实例
ApplicationContext context = new ClassPathXmlApplicationContext("spring.xml");
//得到bean
C c = (C) context.getBean("C");
//调用c的join方法
c.join();
}
结果
可以看到,交给spring容器管理三个类都实例化并且能调用其中的方法,我们全程没有new一个对象,全是springframework帮我们完成的。
三、依赖的手动注入和自动注入
手动注入
spring容器只关心xml文件中的bean,不关心具体的类里面的依赖关系,有就是有,没有就会报异常,
1、spring当中默认的bean是在容器初始化的时候就实例化好了
2、默认情况下,spring的bean是单例的
3、bean的实例化时机(不在容器初始化的时候)
3.1 修改bean为懒加载(在getBean的时候实例化)
在spring.xml中配置
<bean id="A" lazy-init="true" class="com.darkForest.tomcat.test.A">
</bean>
3.2 修改bean的作用域
在spring.xml中配置
<!--prototype:原型bean -->
<bean id="A" scope="prototype" class="com.darkForest.tomcat.test.A">
</bean>
在简单介绍一下单例和原型以及懒加载
单例
:在springframework中单例是在容器初始化的时候就实例化的,因为单例不需要实例化多次,仅仅只需要实例化一次,然后缓存到map里面,需要的时候就直接用就可以了。
原型
:而原型是在每一次getBean的时候(也就是使用的时候)实例化一次,也就是原型可以实例化多次。原型没有缓存。
懒加载
:懒加载虽然也没有在spring容器初始化的时候实例化,但他也是单例的
自动注入
自动注入分两种
1、局部自动注入 ——加一个autowire
<bean id="impl1" autowire="byName" class="com.darkForest.tomcat.test.InsertImpl1">
</bean>
2、全自动注入
在spring依赖自动注入中有三个模型
byType
:spring容器根据需要注入的属性的类型去spring容器当中找bean,如果找到一个,就注入,如果找到多个,则会报异常。
byName
:根据名字从spring容器中找,一般情况下,名字不会出现多个的情况,如果连一个都没有找到,则会报异常。
constructor
:根据构造方法,规则—参数最多并且参数符合spring容器的规则,也就是说参数数量最多,但参数(该类的依赖)要在spring容器中,满足这两个要求,则会自动注入依赖,如果不满足,则会根据算法依次找到符合条件的构造方法。
2.1 使用ByType时
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
https://www.springframework.org/schema/beans/spring-beans.xsd"
default-autowire="byType">
<bean id="impl1" class="com.darkForest.tomcat.test.InsertImpl1">
</bean>
<bean id="impl2" class="com.darkForest.tomcat.test.InsertImpl2">
</bean>
<bean id="insertService" class="com.darkForest.tomcat.test.InsertService">
</bean>
</beans>
需要注意:如果一个接口有多个实现,都存在spring容器中,则自动注入会有异常,因为spring容器不知道你想要注入的是哪一个。
2.2 byType(根据类型去找),因为InsertDao有两个实现,所以在spring容器中找到两个实现类,这个spring不知道你要用哪个,就会报异常给你。
解决方法:把byType改成byName(根据名字去找)
2.3 根据构造方法
我们在OutService类中有三个依赖(InputService 、InsertImpl1、InsertImpl2 ),并且定义了四个构造方法,根据规则,采用自动注入的方式会调用有三个参数的构造方法(也就是参数最多并且其中的三个参数都在xml里面)
public class OutService {
private InputService inputService;
private InsertImpl1 impl1;
private InsertImpl2 impl2;
public OutService(){
log.debug("无参");
}
public OutService(InsertImpl1 impl1){
log.debug("1参数");
}
public OutService(InsertImpl1 impl1,InsertImpl2 impl2){
log.debug("2参数");
}
public OutService(InsertImpl1 impl1,InsertImpl2 impl2,InputService inputService){
log.debug("3参数");
}
}
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
https://www.springframework.org/schema/beans/spring-beans.xsd"
default-autowire="constructor">
<bean id="impl1" class="com.darkForest.tomcat.test.InsertImpl1">
</bean>
<bean id="impl2" class="com.darkForest.tomcat.test.InsertImpl2">
</bean>
<bean id="inputService" class="com.darkForest.tomcat.test.InputService">
</bean>
<bean id="outService" class="com.darkForest.tomcat.test.OutService">
</bean>
</beans>
结果
当然我们也可以手动设置调用的构造方法
在xml文件配置,加上constructor-arg index取0表示调用有一个参数的构造方法,1表示调用有两个参数的构造方法,以此类推,不写表示调用无参构造方法(默认)
<bean id="outService" class="com.darkForest.tomcat.test.OutService">
<constructor-arg index="0" ref="impl1">
</constructor-arg>
</bean>
结果
调用无参构造方法
<bean id="outService" class="com.darkForest.tomcat.test.OutService">
<!-- <constructor-arg index="0" ref="impl1">
</constructor-arg>-->
</bean>
结果
四 springframework中的生命周期
概念
springfeamework中的生命周期不仅仅指Bean的生命周期,他是一个广义上的概念,可指spring容器的生命周期等等,我们先来学习Bean的生命周期。
实现方式
在springframework中提供了几个接口和注解或者自定义方法来实现Bean的生命周期回调(Lifecycle Callbacks)
接口
InitializingBean
和DisposableBean
接口:这两个接口定义了初始化和销毁的回调方法。在Bean初始化完成后,InitializingBean接口的afterPropertiesSet()方法会被调用,而在Bean销毁之前,DisposableBean接口的destroy()方法会被调用。我们可以在这些方法中编写自己的初始化和销毁逻辑。
public class InsertService implements InitializingBean, DisposableBean {
private InsertDao insertDao;
//初始化完成后
@Override
public void afterPropertiesSet() throws Exception {
//执行逻辑
insertDao.insert();
log.debug("callBack");
}
//销毁之前
@Override
public void destroy() throws Exception {
//执行逻辑
insertDao.outsert();
}
}
BeanPostProcessor
接口:这个接口定义了两个回调方法,postProcessBeforeInitialization()和postProcessAfterInitialization()。实现了BeanPostProcessor接口的类可以在Bean的初始化前后执行自定义的逻辑。在初始化之前,可以修改Bean的属性值或进行其他预处理操作;在初始化之后,可以进行后置处理、代理等操作。
自定义方法的方式
1、方法名随便写,方法体里面写需要执行的逻辑
2、在xml文件中写上init-method=“init_method” 双引号里面就是我们自定义的方法的方法名
xml中的配置
public class InsertService{
private InsertDao insertDao;
public void setInsertDao(InsertDao insertDao) {
this.insertDao = insertDao;
}
public InsertDao getInsertDao() {
return insertDao;
}
//自定义方法
public void init_method(){
log.debug("init_method-{}",insertDao.outsert());
}
}
注解
注解的方式过程跟xml的过程是一样的,只不过我们使用注解来代替了xml,下面简单介绍一个如何在spring中使用注解开发。
首先在pom.xml文件中引入注解依赖
<!--注解依赖 -->
<dependency>
<groupId>javax.annotation</groupId>
<artifactId>javax.annotation-api</artifactId>
<version>1.3.2</version>
</dependency>
然后我们定义一个注解类
package com.darkForest.tomcat.annotation.bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
@Configuration
@ComponentScan(basePackages = {"com.darkForest.tomcat.annotation.bean"})//扫描该包下所有的类
public class AppConfig {
}
定义一个接口
package com.darkForest.tomcat.annotation.bean;
import org.springframework.stereotype.Component;
@Component
public interface Bean {
public void method();
}
定义其两个实现类
package com.darkForest.tomcat.annotation.bean;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
@Component
@Slf4j(topic = "e")
public class BeanOne implements Bean {
@Override
public void method() {
log.debug("=====BeanOne create====");
}
}
package com.darkForest.tomcat.annotation.bean;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
@Component
@Slf4j(topic = "e")
public class BeanTwo implements Bean{
@Override
public void method() {
log.debug("=======BeanTwo create========");
}
}
定义一个BeanAction依赖于Bean接口
package com.darkForest.tomcat.annotation.bean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Component
public class BeanAction {
/*
* AutoWired(半自动注入)底层原理
* 先根据类型去找,找到就注入,如果一个接口有多个实现,导致找到多个同一类型
* 那么继续在这个前提下根据名字去找(一般情况下只会找到一个或者找不到,同一名字的情况极少)
* */
@Autowired
private Bean beanOne;
public void setBeanOne(Bean beanOne) {
this.beanOne = beanOne;
}
public Bean getBeanOne() {
return beanOne;
}
public void setBean(Bean bean) {
this.beanOne = bean;
}
public Bean getBean() {
return beanOne;
}
}
让我们来测试一下
@Slf4j(topic = "e")
public class Test {
public static void main(String[] args) {
ApplicationContext context=new AnnotationConfigApplicationContext(AppConfig.class);
log.debug("beanOne -{}",context.getBean(BeanAction.class).getBeanOne());
}
}
结果
从结果中我们可以看到BeanOne是在spring容器中创建了的,完成这一步后,使用Bean的生命周期回调跟我们前面的步骤是一样的。
五 @Import注解
@Import注解在springframewor中是至关重要的一个知识,他大概的作用就是把不在扫描包下的类添加到spring容器中,这其中涉及的技术几乎包含了springframework所有的技术。
1、简单应用
应用非常简单,在AppConfig配置类加入@Import(LoginDaoImpl.class)
LoginDaoImpl(即扫描包外的类)
六、BeanDefinition
BeanDefinition
是一个用来描述spring容器中Bean的类。
为什么需要这样的一个类呢?我们想一想,spring容器是如何帮我们创建好Bean然后供我们使用的?如果直接通过我们定义的类去创建Bean,那么就会出现spring不知道该类是单例的还是原型的,也不知道回调方法是哪一个,如果有一个或者多个属性,也不知道哪个或哪些属性是需要注入的,哪些是不需要的(比如定义的工具类或日期转换类)。出于这样的问题,spring开发了一个BeanDefinition类专门用来描述Bean。
BeanDefinition
类里面含有的基本内容
1、String scope 描述了Bean是单例还是原型
2、String initMethod 描述了Bean初始化回调方法
3、Class class 对应的类
4、Map<“name”,list> list 该Bean依赖于哪些bean,得到之后存进Map里面
5、int autowiredModel 注入模型
在一个完整的spring容器中,Bean的实例化过程大体上如下:
我们定义的类(想要实例化成Bean的类 如Login)—>读取—>Login.class—>解析,然后new一个BeanDefinition对象—>通过各种api解析Login.class得到的信息去实例化BeanDefinition对象—>把BeanDefinition对象存进BeanDefinitionMap里面—>遍历BeanDefinitionMap,做各种判断(比如构造方法的选择、属性注入等等)—>实例化出来一个LoginBean对象,放进一个Map里面(SingletonObjects:单例池)注意:单例池仅仅是spring容器其中的一个组件
七.BeanPostProcessor
bean的后置处理器:对Bean初始化做出干扰
八、BeanFactoryProcessor
bean工厂的后置处理器:对工厂的最原始的东西进行备置
九、BeanFactory
Bean工厂,属于spring容器的一个部分,提供了对Bean的注册,获取,对BeanDefinition的注册,获取,其数量的获取等API。(BeanFactory提供了对bean的一些基础功能。ApplicationContext则是对BeanFactory做了封装,在基础功能上做了扩展,ApplicationContext能对bean进行扫描)
注:不能完成扫描
十、FactoryBean
本身是一个bean,会返回一个bean,通过一个例子来感受一下FactoryBean的作用。
package com.darkForest.tomcat.annotation.bean;
import lombok.extern.slf4j.Slf4j;
/*
* 我们先定义一个NonerClass类
* */
@Slf4j(topic = "e")
public class NonerClass {
public void create(){
log.debug("NonerClass create");
}
}
然后我们再定义一个MyFactoryBean类实现FactoryBean
接口
package com.darkForest.tomcat.annotation.bean;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.stereotype.Component;
/*
* 本身FactoryBean是一个Bean,可以通过getBean获取
* 可以被spring识别
* 通过getBean(beanName)获取的是,getObject()返回的对象(也就是NonerClass),getbean(&beanName)获取的是本身的对象(也就是MyFactoryBean)
* 通过getObjectType()获取的是类型对应的对象
* */
@Component("darkForest")
public class MyFactoryBean implements FactoryBean {
@Override
public Object getObject() throws Exception {
//返回我们刚刚定义的NonerClass类
return new NonerClass();
}
//通过类型
@Override
public Class<?> getObjectType() {
return null;
}
}
来测试一下
public class Test {
public static void main(String[] args) {
ApplicationContext context=new AnnotationConfigApplicationContext(AppConfig.class);
log.debug("MyFactoryBean-{}", context.getBean("&darkForest"));
}
public class Test {
public static void main(String[] args) {
ApplicationContext context=new AnnotationConfigApplicationContext(AppConfig.class);
log.debug("MyFactoryBean-{}", context.getBean("darkForest"));
}
十一、@ImportResource
导入spring的配置文件,应用在采用配置文件和注解配合使用的开发场景中
@Configuration
@ComponentScan(basePackages = {"com.darkForest.tomcat.annotation.bean"})//扫描该包下所有的类
@Import(BeanSelect.class)
@ImportResource("classpath:spring.xml")//使用方式
public class AppConfig {
}
十二、@Bean
如果在扫描不到的情况下,想要把某个类交给spring管理,可以使用@Bean注解,比如mybatis中的session等等
一般写在配置类中
@Configuration
@ComponentScan(basePackages = {"com.darkForest.tomcat.annotation.bean"})//扫描该包下所有的类
@Import(BeanSelect.class)
@ImportResource("classpath:spring.xml")
public class AppConfig {
@Bean
public NonerClass getNonerClass(){
return new NonerClass();
}
}
十三、原型Bean失效的情况
如果在一个单例Bean中注入原型Bean,那么就会导致原型Bean失效,但是我们又有这样的一个需求,那么该怎么解决?
第一种方式:定义一个类实现ApplicationContextAware
@Component
public class ApplicationAware implements ApplicationContextAware {
public static ApplicationContext context;
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
context=applicationContext;
}
}
单例Bean
package com.darkForest.tomcat.annotation.bean;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Lookup;
import org.springframework.stereotype.Component;
@Component("singleton")
@Slf4j(topic = "e")
public abstract class SingletonBean {
private ProtoTypeBean protoTypeBean;
public SingletonBean(){
log.debug("singletonBean create now");
}
public void createNewProto(){
ApplicationAware.context.getBean(ProtoTypeBean.class).createProtoType();
}
}
原型Bean
package com.darkForest.tomcat.annotation.bean;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;
@Component
@Scope("ProtoType")
@Slf4j(topic = "e")
public class ProtoTypeBean {
public void createProtoType(){
log.debug("protoTypeBean create now");
}
}
测试结果
第二种方式 :使用@Lookup
原型Bean跟上面一样
单例Bean
package com.darkForest.tomcat.annotation.bean;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Lookup;
import org.springframework.stereotype.Component;
@Component("singleton")
@Slf4j(topic = "e")
public abstract class SingletonBean {
private ProtoTypeBean protoTypeBean;
@Lookup("protoTypeBean")
public abstract ProtoTypeBean createProto();
public SingletonBean(){
log.debug("singletonBean create now");
}
public void createNewProto(){
createProto().createProtoType();
}
}
结果