Spring Framework系列文章
Spring Framework Core | 01 Spring IoC
1. Spring基础
1.1 Spring框架
Spring是分层的Java SE/EE应用的全栈轻量级开发框架
1.2 Spring核心
- IOC控制反转
- AOP面向切面编程
1.3 Spring优势
-
方便解耦、简化开发(IOC工厂模式解耦)
-
AOP编程的支持
-
声明式事务(通过配置文件实现事务控制)
-
方便测试
-
方便集成各种优秀的框架
1.4 Spring体系结构
核心容器 Core Container (IOC)
所有Spring的其他框架都要运行在核心容器上
+ 三层体系结构
表现层UI:向用户呈现视图
业务层Service:处理数据输出
持久层Dao:访问数据库的数据
2. 耦合与解耦
2.1 耦合
耦合:程序间的依赖关系
耦合分类
-
RULE 1
类之间的耦合:类依赖于其他的类或JAR包,导致模块的独立性差
方法之间的耦合
-
RULE 2
内容耦合、公共耦合等
2.2 解耦
解耦:减少和降低程序间的依赖关系
无法完全消除耦合,只能尽量减少依赖关系。最终目的保证编译期不依赖,运行时才依赖
解耦思路:
-
使用反射来创建对象而不是用关键字new
关键字new依赖于驱动类,而反射只需要全限定类名字符串作为参数
-
通过配置文件来获取全限定类名
可以创建更多的对象而不用频繁的主动修改全限定类名字符串
实际操作(工厂模式解耦):
- 创建配置文件设置全限定类名和唯一标识的关联
- 使用反射获取全限定类名创建对象
2.3 工厂模式解耦
bean:可重用组件
JavaBean:使用Java编写的可重用组件
工厂模式:创建对象是通过共同的接口或类进行
工厂模式的理解
-
工厂类由一个工厂和接口组成
-
工厂相当于一个生产可重用组件Bean的方法
可重用组件Bean可以理解为一个对象就是配置文件中指定的某个全限定类对象
工厂通过读取配置文件,利用反射的方式创建可重用组件
这些对象可以是多例对象也可以是单例对象
- 对于单例对象一次生产后要创建容器来保存再使用 防止内存回收
- 对于多例对象可以在使用时多次生产
-
接口是其他类使用工厂生产对象时调用的方法
通过需要的对象标识给出相应的对象
- 对于单例对象从容器中获取
- 对于多例对象在使用时创建对象
+ 单例与多例
单例:对象只创建一次,类中成员也只初始化一次,有线程问题
多例:对象创建多次,执行效率比较低
如何选择:当类成员较少可以修改时使用单例对象
why?
-
类成员可以修改时
使用单例对象可能会有多个方法去修改该类成员,会受到其他调用者的影响造成线程问题
使用多例对象则可以独立地进行初始化各个使用者之间不会互相影响
-
类成员不可修改时
使用单例对象可以有效避免线程问题还可以提高处理效率
3. Spring IoC
3.1 IoC
两种创建对象的方式
- 主动方式:APP使用关键字new主动创建对象资源 存在依赖性使应用难以独立
- 被动方式:APP从工厂获取有工厂创建的对象资源 降低应用与资源之间的依赖
控制反转IoC的理解
可以看做是工厂模式的升华
为什么叫控制反转(IoC原理)?
- 主动方式下,类可以自主明确的选择想要创建的对象
- 被动方式下,类通过工厂获取对象,但这个对象是否是类所需要的由工厂的配置文件决定,类本身无法得知
- 反转指的就是类放弃了创建对象的控制权,而交由工厂进行管理,有主动到被动的反转
IoC的==工作过程==
- 用户创建配置文件,Spring读取配置文件
- ==反射==创建对象
IoC的作用:IoC可以有效地降低耦合(依赖关系)
IoC的内容:依赖注入、依赖查找
3.2 Spring IoC是啥
Spring IoC的两种理解
-
可以理解为IoC容器就是一个工厂,工厂要生产的对象都在配置文件中定义,对象由反射方式创建
从实现角度,IoC是把以前在工厂方法里写死的对象生成代码改变为由配置文件来定义
-
自己的理解:Spring IoC是第三方工厂Spring以IoC方式生产出的 **Map结构 **的存放对象的容器
其作用仅仅是解耦来降低依赖性
4. 依赖注入
4.1 依赖注入是啥
依赖注入是IoC的具体实现,将必要的依赖关系交给Spring来维护
4.2 依赖注入的数据类型
注入数据的基本原则:依赖注入要实用配置文件,一般使用不变化的数据注入,经常变化的数据不适合注入
注入的数据类型
- 基本类型和String类型
- 其他Bean对象
- 复杂类型(集合类型)
X. 使用的案例
(在此基础上做调整)
程序结构
业务层接口与实现类代码
//业务层接口
public interface IAccountService{
void saveAccount();
}
//业务层实现类
public class AccountServiceImpl implements IAccountService{
private IAccountDao accountDao;
public void saveAccount(){
accoutDao.saveAccount();
}
}
持久层接口与实现类代码
//持久层接口
public interface IAccountDao{
void saveAccount();
}
//持久层实现类
public class AccountDao implements IAccountDao{
public void saveAccount(){
System.out.println("save completed");
}
}
展现层代码(程序主入口)
public class Client{
public static void main(Stringp[] args){
ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
IAccountService as = (IAccountService)ac.getBean("accountService");
System.out.println(as);
}
}
配置文件代码(bean.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 http://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- 添加bean标签 -->
</beans>
5. 基于XML的Spring IoC配置
5.1 配置Spring IoC容器
+ 获取配置信息
使用ApplicationContext接口的三个实现类获取配置信息
- 基于Xml的Spring IoC配置
-
ClassPathXmlApplicationContext 通过加载类路径来获取配置文件
-
FileSystemXmlApplicationContext 通过加载磁盘绝对路径来获取配置文件
ApplicationContext ac1 = new ClassPathXmlApplicationContext("xxx.xml");
ApplicationContext ac2 = new FileSystemXmlApplicationContext("C://.../xxx.xml");
- 基于注解的Spring IoC配置(anno中使用)
- ~~ AnnotationConfigApplicationContext ~~
+ BeanFactory 和 ApplicationContext 俩接口的区别
BeanFactory
//BeanFactory
public class Client{
public static void main(String[] args){
Resource resource = new ClassPathResource("bean.xml");
BeanFactory factory = new XmlBeanFactory(resource);
IAccountService as = (IAccountService)factory.getBean("accountService");
System.out.println(as);//创建对象
}
}
ApplicationContext(应用上下文)
//ApplicationContext
public class Client{
public static void main(String[] args){
ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");//创建对象
IAccountService as = (IAccountService)ac.getBean("accountService");
System.out.println(as);
}
}
Differences.
-
BeanFactory是顶层接口,ApplitionContext是BeanFactoy的子类
-
BeanFactory使用延迟加载模式,使用对象的时候才创建,因此更适合多例对象
ApplicationContext使用直接加载模式,读取完配置文件立即创建对象,因此更适合单例对象
5.2 使用Spring IoC容器管理对象
Spring使用bean标签管理Bean对象
5.2.1 Bean对象的创建
在bean标签中使用 id 属性指定Bean对象名称,class 属性指定创建Bean对象的全限定类名
-
使用默认构造函数创建
标签中只有id和class属性没有其他的属性和标签
没有默认构造函数(无参构造函数)则会失效无法创建对象
<bean id = "accountService" class = "com.yhn.service.impl.AccountServiceImpl"></bean>
-
使用某个类(工厂类)中的方法创建
在bean标签中使用 factory-bean 指定工厂类,factory-method 指定方法
工厂类代码
package com.yhn.factory
public class InstanceFactory{
public IAccountService getAccountService(){
return new AccountServiceImpl();
}
}
配置文件
<bean id = "InstanceFactory" class = "com.yhn.factory.InstanceFactory"></bean>
<bean id = "accountService" factory-bean = "InstanceFactory" factory-method = "getAccountService"></bean>
-
使用某个类中的静态方法创建
在bean标签中使用factory-method 指定静态方法
工厂类代码
package com.yhn.factory
public class StaticFactory{
public static IAccountService getAccountService(){
return new AccountServiceImpl();
}
}
配置文件
<bean id = "accountService" class = "com.yhn.factory.StaticFactory" factory-method = "getAccountService"></bean>
方法与静态方法创建bean对象的区别
-
方法需要使用对象调用必须要创建一个对象:在配置文件中需要创建一个类的bean对象来调用方法
-
静态方法可以直接使用类调用不用创建对象:在配置文件中可以直接使用全限定类名调用方法
使用默认构造函数和工厂类方法的情况
- 在自己编写代码时可以使用默认构造函数的方式创建bean对象
- 当使用别人编写的jar包时没有办法修改源码,因此使用工厂类方法创建
5.2.2 Bean对象的作用范围
在bean标签中使用 scope 属性指定对象的作用范围
- singleton:单例的(默认值)
- prototype:多例的
- request:作用于web的请求范围
- session:作用于web的会话范围
- global-session:作用于集群环境(多台服务器)的会话范围
5.2.3 Bean对象的生命周期
- 在bean标签中使用 init-method 属性指定Bean对象的初始化方法
- 在bean标签中使用 destroy-method 属性指定Bean对象的销毁方法
+ 单例和多例的生死存亡
单例对象:生命周期和容器的周期相同,随容器创建和销毁
多例对象:每次使用的使用创建,在对象长时间不使用时由Java的垃圾回收机制自动回收
- Spring可以主动的销毁单例对象但是无法主动的销毁多例对象
- 对于单例对象:可以在释放容器的执行过程中调用销毁方法销毁容器中的单例对象
- 对于多例对象:无法确认对象什么时候使用结束,为避免销毁对象造成程序找不到该对象而无法主动销毁多例对象
6. 基于XML的依赖注入
6.1 注入的数据类型
- 基本类型和String类型 看注入方式
- 其他Bean对象 看注入方式
- 复杂类型(集合类型) 看下面
集合的类型
注入时使用子标签
- Map结构:Map、Prop
<property name="map">
<map>
<prop key="testA">1</prop>
<entry key="testB" value="2"></entry>
<entry key="testC">
<value>3</value>
</entry>
</map>
</property>
- List结构:Array、List、Set
<property name="array">
<array>
<value>1</value>
<value>2</value>
</array>
</property>
Tips 两个结构内部的标签可以替换使用 即在给Map结构注入时map和prop的标签有一样的效果
6.2 注入的方式
- 构造函数注入(常用)
在bean标签中使用==constructor-arg==子标签
constructor-arg标签属性:
- type:指定参数的数据类型,根据数据类型注入
- index:指定参数在构造函数参数列表中的索引位置
- name:指定参数在构造函数中的名称(常用)例如 构造函数参数name等
- value:赋值基本数据类型和String类型
- ref:赋值配置文件中配置的其他的bean对象
业务层代码
public class AccountServiceImpl implements IAccountService{
private String name;
private int age;
private Date birthday;
public AccountServiceImpl(String name,int age,Date birthday){
this.name = name;
this.age = age;
this.birthday = birthday;
}
public void saveAccount(){
System.out.println("save completed"+name+age+birthday);
}
}
配置文件
<bean id="accountService" class="com.yhn.service.impl.AccountServiceImpl">
<constructor-arg name="name" value="test"></constructor-arg>
<constructor-arg name="age" value="18"></constructor-arg>
<constructor-arg name="birthday" ref="now"></constructor-arg>
</bean>
<bean id="now" class="java.util.Date"></bean>
- set方法注入(常用)
在bean标签中使用property子标签
property标签属性:name、value、ref与构造函数注入中的含义相同
Tips 其中name对应的值为将set方法中字段首字母小写后得到的字符串
业务层代码
public class AccountServiceImpl implements IAccountService{
private String name;
private int age;
private Date birthday;
public void setName(String name) {
this.name = name;
}
public void setAge(int age) {
this.age = age;
}
public void setBirthday(Date birthday) {
this.birthday = birthday;
}
public void saveAccount(){
System.out.println("save completed"+name+age+birthday);
}
}
配置文件
<bean id="accountService" class="com.yhn.service.impl.AccountServiceImpl">
<property name="name" value="test"></property>
<property name="age" value="18"></property>
<property name="birthday" ref="now"></property>
</bean>
<bean id="now" class="java.util.Date"></bean>
- P空间注入
在XML文件头中加入P空间的约束,直接在bean标签中使用P空间属性
eg:xmlns:p=“http://www.springframework.org/schema/p”
p:name=“test” p:age=“21” p:birthday-ref=“now”
配置文件
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:p="http://www.springframework.org/schema/p"
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">
<bean id="accountService" class="com.yhn.service.impl.AccountServiceImpl4" p:name="test" p:age="21" p:birthday-ref="now"/></bean>
</beans>
7. 基于注解的Spring IoC配置
7.1 配置Spring IoC容器
7.1.1 部分使用XML文件
7.1.2 不使用XML文件(纯注解)
X. 使用的配置类
(在此基础上调整)
package config;
public class SpringConfigruation{}
7.1.2.1 引入纯注解
使用配置文件的注解只能配置自己编写的代码
对于外部引用的jar包没有办法在源码中添加注解
7.1.2.2 纯注解使用
指定配置类
使用 @Configuration 指定配置类
@Configruation
public class SpringConfiguration{}
+ 配置类要指定所有要加入IoC容器中的对象
- 自己编写代码中的类对象直接扫描注解创建
- 导入的类在配置类中编写方法创建
- 编写的类
使用 @ComponentScan 指定创建Spring IoC容器时要扫描的包
属性 value 和 basepackages 用于指定要扫描的一个或多个包
Tips:指定包时以数组的形式
@Configruation
@ComponentScan(value={"com.yhn"}/"com.yhn")
public class SpringConfigruation{}
- 导入的类
使用 @Bean 将创建对象方法的返回值存入Spring IoC容器
属性 name 用于指定Bean对象的名称
Tips:默认名称为方法的名称
Tips:Spring IoC容器的map结构中 ‘key’ 为指定的名称 ‘value’ 为方法的返回对象
Tips:当使用注解配置方法时,若方法有参数则Spring会去容器中查找有无可以使用的Bean对象
导入其他配置类
当有多个配置类时怎么设置
-
多个配置类为并列的关系:
直接将各个配置类的字节码都作为AnnotationConfigApplicationContext的参数
-
一个配置类作为主配置类,其余作为子配置类
-
子配置类添加Configuration注解,在父配置类的ContextScan注解中增加子配置类要扫描的包
-
使用 @Import 导入其他的配置类,有 @Import 注解的为主配置类
属性 value 用于指定导入的一个或多个配置类的字节码,指定时使用数组形式
//并列关系
ApplicationContext ac = new AnnotationConfigApplicationContext(SpringConfigruation.class,ConfigB.class);
//父子关系
//1
@ComponentScan({"com.yhn","config"})
public class ParentConfigruation{}
@Configuration
public class ChildConfigruation{}
//2
@ComponentScan("com.yhn")
@Import(value=ChildConfigruation.class/ChildConfigruation.class)
public class ParentConfigruation{}
public class ChildConfigruation{}
使用属性文件
why?为了让程序更加独立,不在源码处直接赋值而使用Properties文件导入数据信息
使用 @PropertySource 指定properties文件的位置
属性 value 用于指定文件的名称和路径
使用关键字 classpath 指定类路径(对于大部分路径都可以使用classpath指定类路径)
@PropertySource("classpath:xxx.properties")
public class ConfigruationClass{}
+ 获取配置信息
使用AnnotationConfigApplicationContext获取配置类的信息生成Spring IoC容器
AnnotationConfigApplicationContext使用 @Configuration 注解的类的字节码作为参数,可以是一个也可以是多个
当配置类作为参数时 @Configuration 注解可以省略不写
ApplicationContext ac3 = new AnnotationConfigApplicationContext(SpringConfiguration.class);
7.2 使用Spring IoC容器管理对象
7.2.1 Bean对象的创建
-
@Component
作用:把当前的类存入Spring IoC容器中
属性value:设置对象的名称,默认情况下为类名首字母小写
@Component(value="accountDao")
public class AccountDaoImple implements IAccountDao{
//类成员和方法
}
@Component("accountService")
public class AccountServiceImpl implements IAccountService{
//类成员和方法
}
- 以下注解与Conponent功能相同,只是可以更好的区分三层对象
- @Controller 一般用于与展现层有关的类
- @Service 一般用于与业务层有关的类
@Service("accountService")
public class AccountServiceImpl implements IAccountService{
//类成员和方法
}
- @Repository 一般用于与持久层有关的类
@Repository("accountDao")
public class AccountDaoImpl implements IAccountDao{
//类成员和方法
}
7.2.2 Bean对象的作用范围
使用 @Scope 设置Bean对象的作用范围
属性 value:设置对象作用范围
- singleton 单例的
- prototype 多例的
@Service("accountService")
@Scope("singleton"/"prototype")
public class AccountServiceImpl implements IAccountService{
//类成员和方法
}
7.2.3 Bean对象的生命周期
- 使用 @PostConstruct 指定Bean对象的初始化方法
- 使用 @PreDestroy 指定Bean对象的销毁方法
8. 基于注解的依赖注入
8.1 注入的数据类型
- 基本类型和String类型
- 其他Bean对象
集合类型无法使用注解形式注入
8.2 注入的方式
-
对于基本类型和String类型数据
使用 @Value 注解注入基本类型和String类型数据
属性 value:用于指定数据
Tips:可以使用EL表达式
@Value("18")
private int variableA;
@Value("str")
private String variableB;
@Value("#{1}")
private List<String> varibleC;
@Value("${(Properties文件中的变量)}")
- 对于其他的Bean对象
- 使用 @Autowired 自动按照类型注入
Tips:只要容器中有唯一的Bean对象类型和注入类型匹配就可以注入
–>当Spring IoC容器中有唯一的对象时单单使用@Autowired即可
Tips:使用Autowired注入时就可以省略set方法
@Service("accountService")
public class AccountServiceImpl implements IAccountService{
@Autowired
private IAccountDao accountDao;
//类方法
}
+ Autowired的匹配
-
若没有类型匹配时则会报错
-
若有多于一个类型匹配时,会先按照类型找到多个匹配的Bean对象,再按变量名从多个对象中匹配
若只有一个匹配则可以注入,还是有多个匹配则会报错
-
使用 @Qulifier 在按照类型注入的基础上再按名称注入
属性 value:用于指定Bean对象的名称
@Service("accountService")
public class AccountServiceImpl implements IAccountService{
@Autowired
@Qulifier(value="accountDao"/"accountDao")
private IAccountDao accountDao;
//类方法
}
+ Qulifier的使用
- 在给类成员注入数据时不能单独使用,要和Autowired一起使用
- 在给方法注入数据时可以单独使用
-
使用 @Resouce 直接按照名称注入
属性 name:用于指定Bean对象的名称
@Service("accountService")
public class AccountServiceImpl implements IAccountService{
@Resource(name="accountDao")
private IAccountDao accountDao;
//类方法
}
9. XML还是注解?
两者都可以完成配置的目的,但存在方便与否的问题
例如:使用纯注解的形式配置导入的jar包时并没有XML形式简单
如何选择?
基本原则:怎么方便怎么来
一般情况而言
- 注解方式更适合配置自己编写的代码类
- XML方式更适合对于导入的jar包使用
10. 较优的配置方式
在配置Spring IoC容器是要逐步的去配置
先从最上层的类开始,如果需要其他的对象再对其他类进行配置,逐步深入
在出现依赖时要及时的通过构造函数或者set方法进行注入