一. Spring概述
Spring是分层Java SE/EE应用全栈轻量级开源框架,以IOC(Inverse of Control : 反转控制)和 AOP(Aspect Oriented Programming :面向切面编程)为内核,提供表现层SpringMVC和持久层Spring JDBC以及业务层事务管理等众多企业级应用技术。
1.2 程序的耦合以及解耦
耦合:程序之间的依赖关系
包括:
方法之间的依赖
类之间的依赖
解耦:
降低程序间的依赖关系。
实际开发中:
应该做到:编译期不依赖,运行时才依赖。
解耦的思路:
第一步:使用反射来创建对象,而避免使用new关键字。
第二步:通过读取配置文件来获取要创建的对象全限定类名。
②曾经案例中的问题
案例一:
之前的JDBC,注册驱动时,我们采用的是Class.forName方式,而DriverManager.registerDriver(),在于我们的类依赖了数据库的具体驱动类(mysql),如果更换了数据库,需要修改源码。此时通过反射来注册驱动,仅仅表示一个字符串。
public static void main(String[] args) throws Exception {
//1.注册驱动
//DriverManager.registerDriver(new com.mysql.jdbc.Driver());
Class.forName("com.mysql.jdbc.Driver");
//2.获取连接
Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/eesy","root","1234");
//3.获取操作数据库的预处理对象
PreparedStatement pstm = conn.prepareStatement("select * from account");
//4.执行SQL,得到结果集
ResultSet rs = pstm.executeQuery();
//5.遍历结果集
while(rs.next()){
System.out.println(rs.getString("name"));
}
//6.释放资源
rs.close();
pstm.close();
conn.close();
}
案例二:
业务层调用持久层时,此时的业务层依赖于持久层的接口和实现类。如果没有持久层实现类,编译都将无法通过。这种编译期依赖关系,应该在实际开发中杜绝,需要优化代码解决。
public class AccountServiceImpl implements IAccountService {
private IAccountDao accountDao = new AccountDaoImpl();
}
二. 从案例引入IOC的概念
应用:
模拟持久层,表现层,业务层之间的关系
分析:
通过创建Bean对象工厂,将service和dao对象存放在定义的一个Map中,称之为容器。在单例模式下,将此过程放在static{ }代码块中,编译过程中,就将持久层和业务层需要的对象进行存储,然后通过配置文件的key值,获取类对象。
①业务层:在services文件下创建IAccountService接口
package com.ithema.services;
/**
* 账户业务层的接口
**/
public interface IAccountService {
/**
* 模拟账户
*/
void saveAccount();
}
②载impl文件夹下创建IAccountService实现类
public class AccountServiceImpl implements IAccountService {
private IAccountDao accountDao = (IAccountDao)BeanFactory.getBean("accountDao");
public void saveAccount() {
int i = 1;
//private IAccountDao accountDao = new AccountDaoImpl();
//accountDao = (IAccountDao) BeanFactory.getBean("accountDao");
accountDao.saveAccount();
System.out.println(i);
i++;
}
}
③持久层:dao文件夹下创建IAccountDao接口
public interface IAccountDao {
void saveAccount();
}
④impl文件夹下创建IAccountDao接口实现类
public class AccountDaoImpl implements IAccountDao {
public void saveAccount() {
System.out.println("保存了账户");
}
}
⑤factory文件夹下创建BeanFactory类
* 它就是创建service和dao对象。
*
* 第一个:需要一个配置文件配置service和dao
* 配置的内容: 唯一标识 = 全限定类名 (key = value)
* 第二个:通过读取配置文件中配置的内容,反射创建对象
*
* 配置文件可以是:xml也可以是properties。
public class BeanFactory {
//定义一个Properties对象
private static Properties props;
//定义一个Map用于存放我们要创建的对象,称之为容器
private static Map<String,Object> beans;
//使用静态代码块为Properties复制
static{
try {
//实例化对象
props = new Properties();
//获取Properties文件的流对象
InputStream in = BeanFactory.class.getClassLoader().getResourceAsStream("bean.properties");
props.load(in);
//实例化容器
beans = new HashMap<String,Object>();
//取出文件中所有的key
Enumeration keys = props.keys();
//遍历枚举
while(keys.hasMoreElements()){
//取出每个key
String key = keys.nextElement().toString();
//根据key获取value
String beanPath = props.getProperty(key);
//反射创建对象
Object value = Class.forName(beanPath).newInstance();
//把key和value存入容器中
beans.put(key,value);
}
System.out.println("beans = " + beans);
}catch (Exception e){
throw new ExceptionInInitializerError("初始化异常");
}
}
/**
* 根据bean的名称获取对象
* @param beanName
* @return
*/
public static Object getBean(String beanName){
return beans.get(beanName);
}
⑥在resources下创建properties配置bean文件
accountService=com.ithema.services.impl.AccountServiceImpl
accountDao=com.ithema.dao.impl.AccountDaoImpl
⑦表现层:ui文件夹下创建Client类
/**
*模拟表现层,用于调用业务层
*/
public class Client {
public static void main(String[] args) {
//IAccountService as = new AccountServiceImpl();
for(int i = 0; i < 5; i++) {
IAccountService as = (IAccountService) BeanFactory.getBean("accountService");
System.out.println(as);
as.saveAccount();
}
}
}
2. IOC概念和spring中的IOC
控制反转(IOC)把创建对象的权利交给框架,并非面向对象编程。
前面的例子中,我们通过bean工厂来创建对象,并将其放在给定的Map中。通过读取bean.properties文件,赋予对应的
key和value。在方法调用时,通过bean工厂在map中查找并创建对象。这个map就称之为容器。
2.1 spring中基于XML的IOC环境搭建
①复制上一个工程
②将resource下的bean.properties替换为bean.xml,并导入约束
约束配置文件:
/spring-framework-5.0.2.RELEASE/docs/spring-framework-reference/html5/core.html
在其中搜索xmlns,复制粘贴。
<?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">
</beans>
③让spring管理资源,配置文件中的service和dao
<!-- bean标签:用于配置让spring创建对象,并且存入ioc容器之中
id属性:对象的唯一标识。
class属性:指定要创建对象的全限定类名
-->
<!-- 把对象的创建交给spring来管理-->
<bean id = "accountService" class = "com.ithema.services.impl.AccountServiceImpl"></bean>
<bean id="accountDao" class="com.ithema.dao.impl.AccountDaoImpl"></bean>
④测试
public static void main(String[] args) {
//1.获取核心容器对象
ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
//ApplicationContext ac = new FileSystemXmlApplicationContext("C:\\Users\\布\\Desktop\\bean.xml");
//2.根据id获取Bean对象
IAccountService as = (IAccountService) ac.getBean("accountService");
IAccountDao adao = ac.getBean("accountDao",IAccountDao.class);
System.out.println(as);
System.out.println(adao);
as.saveAccount();
}
从中可以看到,对应的两个对象都已经创建成功了,只是之间还没有联系,所以as.saveAccount()方法会报空指针异常。
解决方法:(通过依赖注入)
通过在bean.xml文件中注入依赖,并在AccountServiceImpl中实现AccountDao的set方法即可。
<!-- 把对象的创建交给spring来管理-->
<bean id = "accountService" class = "com.ithema.services.impl.AccountServiceImpl">
<property name="accountDao" ref="accountDao"></property>
</bean>
<bean id="accountDao" class="com.ithema.dao.impl.AccountDaoImpl"></bean>
public void setAccountDao(IAccountDao accountDao) {
this.accountDao = accountDao;
}
2.2 spring的IOC核心容器
ApplicationContext的三个常用实现类:
ClassPathXmlApplicationContext
-它可以加载路径下的配置文件,要求配置文件必须在类路径下,不在的话,加载不了。
FileSystemXmlApplicationContext
-它可以加载磁盘任意路径下的配置文件(必须有访问权限)
AnnotationConfigApplicationContext
-用于读取注解创建容器的。
核心容器的两个接口问题:
ApplicationContext 单例对象适用
-它在构建核心容器时,创建对象采取的策略是采用立即加载的方式,
只要一读取配置文件马上就创建配置文件中配置的对象。
BeanFactory 多例对象适用
-它在构建核心容器时,创建对象采用的策略是采用延迟加载的方式,
也就是什么时候根据id获取对象了,什么时候才真正创建对象。
2.3 spring中工厂的类结构图
2.4 BeanFactory和ApplicationContext的区别
2.5 IOC中bean标签
spring中创建bean的三种方式。
①使用默认无参构造器
<!-- 方式一 :使用默认构造函数创建。
在spring的配置文件中使用bean标签,配以id和class属性之后,且没有其他属性和标签时,采用
的就是默认构造函数创建bean对象,此时如果类中没有默认构造函数,则对象无法创建。
<bean id = "accountService" class = "com.ithema.services.impl.AccountServiceImpl"></bean>
-->
②spring管理实例工厂-使用实例工厂方法创建对象
/**
* @date 2020/6/6 - 18:19
* 模拟一个工厂类(该类可能存在于jar包中,无法通过修改源码的方式来提供默认构造器)
**/
public class InstanceFactory {
public InstanceFactory(){
System.out.println("instanceFactory中的构造器...");
}
public IAccountService getAccountService(){
return new AccountServiceImpl();
}
}
<!-- 方式二 : 使用普通工厂中的方法创建对象(使用某个类中的方法创建对象,并存入spring容器)
<bean id = "instanceFactory" class = "com.ithema.factory.InstanceFactory"></bean>
<bean id = "accountService" factory-bean="instanceFactory" factory-method="getAccountService"></bean>
-->
③spring管理静态工厂-使用静态工厂方法创建对象
/**
* @date 2020/6/6 - 18:19
* 模拟一个工厂类(该类可能存在于jar包中,无法通过修改源码的方式来提供默认构造器)
**/
public class StaticFactory {
public StaticFactory(String name){
System.out.println("instanceFactory中的构造器...");
}
public static IAccountService getAccountService(){
return new AccountServiceImpl();
}
}
<!-- 方式三: 使用工厂中的静态方法创建对象(使用某个类中的静态方法创建对象,并存入spring容器)
<bean id = "accountService" class="com.ithema.factory.StaticFactory" factory-method="getAccountService"></bean>
-->
2.6 bean的作用范围和生命周期
<bean id = "accountService" class = "com.ithema.services.impl.AccountServiceImpl" scope="prototype"></bean>
bean对象的生命周期
单例对象
出生:当容器创建时,对象出生
活着:只要容器还在,对象还在
死亡:容器销毁,对象消亡
总结:单例对象的生命周期和容器相同。
多例对象
出生:当我们使用对象时,spring框架为我们创建
活着:对象在使用过程中,一直活着
死亡:当对象长时间不用且没有别的对象引用时,由java的垃圾回收器回收
init-method:指定类中的初始化方法名称。
destroy-method:指定类中销毁方法名称。
<bean id = "accountService" class = "com.ithema.services.impl.AccountServiceImpl"
scope="prototype" init-method="init" destroy-method="destroy"></bean>
例:
/**
* @author busihang
* @date 2020/6/5 - 17:16
* 账户的业务层实现类
**/
public class AccountServiceImpl implements IAccountService {
public AccountServiceImpl(){
System.out.println("对象创建了");
}
public void saveAccount() {
System.out.println("service中的saveAccount方法执行了....");
}
public void init() {
System.out.println("对象初始化了....");
}
public void destroy() {
System.out.println("对象销毁了....");
}
}
3. 依赖注入(Dependency Injection)
依赖注入是spring框架核心IOC的具体实现。前面的程序在编写时,通过反转控制,把对象的创建交给了spring,但是代码中不可能没有依赖的情况,IOC解耦只能降低他们之间的依赖关系,不能消除。例如:业务层仍然会调用持久层的方法。这种业务层和持久层的依赖关系,在使用spring之后,就让spring来维护了。
3.1 能够注入的数据:三类
基本类型和String
其他bean类型(在配置文件中或者注解配置过的bean)
复杂类型/集合类型
3.2 注入方式:三种
****①方式一:使用构造函数提供
要求:类中需要提供一个对应参数列表的构造函数。
标签: constructor-arg
标签出现的位置:bean标签的内部
标签中的属性:
type:用于指定要注入的数据的数据类型,该数据类型也是构造函数中某个或某些参数的类型
index:用于指定要注入的数据给构造函数指定索引位置的参数赋值。索引的位置从0开始
name:用于指定给构造函数中指定名称的参数赋值 (常用)
=====================以上三个用于指定给构造函数中哪个参数赋值===================
value:用于给基本类型和String类型的数据
ref:用于指定其他bean类型数据。它指在spring的IOC核心容器中出现过的bean对象
优势:
在获取bean对象时,注入数据是必须的操作,否则对象无法创建成功。
弊端:
改变了bean对象的实例化方式,使我们在创建对象时,如果用不到这些数据,也必须提供。
<bean id = "accountService" class = "com.ithema.services.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>
此时只关注注入的类型,不关注注入的是什么具体东西。
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;
}
public void saveAccount() {
System.out.println("service中的saveAccount方法执行了...." + name + "," + age + "," + birthday);
}
}
②方式二:使用set方法提供
标签:property
出现的位置:bean标签的内部
标签的属性:
name:用于指定注入时调用的set方法名称。
value:用于给基本类型和String类型的数据
ref:用于指定其他bean类型数据。它指在spring的IOC核心容器中出现过的bean对象
优势:
创建对象时,没有明确的限制,可以直接使用默认构造函数
弊端:
如果某个成员必须有值,则获取对象时,set方法没有执行。
<bean id = "accountService2" class = "com.ithema.services.impl.AccountServiceImpl2">
<property name="name" value="龙弟弟"></property>
<property name="age" value="23"></property>
<property name="birthday" ref="now"></property>
</bean>
public class AccountServiceImpl2 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;
}
public void saveAccount() {
System.out.println("service中的saveAccount方法执行了...." + name + "," + age + "," + birthday);
}
}
****③方式三:使用注解提供(请看第三章)
3.3 注入复杂类型/集合类型
用于给List结构集合注入的标签:
list , array set
用于给Map结构集合注入的标签:
map props
结构相同,标签可以互换
<bean id = "accountService3" class = "com.ithema.services.impl.AccountServiceImpl3">
<property name="myStrs">
<set>
<value>Nicing</value>
<value>Fucking</value>
<value>Amaing</value>
</set>
</property>
<property name="myList">
<array>
<value>Nicing</value>
<value>Fucking</value>
<value>Amaing</value>
</array>
</property>
<property name="mySet">
<list>
<value>Nicing</value>
<value>Fucking</value>
<value>Amaing</value>
</list>
</property>
<property name="myMap">
<props>
<prop key="zhou">zippo</prop>
<prop key="Hao">mu</prop>
</props>
</property>
<property name="myProps">
<map>
<entry key="Nicing" value="FGNB"></entry>
<entry key="Fucking">
<value>angry</value>
</entry>
<entry key="Great" value="Longdd"></entry>
</map>
</property>
</bean>
public class AccountServiceImpl3 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;
}
public void saveAccount() {
System.out.println("myStrs = " + Arrays.toString(myStrs));
System.out.println("myList = " + myList);
System.out.println("myMap = " + myMap);
System.out.println("mySet = " + mySet);
System.out.println("myProps = " + myProps);
}
}
3.4 基于xml的CRUD操作(整个工程)
①pom.xml中导入坐标,此次需要使用dbutils和c3po包。
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>4.0.0.RELEASE</version>
</dependency>
<dependency>
<groupId>commons-dbutils</groupId>
<artifactId>commons-dbutils</artifactId>
<version>1.6</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.43</version>
</dependency>
<dependency>
<groupId>c3p0</groupId>
<artifactId>c3p0</artifactId>
<version>0.9.1.1</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
</dependencies>
②创建业务层接口和实现类,以及持久层接口和实现类
IAccountService
public interface IAccountService {
/**
* 查询所有
* @return
*/
List<Account> findAllAccount();
/**
* 查询一个
* @return
*/
Account findAccountById(Integer accountId);
/**
* 保存操作
* @param account
*/
void saveAccount(Account account);
/**
* 更新
* @param account
*/
void updateAccount(Account account);
/**
* 删除
* @param accountId
*/
void deleteAccount(Integer accountId);
}
public class AccountServiceImpl implements IAccountService {
private IAccountDao accountDao;
public void setAccountDao(IAccountDao accountDao) {
this.accountDao = accountDao;
}
public List<Account> findAllAccount() {
return accountDao.findAllAccount();
}
public Account findAccountById(Integer accountId) {
return accountDao.findAccountById(accountId);
}
public void saveAccount(Account account) {
accountDao.saveAccount(account);
}
public void updateAccount(Account account) {
accountDao.updateAccount(account);
}
public void deleteAccount(Integer accountId) {
accountDao.deleteAccount(accountId);
}
}
IAccountDao
public interface IAccountDao {
/**
* 查询所有
* @return
*/
List<Account> findAllAccount();
/**
* 查询一个
* @return
*/
Account findAccountById(Integer accountId);
/**
* 保存操作
* @param account
*/
void saveAccount(Account account);
/**
* 更新
* @param account
*/
void updateAccount(Account account);
/**
* 删除
* @param accountId
*/
void deleteAccount(Integer accountId);
}
public class AccountDaoImpl implements IAccountDao {
private QueryRunner runner;
public void setRunner(QueryRunner runner) {
this.runner = runner;
}
public List<Account> findAllAccount() {
try {
return runner.query("select * from account", new BeanListHandler<Account>(Account.class));
}catch (Exception e){
throw new RuntimeException(e);
}
}
public Account findAccountById(Integer accountId) {
try {
return runner.query("select * from account where id = ?", new BeanHandler<Account>(Account.class),accountId);
}catch (Exception e){
throw new RuntimeException(e);
}
}
public void saveAccount(Account account) {
try {
runner.update("insert into account(name,money)values(?,?)", account.getName(),account.getMoney());
}catch (Exception e){
throw new RuntimeException(e);
}
}
public void updateAccount(Account account) {
try {
runner.update("update account set name = ?,money = ? where id = ?", account.getName(),account.getMoney(),account.getId());
}catch (Exception e){
throw new RuntimeException(e);
}
}
public void deleteAccount(Integer accountId) {
try {
runner.update("delete from account where id = ?",accountId);
}catch (Exception e){
throw new RuntimeException(e);
}
}
}
③创建Account类
public class Account implements Serializable {
private Integer id;
private String name;
private Float money;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Float getMoney() {
return money;
}
public void setMoney(Float money) {
this.money = money;
}
@Override
public String toString() {
return "Account{" +
"id=" + id +
", name='" + name + '\'' +
", money=" + money +
'}';
}
}
④配置bean.xml文件
在网站的core中搜索xmlns导入约束
<?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">
<!--配置service-->
<bean id="accountService" class="com.ithema.service.impl.AccountServiceImpl">
<!--注入dao对象-->
<property name="accountDao" ref="accountDao"></property>
</bean>
<!--配置dao对象-->
<bean id="accountDao" class="com.ithema.dao.impl.AccountDaoImpl">
<!--注入QueryRunner-->
<property name="runner" ref="runner"></property>
</bean>
<!--配置QueryRunner对象-->
<bean id="runner" class="org.apache.commons.dbutils.QueryRunner" scope="prototype">
<!--注入数据源-->
<constructor-arg name="ds" ref="dataSource"></constructor-arg>
</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://localhost:3306/eesy"></property>
<property name="user" value="root"></property>
<property name="password" value="1234"></property>
</bean>
</beans>
⑤测试方法:
public class AccountServiceTest {
@Test
public void testFindAll() {
//1.获取容器
ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
//2.得到业务层对象
IAccountService as = ac.getBean("accountService",IAccountService.class);
//3.执行方法
List<Account> accounts = as.findAllAccount();
for(Account a : accounts){
System.out.println(a);
}
}
@Test
public void testFindOne() {
//1.获取容器
ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
//2.得到业务层对象
IAccountService as = ac.getBean("accountService",IAccountService.class);
Account account = as.findAccountById(1);
System.out.println(account);
}
@Test
public void testSave() {
Account account = new Account();
account.setName("FGNB");
account.setMoney(123456f);
//1.获取容器
ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
//2.得到业务层对象
IAccountService as = ac.getBean("accountService",IAccountService.class);
as.saveAccount(account);
}
@Test
public void testUpdate() {
//1.获取容器
ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
//2.得到业务层对象
IAccountService as = ac.getBean("accountService",IAccountService.class);
Account account = as.findAccountById(4);
account.setName("LongDD");
as.updateAccount(account);
}
@Test
public void testDelete() {
//1.获取容器
ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
//2.得到业务层对象
IAccountService as = ac.getBean("accountService",IAccountService.class);
as.deleteAccount(4);
}
三. 基于注解的IoC以及IoC的案例
3.1 spring中的ioc常用注解
曾经XML的配置
<bean id = "accountService" class = "com.ithema.services.impl.AccountServiceImpl"
scope = "" init-method = "" destroy-method = "">
<property name = "" value = "" / ref = ""></property>
</bean>
①用于创建对象的:
它的作用和在XML配置文件中编写一个标签实现的功能是一样的
@Component
作用:用于将当前类对象存入spring容器中
属性:
value:用于指定bean的id。当不写时,它的默认值是当前类名。且首字母改小写。
@Controller:一般用于表现层
@Service:一般用在业务层
@Repository:一般用在持久层
以上三个注解的作用和属性与Component是一模一样的。
它们三个是spring框架提供明确的三层使用的注解,使我们对象更加清晰。
②用于注入数据的:
它的作用和在XML配置文件中的bean标签中写一个标签的作用是一样的。
@Autowired
作用:自动按照类型注入。只要容器中有唯一的一个bean对象类型和要注入的变量类型匹配,就可以注入成功。
如果IOC容器中,没有任何bean的类型和要注入的变量类型匹配,则报错.
如果IOC容器中有多个类型匹配时:首先匹配对象类型,如果对象类型不同,会去匹配变量名,如果相同,则注入成功,否则报错。
出现位置:
可以是变量上,也可以是方法上。
细节:
在使用注解注入时,set方法就不是必须的。
@Qualifier:
作用:在按照类中注入的基础上再按照名称注入。它在给类成员注入时不能单独使用,但是在给方法参数注入时可以。
属性:
value:用于指定注入bean的id.
@Resource
作用:直接按照bean的id植入。它可以独立使用。
属性:
name:用于指定bean的id。
以上三个注入都只能注入其他bean类型的数据,而基本类型和String类型无法使用上述注解实现。
另外,集合类型的注入只能通过XML来实现。
@Value
作用:用于注入基本类型和String类型的数据
属性:
value:用于指定数据的值。它可以使用spring中的SpEL(也就是spring的el表达式)
SpEL的写法:${表达式}
③用于改变作用范围的:
它的作用和在bean标签中使用scope属性实现的功能一样。
@Scope:
作用:用于指定bean的属性范围。
属性:
value:指定范围的取值。常用取值:singleton prototype
④和生命周期相关的:(了解)
它的作用和在bean标签中使用init-method和destroy-method的作用是一样的。
@PreDestroy
作用:用于指定销毁方法。
@PostConstruct
作用:用于指定初始化方法。
3.2 工程逐步向注解改进
①加注解
②bean.xml约束修改为xmlns:context的
<?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">
<!--告知spring在创建容器时要扫描的包-->
<context:component-scan base-package="com.ithema"></context:component-scan>
<!--配置QueryRunner对象-->
<bean id="runner" class="org.apache.commons.dbutils.QueryRunner" scope="prototype">
<!--注入数据源-->
<constructor-arg name="ds" ref="dataSource"></constructor-arg>
</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://localhost:3306/eesy"></property>
<property name="user" value="root"></property>
<property name="password" value="1234"></property>
</bean>
</beans>
3.3 进一步改进
此时发现,仍然需要xml文件来导入配置,需要告知spring框架创建容器时,需要扫描的包,数据源和JdbcTemplate的配置。
新注解出现:
@Configuration:
作用:指定当前类是一个配置类
细节:当配置类作为AnnotationConfigApplicationContext对象创建的参数时,此注可以不写。
@ComponentScan:
作用:通过注解指定spring在创建容器时要扫描的包。
属性:
value:它的basePackages的作用是一样的,都是用于指定创建容器时要扫描的包。
使用此注解就等同于在xml中配置了:
@Bean:
作用:
用于将当前方法的返回值作为bean对象存入spring的ioc容器中。
属性:
name:用于指定bean的id。当不写时,默认值是当前方法的名称。
细节:
当我们使用注解配置方法时,如果方法有参数,spring回去容器中查找有没有可用的bean对象。
查找的方式和AutoWired注解的作用是一样的。
@Import
作用:用于导入其他的配置类
属性:
value:用于指定其他配置类的字节码。
当我们使用Import注解之后,有Import注解的类就是父配置类,而导入的都是子配置类。
@PropertySource
作用:用于指定properties文件的位置。
属性:
value:指定文件的名称和路径
关键字:classpath,表示类路径下
如果有包,写包例如:classpath:config/spring/jdbcConfig.properties
3.4 基于注解IOC,纯注解的方式实现
①创建maven工程,并导入坐标。
②创IAccountService接口及其实现类
public interface IAccountService {
/**
* 查询所有
* @return
*/
List<Account> findAllAccount();
/**
* 查询一个
* @return
*/
Account findAccountById(Integer accountId);
/**
* 保存操作
* @param account
*/
void saveAccount(Account account);
/**
* 更新
* @param account
*/
void updateAccount(Account account);
/**
* 删除
* @param accountId
*/
void deleteAccount(Integer accountId);
}
@Service("accountService")
public class AccountServiceImpl implements IAccountService {
@Autowired
private IAccountDao accountDao;
public List<Account> findAllAccount() {
return accountDao.findAllAccount();
}
public Account findAccountById(Integer accountId) {
return accountDao.findAccountById(accountId);
}
public void saveAccount(Account account) {
accountDao.saveAccount(account);
}
public void updateAccount(Account account) {
accountDao.updateAccount(account);
}
public void deleteAccount(Integer accountId) {
accountDao.deleteAccount(accountId);
}
}
③创建IAccountDao接口及其实现类
public interface IAccountDao {
/**
* 查询所有
* @return
*/
List<Account> findAllAccount();
/**
* 查询一个
* @return
*/
Account findAccountById(Integer accountId);
/**
* 保存操作
* @param account
*/
void saveAccount(Account account);
/**
* 更新
* @param account
*/
void updateAccount(Account account);
/**
* 删除
* @param accountId
*/
void deleteAccount(Integer accountId);
}
@Repository("accountDao")
public class AccountDaoImpl implements IAccountDao {
@Autowired
private QueryRunner runner;
public List<Account> findAllAccount() {
try {
return runner.query("select * from account", new BeanListHandler<Account>(Account.class));
}catch (Exception e){
throw new RuntimeException(e);
}
}
public Account findAccountById(Integer accountId) {
try {
return runner.query("select * from account where id = ?", new BeanHandler<Account>(Account.class),accountId);
}catch (Exception e){
throw new RuntimeException(e);
}
}
public void saveAccount(Account account) {
try {
runner.update("insert into account(name,money)values(?,?)", account.getName(),account.getMoney());
}catch (Exception e){
throw new RuntimeException(e);
}
}
public void updateAccount(Account account) {
try {
runner.update("update account set name = ?,money = ? where id = ?", account.getName(),account.getMoney(),account.getId());
}catch (Exception e){
throw new RuntimeException(e);
}
}
public void deleteAccount(Integer accountId) {
try {
runner.update("delete from account where id = ?",accountId);
}catch (Exception e){
throw new RuntimeException(e);
}
}
}
④创建Account类
public class Account implements Serializable {
private Integer id;
private String name;
private Float money;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Float getMoney() {
return money;
}
public void setMoney(Float money) {
this.money = money;
}
@Override
public String toString() {
return "Account{" +
"id=" + id +
", name='" + name + '\'' +
", money=" + money +
'}';
}
}
⑤注入依赖
//@Configuration
@ComponentScan("com.ithema")
@Import(JdbcConfig.class)
@PropertySource("classpath:jdbcConfig.properties")
public class SpringConfiguration {
}
public class JdbcConfig {
@Value("${jdbc.driver}")
private String driver;
@Value("${jdbc.url}")
private String url;
@Value("${jdbc.username}")
private String username;
@Value("${jdbc.password}")
private String password;
/**
* 用于创建一个QueryRunner对象
* @param dataSource
* @return
*/
@Bean(name = "runner")
@Scope("prototype")
public QueryRunner creatQueryRunner(@Qualifier("ds2") DataSource dataSource){
return new QueryRunner(dataSource);
}
//创建数据源对象
@Bean(name = "ds2")
public DataSource creatDataSource(){
try {
ComboPooledDataSource ds = new ComboPooledDataSource();
ds.setDriverClass(driver);
ds.setJdbcUrl(url);
ds.setUser(username);
ds.setPassword(password);
return ds;
}catch (Exception e){
throw new RuntimeException(e);
}
}
@Bean(name = "ds1")
public DataSource creatDataSource1(){
try {
ComboPooledDataSource ds = new ComboPooledDataSource();
ds.setDriverClass(driver);
ds.setJdbcUrl("jdbc:mysql://localhost:3306/eesy02");
ds.setUser(username);
ds.setPassword(password);
return ds;
}catch (Exception e){
throw new RuntimeException(e);
}
}
}
⑥测试(放在与Junit整合处说明)
3.5 spring和Junit整合
在进行测试时,发现有大量重复的代码。其中通过之前的,通过@Before注解来实现也可以。此处通过注解说明。
spring整合juint的配置:
1.导入spring整合Junit的jar包
2.使用Junit提供的一个注解把原有的main方法替换了,替换成spring提供的
@Runwith
3.告知spring的运行器,spring的IOC是基于xml,还是注解,并说明位置
@ContextConfiguration:
locations:指定xml文件的位置,加上classpath关键字,表示在类路径下
classes:指定注解类所在位置
当使用spring5版本时,要求Junit的jar包必须是4.12及以上。
①pom.xml导入spring-test坐标
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>4.0.0.RELEASE</version>
</dependency>
②测试类
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = SpringConfiguration.class)
public class AccountServiceTest {
@Autowired
private IAccountService as = null;
@Test
public void testFindAll() {
//3.执行方法
List<Account> accounts = as.findAllAccount();
for(Account a : accounts){
System.out.println(a);
}
}
@Test
public void testFindOne() {
Account account = as.findAccountById(1);
System.out.println(account);
}
@Test
public void testSave() {
Account account = new Account();
account.setName("LongDD");
account.setMoney(123456f);
as.saveAccount(account);
}
@Test
public void testUpdate() {
Account account = as.findAccountById(4);
account.setName("LongDD");
as.updateAccount(account);
}
@Test
public void testDelete() {
as.deleteAccount(4);
}
}
四. Spring中的AOP
4.1 AOP的相关概念
AOP(Aspect Oriented Programming)的缩写,意为:面向切面编程。通过预编译方式和运行器动态代理实现程序功能的统一维护的一种技术。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高开发的效率。
①AOP的作用和优势
作用:
在程序运行期间,不修改源码对已有方法进行增强。
优势:
减少重复代码
提高开发效率
维护方便
②AOP的实现方式
使用动态代理技术
4.2 AOP 的具体应用
之前的案例,我们做的增删改例子。其事务被自动控制了。通过此方式控制事务,如果每次都执行一条SQL语句,没有问题,如果业务方法一次执行多条SQL语句,这种方式就无法实现功能了。
①问题
public void transfer(String sourceName, String targetName, Float money) {
System.out.println("tranfer.............");
Account source = accountDao.findAccountByName(sourceName);
//2.2根据名称查询转入账户
Account target = accountDao.findAccountByName(targetName);
//2.3转出账户减钱
source.setMoney(source.getMoney() - money);
//2.4转入账户加钱
target.setMoney(target.getMoney() + money);
//2.5更新转出账户
accountDao.updateAccount(source);
int i = 1 / 0;
//2.6更新转入账户
accountDao.updateAccount(target);
}
当执行时,由于异常,转账失败,但由于每次执行持久层方法都是独立事务,导致无法实现事务控制。转出账户减少了,转入账户没有增加。
②问题解决
public void transfer(String sourceName, String targetName, Float money) {
try{
//1.开启事务
txManager.beginTransaction();
//2.执行操作
//2.1根据名称查询转出账户
Account source = accountDao.findAccountByName(sourceName);
//2.2根据名称查询转入账户
Account target = accountDao.findAccountByName(targetName);
//2.3转出账户减钱
source.setMoney(source.getMoney() - money);
//2.4转入账户加钱
target.setMoney(target.getMoney() + money);
//2.5更新转出账户
accountDao.updateAccount(source);
int i = 1 / 0;
//2.6更新转入账户
accountDao.updateAccount(target);
//3.提交事务
txManager.commit();
//4.返回结果
}catch(Exception e){
//5.回滚操作
txManager.rollback();
e.printStackTrace();
}finally {
//释放连接
txManager.release();
}
}
③新的问题
虽然实现了事务控制,但是业务层的方法变得臃肿了,出现了太多的重复代码,并且业务层方法和事务控制耦合了。
4.3 动态代理回顾
4.3.1 动态代理常用的两种方式
①基于接口的动态代理
涉及的类:Proxy
如何创建代理对象:
使用Proxy类中的newProxyInstance方法
创建代理对象的要求:
被代理类最少实现一个接口,如果没有则不能使用
newProxyInstance方法的参数:
ClassLoader:类加载器
用于加载代理对象字节码。和被代理对象使用相同的类加载器。(固定写法)
Class[]:字节码数组。
用于让代理对象和被代理对象有相同方法。(固定写法)
InvovationHandler:用于提供增强的代码
让我们写如何代理,一般都是些一个该接口的实现类,通常情况下都是匿名内部类,但不是必须的。
此接口的实现类,都是谁用谁写。
public static void main(String[] args) {
final Producer producer = new Producer();
IProducer proxyProducer = (IProducer) Proxy.newProxyInstance(producer.getClass().getClassLoader(), producer.getClass().getInterfaces(), new InvocationHandler() {
/**
* 作用:执行被代理对象的任何接口方法都会经过该方法。
* 方法参数的含义:
* @param proxy :代理对象的引用
* @param method : 当前执行的方法
* @param args:当前执行方法所需的参数
* @return : 和被代理对象方法有相同的返回值
* @throws Throwable
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//提供增强的代码
Object returnValue = null;
//1.获取方法执行的参数
Float money = (Float)args[0];
//2.判断当前方法是不是销售
if("saleProduct".equals(method.getName())){
returnValue = method.invoke(producer,money * 0.8f);
}
return returnValue;
}
});
proxyProducer.saleProduct(10000f);
}
②基于子类的动态代理
涉及的类:Enhancer
第三方库:cglib库
如何创建代理对象:
使用Enhancer类中的create方法
创建代理对象的要求:
被代理类不能是最终类.
create方法的参数:
Class:字节码
用于指定被代理对象的字节码。
callback: 用于增强的代码
让我们写如何代理,一般都是些一个该接口的实现类,通常情况下都是匿名内部类,但不是必须的。
此接口的实现类,都是谁用谁写。
一般写的都是该接口的子接口实现类,MethodInterceptor:
public static void main(String[] args) {
final Producer producer = new Producer();
Producer cglibProducer = (Producer) Enhancer.create(producer.getClass(), new MethodInterceptor() {
/**
* 执行被代理对象的任何方法,都会经过该方法
* @param proxy
* @param method
* @param args
* 以上三个参数和基于接口的动态代理中invoke方法的参数是一样的。
* @param methodProxy :当前执行方法的代理对象
* @return
* @throws Throwable
*/
@Override
public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
//提供增强的代码
Object returnValue = null;
//1.获取方法执行的参数
Float money = (Float)args[0];
//2.判断当前方法是不是销售
if("saleProduct".equals(method.getName())){
returnValue = method.invoke(producer,money * 0.8f);
}
return returnValue;
}
});
cglibProducer.saleProduct(10000f);
}
4.4 Spring中的AOP
①AOP的相关术语
joinpoint(连接点):
指那些被拦截到的点。这些点指的是放法,因为spring只支持方法类型的连接点。
pointcut(切入点):
指要对哪些joinpoint进行拦截。
Advice(通知/增强):
指拦截到jointpoint之后所要做的事情就是通知。
通知的类型:前置通知,后置通知,异常通知,最终通知,环绕通知。
Introduction(引介):
引介是一种特殊的通知在不修改类代码的前提下,
Introduction可以在运行期为类动态地添加一些方法或Field。
Target(目标对象):
代理的目标对象。
Weaving(织入):
是指把增强应用到目标对象来创建新的代理对象的过程。 spring采用动态代理织入,而AspectJ采用编译期织入和类装载期织入。
Proxy(代理):
一个类被AOP织入增强后,就产生一个结果代理类。
Aspect(切面): 是切入点和通知(引介)的结合。
4.4.1 Spring中XML的AOP的细节
1.把通知Bean也交给spring管理
2.使用aop:config标签表明开始AOP的配置
3.使用aop:aspect标签标示配置切面
属性:
id:给切面提供一个唯一标识
ref属性:给指定通知类bean的id。
4.在aop:aspect标签内部使用对应标签来配置通知的类型。
现在示例是让pringLog方法在切入点方法执行之前执行,所以是前置通知。
aop:before:配置前置通知
属性:
method:用于指定Logger类中哪个方法是前置通知。
pointcut:用于指定切入点表达式,该表达式的含义指的是对业务层中哪些方法增强。
切入点表达式写法:
关键字:execution(表达式)
表达式:
访问修饰符 返回值 包名.包名.包名...类名.方法名(参数列表)
标准写法:
public void com.ithema.service.impl.AccountServiceImpl.saveAccount()
访问修饰符可以省略:
void com.ithema.service.impl.AccountServiceImpl.saveAccount()
返回值可以使用通配符
* com.ithema.service.impl.AccountServiceImpl.saveAccount()
包名可以使用通配符表示任意包,但是有几级包,就需要写几个。
* *.*.*.*.AccountServiceImpl.saveAccount();
包名可以使用 .. 表示当前包及其子包
* *..AccountServiceImpl.saveAccount();
类名和方法名都可使用*来实现统配
* *..*.*();
参数列表:
可以直接写数据类型:
基本类型直接写名称 int
引用类型写包名.类名的方式 java.lang.String
可以使用通配符表示任意类型,但是必须有参数
可以使用..表示有无参数均可,有参数可以是任意类型
全通配写法:
* *..*.*(..)
实际开发中切入点表达式的通常写法:
且切到业务层实现类的所有方法
* com.ithema.service.impl.*.*(..)
4.4.2 基于XML的AOP配置
①工具类里面包含公共的代码
public class Logger {
/**
*前置通知
*/
public void beforePrintLog(){
System.out.println("前置通知:Logger类中的beforePrintLog方法开始记录日志了...");
}
/**
*后置通知
*/
public void afterReturningPrintLog(){
System.out.println("后置通知:Logger类中的afterReturningPrintLog方法开始记录日志了...");
}
/**
*异常通知
*/
public void afterThrowingPrintLog(){
System.out.println("异常通知:Logger类中的afterThrowingPrintLog方法开始记录日志了...");
}
/**
*最终通知
*/
public void afterPrintLog(){
System.out.println("最终通知:Logger类中的afterPrintLog方法开始记录日志了...");
}
/**
* 环绕通知
*
* 问题:
* 当我们配置了环绕通知之后,切入点方法没有执行,而通知方法执行了。
* 分析:
* 通过对比动态代理中的环绕通知代码,发现动态代理中的环绕通知有明确的切入点方法调用,而我们中的代码中没有。
* 解决:
* spring框架提供了一个接口:ProceedingJoinPoint。该接口有一个方法proceed(),此方法就相当于明确调用切入点方法。
* 此接口可以作为环绕通知的方法参数,在程序执行时,spring框架会为我们提供该接口的实现类供我们使用。
* spring中的环绕通知:
* 它是spring框架为我们提供的一种可以在代码中手动控制增强方法何时执行的方式。
*
*/
public Object aroundPrintLog(ProceedingJoinPoint pjp){
Object returnValue = null;
try {
Object[] args = pjp.getArgs();
System.out.println("Logger类中的aroundPrintLog方法开始记录日志了...前置");
returnValue = pjp.proceed(args);//明确调用业务层方法(切入点方法)
System.out.println("Logger类中的aroundPrintLog方法开始记录日志了...后置");
return returnValue;
}catch (Throwable t){
System.out.println("Logger类中的aroundPrintLog方法开始记录日志了...异常");
throw new RuntimeException(t);
}finally {
System.out.println("Logger类中的aroundPrintLog方法开始记录日志了...最终");
}
}
}
②配置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"
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">
<!--配置spring的IOC,把service对象配置进来-->
<bean id = "accountService" class = "com.ithema.service.impl.AccountServiceImpl"></bean>
<bean id="logger" class="com.ithema.utils.Logger"></bean>
<!--配置AOP-->
<aop:config>
<!--配置切入点表达式:id属性用于指定表达式的唯一标识, expression:用于指定表达式内容
此标签写在aop:aspect标签内部只能当前切面使用。
它还可以写在aop:aspect外面,此时就变成了所有切面可用。必须写在切面之前
-->
<aop:pointcut id="pt1" expression="execution(* com.ithema.service.impl.*.*(..))"/>
<!--配置切面-->
<aop:aspect id="logAdvice" ref="logger">
<!--配置前置通知:在切入点方法执行之前执行
<aop:before method="beforePrintLog" pointcut-ref="pt1"></aop:before>-->
<!--配置后置通知:在切入点方法正常执行之后执行。它和异常通知永远只能执行一个。
<aop:after-returning method="afterReturningPrintLog" pointcut-ref="pt1"></aop:after-returning>-->
<!--配置异常通知:在切入点方法执行产生异常之后执行。它和后置通知永远只能执行一个
<aop:after-throwing method="afterThrowingPrintLog" pointcut-ref="pt1"></aop:after-throwing>-->
<!--配置最终通知:无论切入点方法是否正常执行,它都会在其后面执行
<aop:after method="afterPrintLog" pointcut-ref="pt1" ></aop:after>-->
<!--配置环绕通知 详细的注释请看log类-->
<aop:around method="aroundPrintLog" pointcut-ref="pt1"></aop:around>
</aop:aspect>
</aop:config>
</beans>
4.4.3基于注解的AOP配置
①加入注解@Aspect,其表示当前类是一个切面类。
@Component("logger")
@Aspect //表示当前类是一个切面类
public class Logger {
//@Pointcut("execution(* com.ithema.service.impl.*.*(..))")
private void pt1(){
}
/**
*前置通知
*/
@Before("execution(* com.ithema.service.impl.*.*(..))")
public void beforePrintLog(){
System.out.println("前置通知:Logger类中的beforePrintLog方法开始记录日志了...");
}
/**
*后置通知
*/
//@AfterReturning("execution(* com.ithema.service.impl.*.*(..))")
public void afterReturningPrintLog(){
System.out.println("后置通知:Logger类中的afterReturningPrintLog方法开始记录日志了...");
}
/**
*异常通知
*/
//@AfterThrowing("execution(* com.ithema.service.impl.*.*(..))")
public void afterThrowingPrintLog(){
System.out.println("异常通知:Logger类中的afterThrowingPrintLog方法开始记录日志了...");
}
/**
*最终通知
*/
//@After("execution(* com.ithema.service.impl.*.*(..))")
public void afterPrintLog(){
System.out.println("最终通知:Logger类中的afterPrintLog方法开始记录日志了...");
}
/**
* 环绕通知
*
* 问题:
* 当我们配置了环绕通知之后,切入点方法没有执行,而通知方法执行了。
* 分析:
* 通过对比动态代理中的环绕通知代码,发现动态代理中的环绕通知有明确的切入点方法调用,而我们中的代码中没有。
* 解决:
* spring框架提供了一个接口:ProceedingJoinPoint。该接口有一个方法proceed(),此方法就相当于明确调用切入点方法。
* 此接口可以作为环绕通知的方法参数,在程序执行时,spring框架会为我们提供该接口的实现类供我们使用。
* spring中的环绕通知:
* 它是spring框架为我们提供的一种可以在代码中手动控制增强方法何时执行的方式。
*
*/
@Around("execution(* com.ithema.service.impl.*.*(..))")
public Object aroundPrintLog(ProceedingJoinPoint pjp){
Object returnValue = null;
try {
Object[] args = pjp.getArgs();
System.out.println("Logger类中的aroundPrintLog方法开始记录日志了...前置");
returnValue = pjp.proceed(args);//明确调用业务层方法(切入点方法)
System.out.println("Logger类中的aroundPrintLog方法开始记录日志了...后置");
return returnValue;
}catch (Throwable t){
System.out.println("Logger类中的aroundPrintLog方法开始记录日志了...异常");
throw new RuntimeException(t);
}finally {
System.out.println("Logger类中的aroundPrintLog方法开始记录日志了...最终");
}
}
}
②bean.xml配置
导入约束,并配置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"
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">
<!--配置spring创建容器时要扫描的包-->
<context:component-scan base-package="com.ithema"></context:component-scan>
<!--配置spring开启注解aop的支持-->
<aop:aspectj-autoproxy proxy-target-class="true"></aop:aspectj-autoproxy>
</beans>
五. Spring中的事务控制
5.1 spring中的JdbcTemplate
JdbcTemplate是spring框架中提供的一个对象,是对原始Jdbc API对象的简单封装。类似于dbutils。
相关jar包:spring-jdbc-5.0.2.RELEASE.jar,还需要导入spring-tx-5.0.2.RELEASE.jar(它是和事务相关的)
①JbcTemplate对象的创建
参考源码:
除了默认构造函数之外,都需要提供一个数据源。既然有set方法,可以通过依赖注入来配置这些对象。
5.1.1 让dao继承JdbcDaoSupport
JdbcDaoSupport是spring框架提供的一个类,该类中定义了一个JdbcTemplate对象,可以直接获取使用,但是创建该对象,需要提供一个数据源。
public class AccountDaoImpl extends JdbcDaoSupport implements IAccountDao {
@Override
public Account findAccountById(Integer accountId) {
List<Account> accounts = getJdbcTemplate().query("select * from account where id = ?", new BeanPropertyRowMapper<Account>(Account.class),accountId);
return accounts.isEmpty() ? null : accounts.get(0);
}
@Override
public Account findAccountByName(String accountName) {
List<Account> accounts = getJdbcTemplate().query("select * from account where name = ?", new BeanPropertyRowMapper<Account>(Account.class),accountName);
if(accounts.isEmpty()){
return null;
}
if(accounts.size() > 1){
throw new RuntimeException("结果集不唯一");
}
return accounts.get(0);
}
@Override
public void updateAccount(Account account) {
getJdbcTemplate().update("update account set name = ?, money = ? where id = ?", account.getName(),account.getMoney(),account.getId());
}
}
让dao继承JbcDaoSupport的方式,只能用于基于xml的方式,注解用不了。如果是自定义JdbcDaoSupport类,在该类中实现各种方法,两种方式都可以使用。
5.2 基于XML 的事务控制
①事务控制的API
②基于XML的事务控制
配置spring中基于xml的声明式事务控制配置步骤
1.配置事务管理器
2.配置事务的通知
此时需要导入事务的约束,在data access中搜索xmlns:tx名称空间和约束,同时也需要aop的
使用tx:advice标签配置事务通知
属性:
id:给事务通知起一个唯一标识
transaction-manager:给事务通知提供一个事务管理器引用
3.配置aop中的通用切入点表达式
4.建立事务通知和切入点表达式的对应关系
5.配置事务的属性
是在事务的通知tx:advice标签的内部
①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"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
<!-- 配置业务层-->
<bean id="accountService" class="com.ithema.service.impl.AccountServiceImpl">
<property name="accountDao" ref="accountDao"></property>
</bean>
<!--配置账户的持久层-->
<bean id="accountDao" class="com.ithema.dao.impl.AccountDaoImpl">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 配置数据源dataSource-->
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
<property name="url" value="jdbc:mysql://localhost:3306/eesy"></property>
<property name="username" value="root"></property>
<property name="password" value="1234"></property>
</bean>
<!-- 配置事务管理器-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 配置事务的通知-->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<!--配置事务的属性
isolation:用于指定事务的隔离级别。默认值是DEFAULT,表示数据库的默认隔离级别。
propagation:指定事务的传播行为。默认值是REQUIRED,表示一定会有事务,增删改的选择。查询方法可以选择SUPPORTS。
read-only:用于指定事务是否只读。只有查询方法才能设置为true。默认值是false,表示读写。
timeout:用于指定事务的超时时间,默认值为-1,表示永不超时。如果指定了数值,以秒为单位。
rollback-for:用于指定一个异常,当产生该异常时,事务回滚,产生其他异常时,事务不回滚。没有默认值,表示任何异常都回滚。
no-rollback-for:用于指定一个异常,当产生该异常时,事务不回滚。产生其他异常时事务回滚。没有默认值,表示任何异常都回滚。
-->
<tx:attributes>
<tx:method name="*" propagation="REQUIRED" read-only="false"/>
<tx:method name="find*" propagation="REQUIRED" read-only="true"></tx:method> <!--其优先级高于上面的-->
</tx:attributes>
</tx:advice>
<!-- 配置aop-->
<aop:config>
<!-- 配置切入点表达式-->
<aop:pointcut id="pt1" expression="execution(* com.ithema.service.impl.*.*(..))"/>
<!-- 建立切入点表达式和事务通知的对应关系-->
<aop:advisor advice-ref="txAdvice" pointcut-ref="pt1"></aop:advisor>
</aop:config>
</beans>
②创建Account类,业务层,持久层接口以及实现类。
public class Account implements Serializable{
private Integer id;
private String name;
private Float money;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Float getMoney() {
return money;
}
public void setMoney(Float money) {
this.money = money;
}
@Override
public String toString() {
return "Account{" +
"id=" + id +
", name='" + name + '\'' +
", money=" + money +
'}';
}
}
业务层:
public class AccountServiceImpl implements IAccountService {
private IAccountDao accountDao;
public void setAccountDao(IAccountDao accountDao) {
this.accountDao = accountDao;
}
public Account findAccountById(Integer accountId) {
return accountDao.findAccountById(accountId);
}
public void transfer(String sourceName, String targetName, Float money) {
System.out.println("tranfer.............");
Account source = accountDao.findAccountByName(sourceName);
//2.2根据名称查询转入账户
Account target = accountDao.findAccountByName(targetName);
//2.3转出账户减钱
source.setMoney(source.getMoney() - money);
//2.4转入账户加钱
target.setMoney(target.getMoney() + money);
//2.5更新转出账户
accountDao.updateAccount(source);
int i = 1 / 0;
//2.6更新转入账户
accountDao.updateAccount(target);
}
}
public interface IAccountService {
/**
* 根据id查询账户信息
* @param accountId
* @return
*/
Account findAccountById(Integer accountId);
/**
* 转账
* @param sourceName 转出账户名称
* @param targetName 转入账户名称
* @param money 转账金额
*/
void transfer(String sourceName,String targetName,Float money);
}
持久层:
public class AccountDaoImpl extends JdbcDaoSupport implements IAccountDao {
public Account findAccountById(Integer accountId) {
List<Account> accounts = getJdbcTemplate().query("select * from account where id = ?", new BeanPropertyRowMapper<Account>(Account.class),accountId);
return accounts.isEmpty() ? null : accounts.get(0);
}
public Account findAccountByName(String accountName) {
List<Account> accounts = getJdbcTemplate().query("select * from account where name = ?", new BeanPropertyRowMapper<Account>(Account.class),accountName);
if(accounts.isEmpty()){
return null;
}
if(accounts.size() > 1){
throw new RuntimeException("结果集不唯一");
}
return accounts.get(0);
}
public void updateAccount(Account account) {
getJdbcTemplate().update("update account set name = ?, money = ? where id = ?", account.getName(),account.getMoney(),account.getId());
}
}
public interface IAccountDao {
/**
* 根据id查询账户
* @param accountId
* @return
*/
Account findAccountById(Integer accountId);
/**
* 根据名称查询账户
* @param accountName
* @return
*/
Account findAccountByName(String accountName);
/**
* 更新账户
* @param account
*/
void updateAccount(Account account);
}
③测试
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:bean.xml")
public class AccountServiceTest {
@Autowired
private IAccountService as;
@Test
public void testTransfer(){
as.transfer("aaa","bbb",100f);
}
}
5.3 基于注解的事务控制
①工程如上,创建Account类,业务层,持久层接口及其实现类。
②创建config包,其中SpringConfiguration来配置spring相关的配置,JcConfig类下放和数据库相关的配置,TransactionConfig类下放事务控制相关的配置。
@Configuration
@ComponentScan("com.ithema")
@Import({
JdbcConfig.class,TransactionConfig.class})
@PropertySource("jdbcConfig.properties")
@EnableTransactionManagement //表示开启事务管理器
public class SpringConfiguration {
}
JdbcConfig类:
public class JdbcConfig {
@Value("${jdbc.driver}")
private String driver;
@Value("${jdbc.url}")
private String url;
@Value("${jdbc.username}")
private String username;
@Value("${jdbc.password}")
private String password;
/**
* 创建JdbcTemplate对象
* @param dataSource
* @return
*/
@Bean(name = "jdbcTemplate")
public JdbcTemplate creatJdbcTemplate(DataSource dataSource){
return new JdbcTemplate(dataSource);
}
/**
* 创建一个数据源对象
* @return
*/
@Bean(name = "dataSource")
public DataSource createDateSource(){
DriverManagerDataSource ds = new DriverManagerDataSource();
ds.setDriverClassName(driver);
ds.setUrl(url);
ds.setUsername(username);
ds.setPassword(password);
return ds;
}
}
TansactionConfig类:
public class TransactionConfig {
/**
* 用于创建事务管理器对象
* @param dataSource
* @return
*/
@Bean(name = "transactionManager")
public PlatformTransactionManager createTransactionManager(DataSource dataSource){
return new DataSourceTransactionManager(dataSource);
}
}
③测试
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = SpringConfiguration.class)
public class AccountServiceTest {
@Autowired
private IAccountService as;
@Test
public void testTransfer(){
as.transfer("aaa","bbb",100f);
}
}