Spring入门
- 首先创建一个类
- 我创建了一个hello类
- 在resources下创建spring配置文件
- 名字当前还是可以自己随便取的
- 配置spring配置文件
<?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 http://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- 第一行是XMl文件的声明,接着几行是XML文件的约束,定义了XML的命名空间,所为命名空间就是规定了这个XML文件中你能使用的标签名,
因为一般XML文件不都是自由使用标签嘛-->
<beans>
<!--这里beans里面可以管理bean,你可以说是spring中一个个组件也可以说是一个个对象-->
<!-- bean标签将配置一个bean对象交给IOC容器管理,id属性为唯一标识,class属性表示bean对象对应的类型,这个class属性是我们spring使用反射来创建对象的关键,而且要求对应的类要有无参构造器,而且这个要求类不能是接口-->
<bean id="helloword" class="com.cn.zt.myspring.hello"></bean>
</beans>
</beans>
- 测试
//使用ClassPathXmlApplication实现类来获取IOC容器,参数为类路径下的对应springXML文件
ApplicationContext ioc = new ClassPathXmlApplicationContext("applicationContext.xml");
//获取IOC容器中的bean,主要有三种获取方式getBean(Class<T> requiredType),getBean(String name, Class<T> requiredType),getBean(String name)
hello helloworld = (hello)ioc.getBean("helloword");
helloworld.sayhello();
获取bean的三种方式
- 上面的例子有三种在ioc容器中获取bean对象的方法
- 底层都是通过bean标签中的class属性利用反射获取实例对象
getBean(String name)
- 通过xml文件中beanid获取
getBean(Class<T> requiredType)
- 通过类型来获取
- 在SpringXML标签中bean标签中不能存在多个类型匹配的bean,就是多个beanid对应一个同一个类
- 不然会报
NOUniqueBeanDefinitionExceptin
异常
- 不然会报
- 而且在bean标签中必须有一beanid对应该类,才能用类型获取
- 不然会报
NOSuchBeanDefinitioinException
异常
- 不然会报
getBean(String name, Class<T> requiredType)
- 根据bean的id和类型来获取
- 这个允许多个beanid对应同一个类,因为他是通过id和类型获取,这样不会造成混淆
- 最常用的是
getBean(Class<T> requiredType)
- 因为它不用获取bean后强转类型,而且就算发生了多个id同个类,也可以用bean标签中scope属性来设置单例多例
- 扩展
- 如果组件类实现了接口,根据接口类型可以获取bean
- 就是如果你写在xml文件里bean标签对应的那个类它实现了一个接口,你可在getbean中的参数填接口的类型来获取相应的bean,前提是你的bean是唯一的,就是要求你这个接口的实现类中只有一个类配置了bean标签
- 如果组件类实现了接口,根据接口类型可以获取bean
- 结论
- 如果你使用类型来获取bean是,在满足bean唯一性的前提下,其实只要看[对象 instanceof 指定的类型] 的返回结果是否为true
依赖注入DI
- 依赖注入就是为类的的属性赋值
依赖注入的方式
- 依赖注入之用setter注入(使用property标签)
<bean id="student" class="com.cn.zt.myspring.Student">
<property name="age" value="12"></property>
<property name="gender" value="男"></property>
<property name="name" value="zt"></property>
</bean>
这种方式是通过实体类中的set方法注入,要求实体类中有这个属性的set方法,通过这种方式为属性赋值
- name是需要赋值的属性名(注意区别属性和成员变量)
- value就是为属性赋的值
- 构造器注入(使用constructor-arg标签)
- 我们可以对有参构造器注入
public Student(String name, int age, String gender) {
this.name = name;
this.age = age;
this.gender = gender;
}
<bean id="student" class="com.cn.zt.myspring.Student">
<constructor-arg name="age" value="18"></constructor-arg>
<constructor-arg name="gender" value="男"></constructor-arg>
<constructor-arg name="name" value="ykk"></constructor-arg>
</bean>
- construtor中可以不写name属性,如果我不写,那spring就会帮我注入到一个有三个参数的构造器中,第一个constructor标签注入到以一个参数…,但是它不区分构造器,如果你有两个参数个数都是三的构造器,要区分就还是要加name属性
依赖注入特殊值
- 字面量赋值
- 字面量一般指的是基本数据类型及其包装类和String
- 它赋值给属性就是value=“字面量”
<property name="age" value="12">
- 赋值null(是赋值空对象而不是null字符串)
- 比如我想为属性age赋值null
<property name="age" value="12">
<null></null>
</property>
- 这是用setter方法注入null,构造器也是这样注入null,在标签内加一个null子标签
- 特殊符号赋值
- 如果我们赋的值带有特殊符号,
<property name="gender" value="<男>"></property>
- 不能直接使用这类符号,要使用实体替换
< 对应 $lt
> 对应 $gt
- 或者使用CDATA节
- 语法
<![CDATA[ 文本数据 ]]>
- 使用CDATA节不能在注入标签中用属性value赋值,必须在标签内使用子标签的形式赋值,因为CDATA节它也是一个标签,不能写到属性中
<constructor-arg name="gender">
<value><![CDATA[<男>]]></value>
</constructor-arg>
- CDATA节能原样展示其中的内容
- 语法
- 如果我们赋的值带有特殊符号,
- 为类类型赋值
- 我在上面的student类中加了一个clazz对象属性
- 第一种方式,引用外部bean
<property name="clazz" ref="clazz"></property>
- ref属性的值是外部bean的id,他是用来引用ioc容器中的bean的
<bean id="clazz" class="com.cn.zt.myspring.Clazz">
<property name="cid" value="123"></property>
<property name="cname" value="456"></property>
</bean>
- 这样就可以为clazz对象属性注入了
- 第二种方式,级联方式
<property name="clazz.cname" value="555"></property>
<property name="clazz.cid" value="777"></property>
- 如果直接这样写会报空指针错,因为在student中没有实例化clazz,而直接使用clazz来获取属性会报错
- 所以我们要在前面加一条
<property name="clazz" ref="clazz"></property>
- 这样是不是和第一种方式类似呢
- 所以第二种方式一般用来修改对象属性中的值,引入外部的bean,然后修改其中的属性值
- 第三种方式,使用内部bean,只能能在bean的内部使用,不能直接用ioc容器获取
<property name="clazz">
<bean id="clazzInner" class="com.cn.zt.myspring.Clazz">
<property name="cid" value="999"></property>
<property name="cname" value="111"></property>
</bean>
</property>
- 这种方式相当于在bean中建立了一个内部bean,跟内部类很像,它不能直接被ioc容器获取,只能在该bean中访问这个内部bean
- 数组类型赋值
- 在property标签中有array标签,用来为数组类型属性注入赋值
<property name="hooby">
<array>
<value>抽烟</value>
<value>喝酒</value>
<value>烫头</value>
</array>
</property>
- 如果数组中的值都是字面量类型,就是使用value
- 如果数组中的值是类类型,就使用ref标签来引入外部bean
<property name="clazzes">
<array>
<ref bean="clazz"></ref>
</array>
</property>
- 集合类型的属性赋值
- list集合属性赋值
- 第一种方式
- 使用property标签中的list标签
<property name="list">
<list>
<ref bean="student1"></ref>
</list>
</property>
- 在list标签中使用ref还是value取决于是否是字面量
- 还有一件事,我犯了一个错误,我在之前的Student bean中有一个clazz的类类型属性,它引用的是id为clazz的bean,但是我在id为clazz的bean中有一个id为student的bean,它们两个套娃了,就会报错
- 第二种方式,引用list集合bean
- 首先提一个错误的做法
- 直接创建一个bean,它的class为Arraylist,这样
<bean id="list" class="java.util.ArrayList"></bean>
- 这样是不行的,这个只能让我们向Arraylist中的属性赋值,而不是往里面添加数据
- 直接创建一个bean,它的class为Arraylist,这样
- 配置一个集合类型的bean,需要使用util的约束,你直接使用util约束中的标签名,idea会自动在xml中导入约束
- 由于util约束中可能会带有重名标签,所以我们要在标签名中带前缀
<util:list id="studentlist">
<ref bean="student1"></ref>
如果是字面量,可以用value标签<ref bean="student2"></ref>
</util:list>
- 这样就建立好了一个集合类型的bean,我们可以在其他bean中引用这个集合,把它赋值它那个bean的集合类型属性中
<property name="list" ref="studentlist"></property>
- 直接这样引用就可以了
- 首先提一个错误的做法
- 第一种方式
- map集合属性赋值
- 创建一个map集合属性
private Map<String,Teacher> map;
- map集合属性赋值和list集合属性赋值大体相似,也是两种方式
- 一个是在property标签中使用map标签
<property name="map">
<map>
<entry key="123" value-ref="teacher1"></entry>
<entry key="222" value-ref="teacher2"></entry>
</map>
</property>
- 解释一下,map中的entry标签表示每一个键值对
- 在enry标签中有四个属性,key,value,key-ref,value-ref
- 分别表示key和value是否是字面量,是字面量就使用key=xxx,不是就是key-ref=beanid
- 一个是使用 util:map 创建一个map集合类型bean,再引入
<util:map id="teachermap">
<entry key="111" value-ref="teacher1"></entry>
<entry key="123" value-ref="teacher2"></entry>
</util:map>
- 引用
<property name="map" ref="teachermap"></property>
- 一个是在property标签中使用map标签
- map集合属性赋值和list集合属性赋值大体相似,也是两种方式
- list集合属性赋值
Spring管理数据源和引入外部属性
- 使用spring配置文件管理数据源
- 首先在resources下创建一个管理数据源的配置文件,名字自取,我取的是spring-datasource.xml
- 接着在新建好的配置文件中设置bean对象,这个bean对象对应的是你用的数据源,这里我用的是druid数据源
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
- 在bean标签中使用property标签注入属性
<property name="driverClassName" value="com.mysql.cj.jdbc.Driver"></property>
<property name="url" value="JDBC:mysql://localhost:3306/create?serverTimezone=UTC"></property>
<property name="username" value="root"></property>
<property name="password" value="root"></property>
- 这四个属性是需要我们注入的,其他的属性都有默认值,想修改也可以通过property标签注入修改
- 测试使用
- 在测试方法中通过xml创建ioc容器
- 用ioc容器获取druid的bean对象
- 使用druid的getconnection方法看是否能返回连接地址
- 在spring数据源配置文件中引用properties配置文件
- 这个主要是方便我们在数据源配置中的属性赋值,使用properties配置文件中的值
- 首先,创建jdbc.properties配置文件,在配置文件中写好键值对
jdbc.driver=com.mysql.cj.jdbc.Driver
jdbc.url=JDBC:mysql://localhost:3306/create?serverTimezone=UTC
jdbc.username=root
jdbc.password=root
- 然后再spring数据源文件中使用新的xml约束(命名空间) context
<context:property-placeholder location="jdbc.properties"></context:property-placeholder>
- 然后再context:property-placeholder 标签的属性location中引入jdbc配置文件,这样我们就可以在spring数据源配置文件中使用
${}
来访问jdbc配置文件中的键值对了<property name="driverClassName" value="${jdbc.driver}"></property>
<property name="url" value="${jdbc.url}"></property>
<property name="username" value="${jdbc.username}"></property>
<property name="password" value="${jdbc.password}"></property>
bean相关
- bean的作用域
- 在IOC容器中,无论是哪种获取bean的方式,你获取的bean都是同一个实例,因为ioc容器管理的bean默认是单例的
<bean id="student" class="com.cn.zt.myspring.Student" scope="singleton">
- bean标签中的scope属性可以设置这个bean是单例还是多例
- 默认情况下,scope属性为singleton(单例),也可以设置多例(prototype),多例就是会生成新对象
- 在IOC容器中,无论是哪种获取bean的方式,你获取的bean都是同一个实例,因为ioc容器管理的bean默认是单例的
- bean的生命周期
- bean的生命周期流程
- 第一步,通过反射调用无参构造实例化对象
- 第二步,依赖注入属性
- 第三步,初始化
- 第四步,使用bean
- 第五步,销毁
- 然后提一下,初始化方法和销毁方法都需要在bean标签中使用属性指定
<bean id="student" class="com.cn.zt.myspring.Student" init-method="toString" destroy-method="toString">
- 分别使用init-method和destroy-method来指定,指定的方法是bean对象中的方法,就是bean对应类例写的方法
- 销毁方法只有再ioc容器关闭时才会输出,ApplicationContext接口中没有关闭和刷新方法,所以要转为ConfigurableApplicationContext接口来使用关闭方法关闭容器
- bean的生命周期流程
- bean的作用域对生命周期的影响
- 若bean的作用域为单例时,所有bean的实例化,依赖注入,初始化都会在获取ioc容器时候执行
- 若bean的作用域为多例时,bean的前三个步骤会在获取bean的时候执行
- bean的后置处理器(BeanPostProcesssor)
- bean在初始化时会触发后置处理器
- 后置处理器中的两个方法对应初始化之前和初始化之后,我们可以在里面写功能
public Object postProcessAfterInitialization(Object bean, String beanName)
public Object postProcessBeforeInitialization(Object bean, String beanName)
- 后置处理器使用方法
- 首先创建一个类实现后置处理器接口(BeanPostProcessor)
- 然后重写两个方法,添加自定义功能
- 在spring配置文件中写bean标签
<bean id="beanPostProcess" class="com.cn.zt.myspring.myBeanPostProcess"></bean>
- 完成后,用这个配置文件获取的ioc容器中的所有bean对象在初始化的时候都会触发后置处理器中你写的功能
- 经过后置处理器后,bean生命周期流程变成 实例化->依赖注入->初始化之前的后置器操作->初始化操作->初始化之后的后置器操作
FactoryBean
FactoryBean和BeanFactory区别很大,beanFactory是ioc容器的基本实现,而Factory Bean是一个bean
- xxxFactory是什么意思,其实就是一个生产对象工厂,它可以返回xxx对象
- Factorybean是一个接口,需要一个类实现这个接口,它里面有几个重写方法
- getObject() 创建一个对象交给ioc容器管理
- getObjectType() 设置所提供对象的类型
- isSingleton() 所提供的对象是否是单例
- 当把FactoryBean的实现类配置到bean标签中后,它会有个神奇作用,我演示一下
- 首先我创建一个user类
- 然后我创建一个userfactory实现factorybean接口
- 在userfactory中重写了factorybean中的方法,让getObject返回user对象
- 然后在spring配置文件中配置userfactory的bean
- 使用ioc容器获取userfactory的bean对象
- 发现返回的对象是user对象
- 这就是Factorybean的作用
- 如果不用factorybean,我们需要先配置userfactory,用ioc容器获取到userfactory的bean后,再用这个bean获取user,使用factorybean后,我们直接配置userfactory的bean标签,直接可以获取到userfactory提供的user对象