Spring学习笔记(一)
MyBatis的学习已经告一段落了,持久层往上就到了服务层。服务层的技术便是我们顶顶大名的spring。
一、Spring概述
Spring是一款javaEE轻量级开源框架,以IOC(反转控制)和AOP(面向切面编程)为内核,提供了SpringMVC和持久层Spring JDBC以及业务层事务管理等众多的企业级应用技术,还能整合开源世界众多著名的第三方框架,和类库,逐渐成为使用最多的JavaEE企业应用开源框架。
二、Spring的优势
1.方便解耦,简化开发
2.AOP编程的支持
3.声明式事务的支持
4.方便程序的测试
5.方便集成各种优秀框架
6.降低JavaEE的使用难度
7.Spring源码是经典的学习案例
三、程序耦合
在讲解Spring的一大核心IOC之前,我们首先要弄明白什么是程序的耦合。
在这里引入一小段耦合的概念,看不懂一笑而过即可。
在软件工程中,耦合指的就是就是对象之间的依赖性。对象之间的耦合越高,维护成本越高。因此对象的设计 应使类和构件之间的耦合最小。软件设计中通常用耦合度和内聚度作为衡量模块独立程度的标准。划分模块的一个 准则就是高内聚低耦合。
用通俗的话来说,耦合就是程序尤其是对象之间的关联程度。关联程度越高,我们修改代码时,需要修改的地方就越多。这样既会导致维护成本提高,也会致使出错的概率提升。所以我们要尽量减少程序间的关联程度,即耦合度。
耦合有以下分类:
1) 内容耦合。当一个模块直接修改或操作另一个模块的数据时,或一个模块不通过正常入口而转入另 一个模块时,这样的耦合被称为内容耦合。内容耦合是最高程度的耦合,应该避免使用之。
2) 公共耦合。两个或两个以上的模块共同引用一个全局数据项,这种耦合被称为公共耦合。在具有大 量公共耦合的结构中,确定究竟是哪个模块给全局变量赋了一个特定的值是十分困难的。
3) 外部耦合 。一组模块都访问同一全局简单变量而不是同一全局数据结构,而且不是通过参数表传
递该全局变量的信息,则称之为外部耦合。4) 控制耦合 。一个模块通过接口向另一个模块传递一个控制信号,接受信号的模块根据信号值而进 行适当的动作,这种耦合被称为控制耦合。
5) 标记耦合 。若一个模块 A 通过接口向两个模块 B 和 C 传递一个公共参数,那么称模块 B 和 C 之间
存在一个标记耦合。6) 数据耦合。模块之间通过参数来传递数据,那么被称为数据耦合。数据耦合是最低的一种耦合形
式,系统中一般都存在这种类型的耦合,因为为了完成一些有意义的功能,往往需要将某些模块的输出数据作为另 一些模块的输入数据。7) 非直接耦合 。两个模块之间没有直接关系,它们之间的联系完全是通过主模块的控制和调用来实
现的。
总结:耦合是影响软件复杂程度和设计质量的一个重要因素,在编写程序的过程中,我们应采取一下原则:如果模块间必须存在耦合,那么尽量使用数据耦合,少用控制耦合,限制公共耦合的范围,尽量避免内容耦合
四、程序解耦举例
我们可以将解耦理解成一句话,即:做到程序编译器不报错,运行期才报错
以JDBC基本操作为例,我们在注册驱动时可以采用以下的写法:
DriverManager.registerDriver(new com.mysql.jdbc.Driver());
由于我们采用了new关键字,一旦我们将mysql驱动的jar包删除,这条语句将在编译器就会报错,这体现出JDBC与Driver类的耦合度过高。
要想降低两者之间的耦合度,可以用反射的机制来注册驱动:
Class.forName("com.mysql.jdbc.Driver");//此处只是一个字符串
采用这种写法,即使删除jar包,编译器也不会报错,这便实现了解耦。(当然程序是无法运行的,在运行期必然会报错。)
实际上一种更常见的解耦方式就是采用工厂模式来实现控制反转(IOC)
五、IOC–控制反转
概念:
控制反转把创建对象的权利交给框架,是框架的重要特征,并非面向对象编程的专用术语。它包括依赖注入和依赖查找
作用:
削减计算机·程序的耦合,降低程序间的以来关系
分析:
原来我们获取对象时,采用new的方式,是主动去找
现在我们获取对象时,是由工厂创建或查找得到的,是被动获得。
这种控制权交给工厂的形式就叫做控制反转(IOC)
六、Spring中的IOC
Spring框架一大精妙之处就在于,能够帮助实现控制反转,通过配置bean对象,spring可以自动生成该对象的工厂,并且提供服务。这不仅有利于降低程序间的耦合,还大大提高了开发效率。
入门案例:
通过spring中xml配bean的方式,实现dao与service对象的创建:
1.导入spring依赖
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
2.编写service与dao层的接口与实现类(省略)
3.配置bean.xml文件
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- 把对象的创建交给spring来管理 -->
<bean id="accountService" class="com.itheima.service.Impl.AccountServiceImpl"></bean>
<bean id="accountDao" class="com.itheima.dao.Impl.AccountDaoImpl"></bean>
</beans>
4.创建使用bean对象
public static void main(String[] args) {
//获取核心容器对象
ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
//根据id获取bean对象
IAccountService as = (IAccountService) ac.getBean("accountService");
IAccountDao adao = ac.getBean("accountDao",IAccountDao.class);
System.out.println(as);
System.out.println(adao);
}
5.(成功)测试打印结果
com.itheima.service.Impl.AccountServiceImpl@5ea434c8
com.itheima.dao.Impl.AccountDaoImpl@3bbc39f8
值得注意的·是,根据id获取bean对象时,有两种方式进行类型转换:
1.强制类型转换
2.传入转后类型的字节码作为参数
六、SpringIOC的细节与bean标签
1.beanFactory与ApplicationContext的联系与区别:
BeanFactory是Spring的顶层接口,ApplicationContext是BeanFactroy的底层接口
ApplicationContext接口的实现类:
(1)ClassPathXmlApplicationContext:
从类的根路径下加载配置文件 (推荐使用)
(2)FileSystemXmlApplicationContext:
从磁盘的路径下加载配置文件,配置文件可以在磁盘的任意位置
(3)AnnotationConfigApplicationContext:
使用注解配置容器对象时,需要此类来创建spring容器。它用来读取注解
BeanFactroy与ApplicationConext的创建对象的时间不同:
(1)BeanFactory只有当对象被使用的时候,才会创建对象(延迟加载)
(2) ApplicationConext只要加载配置文件,就会创建对象(立即加载)
2.bean标签
作用:
用于配置对象,以便Spring创建。
默认情况下它调用对象类中的无参构造函数。如果没有无参构造函数则创建失败
属性:
(1)id:给对象在容器中提供唯一标识。用于获取对象
(2)class:指定类的全限定类名。用于反射创建对象。默认情况下调用无参构造函数
(3)scope:指定对象的作为范围:
a) singleton: 默认值,单例的
b) prototype: 多例的
c) request: WEB项目中,Spring创建一个Bean对象,将对象存到request域中
d)session:在WEB项目中,Spring创建一个Bean对象,将对象存到session域中
e)globalSession:WEB项目中,运行在Portlet环境中,如果没有Protlet环境, globalSession相当于session
(4) init-method:指定类中的初始化方法名称
(5) destroy-method:指定类中的销毁方法名称
bean对象的作用范围与生命周期:
单例对象:scope=“singleton”:
一个应用只有一个对象的实例。作用范围即整个应用
生命周期:
对象出生:当应用加载,创建容器时,对象就被创建了
对象活着:只要容器在,对象一直活着
对象死亡:当应用卸载,销毁容器时,对象就被销毁了
多例对象:scope=“protoType”:
每次对象访问的时候,都会重新创建对象实例
生命周期:
对象出生:当使用对象时,新的对象被创建
对象活着:只要对象在使用中,就一直活着
对象死亡:当对象长时间不用时,被java垃圾回收器回收了
实例化bean对象的三种方式:
(1)使用默认无参构造函数:
<!--在默认情况下:
它会根据默认无参构造函数来创建类对象。如果 bean 中没有默认无参构造函数,将会创建失败。-->
<bean id="accountService" class="com.itheima.service.impl.AccountServiceImpl"/>
(2)spring管理工厂–使用实例工厂的方法创建对象(当我们要创建jar包中的类时,这个类又没有无参构造函数,常常使用这种方式)
<!-- 此种方式是:
先把工厂的创建交给 spring 来管理。
然后在使用工厂的 bean 来调用里面的方法
factory-bean 属性:用于指定实例工厂 bean 的 id。
factory-method 属性:用于指定实例工厂中创建对象的方法。
-->
<bean id="instancFactory" class="com.itheima.factory.InstanceFactory"></bean>
<bean id="accountService"
factory-bean="instancFactory"
factory-method="createAccountService"></bean>
(3) spring静态管理工厂--使用静态工厂的方法创建对象:
<!-- 此种方式是:
使用 StaticFactory 类中的静态方法 createAccountService 创建对象,并存入 spring 容器
id 属性:指定 bean 的 id,用于从容器中获取
class 属性:指定静态工厂的全限定类名
factory-method 属性:指定生产对象的静态方法
-->
<bean id="accountService"
class="com.itheima.factory.StaticFactory"
factory-method="createAccountService"></bean>
七、Spring的依赖注入
依赖注入:Dependency Injection。它是Spring框架核心IOC的具体体现
我们在程序编写时,通过控制反转,把对象的创建交给了Spring,但是代码中不可能出现没有依赖的情况。IOC解耦只是降低耦合度,但不会消除耦合。例如我们的业务层仍会调用持久层的方法。
那这种业务层和持久层的依赖关系,在使用Spring之后,就让Spring去维护了/
简答的说,就是坐等框架把持久层对象传入业务层,而不用我们自己去获取。
Spring依赖注入的三种规范方式:
(1)构造函数注入:
顾名思义,即通过调用使用类中的构造函数,给成员变量赋值。
例如某个AccountService类如下:
public class AccountServiceImpl implements IAccountService {
private String name;
private Integer age;
private Date birthday;
public AccountServiceImpl(String name, Integer age, Date birthday) {
this.name = name; this.age = age; this.birthday = birthday;
}
}
注入方式:
<!-- 使用构造函数的方式,给 service 中的属性传值
要求:
类中需要提供一个对应参数列表的构造函数。
涉及的标签:
constructor-arg
属性:
index:指定参数在构造函数参数列表的索引位置
type:指定参数在构造函数中的数据类型
name:指定参数在构造函数中的名称 用这个找给谁赋值
=======上面三个都是找给谁赋值,下面两个指的是赋什么值的==============
value:它能赋的值是基本数据类型和 String 类型
ref:它能赋的值是其他 bean 类型,也就是说,必须得是在配置文件中配置过的 bean
-->
<bean id="accountService" class="com.itheima.service.Impl.AccountServiceImpl">
<constructor-arg name="name" value="张三"></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.Data"></bean>
(2)set方法注入:
顾名思义,就是在类中提供需要注入成员的set方法。
例如某个AccountService类如下:
public class AccountServiceImpl implements IAccountService {
private String name;
private Integer age;
private Date birthday;
public void setName(String name) {
this.name = name;
}
public void setAge(Integer age) {
this.age = age;
}
public void setBirthday(Date birthday) {
this.birthday = birthday;
}
}
注入方式如下;
<!-- 通过配置文件给 bean 中的属性传值:
使用 set 方法的方式 涉及的标签:
property
属性:
name:找的是类中 set 方法后面的部分
ref:给属性赋值是其他 bean 类型的
value:给属性赋值是基本数据类型和 string 类型的
实际开发中,此种方式用的较多。
-->
<bean id="accountService" class="com.itheima.service.impl.AccountServiceImpl"> <property name="name" value="test"></property>
<property name="age" value="21"></property>
<property name="birthday" ref="now"></property>
</bean>
<bean id="now" class="java.util.Date"></bean>
(3)使用p名称空间注入数据(本质还是调用set方法)
此种方式是通过在xml种导入p名称空间,使用p:propertyName来注入数据,它的本质仍是调用类中的set方法实现注入功能。
AccountService类同上
注入方式:
<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.itheima.service.impl.AccountServiceImpl4"
p:name="test" p:age="21" p:birthday-ref="now"/>
</beans>
(4)注入集合属性
顾名思义,就是给类种的集合成员传值,它用的也是set方法注入的方式,只不过变量的数据类型都是集合。我们这里介绍注入数组,List,Set,Map,Properties。
例如某AccountService类如下:
public class AccountServiceImpl implements IAccountService {
private String[] myStrs;
private List<String> myList;
private Set<String> mySet;
private Map<String,String> myMap;
private Properties myProps;
public void setMyStrs(String[] myStrs) {
this.myStrs = myStrs;
}
public void setMyList(List<String> myList) {
this.myList = myList;
}
public void setMySet(Set<String> mySet) {
this.mySet = mySet;
}
public void setMyMap(Map<String, String> myMap) {
this.myMap = myMap;
}
public void setMyProps(Properties myProps) {
this.myProps = myProps;
}
}
注入方法:
!-- 注入集合数据
List 结构的:
array,list,set
Map 结构的
map,entry,props,prop
-->
<bean id="accountService" class="com.itheima.service.impl.AccountServiceImpl">
<!-- 在注入集合数据时,只要结构相同,标签可以互换 -->
<!-- 给数组注入数据 -->
<property name="myStrs">
<set>
<value>AAA</value>
<value>BBB</value>
<value>CCC</value>
</set>
</property>
<!-- 注入 list 集合数据 -->
<property name="myList">
<array>
<value>AAA</value>
<value>BBB</value>
<value>CCC</value>
</array>
</property>
<!-- 注入 set 集合数据 -->
<property name="mySet">
<list>
<value>AAA</value>
<value>BBB</value>
<value>CCC</value>
</list>
</property>
<!-- 注入 Map 数据 -->
<property name="myMap">
<props>
<prop key="testA">aaa</prop>
<prop key="testB">bbb</prop>
</props>
</property>
<!-- 注入 properties 数据 -->
property name="myProps">
<map>
<!-- 方式1 -->
<entry key="testA" value="aaa"></entry>
<!-- 方式2 -->
<entry key="testB">
<value>bbb</value>
</entry>
</map>
</property>
</bean>