Spring 是分层的 full-stack(全栈) 轻量级开源框架,以 IoC 和 AOP 为内核,提供了展现层 Spring MVC 和业务层事务管理等众多的企业级应用技术,还能整合开源世界众多著名的第三方框架和类库,已经成为使用最多的 Java EE 企业应用开源框架。
Spring 官方网址:https://spring.io
我们经常说的 Spring 其实指的是Spring Framework(spring 框架)。
整个 Spring 优势,传达出一个信号,Spring 是一个综合性,且有很强的思想性框架,每学习一 天,就能体会到它的一些优势。
方便解耦,简化开发
通过Spring提供的IoC容器,可以将对象间的依赖关系交由Spring进行控制,避免硬编码所造成的 过度程序耦合。用户也不必再为单例模式类、属性文件解析等这些很底层的需求编写代码,可以更 专注于上层的应用。
AOP编程的支持
通过Spring的AOP功能,方便进行面向切面的编程,许多不容易用传统OOP实现的功能可以通过AOP轻松应付。
声明式事务的支持
@Transactional
可以将我们从单调烦闷的事务管理代码中解脱出来,通过声明式方式灵活的进行事务的管理,提高 开发效率和质量。
方便程序的测试
可以用非容器依赖的编程方式进行几乎所有的测试工作,测试不再是昂贵的操作,而是随手可做的 事情。
方便集成各种优秀框架
Spring可以降低各种框架的使用难度,提供了对各种优秀框架(Struts、Hibernate、Hessian、 Quartz等)的直接支持。
降低JavaEE API的使用难度
Spring对JavaEE API(如JDBC、JavaMail、远程调用等)进行了薄薄的封装层,使这些API的使用难度大为降低。
源码是经典的 Java 学习范例
Spring的源代码设计精妙、结构清晰、匠心独用,处处体现着大师对Java设计模式灵活运用以及对Java技术的高深造诣。它的源代码无意是Java技术的最佳实践的范例。
Spring是一个分层非常清晰并且依赖关系、职责定位非常明确的轻量级框架。
主要包括几个大模块:
Date Access 数据处理模块
Spring的JDBC和DAO模块封装了大量样板代码,这样可以使得数据库代码变得简洁,也可以更专 注于我们的业务,还可以避免数据库资源释放失败而引起的问题。 另外,Spring AOP为数据访问 提供了事务管理服务,同时Spring还对ORM进行了集成,如Hibernate、MyBatis等。该模块由 JDBC、Transactions、ORM、OXM 和 JMS 等模块组成。
Web模块
该模块提供了SpringMVC框架给Web应用,还提供了多种构建和其它应用交互的远程调用方 案。 SpringMVC框架在Web层提升了应用的松耦合水平。
AOP(Aspect Oriented Programming)/Aspects模块
面向切面编程(AOP)/Aspects Spring对面向切面编程提供了丰富的支持。这个模块是Spring应 用系统中开发切面的基础,与DI一样,AOP可以帮助应用对象解耦。
Core Container模块
Spring核心容器(Core Container) 容器是Spring框架最核心的部分,它管理着Spring应用中 bean的创建、配置和管理。在该模块中,包括了Spring bean工厂,它为Spring提供了DI的功能。 基于bean工厂,我们还会发现有多种Spring应用上下文的实现。所有的Spring模块都构建于核心 容器之上。
Test 模块
为了使得开发者能够很方便的进行测试,Spring提供了测试模块以致力于Spring应用的测 试。 通过该模块,Spring为使用Servlet、JNDI等编写单元测试提供了一系列的mock对象实现。
Spring依靠这些基本模块,实现了一个令人愉悦的融合了现有解决方案的零 侵入的轻量级框架。
##################################################################################################
Inversion of Control (控制反转/反转控制)
它是一种技术思想而不是一个技术实现,处理的核心是Java开发领域对象的创建,管理。
对于传统的开发方式:比如类A依赖于类B,往往会在类A中new一个B的对象,而在IoC思想下开发方式:不用自己去new对象了,而是由IoC容器(Spring框架)去帮助我们实例化对象并且管理它,我们需要使用哪个对象,去问IoC容器要即可。
控制: 指的是对象实例化、管理的权利 。
DI: Dependency Injection(依赖注入) ,IOC和DI描述的是同一件事情,只不过思考⻆度不一样
AOP: Aspect oriented Programming 面向切面编程/面向方面编程 (AOP是OOP的延续)
OOP三大特征:封装、继承和多态 (一种垂直继承体系)
OOP编程思想可以解决大多数的代码重复问题,但是有一些情况是处理不了的,比如下面的在顶级父类 Animal中的多个方法中相同位置出现了重复代码:例如 日志/权限/事务等等,OOP就解决不了
横切逻辑代码
存在的问题:
AOP提出横向抽取机制,将横切逻辑代码和业务逻辑代码进行拆分,解耦合,避免横切逻辑代码重复。
「切」: 指的是横切逻辑,原有业务逻辑代码我们不能动,只能操作横切逻辑代码,所以面向横切逻辑。
「面」: 横切逻辑代码往往要影响的是很多个方法,每一个方法都如同一个点,多个点构成面,有一个面的概念在里面
##################################################################################################
此处准备了一 个『银行转账』的案例,请分析该案例在代码层次有什么问题 ?分析之后使用我们已有知识解决这些问 题(痛点)。其实这个过程我们就是在一步步分析并手写实现 IoC 和 AOP。
@WebServlet(name="transferServlet",urlPatterns = "/transferServlet")
public class TransferServlet extends HttpServlet {
// 1. 实例化service层对象
private TransferService transferService = new TransferServiceImpl();
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doPost(req,resp);
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 设置请求体的字符编码 req.setCharacterEncoding("UTF-8");
String fromCardNo = req.getParameter("fromCardNo");
String toCardNo = req.getParameter("toCardNo");
String moneyStr = req.getParameter("money");
int money = Integer.parseInt(moneyStr);
Result result = new Result();
try {
// 2. 调用service层方法
transferService.transfer(fromCardNo,toCardNo,money);
result.setStatus("200");
} catch (Exception e) {
e.printStackTrace();
result.setStatus("201");
result.setMessage(e.toString());
}
// 响应
resp.setContentType("application/json;charset=utf-8");
resp.getWriter().print(JsonUtils.object2Json(result));
}
public interface TransferService {
void transfer(String fromCardNo,String toCardNo,int money) throws Exception;
}
public class TransferServiceImpl implements TransferService {
private AccountDao accountDao = new JdbcAccountDaoImpl();
@Override
public void transfer(String fromCardNo, String toCardNo, int money) throws Exception {
Account from = accountDao.queryAccountByCardNo(fromCardNo);
Account to = accountDao.queryAccountByCardNo(toCardNo);
from.setMoney(from.getMoney()-money);
to.setMoney(to.getMoney()+money);
accountDao.updateAccountByCardNo(from);
accountDao.updateAccountByCardNo(to);
}
}
public interface AccountDao {
Account queryAccountByCardNo(String cardNo) throws Exception;
int updateAccountByCardNo(Account account) throws Exception;
}
public class JdbcAccountDaoImpl implements AccountDao {
@Override
public Account queryAccountByCardNo(String cardNo) throws Exception {
//从连接池获取连接
Connection con = DruidUtils.getInstance().getConnection();
String sql = "select * from account where cardNo=?";
PreparedStatement preparedStatement = con.prepareStatement(sql);
preparedStatement.setString(1,cardNo);
ResultSet resultSet = preparedStatement.executeQuery();
Account account = new Account();
while(resultSet.next()) {
account.setCardNo(resultSet.getString("cardNo"));
account.setName(resultSet.getString("name"));
account.setMoney(resultSet.getInt("money"));
}
resultSet.close();
preparedStatement.close();
con.close();
return account;
}
@Override
public int updateAccountByCardNo(Account account) throws Exception {
//从连接池获取连接
Connection con = DruidUtils.getInstance().getConnection();
String sql = "update account set money=? where cardNo=?";
PreparedStatement preparedStatement = con.prepareStatement(sql);
preparedStatement.setInt(1,account.getMoney());
preparedStatement.setString(2,account.getCardNo());
int i = preparedStatement.executeUpdate();
preparedStatement.close();
con.close();
return i;
}
}
问题一:
在上述案例实现中,直接在service实现类TransferServiceImpl中通过 AccountDao accountDao = new JdbcAccountDaoImpl() new对象方式获取dao层对象使用,而直接new对象方式却将TransferServiceImpl和dao层具体的一个实现类JdbcAccountDaoImpl 耦合在了一起,如果说技术架构发生一些变动,dao层的实现要使用其它技术, 比如Mybatis, 那么每一个new Dao对象的service实现类的地方都需要修改源代码,重新编译,成本巨大。
问题二:
service层代码没有进行事务控制,如果在转账过程中出现异常,将可能导致数据库数据错乱,后果可能会很严重,尤其是在金融业务中。
针对问题一: 实例化对象的方式除了 new 之外,还可以通过反射技术,考虑使用工厂模式来解耦合,而且项目中往往有很多对象需要实例化,那就在工厂中使用反射技术实例化对象,很合适。(需要把类的全限定类名配置在xml中)
更进一步,代码中只声明所需实例的接口类型,不出现 new 也不出现工厂类的字眼,此时就需要声明一个变量并提供 set 方法,在反射的时候将所需要的对象注入进去
针对问题二:service 层添加上事务控制,手动控制JDBC Connection 事务,但要特别注意将Connection和当前线程绑定(即保证一个线程只有一个 Connection,这样操作才针对的是同一个 Connection,进而控制的是同一个事务)
beans.xml
<beans>
<bean id="transferService" class="com.demo.edu.service.impl.TransferServiceImpl">
<property name="AccountDao" ref="accountDao">property>
bean>
<bean id="accountDao" class="com.demo.edu.dao.impl.JdbcAccountDaoImpl"/>
beans>
增加Bean解析创建工厂 BeanFactory.java
public class BeanFactory {
/**
* 工厂类的两个任务
* 任务一:加载解析xml,读取xml中的bean信息,通过反射技术实例化bean对象,然后放入map待用
* 任务二:提供接口方法根据id从map中获取bean(静态方法)
* */
private static Map<String,Object> map = new HashMap<>();
static {
InputStream resourceAsStream = BeanFactory.class.getClassLoader().getResourceAsStream("beans.xml");
SAXReader saxReader = new SAXReader();
try {
Document document = saxReader.read(resourceAsStream);
Element rootElement = document.getRootElement();
List<Element> list = rootElement.selectNodes("//bean");
// 实例化bean对象
for (int i = 0; i < list.size(); i++) {
Element element = list.get(i);
String id = element.attributeValue("id");
String clazz = element.attributeValue("class");
Class<?> aClass = Class.forName(clazz);
Object o = aClass.newInstance();
map.put(id,o);
}
// 维护bean之间的依赖关系
List<Element> propertyNodes = rootElement.selectNodes("//property");
for (int i = 0; i < propertyNodes.size(); i++) {
Element element = propertyNodes.get(i);
// 处理property元素
String name = element.attributeValue("name");
String ref = element.attributeValue("ref");
String parentId = element.getParent().attributeValue("id");
Object parentObject = map.get(parentId);
Method[] methods = parentObject.getClass().getMethods();
for (int j = 0; j < methods.length; j++) {
Method method = methods[j];
if(("set" + name).equalsIgnoreCase(method.getName())){
// bean之间的依赖关系(注入bean)
Object propertyObject = map.get(ref);
method.invoke(parentObject,propertyObject);
}
}
// 维护依赖关系后重新将bean放入map中
map.put(parentId,parentObject);
}
} catch (DocumentException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace(); }
}
}
public static Object getBean(String id) { return map.get(id);
}
}
@WebServlet(name="transferServlet",urlPatterns = "/transferServlet")
public class TransferServlet extends HttpServlet {
// 实例化service层对象
//private TransferService transferService = new TransferServiceImpl();
// 首先从BeanFactory获取到proxyFactory代理工厂的实例化对象
private ProxyFactory proxyFactory = (ProxyFactory) BeanFactory.getBean("proxyFactory");
// ...
}
public class TransferServiceImpl implements TransferService {
//private AccountDao accountDao = new JdbcAccountDaoImpl();
//注入 声明Dao接口对象
private AccountDao accountDao;
// 构造函数传值/set方法传值
public void setAccountDao(AccountDao accountDao) {
this.accountDao = accountDao;
}
}
增加 ConnectionUtils
public class ConnectionUtils {
// 存储当前线程的连接
private ThreadLocal<Connection> threadLocal = new ThreadLocal<>();
/**
* 从当前线程获取连接
*/
public Connection getCurrentThreadConn() throws SQLException {
/**
* 判断当前线程中是否已经绑定连接,如果没有绑定,需要从连接池获取一个连接绑定到当前线程
*/
Connection connection = threadLocal.get();
if(connection == null) {
// 从连接池拿连接并绑定到线程
connection = DruidUtils.getInstance().getConnection();
// 绑定到当前线程
threadLocal.set(connection);
}
return connection;
}
}
增加 TransactionManager 事务管理器类
public class TransactionManager {
private ConnectionUtils connectionUtils;
public void setConnectionUtils(ConnectionUtils connectionUtils) {
this.connectionUtils = connectionUtils;
}
// 开启手动事务控制
public void beginTransaction() throws SQLException {
connectionUtils.getCurrentThreadConn().setAutoCommit(false);
}
// 提交事务
public void commit() throws SQLException {
connectionUtils.getCurrentThreadConn().commit();
}
// 回滚事务
public void rollback() throws SQLException {
connectionUtils.getCurrentThreadConn().rollback();
}
}
增加 ProxyFactory 代理工厂类
public class ProxyFactory {
private TransactionManager transactionManager;
public void setTransactionManager(TransactionManager transactionManager) {
this.transactionManager = transactionManager;
}
/**
* Jdk动态代理
* @param obj 委托对象
* @return 代理对象
*/
public Object getJdkProxy(Object obj) {
// 获取代理对象
return Proxy.newProxyInstance(obj.getClass().getClassLoader(), obj.getClass().getInterfaces(),
new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object result = null;
try{
// 开启事务(关闭事务的自动提交)
transactionManager.beginTransaction();
result = method.invoke(obj,args);
// 提交事务
transactionManager.commit();
}catch (Exception e) {
e.printStackTrace();
// 回滚事务
transactionManager.rollback();
// 抛出异常便于上层servlet捕获
throw e;
}
return result;
}
});
}
/**
* 使用cglib动态代理生成代理对象
* @param obj 委托对象
* @return
*/
public Object getCglibProxy(Object obj) {
return Enhancer.create(obj.getClass(), new MethodInterceptor() {
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable
Object result = null;
try{
// 开启事务(关闭事务的自动提交)
transactionManager.beginTransaction();
result = method.invoke(obj,objects);
// 提交事务
transactionManager.commit();
}catch (Exception e) {
e.printStackTrace();
// 回滚事务
transactionManager.rollback();
// 抛出异常便于上层servlet捕获
throw e;
}
return result;
}
});
}
}
<beans>
<bean id="accountDao" class="com.demo.edu.dao.impl.JdbcAccountDaoImpl">
<property name="ConnectionUtils" ref="connectionUtils"/>
bean>
<bean id="transferService" class="com.demo.edu.service.impl.TransferServiceImpl">
<property name="AccountDao" ref="accountDao">property>
bean>
<bean id="connectionUtils" class="com.demo.edu.utils.ConnectionUtils"/>
<bean id="transactionManager" class="com.demo.edu.utils.TransactionManager">
<property name="ConnectionUtils" ref="connectionUtils"/>
bean>
<bean id="proxyFactory" class="com.demo.edu.factory.ProxyFactory">
<property name="TransactionManager" ref="transactionManager"/>
bean>
beans>
修改 JdbcAccountDaoImpl
public class JdbcAccountDaoImpl implements AccountDao {
private ConnectionUtils connectionUtils;
public void setConnectionUtils(ConnectionUtils connectionUtils) {
this.connectionUtils = connectionUtils;
}
public void init() {
System.out.println("初始化方法.....");
}
public void destory() {
System.out.println("销毁方法......");
}
@Override
public Account queryAccountByCardNo(String cardNo) throws Exception {
//从连接池获取连接
// Connection con = DruidUtils.getInstance().getConnection();
Connection con = connectionUtils.getCurrentThreadConn();
String sql = "select * from account where cardNo=?";
PreparedStatement preparedStatement = con.prepareStatement(sql);
preparedStatement.setString(1,cardNo);
ResultSet resultSet = preparedStatement.executeQuery();
Account account = new Account();
while(resultSet.next()) {
account.setCardNo(resultSet.getString("cardNo"));
account.setName(resultSet.getString("name"));
account.setMoney(resultSet.getInt("money"));
}
resultSet.close();
preparedStatement.close();
//con.close();
return account;
}
@Override
public int updateAccountByCardNo(Account account) throws Exception {
// 从连接池获取连接
// 改造为:从当前线程当中获取绑定的connection连接
//Connection con = DruidUtils.getInstance().getConnection();
Connection con = connectionUtils.getCurrentThreadConn();
String sql = "update account set money=? where cardNo=?";
PreparedStatement preparedStatement = con.prepareStatement(sql);
preparedStatement.setInt(1,account.getMoney());
preparedStatement.setString(2,account.getCardNo());
int i = preparedStatement.executeUpdate();
preparedStatement.close();
//con.close();
return i;
}
}
修改 TransferServlet
@WebServlet(name="transferServlet",urlPatterns = "/transferServlet")
public class TransferServlet extends HttpServlet {
// 1. 实例化service层对象
//private TransferService transferService = new TransferServiceImpl();
// 从工厂获取委托对象(委托对象是增强了事务控制的功能)
// 首先从BeanFactory获取到proxyFactory代理工厂的实例化对象
private ProxyFactory proxyFactory = (ProxyFactory) BeanFactory.getBean("proxyFactory");
private TransferService transferService = (TransferService)proxyFactory.getJdkProxy(BeanFactory.getBean("transferService")) ;
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doPost(req,resp);
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 设置请求体的字符编码
req.setCharacterEncoding("UTF-8");
String fromCardNo = req.getParameter("fromCardNo");
String toCardNo = req.getParameter("toCardNo");
String moneyStr = req.getParameter("money");
int money = Integer.parseInt(moneyStr);
Result result = new Result();
try {
// 2. 调用service层方法
transferService.transfer(fromCardNo,toCardNo,money);
result.setStatus("200");
} catch (Exception e) {
e.printStackTrace();
result.setStatus("201");
result.setMessage(e.toString());
}
// 响应
resp.setContentType("application/json;charset=utf-8");
resp.getWriter().print(JsonUtils.object2Json(result));
}
}
##################################################################################################
Java环境下启动IoC容器
1、ClassPathXmlApplicationContext:从类的根路径下加载配置文件(推荐使用)
2、FileSystemXmlApplicationContext:从磁盘路径上加载配置文件
3、AnnotationConfigApplicationContext:纯注解模式下启动Spring容器
Web环境下启动IoC容器
DOCTYPE web-app PUBLIC
"-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
"http://java.sun.com/dtd/web-app_2_3.dtd" >
<web-app>
<display-name>Archetype Created Web Applicationdisplay-name>
<context-param>
<param-name>contextClassparam-name>
<param-value>org.springframework.web.context.support.AnnotationConfigWebApplicationContextparam-value>
context-param>
<context-param>
<param-name>contextConfigLocationparam-name>
<param-value>com.lagou.edu.SpringConfigparam-value>
context-param>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListenerlistener-class>
listener>
web-app>
BeanFactory是Spring框架中IoC容器的顶层接口,它只是用来定义一些基础功能,定义一些基础规范;ApplicationContext是它的一个子接口。
通常,我们称BeanFactory为SpringIOC的基础容器,ApplicationContext是容器的高级接口,比BeanFactory要拥有更多的功能,比如说国际化支持和资源访问(xml,java配置类)等等
xml文件头
<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
https://www.springframework.org/schema/beans/spring-beans.xsd">
实例化Bean,有三种方式
1、无参构造函数:在默认情况下,它会通过反射调用无参构造函数来创建对象;如果类中没有无参构造函数,将创建失败。
配置XML
<bean id="userService" class="com.lagou.service.impl.TransferServiceImpl"> bean>
另外两种方式是为了我们自己new的对象加入到SpringIOC容器管理
2、静态方法:在实际开发中,我们使用的对象有些时候并不是直接通过构造函数就可以创建出来的,它可能在创建的过程中会做很多额外的操作。此时会提供一个创建对象的方法,不用先new对象,static方法可以直接调用,在该方法里实例化对象。
例如JDBC操作 链接MySql数据库,我们不会直接去写 JDBC4Connection connection = new JDBC4Connection(),因为我们要注册驱动,还要提供URL和凭证信息,用 DriverManager.getConnection 方法来获取连接。
public class CreateBeanFactory {
public static ConnectionUtils getInstanceStatic() {
return new ConnectionUtils();
}
}
<bean id="connectionUtils" class="com.lagou.factory.CreateBeanFactory" factory-method="getInstanceStatic">bean>
3、实例化方法创建
public class CreateBeanFactory {
public ConnectionUtils getInstance() {
return new ConnectionUtils();
}
}
<bean id="createBeanFactory" class="com.lagou.edu.factory.CreateBeanFactory">bean>
<bean id="connectionUtils" factory-bean="createBeanFactory" factory-method="getInstance"/>
Bean生命周期
在spring框架管理Bean对象的创建时,Bean对象默认都是单例的,但是它支持配置的方式改变作用范围。
实际开发中用到最多的作用范围就是singleton(单例模式)和 prototype(原型模式,也叫多例模式)。
bean标签属性 scope
单例模式 singleton : 与容器生命周期一致
多例模式 prototype: 多例模式的bean对象,spring框架只负责创建,不负责销毁
Bean标签属性
DI 依赖注入 XML配置
public class JdbcAccountDaoImpl implements AccountDao {
private ConnectionUtils connectionUtils;
private String name;
private int sex;
private float money;
public void setConnectionUtils(ConnectionUtils connectionUtils) {
this.connectionUtils = connectionUtils;
}
public void setName(String name) {
this.name = name;
}
public void setSex(int sex) {
this.sex = sex;
}
public void setMoney(float money) {
this.money = money;
}
public JdbcAccountDaoImpl(ConnectionUtils connectionUtils, String name, int sex, float money) {
this.connectionUtils = connectionUtils;
this.name = name;
this.sex = sex;
this.money = money;
}
private String[] myArray;
private Map<String,String> myMap;
private Set<String> mySet;
private Properties myProperties;
public void setMyArray(String[] myArray) {
this.myArray = myArray;
}
public void setMyMap(Map<String, String> myMap) {
this.myMap = myMap;
}
public void setMySet(Set<String> mySet) {
this.mySet = mySet;
}
public void setMyProperties(Properties myProperties) {
this.myProperties = myProperties;
}
public void init() {
System.out.println("初始化方法.....");
}
public void destory(){
System.out.println("销毁方法.....");
}
}
<bean id="accountDao" class="com.lagou.edu.dao.impl.JdbcAccountDaoImpl" scope="singleton" init-method="init" destroy-method="destory">
<constructor-arg name="connectionUtils" ref="connectionUtils"/>
<constructor-arg name="name" value="zhangsan"/>
<constructor-arg name="sex" value="1"/>
<constructor-arg name="money" value="100.6"/>
<property name="myArray">
<array>
<value>array1value>
<value>array2value>
<value>array3value>
array>
property>
<property name="myMap">
<map>
<entry key="key1" value="value1"/>
<entry key="key2" value="value2"/>
map>
property>
<property name="mySet">
<set>
<value>set1value>
<value>set2value>
set>
property>
<property name="myProperties">
<props>
<prop key="prop1">value1prop>
<prop key="prop2">value2prop>
props>
property>
bean>
按照注入的方式分类
1、构造函数注入:利用带参构造函数实现对类成员的数据赋值。
2、set方法注入:通过类成员的set方法实现数据的注入。(使用最多的)
按照注入的数据类型分类
1、基本类型和String:注入的数据类型是基本类型或者是字符串类型的数据。
2、其他Bean类型:注入的数据类型是对象类型,称为其他Bean的原因是,这个对象是要求出现在IoC容器 中的。那么针对当前Bean来说,就是其他Bean了。
3、复杂类型(集合类型):注入的数据类型是Aarry,List,Set,Map,Properties中的一种类型。
需要注意的是:
在List结构的集合数据注入时, array , list , set 这三个标签通用,另外注值的 value 标签内部 可以直接写值,也可以使用 bean 标签配置一个对象,或者用 ref 标签引用一个已经配合的bean 的唯一标识。
在Map结构的集合数据注入时, map 标签使用 entry 子标签实现数据注入, entry 标签可以使 用key和value属性指定存入map中的数据。使用value-ref属性指定已经配置好的bean的引用。 同时 entry 标签中也可以使用 ref 标签,但是不能使用 bean 标签。而 property 标签中不能使 用 ref 或者 bean 标签引用对象
引入注解功能,不需要引入额外的jar
由于xml文件依然存在,所以Spring IoC容器的启动仍然从加载xml开始
第三方jar中的bean定义在xml,比如德鲁伊数据库连接池DruidDataSource
自己开发的bean定义使用注解
XML标签与注解的对应关系
XML形式 | 注解形式 |
---|---|
标签 | @Component(“XXX”),注解加在类上 |
id属性 | @Component(“XXX”) 中的value,如果为空,默认bean的id所在类名并且首字母小写; |
scope属性 | @Scope(“prototype”),默认单例,注解加在类上 |
init-method 属性 | @PostConstruct,注解加在方法上,该方法就是初始化后调用的方法 |
destroy-method 属性 | @PreDestory,注解加在方法上,该方法就是销毁前调用的方法 |
DI 依赖注入的注解实现方式
1、@Autowired (推荐使用) 采取的策略为按照类型注入。
public class TransferServiceImpl implements TransferService{
@Autowired
private AccountDao accountDao;
}
但是这样又会产生另外一个问题,当一个类型有多个bean值的时候(对接口的多个实现),会造成无法选择具体注入哪一个的情况, 这个时候我们需要配合着@Qualifier使用。
public class TransferServiceImpl implements TransferService{
@Autowired
@Qualifier(name="jdbcAccountDaoImpl")
private AccountDao accountDao;
}
2、@Resource 默认按照 ByName 自动注入。
注解由 J2EE 提供,需要导入包 javax.annotation.Resource。 因为JDK11 已移除
public class TransferService {
@Resource
private AccountDao accountDao;
@Resource(name="studentDao")
private StudentDao studentDao;
@Resource(type="TeacherDao")
private TeacherDao teacherDao;
@Resource(name="manDao",type="ManDao")
private ManDao manDao;
}
实际就是改造XML+注解模式,将XML中遗留的配置全部以注解的形式迁移出去,最终删除XML,从Java配置类启动。
//注解表明当前类是一个配置类
@Configuration
//开启注解扫描,base-package指定扫描的包路径
@ComponentScan({"com.lagou.edu"})
//引入外部资源文件
@PropertySource({"classpath:jdbc.properties"})
//开启spring对注解事物的支持
@EnableTransactionManagement
public class SpringConfig {
@Value("${jdbc.driver}")
private String driverClassName;
@Value("${jdbc.url}")
private String url;
@Value("${jdbc.username}")
private String username;
@Value("${jdbc.password}")
private String password;
@Bean("dataSource")
public DataSource createDataSource() {
DruidDataSource druidDataSource = new DruidDataSource();
druidDataSource.setDriverClassName(driverClassName);
druidDataSource.setUrl(url);
druidDataSource.setUsername(username);
druidDataSource.setPassword(password);
return druidDataSource;
}
@Bean("jdbcTemplate")
public JdbcTemplate createJdbcTemplate(DataSource dataSource) {
return new JdbcTemplate(dataSource);
}
/**
* 异常:org.springframework.beans.factory.NoSuchBeanDefinitionException:
* No qualifying bean of type 'org.springframework.transaction.PlatformTransactionManager' available
*
* 原因:因为本事务管理器的类是基于jdbc连接来管理事务,所以在此要注处一个和SqlSessionFactory使用的相同数据源对象
*/
@Bean("transactionManager")
public PlatformTransactionManager createTransactionManger(DataSource dataSource){
return new DataSourceTransactionManager(dataSource);
}
1、lazy-Init 延迟加载 (一定程度上提高容器启动和运转性能,不常使用的 Bean 设置延迟加载,偶尔使用的时候再加载,避免过多占用Bean资源)
也就是Bean的延迟加载(延迟创建),ApplicationContext 容器的默认行为是在启动服务器时将所有 singleton bean 提前进行实例化。
<bean id="testBean" class="cn.lagou.LazyBean" />
<bean id="testBean" calss="cn.lagou.LazyBean" lazy-init="false" />
lazy-init = “false”,立即加载,表示在spring启动时,立刻进行实例化。
lazy-init = “true” bean将不会在ApplicationContext 启动时提前被实例化,而是第一次向容器通过 getBean索取 bean时实例化的。
如果一个设置了立即加载的 bean1,引用了一个延迟加载的 bean2 ,那么 bean1在容器启动时被实例 化,而 bean2由于被bean1引用,所以也被实例化,这种情况也符合延时加载的 bean 在第一次调用 时才被实例化的规则。
也可以在容器层次中通过在 元素上使用 “default-lazy-init” 属性来控制延时初始化
<beans default-lazy-init="true">
beans>
***** 特别注意*****:如果一个 bean 的 scope 属性为 scope=“pototype” 时,即使设置了 lazy-init=“false”,容器启动时也不 会实例化bean,而是调用 getBean 方法实例化的。
2、工厂Bean和Bean工厂 (FactoryBean / BeanFactory)
BeanFactory接口是容器的顶级接口,定义了容器的一些基础行为,负责生产和管理Bean的一个工厂,具体使用它下面的子接口类型,比如ApplicationContext
Spring中Bean有两种,一种是普通Bean,一种是工厂Bean(FactoryBean),FactoryBean可以生成某一个类型的Bean实例(返回给我们),也就是说我们可以借助于它自定义Bean的创建过程。
例子
//Spring提供的工厂Bean接口,可以让我们自定义Bean的创建过程(完成复杂Bean的定义)
public interface FactoryBean<T> {
@Nullable
// 返回FactoryBean创建的Bean实例,如果isSingleton返回true,则该实例会放到Spring容器 的单例对象缓存池中Map
T getObject() throws Exception;
@Nullable
// 返回FactoryBean创建的Bean类型
Class<?> getObjectType();
// 返回作用域是否单例
default boolean isSingleton() {
return true;
}
}
//Company类
public class Company {
private String name;
private String address;
private int scale;
//setter / getter / toString
}
public class CompanyFactoryBean implements FactoryBean<Company> {
private String companyInfo; // 公司名称,地址,规模
public void setCompanyInfo(String companyInfo) {
this.companyInfo = companyInfo;
}
@Override
public Company getObject() throws Exception {
// 模拟创建复杂对象Company
Company company = new Company();
String[] strings = companyInfo.split(",");
company.setName(strings[0]);
company.setAddress(strings[1]);
company.setScale(Integer.parseInt(strings[2]));
return company;
}
@Override
public Class<?> getObjectType() {
return Company.class;
}
@Override
public boolean isSingleton() {
return true;
}
}
<bean id="companyBean" class="com.lagou.edu.factory.CompanyFactoryBean">
<property name="companyInfo" value="拉勾,中关村,500"/>
bean>
结果
Object companyBean = applicationContext.getBean("companyBean");
System.out.println("bean:" + companyBean);
// 结果如下
bean:Company{name='拉勾', address='中关村', scale=500}
如果是要获取FactoryBean对象,需要在id之前添加“&”
Object companyBean = applicationContext.getBean("&companyBean");
System.out.println("bean:" + companyBean);
// 结果如下
bean:com.lagou.edu.factory.CompanyFactoryBean@43f3f32
3、后置处理器
Spring提供了两种后处理bean的扩展接口 BeanPostProcessor 和 BeanFactoryPostProcessor
在BeanFactory初始化之后可以使用BeanFactoryPostProcessor进行后置处理做一些事情
在Bean对象实例化之后可以使用BeanPostProcessor进行后置处 理做一些事情
注意:对象不一定是SpringBean,而SpringBean一定是个对象
public interface BeanPostProcessor {
//具体这个初始化方法指类似我们在定义bean时,定义了init-method所指定的方法
@Nullable
//初始化方法前执行
default Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
return bean;
}
@Nullable
//初始化方法后执行
default Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
return bean;
}
}
定义一个类实现了BeanPostProcessor,默认是会对整个Spring容器中所有的bean进行处理。
如果要对具体的某个bean处理,可以通过方法参数判断,两个类型参数分别为Object和String,第一个参数是每个bean的实例,第二个参数是每个bean的name或者id属性的值。所以我们可以通过第二个参数,来判断我们将要处理的具体的bean。
注意: 处理是发生在Spring容器的实例化和依赖注入之后。
public interface BeanFactoryPostProcessor {
/**
* Modify the application context's internal bean factory after its standard
* initialization. All bean definitions will have been loaded, but no beans
* will have been instantiated yet. This allows for overriding or adding
* properties even to eager-initializing beans.
* @param beanFactory the bean factory used by the application context
* @throws org.springframework.beans.BeansException in case of errors
*/
void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException;
}
此接口只提供了一个方法,方法参数为ConfigurableListableBeanFactory,该参数类型定义了一些方法
其中有个方法名为getBeanDefinition的方法,我们可以根据此方法,找到我们定义bean 的 BeanDefinition对象。然后我们可以对定义的属性进行修改
方法名字类似我们bean标签的属性,setBeanClassName对应bean标签中的class属性,所以当我们拿 到BeanDefinition对象时,我们可以手动修改bean标签中所定义的属性值。
BeanDefinition对象: 我们在XML中定义的bean标签,Spring解析bean标签成为一个JavaBean( 即 BeanDefinition)
注意: 调用BeanFactoryPostProcessor 方法时,这时候bean还没有实例化,此时 bean 刚被解析成 BeanDefinition对象
##################################################################################################
Spring源码构建
1、下载源码 spring-framework-5.1.x
2、安装并配置环境变量 Gradle 5.6.3
3、导入项目spring-framework源码 (选择 Gradle model)
4、编译顺序: core-oxm-context-beans-aspects-aop
如有疑问可查看详细教程 https://blog.csdn.net/chuanchengdabing/article/details/115330718
IoC容器是Spring的核心模块,是抽象了对象管理、依赖关系管理的框架解决方案。Spring 提供了很多 的容器,其中 BeanFactory 是顶层容器(根容器),不能被实例化,它定义了所有 IoC 容器 必须遵从 的一套原则,具体的容器实现可以增加额外的功能,比如我们常用到的ApplicationContext,其下更具 体的实现如 ClassPathXmlApplicationContext 包含了解析 xml 等一系列的内容, AnnotationConfigApplicationContext 则是包含了注解解析等一系列的内容。
Bean对象创建的几个关键时机点
Spring IoC 容器初始化的关键环节就在 AbstractApplicationContext#refresh() 方法中
@Override
public void refresh() throws BeansException, IllegalStateException {
synchronized (this.startupShutdownMonitor) {
// 第一步:刷新前的预处理 prepareRefresh();
/*
第二步: 获取BeanFactory;默认实现是DefaultListableBeanFactory
加载BeanDefition 并注册到 BeanDefitionRegistry
*/
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
// 第三步:BeanFactory的预准备工作(BeanFactory进行一些设置,比如context的类加 载器等)
prepareBeanFactory(beanFactory);
try {
// 第四步:BeanFactory准备工作完成后进行的后置处理工作
postProcessBeanFactory(beanFactory);
// 第五步:实例化并调用实现了BeanFactoryPostProcessor接口的Bean
invokeBeanFactoryPostProcessors(beanFactory);
// 第六步:注册BeanPostProcessor(Bean的后置处理器),在创建bean的前后等执
registerBeanPostProcessors(beanFactory);
// 第七步:初始化MessageSource组件(做国际化功能;消息绑定,消息解析);
initMessageSource();
// 第八步:初始化事件派发器
initApplicationEventMulticaster();
// 第九步:子类重写这个方法,在容器刷新的时候可以自定义逻辑
onRefresh();
// 第十步:注册应用的监听器。就是注册实现了ApplicationListener接口的监听器
registerListeners();
/*
第十一步:
初始化创建非懒加载方式的单例Bean实例(未设置属性)
填充属性
初始化方法调用(比如调用afterPropertiesSet方法、init-method方法)
调用BeanPostProcessor(后置处理器)对实例bean进行后置处
*/
finishBeanFactoryInitialization(beanFactory);
/*
第十二步: 完成context的刷新。主要是调用LifecycleProcessor的onRefresh()方法,并且发布事件
(ContextRefreshedEvent)
*/
finishRefresh();
}
.......
}
}
BeanDefinition加载解析及注册子流程
该子流程涉及到如下几个关键步骤
过程分析
1、子流程入口在 AbstractRefreshableApplicationContext#refreshBeanFactory 方法中
2、依次调用多个类的 loadBeanDefinitions 方法 —> AbstractXmlApplicationContext —> AbstractBeanDefinitionReader —> XmlBeanDefinitionReader 一直执行到 XmlBeanDefinitionReader 的 doLoadBeanDefinitions 方法
3、我们重点观察XmlBeanDefinitionReader 类的 registerBeanDefinitions 方法,期间产生了多次重载调用,我们定位到最后一个
此处我们关注两个地方:
先进入 createRederContext 方法看看, 此处 Spring 首先完成了 NamespaceHandlerResolver 的初始化。
再进入 registerBeanDefinitions 方法中追踪,调用了DefaultBeanDefinitionDocumentReader#registerBeanDefinitions 方法
层层方法进入
至此,注册流程结束,我们发现,所谓的注册就是把封装的 XML 中定义的 Bean信息封装为 BeanDefinition 对象之后放入一个Map中,BeanFactory 是以 Map 的结构组织这些 BeanDefinition 的。
通过上边的Bean生命周期关键时机点分析,我们知道Bean创建子流程入口在AbstractApplicationContext#refresh()方法的finishBeanFactoryInitialization(beanFactory) 处
普通 Bean 的初始化是在容器启动初始化阶段执行的,而被lazy-init=true修饰的 bean 则是在从容器里 第一次进行context.getBean() 时进行触发。Spring 启动的时候会把所有bean信息(包括XML和注解)解 析转化成Spring能够识别的BeanDefinition并存到Hashmap里供下面的初始化时用,然后对每个 BeanDefinition 进行处理,如果是懒加载的则在容器初始化阶段不处理,其他的则在容器初始化阶段进 行初始化并依赖注入。
DefaultListableBeanFactory.java
public void preInstantiateSingletons() throws BeansException {
if (logger.isTraceEnabled()) {
logger.trace("Pre-instantiating singletons in " + this);
}
// Iterate over a copy to allow for init methods which in turn register new bean definitions.
// While this may not be part of the regular factory bootstrap, it does otherwise work fine.
// 所有beanDefinition集合
List<String> beanNames = new ArrayList<>(this.beanDefinitionNames);
// Trigger initialization of all non-lazy singleton beans...
for (String beanName : beanNames) {
// 获取bean 定义
RootBeanDefinition bd = getMergedLocalBeanDefinition(beanName);
// 判断是否是懒加载单例bean,如果是单例的并且不是懒加载的则在容器创建时初始化
if (!bd.isAbstract() && bd.isSingleton() && !bd.isLazyInit()) {
if (isFactoryBean(beanName)) {
Object bean = getBean(FACTORY_BEAN_PREFIX + beanName);
if (bean instanceof FactoryBean) {
FactoryBean<?> factory = (FactoryBean<?>) bean;
boolean isEagerInit;
if (System.getSecurityManager() != null && factory instanceof SmartFactoryBean) {
isEagerInit = AccessController.doPrivileged(
(PrivilegedAction<Boolean>) ((SmartFactoryBean<?>) factory)::isEagerInit,
getAccessControlContext());
}
else {
isEagerInit = (factory instanceof SmartFactoryBean &&
((SmartFactoryBean<?>) factory).isEagerInit());
}
if (isEagerInit) {
getBean(beanName);
}
}
}
else {
/*
如果是普通bean则进行初始化并依赖注入
此getBean(beanName)接下来触发的逻辑和懒加载时context.getBean("beanName") 所触发的逻辑是一样的
*/
getBean(beanName);
}
}
}
// Trigger post-initialization callback for all applicable beans...
for (String beanName : beanNames) {
Object singletonInstance = getSingleton(beanName);
if (singletonInstance instanceof SmartInitializingSingleton) {
SmartInitializingSingleton smartSingleton = (SmartInitializingSingleton) singletonInstance;
if (System.getSecurityManager() != null) {
AccessController.doPrivileged((PrivilegedAction<Object>) () -> {
smartSingleton.afterSingletonsInstantiated();
return null;
}, getAccessControlContext());
}
else {
smartSingleton.afterSingletonsInstantiated();
}
}
}
}
1、循环依赖 :循环引用,也就是两个或者两个以上的 Bean互相持有对方,最终形成闭环。比如A 依赖于B,B依赖于C,C又依赖于A。
注意:这里不是函数的循环调用,是对象的相互依赖关系。循环调用其实就是一个死循环,除非有终结条件。
Spring中循环依赖场景有: 构造器的循环依赖(构造器注入) 和 Field 属性的循环依赖(set注入)
其中,构造器的循环依赖问题无法解决,因为初始化时,构造方法必须要传入对象,而此时对象没有初始化,只能拋出BeanCurrentlyInCreationException 异常,
2、循环依赖处理机制
A、Spring 不支持原型 bean 的循环依赖
B、单例bean通过setXxx或者@Autowired进行循环依赖
其中原理是依据基于 Java 的引用传递,当获得对象的引用时,对象的属性是可以延后设置的,但是构造器必须是在获取引用之前
其实是通过提前暴露一个ObjectFactory对象来完成的,简单来说ClassA在调用构造器完成对象初始化之后,在调用ClassA的setClassB方法 之前就把ClassA实例化的对象通过ObjectFactory提前暴露到Spring容器中。
ClassA调用setClassB方法,Spring首先尝试从容器中获取ClassB,此时ClassB不存在Spring 容器中;
此时,Spring容器初始化ClassB,同时也会将ClassB提前暴露到Spring容器中;
ClassB调用setClassA方法,Spring从容器中获取ClassA ,因为第一步中已经提前暴露了ClassA,因此可以获取到ClassA实例,ClassA通 过spring容器获取到ClassB,完成了对象初始化操作。
这样ClassA和ClassB都完成了对象初始化操作,解决了循环依赖问题。
本质:在不改变原有业务逻辑的情况下增强横切逻辑,横切逻辑代码往往是权限校验代码、日志代 码、事务控制代码、性能监控代码等。
在讲解AOP术语之前,我们先来看一下下面这两张图
上图描述的就是未采用AOP思想设计的程序,当我们红色框中圈定的方法时,会带来大量的重复劳动。 程序中充斥着大量的重复代码,使我们程序的独立性很差。而下图中是采用了AOP思想设计的程序,它 把红框部分的代码抽取出来的同时,运用动态代理技术,在运行期对需要使用的业务逻辑方法进行增强。
Joinpoint(连接点) :它指的是那些可以用于把增强代码加入到业务主线中的点,那么由上图中我们可以看出,这些点指的就是方法。在方法执行的前后通过动态代理技术加入增强的代码。在Spring框架AOP思想的技术实现中,也只支持方法类型的连接点。
方法开始时、结束时、正常运行完毕时、方法异常时等这些特殊的时机点,我们称之为连接点,项目中每个方法都有连接点,连接点是一种候选点
Pointcut(切入点): 它指的是那些已经把增强代码加入到业务主线进来之后的连接点。由上图中,我 们看出表现层 transfer 方法就只是连接点,因为判断访问权限的功能并没有对其增强。
指定AOP思想想要影响的具体方法是哪些,描述感兴趣的方法
Advice(通知/增强) :它指的是切面类中用于提供增强功能的方法。并且不同的方法增强的时机是不一 样的。比如,开启事务肯定要在业务方法执行之前执行;提交事务要在业务方法 正常执行之后执行,而回滚事务要在业务方法执行产生异常之后执行等等。那么 这些就是通知的类型。其分类有:前置通知 后置通知 异常通知 最终通知 环绕通知。
第一个层次:指的是横切逻辑
第二个层次:方位点(在某一些连接点上加入横切逻辑,那么这些连接点就叫做方位点,描述的是具体 的特殊时机)
Target(目标对象): 它指的是代理的目标对象。即被代理对象。
Proxy(代理) :它指的是一个类被AOP织入增强后,产生的代理类。即代理对象。
Weaving(织入):它指的是把增强应用到目标对象来创建新的代理对象的过程。spring采用动态代 理织入,而AspectJ采用编译期织入和类装载期织入。
Aspect(切面) :它指定是增强的代码所关注的方面,把这些相关的增强代码定义到一个类中,这个类就是切面类。例如,事务切面定义的方法就是和事务相关的,像开启/提交/回滚事务等,不会定义其他与事务无关的方法。上面的案例中TrasnactionManager 就是一个切面。
Aspect切面= 切入点+增强 = 切入点(锁定方法) + 方位点(锁定方法中的特殊时机)+ 横切逻辑
Spring 实现AOP思想使用的是动态代理技术
默认情况下,Spring会根据被代理对象是否实现接口来选择使用JDK还是CGLIB。当被代理对象没有实现任何接口时,Spring会选择CGLIB。当被代理对象实现了接口,Spring会选择JDK官方的代理技术,不过我们可以通过配置的方式,让Spring强制使用CGLIB。
在Spring的AOP配置中,也和IoC配置一样,支持3类配置方式。
需求:横切逻辑代码是打印日志,希望把打印日志的逻辑织入到目标方法的特定位置(service层transfer方法)
第一类:使用XML配置
引入AOP的jar包
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-aopartifactId>
<version>5.1.12.RELEASEversion>
dependency>
<dependency>
<groupId>org.aspectjgroupId>
<artifactId>aspectjweaverartifactId>
<version>1.9.4version>
dependency>
<bean id="logUtil" class="com.lagou.utils.LogUtil">bean>
<aop:config>
<aop:aspect id="logAspect" ref="logUtils">
<aop:pointcut id="pt1" expression="execution(* com.lagou.edu.service.impl.TransferServiceImpl.*(..))"/>
<aop:before method="beforeMethod" pointcut-ref="pt1"/>
aop:aspect>
aop:config>
在前面提到,Spring在选择创建代理对象时,会根据被代理对象的实际情况来选择的。被代理对象实现了接口,则采用基于接口的JDK动态代理。当被代理对象没有实现任何接口 的时候,Spring会自动切换到基于子类的cglib动态代理方式。
但是我们都知道,无论被代理对象是否实现接口,只要不是final修饰的类都可以采用cglib提供的方式创建代理对象。所以Spring也考虑到了这个情况,提供了配置的方式实现强制使用基于子类的动态代理(即cglib的方式),配置的方式有两种
1、使用aop:config标签配置
<aop:config proxy-target-class="true">
2、 使用aop:aspectj-autoproxy标签配置
<aop:aspectj-autoproxy proxy-target-class="true">aop:aspectj- autoproxy>
第二类:使用XML配置
XML 中开启 Spring 对注解 AOP 的支持
<aop:aspectj-autoproxy/>
@Component
@Aspect
public class LogUtils {
@Pointcut("execution(* com.lagou.edu.service.impl.TransferServiceImpl.*(..))")
public void pt1(){
}
/**
* 业务逻辑开始之前执行
*/
@Before("pt1()")
public void beforeMethod(JoinPoint joinPoint) {
Object[] args = joinPoint.getArgs();
for (int i = 0; i < args.length; i++) {
Object arg = args[i];
System.out.println(arg);
}
System.out.println("业务逻辑开始执行之前执行.......");
}
/**
* 业务逻辑结束时执行(无论异常与否)
*/
@After("pt1()")
public void afterMethod() {
System.out.println("业务逻辑结束时执行,无论异常与否都执行.......");
}
/**
* 异常时时执行
*/
@AfterThrowing("pt1()")
public void exceptionMethod() {
System.out.println("异常时执行.......");
}
/**
* 业务逻辑正常时执行
*/
@AfterReturning(value = "pt1()",returning = "retVal")
public void successMethod(Object retVal) {
System.out.println("业务逻辑正常时执行......." + retVal);
}
/**
* 环绕通知
*/
// @Around("pt1()")
public Object aroundMethod(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
System.out.println("环绕通知中的before method....");
Object result = null;
try{
// 控制原有业务逻辑是否执行
result = proceedingJoinPoint.proceed(proceedingJoinPoint.getArgs());
}catch(Exception e) {
System.out.println("环绕通知中的exception method....");
}finally {
System.out.println("环绕通知中的after method....");
}
return result;
}
}
第三类 :注解模式
去除XML配置,并在配置类中使用如下注解进行替换上述配置
@Configuration
@ComponentScan("com.lagou")
@EnableAspectJAutoProxy //开启spring对注解AOP的支持
public class SpringConfiguration {
}
编程式事务: 在业务代码中添加事务控制代码,这样的事务控制机制就叫做编程式事务
声明式事务: 通过xml或者注解配置的方式达到事务控制的目的,叫做声明式事务
1、事务的四大特性
原子性(Atomicity) :原子性是指事务是一个不可分割的工作单位,事务中的操作要么都发生,要么都不发生。从操作的⻆度来描述,事务中的各个操作要么都成功要么都失败
一致性(Consistency) :事务必须使数据库从一个一致性状态变换到另外一个一致性状态。
转账前A有1000,B有1000。转账后A+B也得是2000。
一致性是从数据的⻆度来说的,(1000,1000) (900,1100),不应该出现(900,1000)
隔离性(Isolation) : 事务的隔离性是多个用户并发访问数据库时,数据库为每一个用户开启的事务,每个事务不能被其他事务的操作数据所干扰,多个并发事务之间要相互隔离。
事务1给员工涨工资2000,但是事务1尚未被提交,员工发起事务2查询工资,发现工资涨了2000块钱,读到了事务1尚未提交的数据(脏读)
持久性(Durability) : 持久性是指一个事务一旦被提交,它对数据库中数据的改变就是永久性的,接下来即使数据库发生故障也不应该对其有任何影响。
2、事务的隔离级别
不考虑隔离级别,会出现的错误情况
脏读: 一个线程中的事务读到了另外一个线程中未提交的数据。
不可重复读: 一个线程中的事务读到了另外一个线程中已经提交的update的数据(前后内容不一样)
场景:
员工A发起事务1,查询工资,工资为1w,此时事务1尚未关闭
财务人员发起了事务2,给员工A张了2000块钱,并且提交了事务
员工A通过事务1再次发起查询请求,发现工资为1.2w,原来读出来1w读不到了,叫做不可重复读
虚读(幻读):一个线程中的事务读到了另外一个线程中已经提交的insert或者delete的数据(前后条 数不一样)
场景: 事务1查询所有工资为1w的员工的总数,查询出来了10个人,此时事务尚未关闭 事务2财务人员发起,新来员工,工资1w,向表中插入了2条数据,并且提交了事务,事务1再次查询工资为1w的员工个数,发现有12个人
数据库四种隔离级别:
Serializable(串行化):可避免脏读、不可重复读、虚读情况的发生。(串行化) 最高
Repeatable read(可重复读):可避免脏读、不可重复读情况的发生。(幻读有可能发生) 第二
该机制下会对要update的行进行加锁
Read committed(读已提交):可避免脏读情况发生。不可重复读和幻读一定会发生。 第三
Read uncommitted(读未提交):最低级别,以上情况均无法保证。(读未提交) 最低
MySQL的默认隔离级别是:REPEATABLE READ
3、事务的传播行为
事务往往在service层进行控制,如果出现service层方法A调用了另外一个service层方法B,A和B方法本身都已经被添加了事务控制,那么A调用B的时候,就需要进行事务的一些协商,这就叫做事务的传播行为。
PROPAGATION_REQUIRED | 如果当前没有事务,就新建一个事务,如果已经存在一个事务中, 加入到这个事务中。这是最常⻅的选择。 |
---|---|
PROPAGATION_SUPPORTS | 支持当前事务,如果当前没有事务,就以非事务方式执行 |
PROPAGATION_MANDATORY | 使用当前的事务,如果当前没有事务,就抛出异常 |
PROPAGATION_REQUIRES_NEW | 新建事务,如果当前存在事务,把当前事务挂起 |
PROPAGATION_NOT_SUPPORTED | 以非事务方式执行操作,如果当前存在事务,就把当前事务挂起 |
PROPAGATION_NEVER | 以非事务方式执行,如果当前存在事务,则抛出异常 |
PROPAGATION_NESTED | 如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则 执行与PROPAGATION_REQUIRED类似的操作 |
PlatformTransactionManager
public interface PlatformTransactionManager {
/**
* 获取事务状态信息
*/
TransactionStatus getTransaction(@Nullable TransactionDefinition definition) throws TransactionException;
/**
* 提交事务
*/
void commit(TransactionStatus status) throws TransactionException;
/**
* 回滚事务
*/
void rollback(TransactionStatus status) throws TransactionException;
}
此接口是Spring的事务管理器核心接口。Spring本身并不支持事务实现,只是负责提供标准,应用底层 支持什么样的事务,需要提供具体实现类。此处也是策略模式的具体应用。在Spring框架中,也为我们 内置了一些具体策略,
例如:DataSourceTransactionManager , HibernateTransactionManager 等等。
( HibernateTransactionManager 事务管理器在 spring-orm-5.1.12.RELEASE.jar 中)
Spring JdbcTemplate(数据库操作工具)、Mybatis(mybatis-spring.jar)————> DataSourceTransactionManager
Hibernate框架 ——————> HibernateTransactionManager
DataSourceTransactionManager 归根结底是横切逻辑代码,声明式事务要做的就是使用Aop(动态代理)来将事务控制逻辑织入到业务代码
pom.xml 导入jar
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-contextartifactId>
<version>5.1.12.RELEASEversion>
dependency>
<dependency>
<groupId>org.aspectjgroupId>
<artifactId>aspectjweaverartifactId>
<version>1.9.4version>
dependency>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-jdbcartifactId>
<version>5.1.12.RELEASEversion>
dependency>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-txartifactId>
<version>5.1.12.RELEASEversion>
dependency>
第一种:XML配置
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="${jdbc.driver}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
bean>
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<constructor-arg name="dataSource" ref="dataSource"/>
bean>
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<constructor-arg name="dataSource" ref="dataSource"/>
bean>
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<tx:method name="*" read-only="false" propagation="REQUIRED" isolation="DEFAULT" timeout="-1"/>
<tx:method name="query*" read-only="true" propagation="SUPPORTS"/>
tx:attributes>
tx:advice>
<aop:config>
<aop:advisor advice-ref="txAdvice" pointcut="execution(* com.lagou.edu.service.impl.TransferServiceImpl.*(..))"/>
aop:config>
第二种:XML+注解
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource">property>
bean>
<tx:annotation-driven transaction-manager="transactionManager"/>
在接口、类或者方法上添加@Transactional注解
@Transactional(readOnly = true,propagation = Propagation.SUPPORTS)
第三种:注解
Spring基于注解驱动开发的事务控制配置,只是需要在Spring 的配置类上添加 @EnableTransactionManagement 注解,用以替换掉xml配置文件中的
@EnableTransactionManagement//开启spring注解事务的支持
public class SpringConfiguration {
}
##################################################################################################
@Component
public class LagouBean {
public void tech(){
System.out.println("java learning......");
}
}
@Component
@Aspect
public class LagouAspect {
@Pointcut("execution(* com.lagou.*.*(..))")
public void pointcut(){
}
@Before("pointcut()")
public void before() {
System.out.println("before method ......");
}
}
/**
* 测试用例:Aop 代理对象创建
*/
@Test
public void testAopProxyBuild(){
ApplicationContext applicationContext = new AnnotationConfigApplicationContext(SpringConfig.class);
LagouBean lagouBean = applicationContext.getBean(LagouBean.class);
lagouBean.tech();
}
我们发现在 getBean 之前,LagouBean对象已经产生(即在第一行初始化代码中完成),而且该对象 是一个代理对象(Cglib代理对象),我们断定,容器初始化过程中目标Ban已经完成了代理,返回了代 理对象。
流程就是用AopProxyFactory创建AopProxy, 再用AopProxy创建代理对象,这里的AopProxyFactory默 认是DefaultAopProxyFactory,看他的createAopProxy方法
2、Spring声明式事务控制
@EnableTransactionManagement
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(TransactionManagementConfigurationSelector.class)
public @interface EnableTransactionManagement {
}
TransactionManagementConfigurationSelector类,这个类又向容器中导入了两个重要的组件
AutoProxyRegistrar
发现最终,注册了一个叫做 InfrastructureAdvisorAutoProxyCreator 的 Bean, 而这个类是 AbstractAutoProxyCreator 的子类,实现了 SmartInstantiationAwareBeanPostProcessor 接口
它实现了SmartInstantiationAwareBeanPostProcessor,说明这是一个后置处理器,而且跟 spring AOP 开启@EnableAspectJAutoProxy 时注册的 AnnotationAwareAspectJProxyCreator实现的是同一个接口,所以说,声明式事务是 springAOP 思想的一种应用
ProxyTransactionManagementConfiguration
ProxyTransactionManagementConfiguration是一个容器配置类,注册了一个组件 transactionAdvisor,称为事务增强器,然后在这个事务增强器中又注入了两个属性: 属性解析器 transactionAttributeSource 和 事务拦截器 transactionInterceptor
属性解析器 AnnotationTransactionAttributeSource 部分源码
属性解析器有一个成员变量是annotationParsers,是一个集合,可以添加多种注解解析器 (TransactionAnnotationParser),我们关注 Spring 的注解解析器,部分源码如下
TransactionInterceptor 事务拦截器,部分源码
事务拦截器实现了MethodInterceptor接口,追溯一下上面提到的 InfrastructureAdvisorAutoProxyCreator后置处理器,它会在代理对象执行目标方法的时候 获取其拦截器链,而拦截器链就是这个TransactionInterceptor,这就把这两个组件联系起来;
构造方法传入PlatformTransactionManager(事务管理器)、TransactionAttributeSource(属 性解析器),但是追溯一下上面贴的ProxyTransactionManagementConfiguration的源码, 在注册事务拦截器的时候并没有调用这个带参构造方法,而是调用的无参构造方法,然后再 调用set方法注入这两个属性,效果一样。
invokeWithinTransaction 方法,部分源码
/**流程分析
@EnableTransactionManagement 注解
1)通过@import引入了TransactionManagementConfigurationSelector类
它的selectImports方法导入了另外两个类:AutoProxyRegistrar和ProxyTransactionManagementConfiguration
2)AutoProxyRegistrar类分析
方法registerBeanDefinitions中,引入了其他类,通过AopConfigUtils.registerAutoProxyCreatorIfNecessary(registry)引入
InfrastructureAdvisorAutoProxyCreator,它继承了AbstractAutoProxyCreator,是一个后置处理器类
3)ProxyTransactionManagementConfiguration 是一个添加了@Configuration注解的配置类
(注册bean)
注册事务增强器(注入属性解析器、事务拦截器)
属性解析器:AnnotationTransactionAttributeSource,内部持有了一个解析器集合Set annotationParsers; 具体使用的是SpringTransactionAnnotationParser解析器,用来解析@Transactional的事务属性
事务拦截器:TransactionInterceptor实现了MethodInterceptor接口,该通用拦截会在产生代理对象之前和aop增强合并,最终一起影响到代理对象
TransactionInterceptor的invoke方法中invokeWithinTransaction会触发原有业务逻辑调用(增强事务)
*/