Spring
一,Spring的概述
1.1.1spring是什么
Spring 是分层的 Java SE/EE 应用 full-stack 轻量级开源框架,以 IOC(Inverse Of Control: 反转控制)和 AOP(Aspect Oriented Programming:面向切面编程)为内核,提供了展现层 Spring MVC 和持久层 Spring JDBC 以及业务层事务管理等众多的企业级应用技术,还能整合开源世界众多 著名的第三方框架和类库,逐渐成为使用最多的 Java EE 企业应用开源框架。
1.1.2Spring的发展历程
1997 年 IBM 提出了 EJB 的思想
1998 年,SUN 制定开发标准规范 EJB1.0
1999 年,EJB1.1 发布
2001 年,EJB2.0 发布
2003 年,EJB2.1 发布
2006 年,EJB3.0 发布
Rod Johnson(spring 之父)
Expert One-to-One J2EE Design and Development(2002) 阐述了 J2EE 使用 EJB 开发设计的优点及解决方案 Expert One-to-One J2EE Development without EJB(2004) 阐述了 J2EE 开发不使用 EJB 的解决方式(Spring 雏 形)
2017 年 9 月份发布了 spring 的最新版本 spring 5.0 通用版(GA)
1.1.3 spring 的优势
- 方便解耦,简化开发
通过 Spring 提供的 IoC 容器,可以将对象间的依赖关系交由 Spring 进行控制,避免硬编码所造 成的过度程序耦合。用户也不必再为单例模式类、属性文件解析等这些很底层的需求编写代码,可 以更专注于上层的应用。
- AOP 编程的支持
通过 Spring 的 AOP 功能,方便进行面向切面的编程,许多不容易用传统 OOP 实现的功能可以通过 AOP 轻松应付。
-
声明式事务的支持
可以将我们从单调烦闷的事务管理代码中解脱出来,通过声明式方式灵活的进行事务的管理, 提高开发效率和质量。
-
方便程序的测试
可以用非容器依赖的编程方式进行几乎所有的测试工作,测试不再是昂贵的操作,而是随手可 做的事情。
-
方便集成各种优秀框架
Spring 可以降低各种框架的使用难度,提供了对各种优秀框架(Struts、Hibernate、Hessian、Quartz 等)的直接支持。
-
降低 JavaEE API 的使用难度
Spring 对 JavaEE API(如 JDBC、JavaMail、远程调用等)进行了薄薄的封装层,使这些 API 的 使用难度大为降低。
-
Java 源码是经典学习范例
Spring 的源代码设计精妙、结构清晰、匠心独用,处处体现着大师对 Java 设计模式灵活运用以 及对 Java 技术的高深造诣。它的源代码无意是 Java 技术的最佳实践的范例。
1.1.4 spring 的体系结构
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1SvdpiEO-1645603139889)(C:\Users\无敌维\AppData\Roaming\Typora\typora-user-images\image-20210122121825054.png)]
二,IOC的概念和作用
2.1.1什么是程序的耦合
耦合性(Coupling),也叫耦合度,是对模块间关联程度的度量。耦合的强弱取决于模块间接口的复杂性、调 用模块的方式以及通过界面传送数据的多少。模块间的耦合度是指模块之间的依赖关系,包括控制关系、调用关 系、数据传递关系。模块间联系越多,其耦合性越强,同时表明其独立性越差( 降低耦合性,可以提高其独立 性)。耦合性存在于各个领域,而非软件设计中独有的,但是我们只讨论软件工程中的耦合。
在软件工程中,耦合指的就是就是对象之间的依赖性。对象之间的耦合越高,维护成本越高。因此对象的设计 应使类和构件之间的耦合最小。软件设计中通常用耦合度和内聚度作为衡量模块独立程度的标准。划分模块的一个 准则就是高内聚低耦合。
-
它有如下分类:
- 内容耦合。当一个模块直接修改或操作另一个模块的数据时,或一个模块不通过正常入口而转入另 一个模块时,这样的耦合被称为内容耦合。内容耦合是最高程度的耦合,应该避免使用之。
- 公共耦合。两个或两个以上的模块共同引用一个全局数据项,这种耦合被称为公共耦合。在具有大 量公共耦合的结构中,确定究竟是哪个模块给全局变量赋了一个特定的值是十分困难的。
- 外部耦合 。一组模块都访问同一全局简单变量而不是同一全局数据结构,而且不是通过参数表传 递该全局变量的信息,则称之为外部耦合。
- 控制耦合 。一个模块通过接口向另一个模块传递一个控制信号,接受信号的模块根据信号值而进 行适当的动作,这种耦合被称为控制耦合。
- 标记耦合 。若一个模块 A 通过接口向两个模块 B 和 C 传递一个公共参数,那么称模块 B 和 C 之间 存在一个标记耦合。
- 数据耦合。模块之间通过参数来传递数据,那么被称为数据耦合。数据耦合是最低的一种耦合形 式,系统中一般都存在这种类型的耦合,因为为了完成一些有意义的功能,往往需要将某些模块的输出数据作为另 一些模块的输入数据。
- 非直接耦合 。两个模块之间没有直接关系,它们之间的联系完全是通过主模块的控制和调用来实 现的。
-
总结:
耦合是影响软件复杂程度和设计质量的一个重要因素,在设计上我们应采用以下原则:如果模块间必须 存在耦合,就尽量使用数据耦合,少用控制耦合,限制公共耦合的范围,尽量避免使用内容耦合。
-
内聚与耦合 内聚标志一个模块内各个元素彼此结合的紧密程度,它是信息隐蔽和局部化概念的自然扩展。内聚是从 功能角度来度量模块内的联系,一个好的内聚模块应当恰好做一件事。它描述的是模块内的功能联系。耦合是软件 结构中各模块之间相互连接的一种度量,耦合强弱取决于模块间接口的复杂程度、进入或访问一个模块的点以及通 过接口的数据。 程序讲究的是低耦合,高内聚。就是同一个模块内的各个元素之间要高度紧密,但是各个模块之 间的相互依存度却要不那么紧密。 内聚和耦合是密切相关的,同其他模块存在高耦合的模块意味着低内聚,而高内聚的模块意味着该模块同其他 模块之间是低耦合。在进行软件设计时,应力争做到高内聚,低耦合。
2.1.2 解决程序耦合的思路
2.1.3 工厂模式解耦
在实际开发中我们可以把三层的对象都使用配置文件配置起来,当启动服务器应用加载的时候,让一个类中的 方法通过读取配置文件,把这些对象创建出来并存起来。在接下来的使用的时候,直接拿过来用就好了。 那么,这个读取配置文件,创建和获取三层对象的类就是工厂。
2.1.4 控制反转-Inversion Of Cont
-
存哪去?
分析:由于我们是很多对象,肯定要找个集合来存。这时候有 Map 和 List 供选择。 到底选 Map 还是 List 就看我们有没有查找需求。有查找需求,选 Map。
所以我们的答案就是 在应用加载时,
创建一个 Map,用于存放三层对象。
我们把这个 map 称之为容器。
-
还是没解释什么是工厂?
工厂就是负责给我们从容器中获取指定对象的类。这时候我们获取对象的方式发生了改变。
原来: 我们在获取对象时,都是采用 new 的方式。是主动的。
现在: 我们获取对象时,同时跟工厂要,有工厂为我们查找或者创建对象。是被动的
明确 ioc 的作用: 削减计算机程序的耦合(解除我们代码中的依赖关系)。
Ioc接口
ApplicationContext接口的实现类
快捷键:CTRL+H
File:电脑盘下路径
class:项目src路径下
IOC操作bean管理
IOC中的两种Bean
三,使用 spring 的 IOC 解决程序耦合
3.1 案例的前期准备
3.1.1 准备 spring 的开发包
官网:http://spring.io/
下载地址: http://repo.springsource.org/libs-release-local/org/springframework/spring
使用maven可以使用在pox中添加依赖:
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
3.1.2 创建业务层接口和实现类
3.1.3 创建持久层接口和实现类
3.2 基于 XML 的配置
3.2.1 第一步:导入jar包
用maven则添加依赖即可
3.2.2 第二步:创建一个 xml 文件
不能是中文
3.2.3 第三步:让 spring 管理资源
在配置文件中配置 service 和 dao
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
<bean id="dao" class="com.ssm.dao.IAccountDaoImpl" init-method="init" ></bean>
<bean id="service" class="com.ssm.service.IAccountServiceImpl">
<property name="dao" ref="dao"/>
</bean>
<!--有参函数注入-->
<bean id="account1" class="com.ssm.pojo.Account">
<constructor-arg name="name" value="张三"></constructor-arg>
<constructor-arg name="password" value="123"></constructor-arg>
<constructor-arg name="money" value="10000"></constructor-arg>
</bean>
<!--set注入-->
<bean id="account2" class="com.ssm.pojo.Account">
<property name="name" value="李四"></property>
<property name="password" value="123"></property>
<property name="money" value="20000"></property>
</bean>
<!--P空间注入-->
<bean id="account3" class="com.ssm.pojo.Account"
p:name="王五" p:password="234" p:money="230000"></bean>
<!--list集合注入-->
<bean id="account4" class="com.ssm.pojo.Account">
<property name="list1">
<list>
<value>令狐冲</value>
<value>张三丰</value>
</list>
</property>
</bean>
<!--依赖注入类-->
<bean name="service2" class="com.ssm.service.IAccountServiceImpl">
<!--注入userDao对象
name属性:类里面属性名称
ref属性:创建userDao对象bean标签id值
-->
<property name="dao" ref="dao"></property>
</bean>
</beans>
3.2.4 测试配置是否成功
public void test1(){
/**不使用spring*/
/*IAccountService service = new IAccountServiceImpl();
service.saveAccount();*/
/**使用service容器*/
//spring上下文对象
ApplicationContext ac = new ClassPathXmlApplicationContext("spring.xml");
//1.强转获得对象
IAccountDao dao1 = (IAccountDao) ac.getBean("dao");
//2.使用反射获取对象
IAccountDao dao2 = ac.getBean("dao", IAccountDao.class);
//调用service层方法
dao1.saveAccount();
dao2.saveAccount();
ac.getBean("service", IAccountService.class).saveAccount();
}
3.3Spring 基于 XML 的 IOC 细节[掌握]
3.3.1 spring 中工厂的类结构图
3.3.1.1 BeanFactory 和 ApplicationContext 的区别
BeanFactory 才是 Spring 容器中的顶层接口。
ApplicationContext 是它的子接口。
BeanFactory 和 ApplicationContext 的区别: 创建对象的时间点不一样。
ApplicationContext:只要一读取配置 文件,默认情况下就会创建对象。
BeanFactory:什么使用什么时候创建对象。
3.3.1.2 ApplicationContext 接口的实现类
ClassPathXmlApplicationContext: 它是从类的根路径下加载配置文件 推荐使用这种 FileSystemXmlApplicationContext: 它是从磁盘路径上加载配置文件,配置文件可以在磁盘的任意位置。 AnnotationConfigApplicationContext: 当我们使用注解配置容器对象时,需要使用此类来创建 spring 容器。它用来读取注解。
3.3.2 IOC 中 bean 标签和管理对象细节
3.3.2.1 bean 标签
作用: 用于配置对象让 spring 来创建的。 默认情况下它调用的是类中的无参构造函数。如果没有无参构造函数则不能创建成功。
属性:
-
id:给对象在容器中提供一个唯一标识。用于获取对象。
-
class:指定类的全限定类名。用于反射创建对象。默认情况下调用无参构造函数。
-
scope:指定对象的作用范围。
-
singleton :默认值,单例的.
-
prototype :多例的.
单例创建一个对象,获取的对象是同一个
多例是创建多个对象
-
request :WEB 项目中,Spring 创建一个 Bean 的对象,将对象存入到 request 域中.
-
session :WEB 项目中,Spring 创建一个 Bean 的对象,将对象存入到 session 域中 global session :WEB 项目中,应用在 Portlet 环境.如果没有 Portlet 环境那么 globalSession 相当于 session
-
init-method:指定类中的初始化方法名称。
destroy-method:指定类中销毁方法名称。
3.3.2.2 bean 的作用范围和生命周期
单例对象:scope="singleton"
一个应用只有一个对象的实例。它的作用范围就是整个引用。
生命周期:
- 对象出生:当应用加载,创建容器时,对象就被创建了。
- 对象活着:只要容器在,对象一直活着。
- 对象死亡:当应用卸载,销毁容器时,对象就被销毁了。
多例对象:scope="prototype"
每次访问对象时,都会重新创建对象实例。
生命周期:
- 对象出生:当使用对象时,创建新的对象实例。
- 对象活着:只要对象在使用中,就一直活着。
- 对象死亡:当对象长时间不用时,被 java 的垃圾回收器回收了。
3.3.2.3 实例化 Bean 的三种方式
-
第一种方式:使用默认无参构造函数
<bean id="accountService" class="com.itheima.service.impl.AccountServiceImpl"/>
-
第二种方式:spring 管理静态工厂-使用静态工厂的方法创建对象
/** * 模拟一个静态工厂,创建业务层实现类 */ public class StaticFactory { public static IAccountService createAccountService(){ return new AccountServiceImpl(); } }
<!-- 此种方式是: 使用 StaticFactory 类中的静态方法 createAccountService 创建对象,并存入 spring 容器 id 属性:指定 bean 的 id,用于从容器中获取 class 属性:指定静态工厂的全限定类名 factory-method 属性:指定生产对象的静态方法 --> <bean id="accountService" class="com.itheima.factory.StaticFactory" factory-method="createAccountService"></bean>
-
第三种方式:spring 管理实例工厂-使用实例工厂的方法创建对象
/** * 模拟一个实例工厂,创建业务层实现类 * 此工厂创建对象,必须现有工厂实例对象,再调用方法 */ public class InstanceFactory { public IAccountService createAccountService(){ return new AccountServiceImpl(); } }
<!-- 此种方式是: 先把工厂的创建交给 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.3.3 spring 的依赖注入
3.3.3.1 依赖注入的概念
依赖注入:Dependency Injection。它是 spring 框架核心 ioc 的具体实现。
我们的程序在编写时,通过控制反转,把对象的创建交给了 spring,但是代码中不可能出现没有依赖的情况。 ioc 解耦只是降低他们的依赖关系,但不会消除。例如:我们的业务层仍会调用持久层的方法。
那这种业务层和持久层的依赖关系,在使用 spring 之后,就让 spring 来维护了。 简单的说,就是坐等框架把持久层对象传入业务层,而不用我们自己去获取。
3.3.3.2 构造函数注入
使用构造函数的方式,给 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.Date"></bean>
3.3.3.3 set 方法注入(常用)
<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.3.3.4 使用 p 名称空间注入数据
本质还是调用 set 方法
此种方式是通过在 xml 中导入 p 名称空间,使用 p:propertyName 来注入数据,它的本质仍然是调用类中的 set 方法实现注入功能。
<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>
3.3.3.5 注入集合属性
顾名思义,就是给类中的集合成员传值,它用的也是set方法注入的方式,只不过变量的数据类型都是集合。 我们这里介绍注入数组,List,Set,Map,Properties。具体代码如下:
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;
}
@Override
public void saveAccount() {
System.out.println(Arrays.toString(myStrs));
System.out.println(myList);
System.out.println(mySet);
System.out.println(myMap);
System.out.println(myProps);
}
}
<!-- 注入集合数据
List 结构的:
array,list,set
Map 结构的
map,entry,props,prop
-->
<bean id="accountService" class="com.itheima.service.impl.AccountServiceImpl">
<!-- 在注入集合数据时,只要结构相同,标签可以互换 -->
<!-- 给set注入数据 -->
<property name="myStrs">
<set>
<value>AAA</value>
<value>BBB</value>
<value>CCC</value>
</set>
</property>
<!-- 注入 array 集合数据 -->
<property name="myList">
<array>
<value>AAA</value>
<value>BBB</value>
<value>CCC</value>
</array>
</property>
<!-- 注入 list 集合数据 -->
<property name="mySet">
<list>
<value>AAA</value>
<value>BBB</value>
<value>CCC</value>
</list>
</property>
<!-- 注入 properties 数据 -->
<property name="myMap">
<props>
<prop key="testA">aaa</prop>
<prop key="testB">bbb</prop>
</props>
</property>
<!-- 注入 Map 数据 -->
<property name="myProps">
<map>
<entry key="testA" value="aaa"></entry>
<entry key="testB">
<value>bbb</value>
</entry>
</map>
</property>
</bean>
bean管理xml中的自动装配
四,基于注解的IOC配置
4.1明确:写在最前
学习基于注解的 IoC 配置,大家脑海里首先得有一个认知,即注解配置和 xml 配置要实现的功能都是一样 的,都是要降低程序间的耦合。只是配置的形式不一样。
关于实际的开发中到底使用xml还是注解,每家公司有着不同的使用习惯。所以这两种配置方式我们都需要掌 握。 我们在讲解注解配置时,采用上一章节的案例,把 spring 的 xml 配置内容改为使用注解逐步实现。
4.2环境搭建
4.2.1 第一步:拷贝必备 jar 包到工程的 lib 目录
4.2.2 第二步:使用@Component 注解配置管理的资源
4.2.3 第三步:创建 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"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
<!--引入资源文件-->
<context:property-placeholder location="db.properties"/>
<!--配置数据连接池-->
<bean id="database" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="${jdbc.driverClassName}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</bean>
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="database"/>
</bean>
<!--注解开发-->
<!--组件扫描-->
<context:component-scan base-package="com" />
</beans>
4.3.常用的注解
4.3.1 用于创建对象的
相当于:< bean id="" class="">
4.3.1.1 @Component
作用: 把资源让 spring 来管理。相当于在 xml 中配置一个 bean。
属性: value:指定 bean 的 id。如果不指定 value 属性,默认 bean 的 id 是当前类的类名。首字母小写。
4.3.1.2 @Controller @Service @Repository
他们三个注解都是针对一个的衍生注解,他们的作用及属性都是一模一样的。 他们只不过是提供了更加明确的语义化。
@Controller:一般用于控制层(controller)的注解。
@Service:一般用于业务层(service)的注解。
@Repository:一般用于持久层(dao)的注解。
细节:如果注解中有且只有一个属性要赋值时,且名称是 value,value 在赋值是可以不写。
4.3.2 用于注入数据的
相当于:
< property name="" ref="" />
< property name="" value="" />
4.3.2.1 @Autowired 自动装配
作用:
当使用注解注入属性时,set 方法可以省略。
它只能注入其他 bean 类型。当有多个类型匹配时,使用要注入的对象变量名称作为 bean 的 id,在 spring 容器查找,找到了也可以注入成功。找不到 就报错。
4.3.2.2 @Qualifier 作用:
在自动按照类型注入的基础之上,再按照 Bean 的 id 注入。
它在给字段注入时不能独立使用,必须和 @Autowire 一起使用;但是给方法参数注入时,可以独立使用。 属性: value:指定 bean 的 id。
4.3.2.3 @Resource 作用:
直接按照 Bean 的 id 注入。它也只能注入其他 bean 类型。 属性: name:指定 bean 的 id。
4.3.2.4 @Value 作用:
注入基本数据类型和 String 类型数据的属性: value:用于指定值
4.3.3 用于改变作用范围的:
相当于:< bean id= “” class ="" scope="" >
4.3.3.1 @Scope
作用: 指定 bean 的作用范围。 属性: value:指定范围的值。
取值:singleton prototype request session globalsession
4.3.4 和生命周期相关的:(了解)
相当于:< bean id="" class="" destroy-method="" init-method=""/ >
4.3.4.1 @PostConstruct 作用:
用于指定初始化方法。
4.3.4.2 @PreDestroy 作用:
用于指定销毁方法。
4.3.5 关于 Spring 注解和 XML 的选择问题
注解的优势: 配置简单,维护方便(我们找到类,就相当于找到了对应的配置)。
XML 的优势: 修改时,不用改源码。不涉及重新编译和部署。
Spring 管理 Bean 方式的比较:
4.4spring 管理对象细节
基于注解的 spring IoC 配置中,bean 对象的特点和基于 XML 配置是一模一样的。
4.5spring 的纯注解配置
写到此处,基于注解的 IoC 配置已经完成,但是大家都发现了一个问题:我们依然离不开 spring 的 xml 配 置文件,那么能不能不写这个 bean.xml,所有配置都用注解来实现呢? 当然,同学们也需要注意一下,我们选择哪种配置的原则是简化开发和配置方便,而非追求某种技术。
2.5.2 新注解说明
2.5.2.1 @Configuration
作用: 用于指定当前类是一个 spring 配置类,当创建容器时会从该类上加载注解。获取容器时需要使用 AnnotationApplicationContext(有@Configuration 注解的类.class)。
属性: value:用于指定配置类的字节码
2.5.2.2 @ComponentScan
作用: 用于指定 spring 在初始化容器时要扫描的包。作用和在 spring 的 xml 配置文件中的: 是一样的。
属性: basePackages:用于指定要扫描的包。和该注解中的 value 属性作用一样。
2.5.2.3 @Bean
作用: 该注解只能写在方法上,表明使用此方法创建一个对象,并且放入 spring 容器。
属性: name:给当前@Bean 注解方法创建的对象指定一个名称(即 bean 的 id)。
2.5.2.4 @PropertySource
作用:用于加载.properties 文件中的配置。例如我们配置数据源时,可以把连接数据库的信息写到 properties 配置文件中,就可以使用此注解指定 properties 配置文件的位置。
属性: value[]:用于指定 properties 文件位置。如果是在类路径下,需要写上 classpath:
2.5.2.5 @Import
作用: 用于导入其他配置类,在引入其他配置类时,可以不用再写@Configuration 注解。当然,写上也没问 题。 属性: value[]:用于指定其他配置类的字节码。
2.5.2.6 通过注解获取容器:
ApplicationContext ac = new AnnotationConfigApplicationContext(SpringConfiguration.class);
完整代码
package com.config;
import com.dao.BookDao;
import com.dao.BookDaoImpl;
import com.service.BookService;
import com.service.BookServicempl;
import org.springframework.context.annotation.*;
@Configuration
@ComponentScan("com") //com.rd.ssm包下面的所有类都交给spring容器管理
@PropertySource("db.properties") //读取db.properties文件的内容
@Import(JdbcConfig.class)
public class JavaConfig {
//相当于<bean id="bookDao" class="com.rd.ssm.dao.impl.BookDaoImpl">
@Bean("bookDao")
public BookDao getBookDao(){
return new BookDaoImpl();
}
@Bean("bookService")
public BookService getBookService(){
return new BookServicempl();
}
}
五,Spring 整合 Junit[掌握]
3.1.1 问题
在测试类中,每个测试方法都有以下两行代码:
ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
IAccountService as = ac.getBean("accountService",IAccountService.class);
这两行代码的作用是获取容器,如果不写的话,直接会提示空指针异常。所以又不能轻易删掉。
3.1.2 解决思路分析
针对上述问题,我们需要的是程序能自动帮我们创建容器。一旦程序能自动为我们创建 spring 容器,我们就 无须手动创建了,问题也就解决了。
我们都知道,junit 单元测试的原理(在 web 阶段课程中讲过),但显然,junit 是无法实现的,因为它自 己都无法知晓我们是否使用了 spring 框架,更不用说帮我们创建 spring 容器了。不过好在,junit 给我们暴露 了一个注解,可以让我们替换掉它的运行器。 这时,我们需要依靠 spring 框架,因为它提供了一个运行器,可以读取配置文件(或注解)来创建容器。我 们只需要告诉它配置文件在哪就行了。
3.2配置步骤
3.2.1 第一步:拷贝整合 junit 的必备 jar 包到 lib 目录 此处需要注意的是,导入 jar 包时,需要导入一个 spring 中 aop 的 jar 包
3.2.2 第二步:使用@RunWith 注解替换原有运行器
@RunWith(SpringJUnit4ClassRunner.class)
3.2.3 第三步:使用@ContextConfiguration 指定 spring 配置文件的位置
3.2.4 第四步:使用@Autowired 给测试类中的变量注入数据
3.3为什么不把测试类配到 xml 中
在解释这个问题之前,先解除大家的疑虑,配到 XML 中能不能用呢?
答案是肯定的,没问题,可以使用。 那么为什么不采用配置到 xml 中的方式呢?
这个原因是这样的:
第一:当我们在 xml 中配置了一个 bean,spring 加载配置文件创建容器时,就会创建对象。
第二:测试类只是我们在测试功能时使用,而在项目中它并不参与程序逻辑,也不会解决需求上的问 题,所以创建完了,并没有使用。那么存在容器中就会造成资源的浪费。
所以,基于以上两点,我们不应该把测试配置到 xml 文件中。
六,AOP的相关概念
6.1,AOP 概述
6.1.1 什么是 AOP
AOP:全称是 Aspect Oriented Programming 即:面向切面编程。
简单的说它就是把我们程序重复的代码抽取出来,在需要执行的时候,使用动态代理的技术,在不修改源码的 基础上,对我们的已有方法进行增强。
6.1.2 AOP 的作用及优
作用: 在程序运行期间,不修改源码对已有方法进行增强。
优势: 减少重复代码 提高开发效率 维护方便
6.1.3 AOP 的实现方式
使用动态代理技术
-
动态代理的特点
字节码随用随创建,随用随加载。 它与静态代理的区别也在于此。因为静态代理是字节码一上来就创建好,并完成加载。 装饰者模式就是静态代理的一种体现
-
动态代理常用的有两种方
-
基于接口的动态代理
提供者:JDK 官方的 Proxy 类。
要求:被代理类最少实现一个接口。
-
基于子类的动态代理
提供者:第三方的 CGLib,如果报 asmxxxx 异常,需要导入 asm.jar。
要求:被代理类不能用 final 修饰的类(最终类)。
-
6.2,AOP 的具体应用
JDK动态代理
)]
七,Spring 中的 AOP[掌握]
7.1.Spring 中 AOP 的细节
7.1.1 说明
我们学习 spring 的 aop,就是通过配置的方式,实现上一章节的功能。
2.1.2 AOP 相关术语
Joinpoint(连接点): 所谓连接点是指那些被拦截到的点。在 spring 中,这些点指的是方法,因为 spring 只支持方法类型的 连接点。
Pointcut(切入点): 所谓切入点是指我们要对哪些 Joinpoint 进行拦截的定义
Advice(通知/增强): 所谓通知是指拦截到 Joinpoint 之后所要做的事情就是通知。 通知的类型:前置通知,后置通知,异常通知,最终通知,环绕通知。
Introduction(引介): 引介是一种特殊的通知在不修改类代码的前提下, Introduction 可以在运行期为类动态地添加一些方 法或 Field。
Target(目标对象): 代理的目标对象。
Weaving(织入): 是指把增强应用到目标对象来创建新的代理对象的过程。 spring 采用动态代理织入,而 AspectJ 采用编译期织入和类装载期织入。
Proxy(代理,切面): 一个类被 AOP 织入增强后,就产生一个结果代理类。 Aspect(切面): 是切入点和通知(引介)的结合。
AOP操作
相同切入点的抽取
基于XML操作
7.1.3 学习 spring 中的 AOP 要明确的事
a、开发阶段(我们做的)
编写核心业务代码(开发主线):大部分程序员来做,要求熟悉业务需求。
把公用代码抽取出来,制作成通知。(开发阶段最后再做):AOP 编程人员来做。
在配置文件中,声明切入点与通知间的关系,即切面。:AOP 编程人员来做。
b、运行阶段(Spring 框架完成的)
Spring 框架监控切入点方法的执行。一旦监控到切入点方法被运行,使用代理机制,动态创建目标对 象的代理对象,根据通知类别,在代理对象的对应位置,将通知对应的功能织入,完成完整的代码逻辑运行。
7.1.4 关于代理的选择
在 spring 中,框架会根据目标类是否实现了接口来决定采用哪种动态代理的方式。
7.2基于 XML 的 AOP 配置
7.2.1 环境搭建
7.2.1.1 第一步:准备必要的代码
7.2.1.2 第二步:拷贝必备的 jar 包到工程的 lib 目录
7.2.1.3 第三步:创建 spring 的配置文件并导入约束
此处要导入 aop 的约束
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
</beans>
7.2.1.4 第四步:配置 spring 的 ioc
<!-- 配置 service -->
<bean id="accountService" class="com.itheima.service.impl.AccountServiceImpl">
<property name="accountDao" ref="accountDao"></property>
</bean>
<!-- 配置 dao -->
<bean id="accountDao" class="com.itheima.dao.impl.AccountDaoImpl">
<property name="dbAssit" ref="dbAssit"></property>
</bean>
<!-- 配置数据库操作对象 -->
<bean id="dbAssit" class="com.itheima.dbassit.DBAssit">
<property name="dataSource" ref="dataSource"></property>
<!-- 指定 connection 和线程绑定 -->
<property name="useCurrentConnection" value="true"></property>
</bean>
<!-- 配置数据源 -->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="driverClass" value="com.mysql.jdbc.Driver"></property>
<property name="jdbcUrl" value="jdbc:mysql:///spring_day02"></property>
<property name="user" value="root"></property>
<property name="password" value="1234"></property>
</bean>
2.2.1.5 第五步:抽取公共代码制作成通知
package com.util;
public class TransactionManager {
private ConnectionUtil con;
public void setCon(ConnectionUtil con) {
this.con = con;
}
public void begin(){
try {
/**启动事务*/
con.getConnection1().setAutoCommit(false);
System.out.println("【启动事务】......");
}
catch (Exception e){
e.printStackTrace();
}
}
public void commit(){
try {
/**提交事务*/
con.getConnection1().commit();
System.out.println("【提交事务】......");
}
catch (Exception e){
e.printStackTrace();
}
}
public void rollback(){
try {
/**回滚事务*/
con.getConnection1().rollback();
System.out.println("【回滚事务】......");
}
catch (Exception e){
e.printStackTrace();
}
}
}
2.2.2 配置步骤
2.2.2.1 第一步:把通知类用 bean 标签配置起来
<--配置通知: 切面-事务-->
<bean id="transactionManager" class="com.util.TransactionManager">
<property name="con" ref="connectionUtil"/>
</bean>
2.2.2.2 第二步:使用 aop:config 声明 aop
aop:config:
作用:用于声明开始 aop 的配置
<aop:config>
<!-- 配置的代码都写在此处 -->
</aop:config>
2.2.2.3 第三步:使用 aop:aspect 配置切面
aop:aspect:
作用: 用于配置切面。
属性:
id:给切面提供一个唯一标识。
ref:引用配置好的通知类 bean 的 id。
<aop:aspect id="" ref="">
<!--配置通知的类型要写在此处-->
</aop:aspect>
2.2.2.4 第四步:使用 aop:pointcut 配置切入点表达式
aop:pointcut:
作用: 用于配置切入点表达式。就是指定对哪些类的哪些方法进行增强。
属性:
expression:用于定义切入点表达式。
id:用于给切入点表达式提供一个唯一标识
<aop:pointcut id="pc1" expression="execution(* com.service.*.*(..))"></aop:pointcut>
2.2.2.5 第五步:使用 aop:xxx 配置对应的通知类型
**aop:before **
作用:用于配置前置通知。指定增强的方法在切入点方法之前执行
属性:
method:用于指定通知类中的增强方法名称
ponitcut-ref:用于指定切入点的表达式的引用
poinitcut:用于指定切入点表达式 执行时间点: 切入点方法执行之前执行 aop:after-returning
执行时间点: 切入点方法执行之前执行
<aop:before method="beginTransaction" pointcut-ref="pt1"/>
aop:after-returning
作用: 用于配置后置通知
属性:
method:指定通知中方法的名称。
pointct:定义切入点表达式
pointcut-ref:指定切入点表达式的引用
执行时间点: 切入点方法正常执行之后。它和异常通知只能有一个执行
<aop:after-returning method="commit" pointcut-ref="pt1"/>
aop:after-throwing
作用: 用于配置异常通知
属性:
method:指定通知中方法的名称。
pointct:定义切入点表达式
pointcut-ref:指定切入点表达式的引用
执行时间点: 切入点方法执行产生异常后执行。它和后置通知只能执行一个
<aop:after-throwing method="rollback" pointcut-ref="pt1"/>
aop:after
作用: 用于配置最终通知
属性:
method:指定通知中方法的名称。
pointct:定义切入点表达式
pointcut-ref:指定切入点表达式的引用
执行时间点: 无论切入点方法执行时是否有异常,它都会在其后面执行。
<aop:after method="release" pointcut-ref="pt1"/>
完整的配置:例
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
<!--引入资源文件-->
<context:property-placeholder location="db.properties"/>
<!--配置数据连接池-->
<bean id="database" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="${jdbc.driverClassName}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</bean>
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="database"/>
</bean>
<bean id="connectionUtil" class="com.util.ConnectionUtil">
<property name="dataSource" ref="database"/>
</bean>
<bean id="dao" class="com.dao.AccountDaoImpl">
<property name="con" ref="connectionUtil"/>
</bean>
<bean id="service" class="com.service.AccountServiceImpl">
<property name="dao" ref="dao"/>
</bean>
<!--切面:事务-->
<bean id="transactionManager" class="com.util.TransactionManager">
<property name="con" ref="connectionUtil"/>
</bean>
<!--AOP,将事务配置到业务方法中-->
<aop:config proxy-target-class="false">
<aop:pointcut id="pc1" expression="execution(* com.service.*.*(..))"></aop:pointcut>
<aop:aspect id="tm" ref="transactionManager">
<!--前置通知:启动事务-->
<aop:before method="begin" pointcut-ref="pc1"></aop:before>
<!--后置通知:提交事务,如果发生异常则不能提交转为回滚-->
<aop:after-returning method="commit" pointcut-ref="pc1"></aop:after-returning>
<!--异常通知:回滚事务-->
<aop:after-throwing method="rollback" pointcut-ref="pc1"></aop:after-throwing>
</aop:aspect>
</aop:config>
</beans>
7.2.3 切入点表达式说明
**execution:**匹配方法的执行(常用)
- execution(表达式) 表达式
语法:execution ( [修饰符] 返回值类型 包名.类名.方法名(参数))
写法说明: 全匹配方式:
public void com.service.impl.AccountServiceImpl.saveAccount(com.itheima.domain.Account)
访问修饰符可以省略
void com.service.impl.AccountServiceImpl.saveAccount(com.itheima.domain.Account)
-
返回值可以使用*号,表示任意返回值
-
包名可以使用 * 号,表示任意包,但是有几级包,需要写几个*
-
使用 … 来表示当前包,及其子包
-
类名可以使用*号,表示任意类
* com..*.saveAccount(com.itheima.domain.Account)
-
方法名可以使用号,表示任意方法 * com….*( com.itheima.domain.Account)
-
参数列表可以使用*,表示参数可以是任意数据类型,但是必须有参数 * com….(*)
-
参数列表可以使用…表示有无参数均可,有参数可以是任意类型 * com….(…)
-
全通配方式: * ….*(…)
注:
通常情况下,我们都是对业务层的方法进行增强,所以切入点表达式都是切到业务层实现类。
execution( com.service..(…))*
7.2.4 环绕通知
配置方式:
aop:around:
作用: 用于配置环绕通知
属性:
method:指定通知中方法的名称。
pointct:定义切入点表达式
pointcut-ref:指定切入点表达式的引用
说明: 它是 spring 框架为我们提供的一种可以在代码中手动控制增强代码什么时候执行的方式。 注意: 通常情况下,环绕通知都是独立使用的
7.3基于注解的 AOP 配置
2.3.1 环境搭建
- 2.3.1.1 第一步:准备必要的代码和 jar 包2.3.1.2
- 第二步:在配置文件中导入xml文件2.3.1.3
- 第三步:把资源使用注解配置2.3.1.4
- 第四步:在配置文件中指定 spring 要扫描的包
2.3.2 配置步骤
-
第一步:把通知类也使用注解配置
-
第二步:在通知类上使用@Aspect 注解声明为切面
-
第三步:在增强的方法上使用注解配置通知
@Before 作用: 把当前方法看成是前置通知。 属性: value:用于指定切入点表达式,还可以指定切入点表达式的引用。
@AfterReturning 作用: 把当前方法看成是后置通知。 属性: value:用于指定切入点表达式,还可以指定切入点表达式的引用
@AfterThrowing 作用: 把当前方法看成是异常通知。 属性: value:用于指定切入点表达式,还可以指定切入点表达式的引用
@After 作用: 把当前方法看成是最终通知。 属性: value:用于指定切入点表达式,还可以指定切入点表达式的引用
-
第四步:在 spring 配置文件中开启 spring 对注解 AOP 的支持
<!--启动spring对注解的aop支持--> <aop:aspectj-autoproxy />
proxy-target-class属性:
默认为false,表示使用jdk动态代理织入增强。
当配为true时,表示使用CGLib动态代理技术织入增强。
不过即使proxy-target-class设置为false,如果目标类没有声明接口,则spring将自动使用CGLib动态代理。
@Component
@Aspect
public class TransactionManager {
@Resource
private ConnectionUtil con;
@Before("execution(* com.service.*.*(..))")
public void begin(){
try {
/**启动事务*/
con.getConnection1().setAutoCommit(false);
System.out.println("【启动事务】......");
}
catch (Exception e){
e.printStackTrace();
}
}
}
2.3.3 环绕通知注解配置
@Around 作用: 把当前方法看成是环绕通知。
属性: value:用于指定切入点表达式,还可以指定切入点表达式的引用。
@Around("execution(* com.itheima.service.impl.*.*(..))")
2.3.4 切入点表达式注解
@Pointcut
作用: 指定切入点表达式
属性: value:指定表达式的内容
@Pointcut("execution(* com.itheima.service.impl.*.*(..))")
private void pt1() {} 引用方式:
@Around("pt1()")//注意:千万别忘了写括号//注意:千万别忘了写括号