【Spring学习篇】---Spring基础学习


传统Javaweb开发困惑及解决办法

开发遇到的问题:

①如图

  1. service层的实现需要获得dao层对象,每次都new,代码耦合度高
  2. 事务问题
  3. 用户行为信息的一个日志记录
    1. ​ 解决思路:程序代码不要手动new,通过第三方获取需要的Bean对象。

IOC、DI和AOP思想的提出:

  1. IOC思想:Inversion of Control,控制反转,强调的是原来在程序中创建Bean的权利反转给第三方。
  2. DI思想:Dependency Injection,依赖注入,强调的Bean之间的关系,这种概念性第三方负责去设置。
  3. AOP思想:Aspect Oriented Programing ,面向切面编程,功能的横向抽取,主要实现方式就是Proxy。

框架概念的思想提出:

  1. 基本特点:基于基础技术之上,从众多业务抽取出通用解决方案;是个半成品,使用框架规定的语法开发提高效率;框架内部使用大量的设计模式、算法、底层代码操作技术,如反射、内省、xml解析、注解解析等;具备一定扩展性;
    1. 基础框架:如MyBtis、Spring、SpringMvc、Struct2、Hibernate等;
    2. 服务框架:特定领域的框架,一般还可以对外提供服务的框架,如MQ、ES、Nacos等;

SPring框架的诞生:

① 概述:

​ Spring是一个开源的Java开发应用框架,可以简化企业家应用开发。Spring解决了开发者在JavaEE开发中常遇到的问题,提供了强大IOC、AOP及Web MVC等功能。Spring的生态及其完善,不论哪个领域的解决方案都是依附在SpringFramework基础框架的。

[Spring官网]: www.spring.io “Spring官网”

②框架历史发展:

  • Jsp默默扛下所有
  • MVC+三层架构分工明确,但开发成本极高
  • EJB重量级框架出现,走出一个困境,又走进另一个困境
  • Spring春天来到,随之,SSH风生水起,称霸武林;
  • Spring稳坐江湖大哥,SSM开始上位
  • Spring本着“拿来主义”思维快速发展,生态不断健全
  • Spring又一里程碑崛起,把“约定大于配置”思想玩的炉火纯青
  • SpringCloud打包了众多解决方案,应对互联网项目更加easy!

③Spring Framework技术栈

​ 引入Context包会自动引入Beans、Core、SpEL包

④BeanFactory快速入门

1
2
3
4
5
6
7
8
//创建BeanFactory
DefaultListableBeanFactory beanFactory=new DefaultListableBeanFactory();
//创建读取器
XmlBeanDefinitionReader reader=new XmlBeanDefinitionReader(beanFactory);
//加载配置文件
reader.loadBeanDefinitions("application.xml");
//获取Bean对象
UserService userService=(UserService)beanFactory.getBean("userService");

注意:

  • 实现DI依赖注入
  1. 定义userDao
  2. userService添加setUserDao(UserDao dao)用于接收注入的对象
  3. 修改application.xml文件,在userServiceImpl中的标签中嵌入配置注入

④ApplicationContext快速入门

ApplicationContext称为Spring容器,内部封装的了BeanFactory,比BeanFactory功能更加丰富强大,使用ApplicationContext进行开发时,xml配置文件习惯写成applicationContext.xml。

1
2
3
4
5
//创建context
ApplicaitonContext context=new ClassPathXmlApplicaitonContext("applicationContext.xml");

//获取Bean对象
UserService userService=(UserService)context.getBean("userService");

与ApplicationContext的关系:

1)BeanFactory是Spring的早期接口,称为Spring的bean工厂;ApplicationContext是后期的高级接口,称之为Spring容器。

2)ApplicationContext在BeanFactory基础上做了很多扩展。例如国际化、监听功能等;BeanFactory的API更偏向底层。

3)Bean的创建主要逻辑封装在BeanFactory中,ApplicationContext不仅继承了BeanFatory,其内部还维护着BeanFactory的引用。既有继承关系又有融合关系。

4)Bean的初始化时机不同,原始BeanFactory是在首次调用getBean时进行Bean的创建,而ApplicationContext在是加载配置文件,容器一创建就将Bean实例化并初始化好。(即BeanFactory为延迟加载)

BeanFactory的继承体系:

具体实现为DefaultListableBeanFactory,而AppicationContext内部维护的便是它。

ApplicationContext的继承体系:

只在Spring基础环境下,常用的有三个

ClassPathXmlApplicationContext————-加载类路径下的xml配置的ApplicationContext

FileSystemXmlApplicationContext———–加载磁盘路径下的xml配置的ApplicationContext

AnnocationConfigApplicationContext——-加载注解配置类的ApplicationContext

一、基于xml的Spring应用

1.SpringBean的配置详解

常用配置如下

  • Bean的id和全限定名配置(不设id则singletonObjects的map中KEY为全限定名)
  • Bean的别名(存在aliasMap中,key为别名,value为bean的id)
    • 注意:在未配id时,singletonObjects的map中KEY为别名的第一个,没有id和别名则为全限定名
  • Bean的作用范围,BeanFactory作为容器时取值singleton和prototype
  • Bean的实例化时机,是否延迟加载,BeanFactory作为容器时无效
  • Bean的实例化执行的初始化方法
  • Bean的实例销毁前的方法
  • 设置自动注入模式,常用的有按类型byType和按名字byName
  • 指定哪个工厂bean的哪个方法完成Bean的创建

Spring实例化方式主要如下两种:

  • 构造方法实例化:底层通过构造方法对Bean进行实例化

    • 标签中加入实现有参构造。
  • 工厂方式实例化:底层调用自定义的工厂方法进行Bean的实例化

    • 静态工厂方法

      1
      2
      3
      4
      5
      6
      7
      //自定义工厂
      public class MyBeanFactory1{
      public static UserDao getUserDao(){
      //bean创建前可进行其他的业务逻辑操作
      return new UserDaoImpl();
      }
      }
      1
      <bean id="userDao1" class="com.truly.factory.MyBeanFactory1" factory-method="getUserDao"/>

      会实例化userDao;

    • 实例工厂方法

    1
    2
    3
    4
    5
    6
    7
    //自定义工厂
    public class MyBeanFactory2{
    public UserDao getUserDao(){
    //bean创建前可进行其他的业务逻辑操作,第三方jar包提供了工厂时,就这样获取bean
    return new UserDaoImpl();
    }
    }
    1
    2
    <bean id="myFactory2" class="com.truly.factory.MyBeanFactory2"/>
    <bean id="userDao2" factory-bean="myFatory" factory-method="getUserDao"/>
    • 实现FactoryBean接口规范延迟实例化Bean
1
2
3
4
5
6
7
8
9
10
11
12
//自定义工厂
public class MyBeanFactory3 implements FactoryBean<UserDao>{
@Override
public UserDao getObject(){
return new UserDaoImpl();
}

@Override
public Class<?> getObjectType(){
return UserDao.class;
}
}
1
<bean id="userDao3" class="com.truly.factory.MyBeanFactory3"/>

注意 在单步执行过程中,我发现在singletonObjects中存的key为userDao3,但value为MyBeanFactory3这个工厂对象,并不是我们想要的userDao3,但为什么还是能拿到对象呢?

在FactoryBeanObjectCache这个缓存map中有key为userDao3,value为对应userDao的实现

Bean的依赖注入主要如下两种:

  • 通过Bean的set方法:
1
2
<property name="userDao" ref="userDao"/>
<property name="userDao" value="truly"/>
  • 通过构造Bean的方法:
1
2
<constructor-arg name="userDao" ref="userDao"/>
<constructor-arg name="userDao" value="truly"/>

注入数据类型有如下三种:

​ 1)普通数据类型:如String,int等,通过value属性指定

​ 2)引用数据类型:如UserDaoImpl等,通过ref属性指定

​ 3)集合数据类型:如List、map、properties等。

  • 如list为例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<property name="stringList">
<list>
<value>truly</value>
<value>spring</value>
</list>
</property>
<property name="userDaoList">
<list>
<bean class="com.truly.userDaoImpl"/>
<ref bean="userDao3"></ref>
</list>
</property>
//set就为set标签
//map就为map标签,子标签为<entry key="" value=""/>或value-ref


<property name="properties">
<props>
<prop key="pq>value</prop>
</props>
</property>

Bean的依赖注入配置:

​ 1)byName:通过属性名自动装配,即去匹配setXXX与id=“XXX”或name=”XXX”是否一致

​ 2)byType:通过Bean类型从容器中匹配,匹配出多个相同类型会报错。

  • 如userServiceImpl中有setUserDao这个方法
1
2
<bean id="userService" class="com.truly.UserServiceImpl" autowire="byName"/>
<bean id="userDao" class="com.truly.UserDaoImpl"/>

Sping的其他配置标签:

​ 1)除可作为根标签外,是可以嵌套在根标签内,使用profile属性切换开发环境。

1
2
3
4
<beans profile="dev">
<bean id="userService" class="com.truly.UserServiceImpl" autowire="byName"/>
<bean id="userDao" class="com.truly.UserDaoImpl"/>
</beans>

可使用下列两种方法激活环境:

  • 使用命令行动态参数,虚拟机参数位置加载-Dspring.profile.active=dev
  • 使用代码的方式设置环境变量System.serProperty(“spring.profile.active”,”dev”)

2)可导入其他的子配置文件

3给bean起别名

Bean实例化的流程

  • Spring容器进行初始化时,解析xml配置中的的信息并封装成一个BeanDefinition对象
  • BeanDefinition又会存储到一个名为beanDefinitionMap的map集合中
  • Spring框架对beanDefinitionMap进行遍历,使用反射创建Bean实例对象,创建好的Bean放入名为SingletonObjects(单例池)的map中
  • 当你调用getBrean方法时,从SingletonObjects取出Bean实例对象

Spring的后处理器

Spring的后处理器是Spring对外开发的重要扩展点,允许我们介入到Bean的整个实例化流程,以达到注册BeanDefinition,动态修改BeanDefinition,以及动态修改Bean的作用,主要有如下两种:

  • BeanFactoryPostProcessor:Bean工厂后处理器,在BeanDefinition填充完毕后,Bean实例化之前。

    • 可使用beanFactory.getBeanDefinition(Sting beanName)获取对应的对象。

    • Spring提供了BeanFactoryPostProcessor的子接口BeanDefinitionPostProcessor专门用于注册BeanDefinition的操作。

    • 执行顺序如下

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws Execption{
    System.out.println("BeanDefinition填充完毕后调用该方法")
    BeanDefinition beanDefinition=beanFactory.getBeanDefinition("userService");
    //此次将class路径修改
    beanDefinition.setBeanClassName("com.truly.dao.impl.UserDaoImpl");


    //还可以动态注入Bean
    BeanDefinition beanDefinition2=new RootBeanDefinition();
    beanDefinition2.setBeanClassName("com.truly.dao.personDaoImpl");
    //强转成DefaultListableBeanFactory
    DefaultListableBeanFactory defaultListableBeanFactory=(DefaultListableBeanFactory)beanFactory;
    defaultListableBeanFactory.registerBeanDefinition("personDao",beanDefinition2);
    }
  • BeanPostProcessor:Bean后处理器,一般在Bean实例化后,填充到singletonObjects之前。

    • 其中有两个default方法
      • postProcessorBeforeInitialization(Obejct bean,String beanName)
      • postProcessorAfterInitialization(Obejct bean,String beanName)

​ 以后者为例,模拟AOP的基本实现思想

    @Override
    public Object postProcessorAfterInitialization(Obejct bean,String beanName){
        //使用动态代理,返回proxy对象,存到单例池
        Object proxy=Proxy.newInstance(
            bean.getClass.getClassLoader(),
            bean.getClass.getInterfaces(),
            (proxy,method,args)->{
                //1.输出开始时间
                System.out.println("方法"+method.getName()+"-开始时间:"+new Date())
                //2.执行目标方法
                Object result=method.invoke(bean,args);
                //3.输出结束时间
                    System.out.println("方法"+method.getName()+"-结束时间:"+new Date())
            return result;
            }
            );
            return proxy;
    }

最终执行流程如下

Spring的生命周期

  • 大体上分为三个阶段:
    • Bean的实例化阶段:Spring框架取出BeanDefinition的信息进行判断范围是否为singleton的,是否不是延迟加载;是不是FactoryBean等,最终将普通的singleton的Bean通过反射实例化。

    • Bean的初始化阶段:Bean创建之后还是个半成品,还要对Bean实例进行填充,执行一些Aware接口方法,该阶段是最具技术含量的阶段,AOP、注解功能等

      • Bean实例的属性填充

        • 注入普通属性:String、int或基本数据类型时;通过set方法反射设置

        • 注入单项对象引用属性时:从容器getBean获取后再用set设置进去,如果容器内没有,则先创建对象Bean实例,再进行注入。

        • 注入双向对象引用时(循环依赖):解决方案如下

          • 如图,循环依赖会按如下方式循环执行下去

          • 三级缓存

            public class DefaultSingletonBeanRegistry extends SimpleAliasRegistry implements SingletonBeanRegistry {
                    .....
                    //1.最终存储单例Bean成品的容器,及实例化和初始化都完成的Bean,称之为“一级缓存”
                        private final Map<String, Object> singletonObjects = new ConcurrentHashMap(256);
                     //3.单例Bean的工厂池,缓存半成品对象,对象未被引用,使用时通过工厂创建Bean,称之为“三级缓存”
                private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap(16);
                     //2.早期Bean单例池,缓存半成品对象,且当前对象已被其他对象引用,称之为“二级缓存”
                private final Map<String, Object> earlySingletonObjects = new ConcurrentHashMap(16);
                ..........
                }
            

        UserService和userDao循环依赖的过程结合三级缓存描述如下:

        1.UserService实例化对象,但未初始化,将userService放入三级缓存中

        2.UserService属性注入,需要UserDao,从一级到三级缓存取,没有则实例化userDao对象

        3.UserDao实例化对象,但未初始化,将UserDao放入三级缓存

        4.UserDao属性注入,需要UserService,从三级缓存中就找到,将UserService从三级缓存移到二级缓存

        5.UserDao注入了UserService,执行其他生命流程,最终称为一个完整的Bean,放入一级缓存,删除二三级缓存

        6.UserService注入UserDao

        7.UserService执行其他生命流程,,最终称为一个完整的Bean,放入一级缓存,删除二三级缓存

      • Aware接口属性注入

      • BeanPostProcessor的before()方法回调

      • InitializingBean接口的初始化方法回调

      • 自定义舒适化方法init回调

      • BeanPostProcessor的after()方法回调

    • Bean的完成阶段:存储到单例池中,完成SpringBean的生命周期。


文章作者: truly
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 truly !
  目录