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)

接口

InitializingBeanDisposableBean接口:这两个接口定义了初始化和销毁的回调方法。在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();
    }
}

结果
在这里插入图片描述

总结

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值