一、Spring
1.1、Spring 概述
Spring 是什么
Spring 是分层的 Java SE/EE 应用 full-stack(全栈式) 轻量级开源框架,以 IoC(Inverse Of Control:
反转控制)和 AOP(Aspect Oriented Programming:面向切面编程)为内核,提供了展现层 Spring
MVC 和持久层 Spring JDBC 以及业务层事务管理等众多的企业级应用技术,还能整合开源世界众多
著名的第三方框架和类库,逐渐成为使用最多的 Java EE 企业应用开源框架。
Spring 的优势
1.方便解耦,简化开发
通过 Spring 提供的 IoC 容器,可以将对象间的依赖关系交由 Spring 进行控制,避免硬编码所造
成的过度程序耦合。用户也不必再为单例模式类、属性文件解析等这些很底层的需求编写代码,可
以更专注于上层的应用。
2.AOP 编程的支持
通过 Spring 的 AOP 功能,方便进行面向切面的编程,许多不容易用传统 OOP 实现的功能可以
通过 AOP 轻松应付。
3.声明式事务的支持
可以将我们从单调烦闷的事务管理代码中解脱出来,通过声明式方式灵活的进行事务的管理,
提高开发效率和质量。
4.方便程序的测试
可以用非容器依赖的编程方式进行几乎所有的测试工作,测试不再是昂贵的操作,而是随手可
做的事情。
5.方便集成各种优秀框架
Spring 可以降低各种框架的使用难度,提供了对各种优秀框架(Struts、Hibernate、Hessian、Quartz
等)的直接支持。
6.降低 JavaEE API 的使用难度
Spring 对 JavaEE API(如 JDBC、JavaMail、远程调用等)进行了薄薄的封装层,使这些 API 的
使用难度大为降低。
7.Java 源码是经典学习范例
Spring 的源代码设计精妙、结构清晰、匠心独用,处处体现着大师对 Java 设计模式灵活运用以
及对 Java 技术的高深造诣。它的源代码无意是 Java 技术的最佳实践的范例。
1.2、IoC 的概念和作用
程序的耦合和解耦
什么是程序的耦合
耦合性(Coupling),也叫耦合度,是对模块间关联程度的度量。耦合的强弱取决于模块间接口的复杂性、调
用模块的方式以及通过界面传送数据的多少。模块间的耦合度是指模块之间的依赖关系,包括控制关系、调用关
系、数据传递关系。模块间联系越多,其耦合性越强,同时表明其独立性越差( 降低耦合性,可以提高其独立
性)。耦合性存在于各个领域,而非软件设计中独有的,但是我们只讨论软件工程中的耦合。
在软件工程中,耦合指的就是就是对象之间的依赖性。对象之间的耦合越高,维护成本越高。因此对象的设计
应使类和构件之间的耦合最小。软件设计中通常用耦合度和内聚度作为衡量模块独立程度的标准。划分模块的一个
准则就是高内聚低耦合。
它有如下分类:
(1)内容耦合。当一个模块直接修改或操作另一个模块的数据时,或一个模块不通过正常入口而转入另
一个模块时,这样的耦合被称为内容耦合。内容耦合是最高程度的耦合,应该避免使用之。
(2)公共耦合。两个或两个以上的模块共同引用一个全局数据项,这种耦合被称为公共耦合。在具有大
量公共耦合的结构中,确定究竟是哪个模块给全局变量赋了一个特定的值是十分困难的。
(3) 外部耦合 。一组模块都访问同一全局简单变量而不是同一全局数据结构,而且不是通过参数表传
递该全局变量的信息,则称之为外部耦合。
(4) 控制耦合 。一个模块通过接口向另一个模块传递一个控制信号,接受信号的模块根据信号值而进
行适当的动作,这种耦合被称为控制耦合。
(5)标记耦合 。若一个模块 A 通过接口向两个模块 B 和 C 传递一个公共参数,那么称模块 B 和 C 之间
存在一个标记耦合。
(6) 数据耦合。模块之间通过参数来传递数据,那么被称为数据耦合。数据耦合是最低的一种耦合形
式,系统中一般都存在这种类型的耦合,因为为了完成一些有意义的功能,往往需要将某些模块的输出数据作为另
一些模块的输入数据。
(7) 非直接耦合 。两个模块之间没有直接关系,它们之间的联系完全是通过主模块的控制和调用来实
现的。
总结:
耦合是影响软件复杂程度和设计质量的一个重要因素,在设计上我们应采用以下原则:如果模块间必须
存在耦合,就尽量使用数据耦合,少用控制耦合,限制公共耦合的范围,尽量避免使用内容耦合。
内聚与耦合
内聚标志一个模块内各个元素彼此结合的紧密程度,它是信息隐蔽和局部化概念的自然扩展。内聚是从
功能角度来度量模块内的联系,一个好的内聚模块应当恰好做一件事。它描述的是模块内的功能联系。耦合是软件
结构中各模块之间相互连接的一种度量,耦合强弱取决于模块间接口的复杂程度、进入或访问一个模块的点以及通
过接口的数据。 程序讲究的是低耦合,高内聚。就是同一个模块内的各个元素之间要高度紧密,但是各个模块之
间的相互依存度却要不那么紧密。
内聚和耦合是密切相关的,同其他模块存在高耦合的模块意味着低内聚,而高内聚的模块意味着该模块同其他
模块之间是低耦合。在进行软件设计时,应力争做到高内聚,低耦合。
/*
程序的耦合
早期我们的JDBC操作,注册驱动时,
我们为什么不使用DriverManager的register方法,而是采用Class.forName的方式?
*/
DriverManager.deregisterDriver(new com.mysql.cj.jdbc.Driver());
Connection c = DriverManager.getConnection("jdbc:mysql:///user?characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai&rewriteBatchedStatements=true", "root", "admin");
System.err.println(c);
/*
原因就是:我们的类依赖了数据库的具体驱动类(MySQL),
如果这时候更换了数据库品牌(比如Oracle),需要修改源码来重新数据库驱动。这显然不是我们想要的
*/
解决程序耦合的思路
//当时我们讲解jdbc时,是通过反射来注册驱动的,代码如下:
Class.forName("com.mysql.jdbc.Driver"); //此处只是一个字符串
/*
此时的好处是,我们的类中不再依赖具体的驱动类,此时就算删除 mysql 的驱动 jar 包,依然可以编译(运
行就不要想了,没有驱动不可能运行成功的)。
同时,也产生了一个新的问题,mysql 驱动的全限定类名字符串是在 java 类中写死的,一旦要改还是要修改
源码。
解决这个问题也很简单,使用配置文件配置。
*/
曾经代码中的问题
模拟保存账户的控制层
package cn.tedu.controller;
import cn.tedu.service.AccountService;
import cn.tedu.service.impl.AccountServiceImpl;
/**
* 模拟保存账户的控制层
*/
public class AccountController {
public static void main(String[] args) {
AccountService as = new AccountServiceImpl();
as.saveAccount();
}
}
模拟保存账户的业务层接口
package cn.tedu.service;
/**
* 模拟保存账户的业务层接口
*/
public interface AccountService {
void saveAccount();
}
模拟保存账户的业务层实现类
package cn.tedu.service.impl;
import cn.tedu.dao.AccountDao;
import cn.tedu.dao.impl.AccountDaoImpl;
import cn.tedu.service.AccountService;
/**
* 模拟保存账户的业务层实现类
*/
public class AccountServiceImpl implements AccountService {
private AccountDao accountDao = new AccountDaoImpl();
public void saveAccount(){
accountDao.saveAccount();
}
}
模拟保存账户的持久层接口
package cn.tedu.dao;
/**
* 模拟保存账户的持久层接口
*/
public interface AccountDao {
void saveAccount();
}
模拟保存账户的持久层实现类
package cn.tedu.dao.impl;
import cn.tedu.dao.AccountDao;
/**
* 模拟保存账户的持久层实现类
*/
public class AccountDaoImpl implements AccountDao {
@Override
public void saveAccount() {
System.out.println("保存了账户");
}
}
上面代码中出现的问题与之前的jdbc操作类似,都过于依赖与new 出来的实现类,导致程序间的耦合度过高,解决的方式就是通过工厂模式解耦。
package cn.tedu.factory;
import java.io.IOException;
import java.util.Properties;
/**
* 是一个创建Bean对象的工厂类
* Bean :在计算机英语中,是可重用组件的意思
* javaBean :用java语言编写的可重用组件
* 他就是创建service和dao对象的
* 第一步:通过配置文件配置service和dao对象
* 配置的内容:用于取出对象唯一标志 = 对象的全限定类名 (key=value)
* 第二步:通过读取配置文件中的配置,反射创建对象
*/
public class BeanFactory {
//定义一个Properties对象
private static Properties props;
//使用静态代码块为Properties对象赋值
static {
try {
//实例化对象
props = new Properties();
//获取properties文件的流对象
InputStream in = BeanFactory.class.getClassLoader().getResourceAsStream("bean.properties");
props.load(in);
}catch(Exception e){
throw new ExceptionInInitializerError("初始化properties失败!");
}
}
/**
* 根据Bean的名称获取bean对象
* @param beanName
* @return
*/
public static Object getBean(String beanName){
Object bean = null;
try {
String beanPath = props.getProperty(beanName);
// System.out.println(beanPath);
bean = Class.forName(beanPath).newInstance();//每次都会调用默认构造函数创建对象
}catch (Exception e){
e.printStackTrace();
}
return bean;
}
}
使用BeanFactory创建对象实现解耦
修改业务层实现类代码
package cn.tedu.service.impl;
import cn.tedu.dao.AccountDao;
import cn.tedu.dao.impl.AccountDaoImpl;
import cn.tedu.factory.BeanFactory;
import cn.tedu.service.AccountService;
/**
* 模拟保存账户的业务层实现类
*/
public class AccountServiceImpl implements AccountService {
private AccountDao accountDao = (AccountDao)BeanFactory.getBean("accountDao");
public void saveAccount(){
accountDao.saveAccount();
}
}
修改控制层代码
package cn.tedu.controller;
import cn.tedu.factory.BeanFactory;
import cn.tedu.service.AccountService;
import cn.tedu.service.impl.AccountServiceImpl;
/**
* 模拟保存账户的控制层
*/
public class AccountController {
public static void main(String[] args) {
AccountService as = (AccountService) BeanFactory.getBean("accountService");
as.saveAccount();
}
}
目前工厂模式中存在的问题
package cn.tedu.controller;
import cn.tedu.factory.BeanFactory;
import cn.tedu.service.AccountService;
import cn.tedu.service.impl.AccountServiceImpl;
/**
* 模拟保存账户的控制层
*/
public class AccountController {
public static void main(String[] args) {
for (int i = 0; i < 5; i++) {
AccountService as = (AccountService) BeanFactory.getBean("accountService");
System.out.println(as);
}
}
}
//执行结果
//cn.tedu.service.impl.AccountServiceImpl@7f31245a
//cn.tedu.service.impl.AccountServiceImpl@6d6f6e28
//cn.tedu.service.impl.AccountServiceImpl@135fbaa4
//cn.tedu.service.impl.AccountServiceImpl@45ee12a7
//cn.tedu.service.impl.AccountServiceImpl@330bedb4
上述代码中创建的对象默认是多例的,而往往我们的业务需求,只需要对象调用其方法,所以多例对象的执行效率就没有单例对象要高,而BeanFactory中构建bean对象的方法是使用newInstance(),该方法每次都会调用默认构造函数创建对象,所以我们要对getBean()方法进行改造。改造的方式是newInstance()方法只能调用一次,但是newInstance()方法创建对象后,如果长时间没有引用的话,会被GC回收,所以我们需要将创建好的对象存起来。
首先我们需要创建一个容器,用于存放我们需要的对象,在静态块中将容器初始化,并取出配置文件中所有的key
package cn.tedu.factory;
import java.io.IOException;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
/**
* 是一个创建Bean对象的工厂类
* Bean :在计算机英语中,是可重用组件的意思
* javaBean :用java语言编写的可重用组件
* 他就是创建service和dao对象的
* 第一步:通过配置文件配置service和dao对象
* 配置的内容:用于取出对象唯一标志 = 对象的全限定类名 (key=value)
* 第二步:通过读取配置文件中的配置,反射创建对象
*/
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);
}
}catch(Exception e){
throw new ExceptionInInitializerError("初始化properties失败!");
}
}
/**
* 根据bean的名称获取对象
* @param beanName
* @return
*/
public static Object getBean(String beanName){
return beans.get(beanName);
}
}
//再次测试代码
//cn.tedu.service.impl.AccountServiceImpl@7f31245a
//cn.tedu.service.impl.AccountServiceImpl@7f31245a
//cn.tedu.service.impl.AccountServiceImpl@7f31245a
//cn.tedu.service.impl.AccountServiceImpl@7f31245a
//cn.tedu.service.impl.AccountServiceImpl@7f31245a
工厂模式解耦
在实际开发中我们可以把三层的对象都使用配置文件配置起来,当启动服务器应用加载的时候,让一个类中的
方法通过读取配置文件,把这些对象创建出来并存起来。在接下来的使用的时候,直接拿过来用就好了。
那么,这个读取配置文件,创建和获取三层对象的类就是工厂。
IoC -Inversion Of Control 控制反转
前面的想法中存在两个问题:
1:存哪去
分析:由于我们是很多对象,肯定要找个集合来存。这时候有 Map 和 List 供选择。
到底选 Map 还是 List 就看我们有没有查找需求。有查找需求,选 Map。
所以我们的答案就是在应用加载时,创建一个 Map,用于存放三层对象。我们把这个 map 称之为容器。
2:什么是工厂
工厂就是负责给我们从容器中获取指定对象的类。这时候我们获取对象的方式发生了改变。
原来:我们在获取对象时,都是采用 new 的方式。是主动的。
现在:我们获取对象时,同时跟工厂要,有工厂为我们查找或者创建对象。是被动的
这种被动接收的方式获取对象的思想就是控制反转,它是 spring 框架的核心之一
明确 ioc 的作用: 削减计算机程序的耦合(解除我们代码中的依赖关系)
1.3、使用 Spring 的 IOC 解决程序耦合
引入spring坐标
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
改造代码,使用ioc来解决程序间的耦合
删除factory包和bean.properties配置文件
修改持久层、业务层
package cn.tedu.controller;
import cn.tedu.service.AccountService;
import cn.tedu.service.impl.AccountServiceImpl;
/**
* 模拟保存账户的控制层
*/
public class AccountController {
public static void main(String[] args) {
AccountService as = new AccountServiceImpl();
as.saveAccount();
}
}
package cn.tedu.service.impl;
import cn.tedu.dao.AccountDao;
import cn.tedu.dao.impl.AccountDaoImpl;
import cn.tedu.service.AccountService;
/**
* 模拟保存账户的业务层实现类
*/
public class AccountServiceImpl implements AccountService {
private AccountDao accountDao = new AccountDaoImpl();
public void saveAccount(){
accountDao.saveAccount();
}
}
在resourses目录下,新建一个bean.xml文件
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
</beans>
把对象的创建交给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"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="accountService" class="cn.tedu.service.impl.AccountServiceImpl"></bean>
<bean id="accountDao" class="cn.tedu.dao.impl.AccountDaoImpl"></bean>
</beans>
测试代码
package cn.tedu.controller;
import cn.tedu.dao.impl.AccountDaoImpl;
import cn.tedu.service.AccountService;
import cn.tedu.service.impl.AccountServiceImpl;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class AccountController {
public static void main(String[] args) {
/*
获取spring的IoC核心容器,并根据id获取对象
*/
//获取核心容器对象,此方法要求配置文件必须在类路径下
ApplicationContext cpxt = new ClassPathXmlApplicationContext("bean.xml");
//根据id获取Bean对象
AccountService as = cpxt.getBean("accountService", AccountService.class);
AccountDaoImpl ad = (AccountDaoImpl) cpxt.getBean("accountDao");
System.out.println(as);
System.out.println(ad);
}
}
ApplicationContext和BeanFactory的区别:
ApplicationContext:它在构建核心容器时,创建对象采取的策略是采用立即加载的方式,
也就是说,只要一读取完配置文件马上就创建配置文件中配置的对象
BeanFactory:它在构建核心容器时,创建对象采取的策略是延迟加载的方式。
也就时说,什么时候根据id获取对象了,什么时候才真正的创建对象
1.4、依赖注入 Dependency Injection
依赖注入的概念
Dependency Injection 依赖注入,在Spring框架负责创建Bean对象时,动态的将依赖对象注入到Bean组件。
它是Spring框架核心 IoC的具体实现
我们在编写程序时,通过控制反转,把对象的创建交给了Spring,但是代码中不可能出现没有依赖的情况,
IoC的解耦只是降低他们之间的依赖关系,但不会消除。例如我们的业务层仍会调用持久层的方法。
这种业务层和持久层的依赖关系,在使用Spring之后就交由spring来维护了
简单的说,就是坐等框架将持久层对象传入业务层,而不用我们自己获取
依赖注入bean属性(给对象的属性赋值)
使用XML文件配置
根据构造方法注入
修改业务层代码
package cn.tedu.service.impl;
import cn.tedu.dao.AccountDao;
import cn.tedu.dao.impl.AccountDaoImpl;
import cn.tedu.service.AccountService;
/**
* 模拟保存账户的业务层实现类
*/
public class AccountServiceImpl implements AccountService {
private AccountDao accountDao;
public AccountServiceImpl() {
}
public AccountServiceImpl(AccountDao accountDao) {
this.accountDao = accountDao;
}
public AccountDao getAccountDao() {
return accountDao;
}
public void setAccountDao(AccountDao accountDao) {
this.accountDao = accountDao;
}
public void saveAccount(){
System.err.println(accountDao);
accountDao.saveAccount();
}
}
配置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:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd">
<!--
使用构造函数的方式,给 service 中的属性传值
要求:
类中需要提供一个对应参数列表的构造函数
涉及的标签:
constructor-arg
属性:
index:指定参数在构造函数草书列表中索引的位置
type:指定参数在构造函数中的数据类型
name:指定参数在构造函数中的名称
value:它能赋的值是基本数据类型和String类型
ref:它能赋的值是对其他bean对象的引用
-->
<bean id="accountService" class="cn.tedu.service.impl.AccountServiceImpl">
<!--<constructor-arg index="0" ref="accountDao"/>-->
<!--<constructor-arg type="cn.tedu.dao.AccountDao" ref="accountDao"/>-->
<constructor-arg name="accountDao" ref="accountDao"/>
</bean>
<bean id="accountDao" class="cn.tedu.dao.impl.AccountDaoImpl"></bean>
</beans>
使用setter方法注入
配置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:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd">
<!--
通过配置文件给 bean 中的属性传值:使用 set 方法的方式
涉及的标签:
property:
属性:
name:找的是类中 set 方法后面的部分
ref:给属性赋值是其他 bean 类型的
value:给属性赋值是基本数据类型和 string 类型的
-->
<bean id="accountService" class="cn.tedu.service.impl.AccountServiceImpl">
<property name="accountDao" ref="accountDao"/>
</bean>
<bean id="accountDao" class="cn.tedu.dao.impl.AccountDaoImpl"></bean>
</beans>
集合、数组注入
集合、数组的注入都是给<property>
添加子标签
- 数组:
<array>
- List:
<list>
- Set:
<set>
- Map:
<map>
,map存放k/v 键值对,使用<entry>
描述 - Properties:
<props> <prop key=""></prop>
1、数组注入:
public class Me {
private String[] member;
public String[] getMember() {
return member;
}
public void setMember(String[] member) {
this.member = member;
}
}
<bean id="me" class="spring.Me">
<property name="member">
<array>
<value>mama</value>
<value>baba</value>
<value>gege</value>
</array>
</property>
</bean>
2、list集合注入
public class Me {
private List<String> member;
public List<String> getMember() {
return member;
}
public void setMember(List<String> member) {
this.member = member;
}
}
<bean id="me" class="spring.Me">
<property name="member">
<list>
<value>gege</value>
<value>mama</value>
<value>baba</value>
</list>
</property>
</bean>
3、set集合注入
public class Me {
private Set<String> member;
public Set<String> getMember() {
return member;
}
public void setMember(Set<String> member) {
this.member = member;
}
}
<bean id="me" class="spring.Me">
<property name="member">
<set>
<value>gege</value>
<value>mama</value>
<value>baba</value>
</set>
</property>
</bean>
4、map注入
public class Me {
private Map<String,String> member;
public Map<String,String> getMember() {
return member;
}
public void setMember(Map<String,String> member) {
this.member = member;
}
}
<bean id="me" class="spring.Me">
<property name="member">
<map>
<entry key="baba" value="zhangba"></entry>
<entry key="mama" value="lima"></entry>
<entry key="gege" value="zhangda"></entry>
</map>
</property>
</bean>
基于注解的IoC配置
学习基于注解的IoC配置,大家脑海里首先得有一个认知,即注解配置和xml配置要实现的功能都是一样的,都是要降低程序间的耦合。
只是配置的形式不一样。关于实际的开发中到底使用xml还是注解,每家公司有着不同的使用习惯。所以这两种配置方式我们都需要掌握。
常用注解
1、用于创建对象的,相当于 <bean class="XXX">
- @Component
作用:
将资源交由Spring管理。相当于在xml文件中配置一个bean
属性:
value:指定bean的id ,如果没有指定value属性,默认bean的id是当前类的类名首字母小写。
- @Controller @Service @Repository
这三个注解是针对与@Component的衍生注解,他们的作用及属性是完全相同的
他们只不过是提供了更加明确的语义:
@Controller:是用于表现层的注解
@Service:是用于业务层的注解
@Repository:是用于持久层的注解
创建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:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd">
<!--
注意:基于注解整合时,导入约束时需要多导入一个context名称空间下的 约束。
-->
<!--告知spring创建容器时要扫描的包-->
<context:component-scan base-package="cn.tedu"/>
</beans>
2、用于注入数据的,相当于<property name="" value="">
、 <property name="" ref="">
- @Autowired
作用:
自动按照类型注入。当使用注解注入属性时,set方法可以省略不写。它只能注入其他bean类型。
当有多个类型匹配时,使用要注入的对象变量名称作为bean的id,在spring容器查找,找到了也可以注入成功。找不到就报错。
- @Qualifier
作用:
在自动按照类型注入的基础之上,再按照Bean的id注入。
它在给字段注入时不能独立使用,必须和@Autowire一起使用;但是给方法参数注入时,可以独立使用。
属性:
value:指定bean的id。
- @Resource
作用:
直接按照Bean的id注入。它也只能注入其他bean类型。
默认按名称注入,名称找不到自动按类型注入
属性:
name:指定bean的id。
- @Value
作用:
注入基本数据类型和String类型数据的
属性:
value:用于指定值
改进代码,进行测试
修改控制层代码
package cn.tedu.controller;
import cn.tedu.service.AccountService;
import cn.tedu.service.impl.AccountServiceImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.stereotype.Controller;
import javax.annotation.Resource;
/**
* 模拟保存账户的控制层
*/
@Controller
public class AccountController {
@Autowired
public AccountService as;
public void test(){
System.err.println(as);
as.saveAccount();
}
}
修改业务层实现类代码
package cn.tedu.service.impl;
import cn.tedu.dao.AccountDao;
import cn.tedu.dao.impl.AccountDaoImpl;
import cn.tedu.service.AccountService;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
/**
* 模拟保存账户的业务层实现类
*/
@Service
public class AccountServiceImpl implements AccountService {
@Resource(name = "ad")
private AccountDao accountDao;
public void saveAccount(){
System.err.println(accountDao);
accountDao.saveAccount();
}
}
修改持久层实现类代码
package cn.tedu.dao.impl;
import cn.tedu.dao.AccountDao;
import org.springframework.stereotype.Repository;
/**
* 模拟保存账户的持久层实现类
*/
@Repository("ad")
public class AccountDaoImpl implements AccountDao {
@Override
public void saveAccount() {
System.out.println("保存了账户");
}
}
编写测试类代码
spring整合junit,需要导入spring-test的jar包
<!-- https://mvnrepository.com/artifact/junit/junit -->
<!-- 使用spring 5版本的时候,要求junit的版本必须为4.12及以上-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework/spring-test -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.0.2.RELEASE</version>
<scope>test</scope>
</dependency>
package cn.tedu;
import cn.tedu.controller.AccountController;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
//Junit的注解,代替main方法的
@RunWith(SpringJUnit4ClassRunner.class)
//告知spring运行器,spring和ioc的创建时基于xml还是注解的,并说明位置 locations:指定xml文件的位置,classes:指定注解类的位置
@ContextConfiguration(locations = "classpath:bean.xml")
public class AccountTests {
@Autowired
AccountController ac;
@Test
public void text(){
ac.test();
}
}
1.5、AOP
概念
AOP(面向切面编程)
在软件业,AOP为Aspect Oriented Programming的缩写,
意为∶面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。
AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。
利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。
简单的说它就是把我们程序重复的代码抽取出来,在需要执行的时候,使用动态代理的技术,在不修改源码的
基础上,对我们的已有方法进行增强。
AOP 的作用及优势
作用:
在程序运行期间,不修改源码对已有方法进行增强。
优势:
减少重复代码
提高开发效率
维护方便
AOP的实现方式
使用动态代理
AOP的具体应用
案例:事务控制
导入坐标
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>cn.tedu</groupId>
<artifactId>aop</artifactId>
<version>1.0-SNAPSHOT</version>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.21</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.9</version>
</dependency>
<dependency>
<groupId>commons-dbutils</groupId>
<artifactId>commons-dbutils</artifactId>
<version>1.6</version>
</dependency>
</dependencies>
</project>
实体类
package cn.tedu.entity;
public class Account {
private Integer id;
private String name;
private Double balance;
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 Double getBalance() {
return balance;
}
public void setBalance(Double balance) {
this.balance = balance;
}
@Override
public String toString() {
return "Account{" +
"id=" + id +
", name='" + name + '\'' +
", balance=" + balance +
'}';
}
}
转账业务持久层接口
package cn.tedu.dao;
import cn.tedu.entity.Account;
import java.util.List;
public interface IAccountDao {
List<Account> findAll();
void transfer(String sourceName,String targetName,Double money);
}
持久层实现类
package cn.tedu.dao.impl;
import cn.tedu.dao.IAccountDao;
import cn.tedu.entity.Account;
import cn.tedu.utils.ConnectionUtil;
import cn.tedu.utils.TransactionManager;
import org.apache.commons.dbutils.QueryRunner;
import org.apache.commons.dbutils.ResultSetHandler;
import org.apache.commons.dbutils.handlers.BeanListHandler;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.List;
public class AccountDaoImpl implements IAccountDao {
private QueryRunner runner;
public void setRunner(QueryRunner runner) {
this.runner = runner;
}
@Override
public List<Account> findAll() {
try {
List<Account> list = runner.query("select * from account", new BeanListHandler<Account>(Account.class));
return list;
} catch (SQLException e) {
e.printStackTrace();
}
return null;
}
@Override
public void transfer(String sourceName, String targetName, Double money) {
try {
runner.update("update account set balance = balance - ? where name = ?",money,sourceName);
runner.update("update account set balance = balance + ? where name = ?",money,targetName);
} catch (SQLException e) {
e.printStackTrace();
}
}
}
业务层接口
package cn.tedu.service;
import cn.tedu.entity.Account;
import java.util.List;
public interface IAccountService {
List<Account> findAll();
void transfer(String sourceName,String targetName,Double money);
}
业务层实现类
package cn.tedu.service.impl;
import cn.tedu.dao.IAccountDao;
import cn.tedu.entity.Account;
import cn.tedu.service.IAccountService;
import java.util.List;
public class IAccountServiceImpl implements IAccountService {
private IAccountDao dao;
public IAccountDao getDao() {
return dao;
}
public void setDao(IAccountDao dao) {
this.dao = dao;
}
@Override
public List<Account> findAll() {
return dao.findAll();
}
@Override
public void transfer(String sourceName, String targetName, Double money) {
dao.transfer(sourceName, targetName, money);
}
}
控制层
package cn.tedu.controller;
import cn.tedu.entity.Account;
import cn.tedu.service.IAccountService;
import java.util.List;
public class AccountController {
private IAccountService service;
public IAccountService getService() {
return service;
}
public void setService(IAccountService service) {
this.service = service;
}
public List<Account> findAll(){
return service.findAll();
}
public void transfer(String sourceName, String targetName, Double money) {
service.transfer(sourceName, targetName, money);
}
}
IoC配置
<?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
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd">
<bean id="propertyConfigurer" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="locations">
<list>
<value>classpath:druid.properties</value>
</list>
</property>
</bean>
<bean class="cn.tedu.dao.impl.AccountDaoImpl" id="dao">
<property name="runner" ref="runner"/>
</bean>
<bean class="cn.tedu.service.impl.IAccountServiceImpl" id="service">
<property name="dao" ref="dao"/>
</bean>
<bean class="cn.tedu.controller.AccountController" id="controller">
<property name="service" ref="service"/>
</bean>
<bean class="com.alibaba.druid.pool.DruidDataSource" id="source">
<property name="driverClassName" value="${driverClassName}"/>
<property name="url" value="${url}"/>
<property name="username" value="${username}"/>
<property name="password" value="${password}"/>
<property name="maxWait" value="${maxWait}"/>
<property name="maxActive" value="${maxActive}"/>
<property name="initialSize" value="${initialSize}"/>
</bean>
<bean class="org.apache.commons.dbutils.QueryRunner" id="runner">
<constructor-arg name="ds" ref="source"/>
</bean>
</beans>
测试代码
package cn.tedu;
import cn.tedu.controller.AccountController;
import cn.tedu.entity.Account;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import java.util.List;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:bean.xml")
public class UserTests {
private AccountController ac;
@Before
public void init(){
ApplicationContext context = new ClassPathXmlApplicationContext("bean.xml");
ac = context.getBean("controller",AccountController.class);
}
@Test
public void test(){
List<Account> all = ac.findAll();
System.out.println(all);
}
@Test
public void test02(){
ac.transfer("张三", "李四", 500.0);
}
}
上述案例的问题:事务被自动控制了。换言之,我们使用了connection对象的setAutoCommit(true)此方式控制事务,如果我们每次都执行一条sql语句,没有问题,但是如果业务方法一次要执行多条sql语句,这种方式就无法实现功能了。
解决方式:手动控制事务的提交和回滚
添加获取连接工具类和事务控制工具类
package cn.tedu.utils;
import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.SQLException;
public class ConnectionUtil {
ThreadLocal<Connection> tl = new ThreadLocal<Connection>();
private DataSource ds;
public DataSource getDs() {
return ds;
}
public void setDs(DataSource ds) {
this.ds = ds;
}
public Connection getConn(){
try {
Connection c = tl.get();
if (c == null){
c = ds.getConnection();
tl.set(c);
}
return c;
}catch (Exception e){
throw new RuntimeException();
}
}
public void removeConnection(){
tl.remove();
}
}
package cn.tedu.utils;
public class TransactionManager {
private ConnectionUtil connectionUtil;
public ConnectionUtil getConnectionUtil() {
return connectionUtil;
}
public void setConnectionUtil(ConnectionUtil connectionUtil) {
this.connectionUtil = connectionUtil;
}
/**
* 开启事务
*/
public void beginTransaction(){
try {
connectionUtil.getConn().setAutoCommit(false);
}catch (Exception e){
e.printStackTrace();
}
}
/**
* 提交事务
*/
public void commit(){
try {
connectionUtil.getConn().commit();
}catch (Exception e){
e.printStackTrace();
}
}
/**
* 回滚事务
*/
public void rollback(){
try {
connectionUtil.getConn().rollback();
}catch (Exception e){
e.printStackTrace();
}
}
/**
* 释放连接
*/
public void release(){
try {
connectionUtil.getConn().close();//还回连接池中
connectionUtil.removeConnection();
}catch (Exception e){
e.printStackTrace();
}
}
}
修改持久层实现类代码
package cn.tedu.dao.impl;
import cn.tedu.dao.IAccountDao;
import cn.tedu.entity.Account;
import cn.tedu.utils.ConnectionUtil;
import cn.tedu.utils.TransactionManager;
import org.apache.commons.dbutils.QueryRunner;
import org.apache.commons.dbutils.ResultSetHandler;
import org.apache.commons.dbutils.handlers.BeanListHandler;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.List;
public class AccountDaoImpl implements IAccountDao {
private QueryRunner runner;
private ConnectionUtil cu;
private TransactionManager tm;
public TransactionManager getTm() {
return tm;
}
public void setTm(TransactionManager tm) {
this.tm = tm;
}
public QueryRunner getRunner() {
return runner;
}
public void setRunner(QueryRunner runner) {
this.runner = runner;
}
public ConnectionUtil getCu() {
return cu;
}
public void setCu(ConnectionUtil cu) {
this.cu = cu;
}
@Override
public List<Account> findAll() {
try {
tm.beginTransaction();
List<Account> list = runner.query(cu.getConn(),"select * from account", new BeanListHandler<Account>(Account.class));
tm.commit();
return list;
} catch (SQLException e) {
tm.rollback();
e.printStackTrace();
}
return null;
}
@Override
public void transfer(String sourceName, String targetName, Double money) {
try {
tm.beginTransaction();
runner.update(cu.getConn(), "update account set balance = balance - ? where name = ?",money,sourceName);
int i = 1/0;
runner.update(cu.getConn(), "update account set balance = balance + ? where name = ?",money,targetName);
tm.commit();
} catch (SQLException e) {
tm.rollback();
e.printStackTrace();
}
}
}
修改IoC的配置
<?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
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd">
<bean id="propertyConfigurer" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="locations">
<list>
<value>classpath:druid.properties</value>
</list>
</property>
</bean>
<bean class="cn.tedu.dao.impl.AccountDaoImpl" id="dao">
<property name="runner" ref="runner"/>
<property name="tm" ref="manager"/>
<property name="cu" ref="connectionUtil"/>
</bean>
<bean class="cn.tedu.service.impl.IAccountServiceImpl" id="service">
<property name="dao" ref="dao"/>
</bean>
<bean class="cn.tedu.controller.AccountController" id="controller">
<property name="service" ref="service"/>
</bean>
<bean class="com.alibaba.druid.pool.DruidDataSource" id="source">
<property name="driverClassName" value="${driverClassName}"/>
<property name="url" value="${url}"/>
<property name="username" value="${username}"/>
<property name="password" value="${password}"/>
<property name="maxWait" value="${maxWait}"/>
<property name="maxActive" value="${maxActive}"/>
<property name="initialSize" value="${initialSize}"/>
</bean>
<bean class="org.apache.commons.dbutils.QueryRunner" id="runner">
</bean>
<bean class="cn.tedu.utils.ConnectionUtil" id="connectionUtil">
<property name="ds" ref="source"/>
</bean>
<bean class="cn.tedu.utils.TransactionManager" id="manager">
<property name="connectionUtil" ref="connectionUtil"/>
</bean>
</beans>
上述的代码中,虽然已经完成了事务的控制,但是也出现了新的问题,持久层的代码变得臃肿,充斥着一些重复的代码,而且持久层的方法和事务控制的方法耦合了。试想一下,如果我们此时提交,回滚,释放资源中任何一个方法名变更,都需要修改业务层的代码,况且这还只是一个业务层实现类,而实际的项目中这种业务层实现类可能有十几个甚至几十个。
解决方式:使用动态代理
动态代理常用的两种方式:
基于接口的动态代理
提供者:JDK官方的Proxy类。
要求:被代理类最少实现一个接口。
基于子类的动态代理提供者:
第三方的CGLib,如果报asmxxxx异常,需要导入asm.jar。
要求:被代理类不能用final修饰的类(最终类)。
使用JDK官方的Proxy类创建代理对象
创建BeanFactory
package cn.tedu.factory;
import cn.tedu.dao.IAccountDao;
import cn.tedu.utils.TransactionManager;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class BeanFactory {
private IAccountDao ad;
private TransactionManager tm;
public final void setAd(IAccountDao ad) {
this.ad = ad;
}
public void setTm(TransactionManager tm) {
this.tm = tm;
}
public IAccountDao getDao(){
IAccountDao o = (IAccountDao) Proxy.newProxyInstance(ad.getClass().getClassLoader(), ad.getClass().getInterfaces(), new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object value = null;
try {
tm.beginTransaction();
value = method.invoke(ad, args);
tm.commit();
} catch (Exception e) {
tm.rollback();
e.printStackTrace();
} finally {
tm.release();
}
return value;
}
});
return o;
}
}
修改持久层实现类代码
package cn.tedu.dao.impl;
import cn.tedu.dao.IAccountDao;
import cn.tedu.entity.Account;
import cn.tedu.utils.ConnectionUtil;
import cn.tedu.utils.TransactionManager;
import org.apache.commons.dbutils.QueryRunner;
import org.apache.commons.dbutils.ResultSetHandler;
import org.apache.commons.dbutils.handlers.BeanListHandler;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.List;
public class AccountDaoImpl implements IAccountDao {
private QueryRunner runner;
private ConnectionUtil cu;
public QueryRunner getRunner() {
return runner;
}
public void setRunner(QueryRunner runner) {
this.runner = runner;
}
public ConnectionUtil getCu() {
return cu;
}
public void setCu(ConnectionUtil cu) {
this.cu = cu;
}
@Override
public List<Account> findAll() {
try {
List<Account> list = runner.query(cu.getConn(),"select * from account", new BeanListHandler<Account>(Account.class));
return list;
} catch (SQLException e) {
e.printStackTrace();
}
return null;
}
@Override
public void transfer(String sourceName, String targetName, Double money) {
try {
runner.update(cu.getConn(), "update account set balance = balance - ? where name = ?",money,sourceName);
/*int i = 1/0;*/
runner.update(cu.getConn(), "update account set balance = balance + ? where name = ?",money,targetName);
} catch (SQLException e) {
e.printStackTrace();
}
}
}
修改IoC的配置
<?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
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd">
<bean id="propertyConfigurer" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="locations">
<list>
<value>classpath:druid.properties</value>
</list>
</property>
</bean>
<bean class="cn.tedu.dao.impl.AccountDaoImpl" id="dao">
<property name="runner" ref="runner"/>
<property name="cu" ref="connectionUtil"/>
</bean>
<bean class="cn.tedu.service.impl.IAccountServiceImpl" id="service">
<property name="dao" ref="proxyDao"/>
</bean>
<bean class="cn.tedu.controller.AccountController" id="controller">
<property name="service" ref="service"/>
</bean>
<bean class="com.alibaba.druid.pool.DruidDataSource" id="source">
<property name="driverClassName" value="${driverClassName}"/>
<property name="url" value="${url}"/>
<property name="username" value="${username}"/>
<property name="password" value="${password}"/>
<property name="maxWait" value="${maxWait}"/>
<property name="maxActive" value="${maxActive}"/>
<property name="initialSize" value="${initialSize}"/>
</bean>
<bean class="org.apache.commons.dbutils.QueryRunner" id="runner">
</bean>
<bean class="cn.tedu.utils.ConnectionUtil" id="connectionUtil">
<property name="ds" ref="source"/>
</bean>
<bean class="cn.tedu.utils.TransactionManager" id="manager">
<property name="connectionUtil" ref="connectionUtil"/>
</bean>
<bean id="factory" class="cn.tedu.factory.BeanFactory">
<property name="tm" ref="manager"/>
<property name="ad" ref="dao"/>
</bean>
<bean id="proxyDao" factory-bean="factory" factory-method="getDao">
</bean>
</beans>
使用CGLib的Enhancer类创建代理对象
工厂类添加方法
public IAccountDao getDao2(){
IAccountDao o = (IAccountDao) Enhancer.create(ad.getClass(), new MethodInterceptor() {
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
Object value = null;
try {
tm.beginTransaction();
value = method.invoke(ad, objects);
tm.commit();
} catch (Exception e) {
tm.rollback();
e.printStackTrace();
} finally {
tm.release();
}
return value;
}
});
return o;
}
修改IoC配置
<bean id="proxyDao" factory-bean="factory" factory-method="getDao2"></bean>
AOP相关的术语
Joinpoint(连接点):
所谓连接点是指那些被拦截到的点。在spring中,这些点指的是方法,因为spring只支持方法类型的连接点。
Pointcut(切入点):
所谓切入点是指我们要对哪些Joinpoint进行拦截的定义。
Advice(通知/增强):
所谓通知是指拦截到Joinpoint之后所要做的事情就是通知。
通知的类型:
前置通知,后置通知,异常通知,最终通知,环绕通知。
Target(目标对象):
代理的目标对象。
Weaving(织入):
是指把增强应用到目标对象来创建新的代理对象的过程。spring采用动态代理织入,而AspectJ采用编译期织入和类装载期织入。
Proxy(代理):
一个类被AOP织入增强后,就产生一个结果代理类。
Aspect(切面):
是切入点和通知(引介)的结合
基于XML的AOP配置
环境搭建
导入坐标
<!-- https://mvnrepository.com/artifact/org.aspectj/aspectjweaver -->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.5</version>
<scope>runtime</scope>
</dependency>
在配置文件中导入aop的名称空间,修改IoC配置
<?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:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop
https://www.springframework.org/schema/aop/spring-aop.xsd">
<bean id="propertyConfigurer" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="locations">
<list>
<value>classpath:druid.properties</value>
</list>
</property>
</bean>
<bean class="cn.tedu.dao.impl.AccountDaoImpl" id="dao">
<property name="runner" ref="runner"/>
<property name="cu" ref="connectionUtil"/>
</bean>
<bean class="cn.tedu.service.impl.IAccountServiceImpl" id="service">
<property name="dao" ref="dao"/>
</bean>
<bean class="cn.tedu.controller.AccountController" id="controller">
<property name="service" ref="service"/>
</bean>
<bean class="com.alibaba.druid.pool.DruidDataSource" id="source">
<property name="driverClassName" value="${driverClassName}"/>
<property name="url" value="${url}"/>
<property name="username" value="${username}"/>
<property name="password" value="${password}"/>
<property name="maxWait" value="${maxWait}"/>
<property name="maxActive" value="${maxActive}"/>
<property name="initialSize" value="${initialSize}"/>
</bean>
<bean class="org.apache.commons.dbutils.QueryRunner" id="runner">
</bean>
<bean class="cn.tedu.utils.ConnectionUtil" id="connectionUtil">
<property name="ds" ref="source"/>
</bean>
<bean class="cn.tedu.utils.TransactionManager" id="manager">
<property name="connectionUtil" ref="connectionUtil"/>
</bean>
<aop:config>
<aop:pointcut id="pt" expression="execution(* cn.tedu.dao.impl.*.*(..))"/>
<aop:aspect ref="manager" id="txAdvice">
<aop:before method="beginTransaction" pointcut-ref="pt"/>
<aop:after-returning method="commit" pointcut-ref="pt"/>
<aop:after-throwing method="rollback" pointcut-ref="pt"/>
<aop:after method="release" pointcut-ref="pt"/>
</aop:aspect>
</aop:config>
</beans>
标签作用
aop:config:
作用:用于声明开始aop的配置
aop:aspect:作用:
用于配置切面。
属性:
id:给切面提供一个唯一标识。
ref:引用配置好的通知类bean的id。
aop:pointcut:
作用:用于配置切入点表达式。就是指定对哪些类的哪些方法进行增强。
属性:
expression:用于定义切入点表达式。
id:用于给切入点表达式提供一个唯一标识
aop:before
作用:用于配置前置通知。指定增强的方法在切入点方法之前执行
属性:
method:用于指定通知类中的增强方法名称
ponitcut-ref:用于指定切入点的表达式的引用
poinitcut:用于指定切入点表达式
执行时间点:
切入点方法执行之前执行
aop:after-returning
作用:用于配置后置通知
属性:
method:指定通知中方法的名称。
pointct:定义切入点表达式
pointcut-ref:指定切入点表达式的引用
执行时间点:
切入点方法正常执行之后。它和异常通知只能有一个执行
aop:after-throwing
作用:用于配置异常通知
属性:
method:指定通知中方法的名称。
pointct:定义切入点表达式
pointcut-ref:指定切入点表达式的引用
执行时间点:
切入点方法执行产生异常后执行。它和后置通知只能执行一个
aop:after作用:
用于配置最终通知
属性:
method:指定通知中方法的名称。
pointct:定义切入点表达式
pointcut-ref:指定切入点表达式的引用
执行时间点:
无论切入点方法执行时是否有异常,它都会在其后面执行。
切入点表达式说明
execution:
匹配方法的执行(常用)
execution(表达式)
表达式语法:
execution([修饰符]返回值类型包名.类名.方法名(参数))
写法说明:
全匹配方式
public void cn.tedu.dao.impl.AccountDaoImpl.transfer(java.lang.String sourceName,java.lang.String.targetName,java.lang.Double money);
访问修饰符可以省略
void cn.tedu.dao.impl.AccountDaoImpl.transfer(java.lang.String sourceName,java.lang.String.targetName,java.lang.Double money);
返回值可以使用*代替,表示通配任意返回值
* cn.tedu.dao.impl.AccountDaoImpl.transfer(java.lang.String sourceName,java.lang.String.targetName,java.lang.Double money);
包名可以使用*号,表示任意包,但是有几级包,需要写几个*
* *.*.*.*.AccountDaoImpl.transfer(java.lang.String sourceName,java.lang.String.targetName,java.lang.Double money);
使用..来表示当前包,及其子包
* cn..AccountDaoImpl.transfer(java.lang.String sourceName,java.lang.String.targetName,java.lang.Double money);
类名可以使用*号,表示任意类
* cn..*.transfer(java.lang.String sourceName,java.lang.String.targetName,java.lang.Double money);
方法名可以使用*号,表示任意方法
* cn..*.*(java.lang.String sourceName,java.lang.String.targetName,java.lang.Double money);
参数列表可以使用*,表示参数可以是任意数据类型,但是必须有参数
* cn..*.*(*);
参数列表可以使用..表示有无参数均可,有参数可以是任意类型
* cn..*.*(..)
全通配方式:
* *..*.*(..)
环绕通知
通知类添加方法
/**
*
* @param pjp
* spring框架为我们提供了一个接口:ProceedingJoinPoint,它可以作为环绕通知的方法参数。
* 在环绕通知执行时,spring框架会为我们提供该接口的实现类对象,我们直接使用就行。
* @return
*/
public Object txAround(ProceedingJoinPoint pjp){
Object value = null;
try {
//获取方法的参数
Object[] args = pjp.getArgs();
//前置通知:开启事务
beginTransaction();
//执行方法
value = pjp.proceed(args);
//后置通知:提交事务
commit();
} catch (Throwable throwable) {
//异常通知:回滚事务
rollback();
throwable.printStackTrace();
} finally {
//最终通知:释放资源
release();
}
return value;
}
更改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:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop
https://www.springframework.org/schema/aop/spring-aop.xsd">
<bean id="propertyConfigurer" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="locations">
<list>
<value>classpath:druid.properties</value>
</list>
</property>
</bean>
<bean class="cn.tedu.dao.impl.AccountDaoImpl" id="dao">
<property name="runner" ref="runner"/>
<property name="cu" ref="connectionUtil"/>
</bean>
<bean class="cn.tedu.service.impl.IAccountServiceImpl" id="service">
<property name="dao" ref="dao"/>
</bean>
<bean class="cn.tedu.controller.AccountController" id="controller">
<property name="service" ref="service"/>
</bean>
<bean class="com.alibaba.druid.pool.DruidDataSource" id="source">
<property name="driverClassName" value="${driverClassName}"/>
<property name="url" value="${url}"/>
<property name="username" value="${username}"/>
<property name="password" value="${password}"/>
<property name="maxWait" value="${maxWait}"/>
<property name="maxActive" value="${maxActive}"/>
<property name="initialSize" value="${initialSize}"/>
</bean>
<bean class="org.apache.commons.dbutils.QueryRunner" id="runner">
</bean>
<bean class="cn.tedu.utils.ConnectionUtil" id="connectionUtil">
<property name="ds" ref="source"/>
</bean>
<bean class="cn.tedu.utils.TransactionManager" id="manager">
<property name="connectionUtil" ref="connectionUtil"/>
</bean>
<aop:config>
<aop:pointcut id="pt" expression="execution(* cn.tedu.dao.impl.*.*(..))"/>
<aop:aspect ref="manager" id="txAdvice">
<!--<aop:before method="beginTransaction" pointcut-ref="pt"/>
<aop:after-returning method="commit" pointcut-ref="pt"/>
<aop:after-throwing method="rollback" pointcut-ref="pt"/>
<aop:after method="release" pointcut-ref="pt"/>-->
<aop:around method="txAround" pointcut-ref="pt"/>
</aop:aspect>
</aop:config>
</beans>
基于注解的AOP配置
在配置文件中导入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"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop
https://www.springframework.org/schema/aop/spring-aop.xsd">
<context:component-scan base-package="cn.tedu"/>
<bean id="propertyConfigurer" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="locations">
<list>
<value>classpath:druid.properties</value>
</list>
</property>
</bean>
<bean class="com.alibaba.druid.pool.DruidDataSource" id="source">
<property name="driverClassName" value="${driverClassName}"/>
<property name="url" value="${url}"/>
<property name="username" value="${username}"/>
<property name="password" value="${password}"/>
<property name="maxWait" value="${maxWait}"/>
<property name="maxActive" value="${maxActive}"/>
<property name="initialSize" value="${initialSize}"/>
</bean>
<bean class="org.apache.commons.dbutils.QueryRunner" id="runner"/>
<!--开启spring的AOP注解支持-->
<aop:aspectj-autoproxy/>
</beans>
添加注解
通知类
package cn.tedu.utils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
@Component("manager")
@Aspect
public class TransactionManager {
@Resource(name = "connectionUtil")
private ConnectionUtil connectionUtil;
/**
* 开启事务
*/
@Pointcut("execution(* cn.tedu.dao.impl.*.*(..))")
public void pt(){
}
@Before("pt()")
public void beginTransaction(){
try {
System.out.println("开启事务");
connectionUtil.getConn().setAutoCommit(false);
}catch (Exception e){
e.printStackTrace();
}
}
/**
* 提交事务
*/
@AfterReturning("pt()")
public void commit(){
try {
System.out.println("提交事务");
connectionUtil.getConn().commit();
}catch (Exception e){
e.printStackTrace();
}
}
/**
* 回滚事务
*/
@AfterThrowing("pt()")
public void rollback(){
try {
System.out.println("回滚事务");
connectionUtil.getConn().rollback();
}catch (Exception e){
e.printStackTrace();
}
}
/**
* 释放连接
*/
@After("pt()")
public void release(){
try {
System.out.println("归还连接");
connectionUtil.getConn().close();//还回连接池中
connectionUtil.removeConnection();
}catch (Exception e){
e.printStackTrace();
}
}
/**
*
* @param pjp
* spring框架为我们提供了一个接口:ProceedingJoinPoint,它可以作为环绕通知的方法参数。
* 在环绕通知执行时,spring框架会为我们提供该接口的实现类对象,我们直接使用就行。
* @return
*/
//@Around("pt()")
public Object txAround(ProceedingJoinPoint pjp){
Object value = null;
try {
//获取方法的参数
Object[] args = pjp.getArgs();
//前置通知:开启事务
beginTransaction();
//执行方法
value = pjp.proceed(args);
//后置通知:提交事务
commit();
} catch (Throwable throwable) {
//异常通知:回滚事务
rollback();
throwable.printStackTrace();
} finally {
//最终通知:释放资源
release();
}
return value;
}
}
获取连接工具类
package cn.tedu.utils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.SQLException;
@Component("connectionUtil")
public class ConnectionUtil {
ThreadLocal<Connection> tl = new ThreadLocal<Connection>();
@Autowired
private DataSource ds;
public Connection getConn(){
try {
Connection c = tl.get();
if (c == null){
c = ds.getConnection();
tl.set(c);
}
return c;
}catch (Exception e){
throw new RuntimeException();
}
}
public void removeConnection(){
tl.remove();
}
}
业务层实现类
package cn.tedu.service.impl;
import cn.tedu.dao.IAccountDao;
import cn.tedu.entity.Account;
import cn.tedu.service.IAccountService;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.util.List;
@Service("service")
public class IAccountServiceImpl implements IAccountService {
@Resource(name = "dao")
private IAccountDao dao;
@Override
public List<Account> findAll() {
return dao.findAll();
}
@Override
public void transfer(String sourceName, String targetName, Double money) {
dao.transfer(sourceName, targetName, money);
}
}
持久层实现类
package cn.tedu.dao.impl;
import cn.tedu.dao.IAccountDao;
import cn.tedu.entity.Account;
import cn.tedu.utils.ConnectionUtil;
import cn.tedu.utils.TransactionManager;
import org.apache.commons.dbutils.QueryRunner;
import org.apache.commons.dbutils.ResultSetHandler;
import org.apache.commons.dbutils.handlers.BeanListHandler;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.List;
@Repository("dao")
public class AccountDaoImpl implements IAccountDao {
@Autowired
private QueryRunner runner;
@Autowired
private ConnectionUtil cu;
@Override
public List<Account> findAll() {
try {
List<Account> list = runner.query(cu.getConn(),"select * from account", new BeanListHandler<Account>(Account.class));
return list;
} catch (SQLException e) {
e.printStackTrace();
}
return null;
}
@Override
public void transfer(String sourceName, String targetName, Double money) {
try {
System.out.println("方法执行");
runner.update(cu.getConn(), "update account set balance = balance - ? where name = ?",money,sourceName);
/*int i = 1/0;*/
runner.update(cu.getConn(), "update account set balance = balance + ? where name = ?",money,targetName);
} catch (SQLException e) {
e.printStackTrace();
}
}
}
控制层
package cn.tedu.controller;
import cn.tedu.entity.Account;
import cn.tedu.service.IAccountService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import java.util.List;
@Controller("controller")
public class AccountController {
@Autowired
private IAccountService service;
public List<Account> findAll(){
return service.findAll();
}
public void transfer(String sourceName, String targetName, Double money) {
service.transfer(sourceName, targetName, money);
}
}
测试类
package cn.tedu;
import cn.tedu.controller.AccountController;
import cn.tedu.entity.Account;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import java.util.List;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:bean.xml")
public class UserTests {
@Autowired
private AccountController ac;
@Test
public void test(){
List<Account> all = ac.findAll();
System.out.println(all);
}
@Test
public void test02(){
ac.transfer("张三", "李四", 500.0);
}
}