第一章-Spring概述
Spring介绍
概述:Spring 是一个开源框架,是为了解决企业应用程序开发复杂性而创建的。
框架的主要优势之一就是其分层架构,分层架构允许您选择使用哪一个组件,同时为 J2EE 应用程序开发提供集成的框架。
简单来说,Spring是一个分层的JavaSE/EE full-stack(一站式) 轻量级开源框架。
一站式:Spring提供了三层解决方案.
Spring 的发展历程
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)
Spring的优点
1.方便解耦,简化开发
通过Spring提供的IoC容器,我们可以将对象之间的依赖关系交由Spring进行控制,避免硬编码所造成的过度程序耦合。有了Spring,用户不必再为单实例模式类、属性文件解析等这些很底层的需求编写代码,可以更专注于上层的应用。
2.AOP编程的支持
通过Spring提供的AOP功能,方便进行面向切面的编程,许多不容易用传统OOP实现的功能可以通过AOP轻松应付。
3.声明式事务的支持
在Spring中,我们可以从单调烦闷的事务管理代码中解脱出来,通过声明式方式灵活地进行事务的管理,提高开发效率和质量。
4.方便程序的测试
可以用非容器依赖的编程方式进行几乎所有的测试工作,在Spring里,测试不再是昂贵的操作,而是随手可做的事情。例如:Spring对Junit4支持,可以通过注解方便的测试Spring程序。
5.方便集成各种优秀框架
Spring不排斥各种优秀的开源框架,相反,Spring可以降低各种框架的使用难度,Spring提供了对各种优秀框架(如Struts,Hibernate、Hessian、Quartz)等的直接支持。
6.降低Java EE API的使用难度
Spring对很多难用的Java EE API(如JDBC,JavaMail,远程调用等)提供了一个薄薄的封装层,通过Spring的简易封装,这些Java EE API的使用难度大为降低。
Spring的体系结构
第二章-IOC
程序的耦合
耦合性(Coupling),也叫耦合度,是对模块间关联程度的度量。耦合的强弱取决于模块间接口的复杂性、调用模块的方式以及通过界面传送数据的多少。模块间的耦合度是指模块之间的依赖关系,包括控制关系、调用关系、数据传递关系。模块间联系越多,其耦合性越强,同时表明其独立性越差( 降低耦合性,可以提高其独立性)。 耦合性存在于各个领域,而非软件设计中独有的,但是我们只讨论软件工程中的耦合。
自定义IOC(工厂模式解耦)
- 原始方式
- 方式: 创建类, 直接根据类new对象
- 优点: 好写, 简单
- 缺点: 耦合度太高, 不好维护
- 接口方式
- 方式: 定义接口, 创建实现类. 接口=子类的对象
- 优点: 耦合度相对原始方式 减低了一点
- 缺点: 多写了接口, 还是需要改源码 不好维护
- 自定义IOC
- 方式: 使用对象的话, 不直接new()了,直接从工厂里面取; 不需要改变源码
- 思路:
在实际开发中我们可以把三层的对象都使用配置文件配置起来,当启动服务器应用加载的时候, 让一个类中的方法通过读取配置文件,把这些对象创建出来并存起来。在接下来的使用的时候,直接拿过来用就好了。那么,这个读取配置文件, 创建和获取三层对象的类就是工厂
添加坐标依赖:
<dependencies>
<!-- 解析 xml 的 dom4j -->
<dependency>
<groupId>dom4j</groupId>
<artifactId>dom4j</artifactId>
<version>1.6.1</version>
</dependency>
<!-- dom4j 的依赖包 jaxen -->
<dependency>
<groupId>jaxen</groupId>
<artifactId>jaxen</artifactId>
<version>1.1.6</version>
</dependency>
<!--单元测试-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.10</version>
<scope>test</scope>
</dependency>
</dependencies>
IOC概述
IOC(inversion of control)的中文解释是“控制反转”,对象的使用者不是创建者. 作用是将对象的创建 反转给spring框架来创建和管理。
控制反转怎么去理解呢。 其实它反转的是什么呢,是对象的创建工作。 举个例子:平常我们在servlet或者service里面创建对象,都是使用new 的方式来直接创建对象,现在有了spring之后,我们就再也不new对象了,而是把对象创建的工作交给spring容器去维护。我们只需要问spring容器要对象即可
ioc 的作用:削减计算机程序的耦合(解除我们代码中的依赖关系)。
配置文件详解(Bean标签):
<?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标签: 注册bean, 把对象交给Spring管理
1.【掌握】属性:
id/name属性: 随便写,作为bean的唯一标识,不要重复(建议写接口的名字,首字母小写)
class属性: 类的全限定名
scope属性: singleton 单例, 不管获得多少次 都是同一个, 创建出来存到Spring容器里面 【默认】
prototype 多例, 每获得一次 就创建一个新的对象,创建出来不会存到Spring容器里面
request 把创建的对象存到request域,针对web项目【了解】
session 把创建的对象存到session域,针对web项目【了解】
我们一般也是单例的, 特殊情况才是设置为prototype多例 eg:struts2里面的action
2. 【了解】的属性
init-method属性: 指定初始化方法,写方法名
destroy-method属性: 指定销毁的方法, 象征着当前对象从Spring容器里面移除了 写方法名
-->
<bean id="accountService" class="com.itheima.service.impl.AccountServiceImpl" scope="prototype"
init-method="initMethod" destroy-method="destoryMethod"
></bean>
</beans>
- id/name属性
用于标识bean , 其实id 和 name都必须具备唯一标识 ,两种用哪一种都可以。但是一定要唯一、 一般开发中使用id来声明. - class属性: 用来配置要实现化类的全限定名
- scope属性: 用来描述bean的作用范围
singleton: 默认值,单例模式。spring创建bean对象时会以单例方式创建。(默认), 使用的都是同一个对象(同一个id获得的). 单例的bean存到Spring容器里面
prototype: 多例模式。spring创建bean对象时会以多例模式创建。 使用的不是同一个对象(同一个id获得的), 使用一个 创建一个. 多例的bean不会存到Spring容器里面
request: 针对Web应用。spring创建对象时,会将此对象存储到request作用域。
session: 针对Web应用。spring创建对象时,会将此对象存储到session作用域。 - init-method属性:spring为bean初始化提供的回调方法
- destroy-method属性:spring为bean销毁时提供的回调方法. 销毁方法针对的都是单例bean , 如果想销毁bean , 可以关闭工厂
bean的作用范围和生命周期 - 单例对象: scope=“singleton”
一个应用只有一个对象的实例。它的作用范围就是整个引用。
生命周期:
对象出生:当应用加载,创建容器时,对象就被创建了。
对象活着:只要容器在,对象一直活着。
对象死亡:当应用卸载,销毁容器时,对象就被销毁了。 - 多例对象: scope=“prototype”
每次访问对象时,都会重新创建对象实例。
生命周期:
对象出生:当使用对象时,创建新的对象实例。
对象活着:只要对象在使用中,就一直活着。
对象死亡:当对象长时间不用时,被 java 的垃圾回收器回收了.
spring 中工厂的类结构图 - ClassPathXmlApplicationContext:它是从类的根路径下加载配置文件
- FileSystemXmlApplicationContext:它是从磁盘路径上加载配置文件,配置文件可以在磁盘的任意位置。
- AnnotationConfigApplicationContext:当我们使用注解配置容器对象时,需要使用此类来创建 spring 容器。它用来读取注解。
BeanFactory 和 ApplicationContext 的区别
新旧工厂 - 新工厂: 在工厂初始化的话的时候, 就会创建配置的所有的单例bean,存到Spring容器里面
- 旧工厂(已经废弃了): 等用到对象的时候, 再创建
- ApplicationContext 是现在使用的工厂
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
- XmlBeanFactory是老版本使用的工厂,目前已经被废弃【了解】
BeanFactory beanFactory = new XmlBeanFactory(new ClassPathResource("applicationContext.xml"));
两者的区别:
ApplicationContext加载方式是框架启动时就开始创建所有单例的bean,存到了容器里面
BeanFactory加载方式是用到bean时再加载(目前已经被废弃)
实例化Bean的三种方式【了解】
方式一:无参构造方法方式
需要实例化的类,提供无参构造方法
配置代码
<bean id="accountService" class="com.itheima.service.impl.AccountServiceImpl"></bean>
方式二:静态工厂方式
需要额外提供工厂类 , 工厂方法是静态方法
public class StaticFactory {
public static Object getBean(){
return new AccountServiceImpl02();
}
}
配置代码
<!--方式二:静态工厂方式 -->
<bean id="accountService02" class="com.itheima.utils.StaticFactory" factory-method="getBean"/>
方式三:实例工厂实例化的方法
创建实例化工厂类
public class InstanceFactory {
public Object getBean(){
return new AccountServiceImpl03();
}
}
配置
<!-- 方式三: 实例化工厂 -->
<!--注册工厂 -->
<bean id="factory" class="com.itheima.utils.InstanceFactory"></bean>
<!--引用工厂 -->
<bean id="accountService03" factory-bean="factory" factory-method="getBean"/>
一般情况下不会使用第二和三种, 主要使用第一种
第三章-依赖注入(DI)
依赖注入全称是 dependency Injection 翻译过来是依赖注入.其实就是如果我们托管的某一个类中存在属性,需要spring在创建该类实例的时候,顺便给这个对象里面的属性进行赋值。 这就是依赖注入。
现在, Bean的创建交给Spring了, 需要在xml里面进行注册
我们交给Spring创建的Bean里面可能有一些属性(字段), Spring帮我创建的同时也把Bean的一些属性(字段)给赋值, 这个赋值就是注入.
-
注册: 把bean的创建交给Spring
-
依赖注入: bean创建的同时, bean里面可能有一些字段需要赋值, 这个赋值交给Spring, 这个过程就是依赖注入
-
构造方法方式注入
-
set方法方式的注入
-
P名称空间注入
-
SpEL的属性注入
构造方法方式注入【掌握】
- Java代码
public class AccountServiceImpl implements AccountService {
private String name;
public AccountServiceImpl(String name) {
this.name = name;
}
@Override
public void save() {
System.out.println("AccountServiceImpl... save()"+name);
}
}
- 配置文件
<!--注册AccountService-->
<bean id="accountService" class="com.itheima.service.impl.AccountServiceImpl">
<constructor-arg name="name" value="张三"></constructor-arg>
</bean>
set方法方式的注入【重点】
注入简单类型
- java代码
public class AccountServiceImpl implements AccountService {
private String name;
public void setName(String name) {
this.name = name;
}
@Override
public void save() {
System.out.println("AccountServiceImpl... save()"+name);
}
}
- 配置文件
<bean id="accountService" class="com.itheima.service.impl.AccountServiceImpl">
<property name="name" value="李四"></property>
</bean>
注入数组类型
- java代码
public class AccountServiceImpl implements AccountService {
private String[] hobbys;
public void setHobbys(String[] hobbys) {
this.hobbys = hobbys;
}
@Override
public void save() {
System.out.println("AccountServiceImpl... save()"+ Arrays.toString(hobbys));
}
}
- 配置文件
<bean id="accountService" class="com.itheima.service.impl.AccountServiceImpl">
<property name="hobbys">
<array>
<value>篮球</value>
<value>足球</value>
<value>乒乓球</value>
<value>排球</value>
</array>
</property>
</bean>
注入Map类型
- Java代码
public class AccountServiceImpl implements AccountService {
private Map<String,String> map;
public void setMap(Map<String, String> map) {
this.map = map;
}
@Override
public void save() {
Set<Map.Entry<String, String>> set = map.entrySet();
for (Map.Entry<String, String> entry : set) {
System.out.println(entry.getKey()+":"+entry.getValue());
}
System.out.println("AccountServiceImpl... save()");
}
}
- 配置文件
<bean id="accountService" class="com.itheima.service.impl.AccountServiceImpl">
<property name="map">
<map>
<entry key="akey" value="aaa"/>
<entry key="bkey" value="bbb"/>
<entry key="ckey" value="ccc"/>
</map>
</property>
</bean>
或者
<bean id="accountService" class="com.itheima.service.impl.AccountServiceImpl">
<property name="map">
<props>
<prop key="akey">aaa</prop>
<prop key="bkey">bbb</prop>
<prop key="ckey">ccc</prop>
</props>
</property>
</bean>
注入Java对象类型
- Java代码
public class AccountServiceImpl implements AccountService {
private AccountDao accountDao;
public void setAccountDao(AccountDao accountDao) {
this.accountDao = accountDao;
}
@Override
public void save() {
System.out.println("AccountServiceImpl... save()");
accountDao.save();
}
}
- 配置文件
<!--注册AccountService-->
<bean id="accountService" class="com.itheima.service.impl.AccountServiceImpl">
<property name="accountDao" ref="accountDao"></property>
</bean>
<!--注册accountDao-->
<bean id="accountDao" class="com.itheima.dao.impl.AccountDaoImpl">
</bean>
名称空间注入【了解】
p名称空间的方式
- 提供属性的set方法
- 在applicationContext.xml引入p命名空间xmlns:p=“http://www.springframework.org/schema/p”
<?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:context="http://www.springframework.org/schema/context" 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 http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
</beans>
- 使用
<bean id="accountService" class="com.itheima.service.impl.AccountServiceImpl" p:name="张三">
</bean>
Spring3.0之后 SpEL的属性注入【了解】
这种方式是使用spring 提供的一种表达式方式去赋值, 如果是给前面提过的几个类型注入赋值,那么使用spEL 表达式来注入赋值倒显得繁琐。但是这个spEL 方式赋值,最大的好处在于,它能像EL表达式一般,在里面进行运算、 逻辑判断,还可以调用其它Bean的属性和方法给当前属性赋值
- 语法格式 : #{spEL}
- 类中存在一个属性 name , 并且给定 set方法。
<property name="name" value="#{'张三'}" ></property>
小结
- 构造方法方式
- 定义变量 ,提供构造方法
- 在bean标签里面配置子标签constructor-arg
<bean id="" class="">
<constructor-arg name="" value=""></constructor-arg>
<constructor-arg name="" ref=""></constructor-arg>
</bean>
- Set方法方式
- 2.1 步骤
- 定义变量 ,提供Set方法
- bean标签里面配置子标签property
- 2.2 简单类型(基本类型, String)
- 2.1 步骤
<bean id="" class="">
<property name="" value=""></property>
</bean>
- 2.3 数组类型
<bean id="" class="">
<property name="">
<array>
<value></value>
</array>
</property>
</bean>
- 2.3 Map类型
<bean id="" class="">
<property name="">
<map>
<entry key="" value=""></entry>
</map>
</property>
</bean>
- 2.4 对象类型
<bean id="" class="">
<property name="" ref="">
</property>
</bean>
使用Spring的IoC的实现账户的CRUD配置文件
<?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">
<!--注册AccountService-->
<bean id="accountService" class="com.itheima.service.impl.AccountServiceImpl">
<property name="accountDao" ref="accountDao"/>
</bean>
<!--注册accountDao-->
<bean id="accountDao" class="com.itheima.dao.impl.AccountDaoImpl">
<property name="queryRunner" ref="queryRunner"/>
</bean>
<!--注册queryRunner-->
<bean id="queryRunner" class="org.apache.commons.dbutils.QueryRunner">
<constructor-arg name="ds" ref="dataSource"/>
</bean>
<!--注册数据源-->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="url" value="jdbc:mysql://localhost:3306/spring_day02?characterEncoding=utf8"/>
<property name="driverClassName" value="com.mysql.jdbc.Driver"/>
<property name="username" value="root"/>
<property name="password" value="123456"/>
</bean>
</beans>
测试案例
public class DbTest {
@Test
public void fun01() throws Exception {
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
AccountService accountService = (AccountService) applicationContext.getBean("accountService");
List<Account> list = accountService.findAll();
System.out.println(list);
}
}
@NonNull 注解和@Nullable 注解的使用
用 @Nullable 和 @NotNull 注解来显示表明可为空的参数和以及返回值。这样就够在编译的时候处理空值而不是在运行时抛出 NullPointerExceptions。
第四章-Spring的IOC注解开发
添加依赖
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
</dependencies>
引入context的命名空间
applicationContext.xml中需要引入context的命名空间,可在xsd-configuration.html中找到
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
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/context http://www.springframework.org/schema/context/spring-context.xsd">
</beans>
配置扫描
在bean标签内部,使用context:component-scan ,让spring扫描该基础包下的所有子包注解
<context:component-scan base-package="com.itheima"></context:component-scan>
- 在类上面添加@Component(“id”)
- 在applicationContext.xml 开启包扫描
用于创建对象的
相当于:
@Component
作用:
把资源让 spring 来管理。相当于在 xml 中配置一个 bean。
属性:
value:指定 bean 的 id。如果不指定id属性,默认 bean 的 id 是当前类的类名。首字母小写。
web里面的三层结构 中的所有类,在spring里面都称之为 Component (组件) , 但是它也提供了更详细的注解来针对不同的层级声明 。
三个衍生注解如下:
@Controller :修饰WEB层类 —>action | SpringMVC
@Service :修饰业务层类 —>service
@Repository :修饰DAO层类 —>dao
用于改变作用范围的@scope
@scope
singleton: 单例(默认)
prototype:多例
@Scope注解用来描述类的作用范围的,默认值singleton。如同xml中bean标签的属性scope .如果配置成多例的使用prototype。
@Scope("prototype")
@Component("accountService")
public class AccountServiceImpl implements AccountService {}
和生命周期相关的【了解】
初始化和销毁回调方法对应的注解
@PostConstrut:如同xml中bean标签的属性init-method ,用来设置spring框架初始化此类实例时调用的初始化方法,标注在此类的初始化方法上
@PreDestroy:如同xml中bean标签的属性init-method ,用来设置spring框架销毁此类实例时调用的销毁方法,标注在此类的销毁方法上
注意:这两个注解都是配在方法上的
总结:
- 注册Bean的 相当于配置了
- @Component(“id”)
- @Controller(“id”) 配置web层
- @Service(“id”) 配置Service层
- @Repository(“id”) 配置持久层 如果id不配, 默认就是类的名字,首字母小写
- 配置bean的作用范围
- @Scope(“类型”)
- singleton 单例【默认】
- prototype 多例
- 配置和生命周期相关的
- @PostConstruct init-method=""
- @PreDestroy destroy-method=""
使用注解注入属性
@Value
- 作用:
注入基本数据类型和 String 类型数据的 - 属性:
value:用于指定值 , 可以通过表达式动态获得内容再赋值 - 实例:
@Value("奥巴马")
private String name;
@Autowired
- 作用:
自动按照类型注入。当使用注解注入属性时, set 方法可以省略。它只能注入其他 bean 类型。
如果只有一个实现类, 可以自动注入成功
如果有两个或者两个以上的实现类, 找到变量名一致的id对象给注入进去, 如果找不到,就报错 - 实例:
@Component("accountService")
public class AccountServiceImpl implements AccountService {
@Autowired
//当有多个AccountDao实现类时候, @Autowired会在在Spring容器里面找id为accountDao的对象注入,找不到就报错
private AccountDao accountDao;
@Override
public void save() {
System.out.println("AccountServiceImpl---save()");
accountDao.save();
}
}
@Qualifier
- 作用
在自动按照类型注入(@Autowired)的基础之上,再按照Bean的id注入。
它在给字段注入时不能独立使用,必须和@Autowire一起使用;
但是给方法参数注入时,可以独立使用。 - 属性
value:指定bean的id。 - 实例
@Component("accountService")
public class AccountServiceImpl implements AccountService {
@Autowired
@Qualifier(value = "accountDao02")
private AccountDao accountDao;
@Override
public void save() {
System.out.println("AccountServiceImpl---save()");
accountDao.save();
}
}
@Resource
如果上面一个接口有多种实现,那么现在需要指定找具体的某一个实现,那么可以使用
@Component("accountService")
public class AccountServiceImpl implements AccountService {
@Resource(name = "accountDao02")
private AccountDao accountDao;
@Override
public void save() {
System.out.println("AccountServiceImpl---save()");
accountDao.save();
}
}
小结:
- 注入简单类型 @Value(“值/表达式”)
- 注入对象类型
- 如果只有一个实现类, 建议使用@Autowired
- 如果有多个实现类, 建议使用@Resource("name=“id”)
混合开发
注解和XML比较
- xml
- 优点: 方便维护, 改xml文件
- 缺点: 相对注解而言, 麻烦一点
- 注解
- 优点: 开发简洁方便
- 缺点: 维护没有xml那么方便, 需要改源码
混合开发特点
使用xml(注册bean)来管理bean
使用注解来注入属性
混合开发环境搭建
创建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" 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/context http://www.springframework.org/schema/context/spring-context.xsd
">
</beans>
配置扫描
<context:component-scan base-package="com.itheima" />
- 在xml配置中添加Bean的管理
- 在被管理bean对应的类中,为依赖添加注解配置
小结:
- xml注册(管理bean)
- 注解注入(给对象里面字段/属性赋值)
纯注解测试实现
@Configuration
- 作用:
用于指定当前类是一个spring配置类,当创建容器时会从该类上加载注解。
获取容器时需要使用AnnotationApplicationContext(类.class)。 - 属性:
value:用于指定配置类的字节码 - 示例代码:
@Configuration
public class SpringConfiguration {
}
@ComponentScan
- 作用:
用于指定spring在初始化容器时要扫描的包。作用和在spring的xml配置文件中的: <context:component-scan base-package=“com.itheima”/>是一样的。 - 属性:
basePackages:用于指定要扫描的包。和该注解中的value属性作用一样。 - 示例代码:
@Configuration
@ComponentScan("com.itheima")
public class SpringConfiguration {
}
@Bean
- 作用:
该注解只能写在方法上,表明使用此方法创建一个对象,并且放入spring容器。 - 属性:
name:给当前@Bean注解方法创建的对象指定一个名称(即bean的id)。 - 示例代码:
/**
* 该类是一个配置类,它的作用和bean.xml是一样的
*/
@Configuration
@ComponentScan("com.itheima")
public class SpringConfiguration {
private String driver = "com.mysql.jdbc.Driver";
private String url = "jdbc:mysql:///spring_day02";
private String username = "root";
private String password = "123456";
/**
* 用于创建一个QueryRunner对象
* @param dataSource
* @return
*/
@Bean("runner")
public QueryRunner createQueryRunner(@Qualifier(value ="dataSource") DataSource dataSource){
return new QueryRunner(dataSource);
}
/**
* 用于创建DataSource
* @return
* @throws PropertyVetoException
*/
@Bean(name = "dataSource")
public DataSource createDataSource() throws PropertyVetoException {
ComboPooledDataSource ds = new ComboPooledDataSource();
ds.setDriverClass(driver);
ds.setJdbcUrl(url);
ds.setUser(username);
ds.setPassword(password);
return ds;
}
}
@Import
- 作用:
用于导入其他配置类,在引入其他配置类时,可以不用再写@Configuration注解。当然,写上也没问题。 - 属性:
value[]:用于指定其他配置类的字节码。 - 示例代码:
/**
* 该类是一个配置类,它的作用和bean.xml是一样的
*/
@Configuration
@ComponentScan("com.itheima")
@Import({JdbcConfig.class})
public class SpringConfiguration {
}
public class JdbcConfig {
private String driver = "com.mysql.jdbc.Driver";
private String url = "jdbc:mysql:///spring_day02";
private String username = "root";
private String password = "123456";
/**
* 用于创建一个QueryRunner对象
* @param dataSource
* @return
*/
@Bean("runner")
@Scope("prototype")
public QueryRunner createQueryRunner(@Qualifier(value ="dataSource") DataSource dataSource){
return new QueryRunner(dataSource);
}
/**
* 用于创建DataSource
* @return
* @throws PropertyVetoException
*/
@Bean(name = "dataSource")
public DataSource createDataSource() throws PropertyVetoException {
ComboPooledDataSource ds = new ComboPooledDataSource();
ds.setDriverClass(driver);
ds.setJdbcUrl(url);
ds.setUser(username);
ds.setPassword(password);
return ds;
}
}
@PropertySource
- 作用:
用于加载.properties文件中的配置。例如我们配置数据源时,可以把连接数据库的信息写到properties配置文件中,就可以使用此注解指定properties配置文件的位置。 - 属性:
value[]:用于指定properties文件位置。如果是在类路径下,需要写上classpath: - 示例代码:
jdbc.properties
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/spring_day02
jdbc.username=root
jdbc.password=123456
JdbcConfig.java
@PropertySource(value = {“classpath:jdbc.properties”})
public class JdbcConfig {
@Value("
j
d
b
c
.
u
r
l
"
)
p
r
i
v
a
t
e
S
t
r
i
n
g
u
r
l
;
@
V
a
l
u
e
(
"
{jdbc.url}") private String url; @Value("
jdbc.url")privateStringurl;@Value("{jdbc.driver}")
private String driver;
@Value("
j
d
b
c
.
u
s
e
r
n
a
m
e
"
)
p
r
i
v
a
t
e
S
t
r
i
n
g
u
s
e
r
n
a
m
e
;
@
V
a
l
u
e
(
"
{jdbc.username}") private String username; @Value("
jdbc.username")privateStringusername;@Value("{jdbc.password}")
private String password;
@Bean(“queryRunner”)
public QueryRunner createQueryRunner(DataSource dataSource){
QueryRunner queryRunner = new QueryRunner(dataSource);
return queryRunner;
}
@Bean(“dataSource”)
public DataSource createDataSource(){
DruidDataSource dataSource = new DruidDataSource();
dataSource.setUrl(url);
dataSource.setDriverClassName(driver);
dataSource.setUsername(username);
dataSource.setPassword(password);
return dataSource;
}
}
通过注解获取容器
ApplicationContext ac = new AnnotationConfigApplicationContext(SpringConfiguration.class);
小结:
第五章-Spring整合测试
在测试类中,每个测试方法都有以下两行代码:
ApplicationContext ac = new ClassPathXmlApplicationContext(“bean.xml”);
AccountService as= ac.getBean(“accountService”,AccountService.class);
这两行代码的作用是获取容器,如果不写的话,直接会提示空指针异常。所以又不能轻易删掉。
导入spring整合Junit的坐标
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
在测试类上面标记注解
@RunWith(SpringJUnit4ClassRunner.class)//指明运行的测试环境
//@ContextConfiguration("classpath:applicationContext.xml")//指明spring框架的加载的配置文件【有applicationContext.xml解使用这种】
@ContextConfiguration(classes = SpringConfiguration.class)//指明spring框架的加载的配置类【纯注解使用这种】
public class DbTest
{
@Autowired
private AccountService accountService;
@Test
public void testSaveAccount() throws Exception {
Account account = new Account();
account.setName("王二麻子");
account.setMoney(100f);
accountService.save(account);
}
}
思考问题为什么不把测试类配到xml中
- 在解释这个问题之前,先解除大家的疑虑,配到XML中能不能用呢?
答案是肯定的,没问题,可以使用。 - 那么为什么不采用配置到xml中的方式呢?
这个原因是这样的:
第一:当我们在xml中配置了一个bean,spring加载配置文件创建容器时,就会创建对象。
第二:测试类只是我们在测试功能时使用,而在项目中它并不参与程序逻辑,也不会解决需求上的问题,所以创建完了,并没有使用。那么存在容器中就会造成资源的浪费。
所以,基于以上两点,我们不应该把测试配置到xml文件中。
第六章-AOP相关的概念
什么是AOP
AOP:全称是AspectOriented Programming, 即面向切面编程。在不修改源码的基础上,对我们的已有方法进行增强。
说白了就是把我们程序重复的代码抽取出来,在需要执行的时候,使用动态代理的技术,进行增强
AOP实现原理
使用动态代理技术
- JDK动态代理: 必须需要接口的
- Cglib动态代理: 不需要接口的,只需要类就好了
小结:
- AOP: 面向切面编程
- 作用: 不需要改变源代码 对目标方法进行增强
- 底层实现: 动态代理
在AOP 这种思想还没有出现的时候,我们解决 切面的问题思路无非有以下两种:
- 方式一:通过静态方法实现(缺点:需要修改源码,后期不好维护)
把需要添加的代码抽取到一个地方,然后在需要添加那些方法中引用 - 方式二:通过继承方案来解决(缺点:需要修改源码,继承关系复杂,后期不好维护)
抽取共性代码到父类, 子类在需要的位置,调用父类方法。
上述两种方式的缺点
都会打破原来代码的平静(也就是必须要修改代码 , 或者就是必须事先固定好。) 如果我们想在原有代码基础上扩展。 并且不改动原来的代码, 就可以使用AOP了。
其实AOP 字面直译过来是面向切面编程,其实通俗一点它就是对我们的具体某个方法进行了增强而已。在之前我们对某个方法进行增强无非是两种手段 ,一种是装饰者模式 、 一种是代理模式 (静态代理 & 动态代理) 。 AOP 的底层使用的是动态代理方式 。
AOP 的底层动态代理实现有两种方案。
一种是使用JDK的动态代理。 这种是早前我们在前面的基础增强说过的。 这一种主要是针对有接口实现的情况。 它的底层是创建接口的实现代理类, 实现扩展功能。也就是我们要增强的这个类,实现了某个接口,那么我就可以使用这种方式了. 而另一种方式是使用了cglib 的动态代理,这种主要是针对没有接口的方式,那么它的底层是创建被目标类的子类,实现扩展功能.
小结:
- AOP的底层就是封装了动态代理
- JDK的动态代理
- CgLib的动态代理
- JDK的动态代理: 依赖接口的, 必须有接口
CgLib的动态代理: 依赖类的(父类), 不需要接口的
学习spring中的AOP要明确的事
- 开发阶段(我们做的)
编写核心业务代码(开发主线):大部分程序员来做,要求熟悉业务需求。
把公用代码抽取出来,制作成通知。(开发阶段最后再做):AOP编程人员来做。
在配置文件中,声明切入点与通知间的关系,即切面:AOP编程人员来做。 - 运行阶段(Spring框架完成的)
Spring框架监控切入点方法的执行。一旦监控到切入点方法被运行,使用代理机制,动态创建目标对象的代理对象,根据通知类别,在代理对象的对应位置,将通知对应的功能织入,完成完整的代码逻辑运行。
在spring中,框架会根据目标类是否实现了接口来决定采用哪种动态代理的方式。
AOP中的术语
- JoinPoint: 连接点
类里面哪些方法可以被增强,这些方法称为连接点. 在spring的AOP中,指的是所有现有的方法。 - Pointcut: 切入点
在类里面可以有很多方法被增强,但是实际开发中,我们只对具体的某几个方法而已,那么这些实际增强的方法就称之为切入点 - Advice: 通知/增强
增强的逻辑、称为增强,比如给某个切入点(方法) 扩展了校验权限的功能,那么这个校验权限即可称之为增强 或者是通知
通知分为:
前置通知: 在原来方法之前执行.
后置通知: 在原来方法之后执行. 特点: 可以得到被增强方法的返回值
环绕通知:在方法之前和方法之后执行. 特点:可以阻止目标方法执行
异常通知: 目标方法出现异常执行. 如果方法没有异常,不会执行. 特点:可以获得异常的信息
最终通知: 指的是无论是否有异常,总是被执行的。
Aspect: 切面
切入点和通知的结合
第七章-Spring中的AOP
前置通知
导入坐标
<dependencies>
<!--Spring核心容器-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
<!--SpringAOP相关的坐标-->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.8.7</version>
</dependency>
<!--Spring整合单元测试-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
<!--单元测试-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
</dependencies>
定义被增强的业务逻辑类和切面类(增强的)
//被增强的类
public class AccountDaoImpl implements AccountDao {
@Override
public void save() {
System.out.println("save()...");
}
}
//增强的类(切面类)
public class MyAscept {
public void checkPrivilege() {// 通知
System.out.println("权限的校验...");
}
}
xml中声明 增强的bean (扩展的bean) 和 被增强的bean (被扩展的bean)
<!--注册accountDao-->
<bean id="accountDao" class="com.itheima.dao.impl.AccountDaoImpl"/>
<!--注册增强(切面)类-->
<bean id="myAscept" class="com.itheima.aspect.MyAscept"/>
定义aop的配置
<!--配置切入点(哪个包下的哪个类哪个方法需要增强)
expression="execution(* com.xyz.myapp.service.*.*(..))"
第一个* 表示返回值 ,不管任何返回值。 匹配任意返回值
com.xyz.myapp.service : 表示这个包下
第二个* 表示这个包下的所有类
第三个* 表示这个包下的所有类中的所有方法
(..) : 这个表示方法的参数 .. 任意参数
spring 根据这一套规则,能够找到具体哪一个方法来做增强了。
-->
-->
<!--配置AOP-->
<aop:config>
<aop:pointcut id="pointCut01" expression="execution(* com.itheima.dao.impl.AccountDaoImpl.save(..))"/>
<!--配置切面(切入点和通知的结合)-->
<aop:aspect ref="myAscept">
<!--前置通知-->
<aop:before method="checkPrivilege" pointcut-ref="pointCut01"></aop:before>
</aop:aspect>
</aop:config>
</aop:config>
后置通知
在原来方法之后执行. 特点: 可以得到被增强方法的返回值 <aop:after-returning …/>
<!--后置通知 -->
<aop:after-returning method="showLog" pointcut-ref="pointCut2" returning="obj"/>
环绕通知
在方法之前和方法之后执行. 特点:可以阻止目标方法执行 <aop:around …/>
//环绕通知(ProceedingJoinPoint 代表目标方法)
public void showTime(ProceedingJoinPoint joinPoint) throws Throwable {
//打印系统时间
System.out.println("调用之前的时间="+System.currentTimeMillis());
//执行目标方法
joinPoint.proceed();
//打印系统时间
System.out.println("调用之后的时间="+System.currentTimeMillis());
}
//配置
<!--环绕通知 -->
<aop:around method="showTime" pointcut-ref="pointCut3"/>
异常通知【了解】
方法出现异常执行. 如果方法没有异常,不会执行. 特点:可以获得异常的信息 <aop:after-throwing …/>
增强的方法:
public void afterThrowing(Throwable e){
System.out.println("异常抛出通知========"+e.getMessage());
}
配置文件:
<aop:after-throwing method="afterThrowing" throwing="e" pointcut-ref="pointCut4"/>
最终通知【了解】
指的是无论是否有异常,总是被执行的。 <aop:after …/>
<!--最终通知 -->
<aop:after method="after" pointcut-ref="pointCut4"/>
定义切面类,在切面增强类上面使用注解 @Aspect并且在切面类中定义切入点方法
@Aspect
public class MyAscept {
@Before(value = "execution(* com.itheima.dao.impl.AccountDaoImpl.save(..))")
public void checkPrivilege() {// 通知
System.out.println("权限的校验...");
}
}
创建配置文件
<?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">
<!--注册accountDao-->
<bean id="accountDao" class="com.itheima.dao.impl.AccountDaoImpl"/>
<!--注册切面类-->
<bean id="myAscept" class="com.itheima.aspect.MyAscept"/>
<!--开启AOP注解-->
<aop:aspectj-autoproxy/>
</beans>
后置通知
@AfterReturning(value = "execution(* com.itheima.dao.impl.AccountDaoImpl.delete(..))",returning="obj")
public void showLog(Object obj) {
System.out.println("显示删除之后的日志..." + obj);
}
环绕通知
// 打印时间(环绕通知)
@Around(value = "execution(* com.itheima.dao.impl.AccountDaoImpl.findAll(..))")
public void showTime(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println("执行之前的时间:" + System.currentTimeMillis());
//执行findAll
joinPoint.proceed();
System.out.println("执行之后的时间:" + System.currentTimeMillis());
}
异常通知【了解】
//测试异常通知
@AfterThrowing(value="execution(* com.itheima.dao.impl.AccountDaoImpl.update(..))",throwing="e")
public void showException(Throwable e) {
System.out.println("异常通知...." + e.getMessage());
}
最终通知【了解】
//测试最终通知
@After(value = "execution(* com.itheima.dao.impl.AccountDaoImpl.update(..))")
public void showFinal() {
System.out.println("最终通知...");
}
第八章-Spring中的JdbcTemplate 【了解】
- JdbcTemplate是Spring里面的持久层一个工具包, 封装了Jdbc
- 使用步骤
- 创建DataSource
- 创建JDBCTemplate
- 调用update(), query(), queryForObject()
方式一:
AccountDaoImpl.java
public class AccountDaoImpl implements AccountDao {
private JdbcTemplate jdbcTemplate;
public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
}
@Override
public Account findAccountById(Integer id) {
List<Account> list = jdbcTemplate.query("select * from account where id = ? ",new AccountRowMapper(),id);
return list.isEmpty()?null:list.get(0);
}
@Override
public Account findAccountByName(String name) {
List<Account> list = jdbcTemplate.query("select * from account where name = ? ",new AccountRowMapper(),name);
if(list.isEmpty()){
return null;
}
if(list.size()>1){
throw new RuntimeException("结果集不唯一,不是只有一个账户对象");
}
return list.get(0);
}
@Override
public void updateAccount(Account account) {
jdbcTemplate.update("update account set money = ? where id = ? ",account.getMoney(),account.getId());
}
}
applicationContext.xml
<!-- 配置一个dao -->
<bean id="accountDao" class="com.itheima.dao.impl.AccountDaoImpl">
<!-- 注入jdbcTemplate -->
<property name="jdbcTemplate" ref="jdbcTemplate"></property>
</bean>
<!-- 配置一个数据库的操作模板:JdbcTemplate -->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 配置数据源 -->
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
<property name="url" value="jdbc:mysql:///spring_day04"></property>
<property name="username" value="root"></property>
<property name="password" value="123456"></property>
</bean>
方式二:
AccountDaoImpl.java
public class AccountDaoImpl extends JdbcDaoSupport implements AccountDao {
@Override
public Account findAccountById(Integer id) {
//getJdbcTemplate()方法是从父类上继承下来的。
List<Account> list = getJdbcTemplate().query("select * from account where id = ? ",new AccountRowMapper(),id);
return list.isEmpty()?null:list.get(0);
}
@Override
public Account findAccountByName(String name) {
//getJdbcTemplate()方法是从父类上继承下来的。
List<Account> list = getJdbcTemplate().query("select * from account where name = ? ",new AccountRowMapper(),name);
if(list.isEmpty()){
return null;
}
if(list.size()>1){
throw new RuntimeException("结果集不唯一,不是只有一个账户对象");
}
return list.get(0);
}
@Override
public void updateAccount(Account account) {
//getJdbcTemplate()方法是从父类上继承下来的。
getJdbcTemplate().update("update account set money = ? where id = ? ",account.getMoney(),account.getId());
}
}
applicationContext.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">
<!-- 配置dao2 -->
<bean id="accountDao" class="com.itheima.dao.impl.AccountDaoImpl">
<!-- 注入dataSource -->
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 配置数据源 -->
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
<property name="url" value="jdbc:mysql:///spring_day04"></property>
<property name="username" value="root"></property>
<property name="password" value="123456"></property>
</bean>
</beans>
第九章-Spring管理事务
概述:
由于Spring对持久层的很多框架都有支持 , Hibernate 、 jdbc 、JdbcTemplate , MyBatis 由于使用的框架不同,所以使用事务管理操作API 也不尽相同。 为了规范这些操作, Spring统一定义一个事务的规范 ,这其实是一个接口 。这个接口的名称 : PlatformTrasactionManager.并且它对已经做好的框架都有支持.
如果dao层使用的是JDBC, JdbcTemplate 或者mybatis,那么 可以使用DataSourceTransactionManager 来处理事务
如果dao层使用的是 Hibernate, 那么可以使用HibernateTransactionManager 来处理事务
相关的API
PlatformTransactionManager
平台事务管理器是一个接口,实现类就是Spring真正管理事务的对象。
常用的实现类:
DataSourceTransactionManager :JDBC开发的时候(JDBCTemplate,MyBatis),使用事务管理。
HibernateTransactionManager :Hibernate开发的时候,使用事务管理。
TransactionDefinition
事务定义信息中定义了事务的隔离级别,传播行为,超时信息,只读。TransactionStatus:事务的状态信息
事务状态信息中定义事务是否是新事务,是否有保存点。
编程式(硬编码)事务【了解】
public class JDBCTempleTest {
@Test
public void fun01() throws PropertyVetoException{
//1. 创建数据源
ComboPooledDataSource dataSource = new ComboPooledDataSource();
dataSource.setDriverClass("com.mysql.jdbc.Driver");
dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/spring_day03");
dataSource.setUser("root");
dataSource.setPassword("123456");
//2. 创建jdbc模版
final JdbcTemplate jdbcTemplate = new JdbcTemplate();
jdbcTemplate.setDataSource(dataSource);
//3.创建事务管理器
DataSourceTransactionManager transactionManager = new DataSourceTransactionManager();
transactionManager.setDataSource(dataSource);
//4.创建事务模版
TransactionTemplate transactionTemplate = new TransactionTemplate();
transactionTemplate.setTransactionManager(transactionManager);
//4. 进行事务操作
transactionTemplate.execute(new TransactionCallback<Object>() {
@Override
public Object doInTransaction(TransactionStatus status) {
//操作数据库
jdbcTemplate.update("update t_account set money = money - ? where name = ? ", 100.0,"zs");
int i = 1/0;
jdbcTemplate.update("update t_account set money = money + ? where name = ? ", 100.0,"ls");
return null;
}
});
}
}
我们项目开发 一般不会使用编程式(硬编码)方式. 一般使用声明式(配置)事务
- xml方式
- 注解方式
Spring声明式事务-xml配置方式【重点】
配置事务管理器
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
配置事务建议(事务的规则)
<tx:advice id="adviceId" transaction-manager="transactionManager">
<!--在tx:advice标签内部 配置事务的属性 -->
<tx:attributes>
<!-- 指定方法名称:是业务核心方法
read-only:是否是只读事务。默认false,不只读。
isolation:指定事务的隔离级别。默认值是使用数据库的默认隔离级别。
propagation:指定事务的传播行为。
timeout:指定超时时间。默认值为:-1。永不超时。
rollback-for:用于指定一个异常,当执行产生该异常时,事务回滚。产生其他异常,事务不回滚。没有默认值,任何异常都回滚。
no-rollback-for:用于指定一个异常,当产生该异常时,事务不回滚,产生其他异常时,事务回滚。没有默认值,任何异常都回滚。
-->
<tx:method name="*" read-only="false" propagation="REQUIRED"/>
<tx:method name="find*" read-only="true" propagation="SUPPORTS"/>
</tx:attributes>
</tx:advice>
配置事务的AOP
<aop:config>
<!-- 定义切入点 ,也就是到底给那些类中的那些方法加入事务的管理 -->
<aop:pointcut expression="execution(* com.itheima.service.impl.*.*(..))" id="pointCat"/>
<!-- 让切入点和事务的建议绑定到一起 -->
<aop:advisor advice-ref="txAdvice" pointcut-ref="pointCat"/>
</aop:config>
spring声明式事务-注解方式【重点】
在applicationContext里面打开注解驱动
<!--一,配置事务管理器 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!--二配置事务注解 -->
<tx:annotation-driven transaction-manager="transactionManager"/>
在业务逻辑类上面使用注解
@Transactional : 如果在类上声明,那么标记着该类中的所有方法都使用事务管理。也可以作用于方法上, 那么这就表示只有具体的方法才会使用事务。
- xml方式
- 优点: 只需要配一次, 全局生效, 容易维护
- 缺点: 相对注解而言 要麻烦一点
- 注解方式
- 优点: 配置简单
- 缺点: 写一个业务类就配置一次, 事务细节不好处理, 不好维护