关于spring框架,看这一篇就够了~~~
文章目录
- 一、Spring是什么?
- 1. Spring定义
- 2. Spring的优点
- 3. Spring的核心结构
- 4. Spring的核心思想
- 4.1 IOC和DI
- 4.2 AOP
- 二、IOC和AOP的案例实现
- 1. 案例需求
- 2. 案例实现
- 3. 案例问题
- 4. 解决思路
- 5. 代码改造
- 5.1手动添加自定义ioc容器
- 5.2 手动实现转账事务管理
- 三、使用Spring实现IOC和AOP的案例改造
- 1. IOC基础
- 1.1 BeanFactory和ApplicationContext的区别
- 1.2 基础使用
- 2. 案例改造
- 2.1 xml改造
- 2.2 xml + 注解改造 (在纯xml配置的基础上做修改,第三方jar包中定义的bean使用xml,自定义bean使用注解形式)
- 2.3 纯注解改造 (在xml + 注解 的工程上改造)
- 3. AOP基础
- 3.1 Aop基本介绍
- 3.2 基础使用
- 4. spring声明式事务
- 4.1 事务概念
- 4.2 事务的四大特性
- 4.3 事务的隔离级别
- 4.4 事务的传播行为
- 4.5 spring声明式事务是什么
- 5. 案例改造(添加spring事务管理)
- 5.1纯xml改造(在springioc的实现案例上改造)
- 5.2 xml + 注解改造
- 5.3 纯注解改造
- 总结
一、Spring是什么?
1. Spring定义
Spring 是分层的 full-stack(全栈) 轻量级开源框架,以 IoC 和 AOP 为内核,提供了展现层 Spring MVC 和业务层事务管理等众多的企业级应⽤技术,还能整合开源世界众多著名的第三方框架和类库,已 经成为使⽤最多的 Java EE 企业应⽤开源框架。
2. Spring的优点
- 避免硬编码造成代码过度耦合。用户不必再为单例模式类、属性文件解析等底层需求编写代码,专注于上层应用。
- AOP面向切面编程,支持横切逻辑插入
- 声明式事务支持,极大方便和灵活的进行事务管理
- 方便集成各种优秀框架
3. Spring的核心结构
Spring是⼀个分层⾮常清晰并且依赖关系、职责定位⾮常明确的轻量级框架,主要包括⼏个⼤模块:数 据处理模块、Web模块、AOP(Aspect Oriented Programming)/Aspects模块、Core Container模块 和 Test 模块,如下图所示,Spring依靠这些基本模块,实现了⼀个令⼈愉悦的融合了现有解决方案的零侵入的轻量级框架。
Spring核⼼容器(Core Container):容器是Spring框架最核⼼的部分,它管理着Spring应⽤中 bean的创建、配置和管理。在该模块中,包括了Spring bean⼯⼚,它为Spring提供了DI的功能。 基于bean⼯⼚,我们还会发现有多种Spring应用上下文的实现。所有的Spring模块都构建于核心容器之上。
⾯向切⾯编程(AOP)/Aspects:Spring对⾯向切⾯编程提供了丰富的⽀持。这个模块是Spring应用系统中面向切⾯开发的基础,与DI⼀样,AOP可以帮助应⽤对象解耦。
数据访问和集成(Data Access/Integration)
Spring的JDBC和DAO模块封装了⼤量样板代码,这样可以使得数据库代码变得简洁,也可以更专 注于我们的业务,还可以避免数据库资源释放失败⽽引起的问题。 另外,Spring AOP为数据访问 提供了事务管理服务,同时Spring还对ORM进⾏了集成,如Hibernate、MyBatis等。该模块由 JDBC、Transactions、ORM、OXM 和 JMS 等模块组成。
Web 该模块提供了SpringMVC框架给Web应⽤,还提供了多种构建和其它应⽤交互的远程调⽤⽅ 案。 SpringMVC框架在Web层提升了应⽤的松耦合⽔平。
Test 为了使得开发者能够很⽅便的进⾏测试,Spring提供了测试模块以致⼒于Spring应⽤的测 试。 通过该模块,Spring为使⽤Servlet、JNDI等编写单元测试提供了⼀系列的mock对象实现。
4. Spring的核心思想
4.1 IOC和DI
IOC和DI
IoC :Inversion of Control (控制反转/反转控制),注意它是⼀个技术思想,不是⼀个技术实现
- 描述的事情:Java开发领域对象的创建,管理的问题
- 和传统创建对象的方式对比
- 传统开发⽅式:⽐如类A依赖于类B,往往会在类A中new⼀个B的对象
- IoC思想下开发⽅式:我们不⽤⾃⼰去new对象了,⽽是由IoC容器(Spring框架)去帮助我们实例化对 象并且管理它,我们需要使⽤哪个对象,去问IoC容器要即可
DI:Dependancy Injection(依赖注⼊) ,指的是A对象依赖于B对象时,需要把B的实例对象注入给A
为什么使用IOC?
当我们需要使用对象时,向Spring的IOC容器获取即可,这样的好处是解决了对象之间的耦合问题。
- 当需要使用bean对象时,如果使用多态创建对象,接口对象指定自己new的对象,有强耦合,若需要修改,则需要修改每一处创建当前对象的源代码。
- 若使用IOC,则切换实现类只需要在IOC容器中替换注入的bean即可。
4.2 AOP
什么是AOP?
AOP: Aspect oriented Programming ⾯向切⾯编程/⾯向⽅⾯编程
AOP解决的是多个方法中相同的位置出现代码重复的问题。比如:需要查询多个方法的执行时间,需要在方法执行前记录时间,方法结束时记录时间。这种代码就被称为横切逻辑代码。类似可以插入横切逻辑的地方有如:方法开始时、结束时、返回时、异常时等。
为什么使用AOP?
在不改变原有业务逻辑情况下,增强横切逻辑代码,根本上解耦合,避免横切逻辑代码重复
二、IOC和AOP的案例实现
1. 案例需求
实现简单的转账案例,从付款账户减去转账金额,收款账户添加转账金额
2. 案例实现
转账界面搭建
代码:
<!doctype html> <html> <head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8"><title>转账汇款</title><script type="text/javascript" src="js/jquery-3.4.1.min.js"></script><style type="text/css">body {/*background-color:#00b38a;*/text-align:center;}.lp-login {position:absolute;width:500px;height:300px;top:50%;left:50%;margin-top:-250px;margin-left:-250px;background: #fff;border-radius: 4px;box-shadow: 0 0 10px #12a591;padding: 57px 50px 35px;box-sizing: border-box}.lp-login .submitBtn {display:block;text-decoration:none;height: 48px;width: 150px;line-height: 48px;font-size: 16px;color: #fff;text-align: center;background-image: -webkit-gradient(linear, left top, right top, from(#09cb9d), to(#02b389));background-image: linear-gradient(90deg, #09cb9d, #02b389);border-radius: 3px}input[type='text'] {height:30px;width:250px;}span {font-style: normal;font-variant-ligatures: normal;font-variant-caps: normal;font-variant-numeric: normal;font-variant-east-asian: normal;font-weight: normal;font-stretch: normal;font-size: 14px;line-height: 22px;font-family: "Hiragino Sans GB", "Microsoft Yahei", SimSun, Arial, "Helvetica Neue", Helvetica;}</style><script type="text/javascript">$(function(){$(".submitBtn").bind("click",function(){var fromAccount = $("#fromAccount").val();var toAccount = $("#toAccount").val();var money = $("#money").val();if(money == null || $.trim(money).length == 0){alert("sorry,必须输入转账金额~");return;}$.ajax({url:'/transferServlet',type:'POST', //GETasync:false, //或false,是否异步data:{fromCardNo:fromAccount.split(' ')[1],toCardNo:toAccount.split(' ')[1],money:money},timeout:5000, //超时时间dataType:'json', //返回的数据格式:json/xml/html/script/jsonp/textsuccess:function(data){if("200" == data.status){alert("转账成功~~~");}else{alert("转账失败~~~,message:" + data.message);}}})})})//检查输入值是否为整数function checkFormat(obj){var reg = /^[0-9]+[0-9]*]*$/;if($.trim($(obj).val()).length>0){if(!reg.test($(obj).val())){alert("输入格式错误!请输整数!");$(obj).val("");}else{$(obj).val(parseInt($(obj).val()));}}}</script> </head> <body><form><table class="lp-login"><tr><td align="right"><span>收款账户</span></td><td align="center"><input type="text" id="toAccount" value="韩梅梅 6029621011001" disabled></input></td></tr><tr><td align="right"><span>付款账户</span></td><td align="center"><input type="text" id="fromAccount" value="李大雷 6029621011000" disabled></input></td></tr><tr><td align="right"><span>转账金额</span></td><td align="center"><input type="text" id="money" onblur="checkFormat(this)"></input></td></tr><tr align="center"><td colspan="2"><a href="javasrcipt:void(0)" class="submitBtn"><span>转 出</span></a></td></tr></table> </form></body> </html>
注意:需要导入jquery包
创建数据库
创建maven的web工程,引入pom依赖
<!-- mysql数据库驱动包 --><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>5.1.35</version></dependency><!--druid连接池--><dependency><groupId>com.alibaba</groupId><artifactId>druid</artifactId><version>1.1.21</version></dependency><!-- servlet --><dependency><groupId>javax.servlet</groupId><artifactId>javax.servlet-api</artifactId><version>3.1.0</version><scope>provided</scope></dependency><!-- jackson依赖 --><dependency><groupId>com.fasterxml.jackson.core</groupId><artifactId>jackson-databind</artifactId><version>2.9.6</version></dependency>
创建pojo类,包含Account类 和 Result类,Account对应数据库的字段,Result类对应返回结果 和 异常时输出的信息
Account类:
package com.lb.pojo;public class Account {private String cardNo;private String name;private int money;public String getCardNo() {return cardNo;}public void setCardNo(String cardNo) {this.cardNo = cardNo;}public String getName() {return name;}public void setName(String name) {this.name = name;}public int getMoney() {return money;}public void setMoney(int money) {this.money = money;}@Overridepublic String toString() {return "Account{" +"cardNo='" + cardNo + '\'' +", name='" + name + '\'' +", money=" + money +'}';} }
Result类:
package com.lb.pojo;public class Result {private String status;private String message;public String getStatus() {return status;}public void setStatus(String status) {this.status = status;}public String getMessage() {return message;}public void setMessage(String message) {this.message = message;}@Overridepublic String toString() {return "Result{" +"status='" + status + '\'' +", message='" + message + '\'' +'}';} }
创建DruidUtils工具类,获取数据库连接
package com.lb.utils;import com.alibaba.druid.pool.DruidDataSource;public class DruidUtils {private DruidUtils(){}private static DruidDataSource druidDataSource = new DruidDataSource();static {druidDataSource.setDriverClassName("com.mysql.jdbc.Driver");druidDataSource.setUrl("jdbc:mysql:///bank");druidDataSource.setUsername("root");druidDataSource.setPassword("root");}public static DruidDataSource getInstance(){return druidDataSource;}}
创建AccountDao层接口 及 基于jdbc的实现类,实现通过卡号获取数据库中的钱数、通过卡号更新钱数
接口:
package com.lb.dao;import com.lb.pojo.Account;public interface AccountDao {Account queryAccountByCardNo(String cardNo) throws Exception;int updateAccountByCardNo(Account account) throws Exception; }
实现类:
package com.lb.dao.impl;import com.alibaba.druid.pool.DruidPooledConnection; import com.lb.dao.AccountDao; import com.lb.pojo.Account; import com.lb.utils.DruidUtils;import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet;public class JdbcAccountDaoImpl implements AccountDao {@Overridepublic Account queryAccountByCardNo(String cardNo) throws Exception {Connection connection = DruidUtils.getInstance().getConnection();String sql = "select * from account where cardNo = ?";PreparedStatement preparedStatement = connection.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();connection.close();return account;}@Overridepublic int updateAccountByCardNo(Account account) throws Exception {Connection connection = DruidUtils.getInstance().getConnection();String sql = "update account set money = ? where cardNo = ?";PreparedStatement preparedStatement = connection.prepareStatement(sql);preparedStatement.setInt(1, account.getMoney());preparedStatement.setString(2, account.getCardNo());int i = preparedStatement.executeUpdate();preparedStatement.close();connection.close();return i;} }
创建TransferService接口 及 实现类,实现转账的逻辑
接口:
package com.lb.service;public interface TransferService {public void transfer(String fromCardno, String toCardNo, int money) throws Exception;}
实现类:
package com.lb.service.impl;import com.lb.dao.AccountDao; import com.lb.dao.impl.JdbcAccountDaoImpl; import com.lb.pojo.Account; import com.lb.service.TransferService;public class TransferServiceImpl implements TransferService {private AccountDao accountDao = new JdbcAccountDaoImpl();@Overridepublic 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);} }
创建TransferServlet 及 json工具类JsonUtils ,实现获取界面输入的双方转账数目,通过调用TransferService的实现类实现转账功能
package com.lb.servlet;import com.lb.pojo.Result; import com.lb.service.TransferService; import com.lb.service.impl.TransferServiceImpl; import com.lb.utils.JsonUtils;import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException;@WebServlet(name = "transferServlet", urlPatterns = "/transferServlet") public class TransferServlet extends HttpServlet {private TransferService transferService = new TransferServiceImpl();@Overrideprotected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {this.doPost(req, resp);}@Overrideprotected 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 {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));} }
导入本地tomcat依赖,启动
3. 案例问题
- 问题1:service层使用dao层接口的实现类时,直接new了实现类对象,若需要修改dao层的实现类,必须修改源代码,不符合面向接口开发的最优原则
- 问题2:service层没有事务控制,若转账过程中发生异常,可能导致数据库数据错误
4. 解决思路
问题1解决:
将bean对象的实现类配置在xml文件中,结合工厂模式生产出保存所有bean的map容器进行统一管理,当程序启动时,容器就把对应的bean对象初始化管理起来。这样当要切换实现类时,只需要修改配置即可。
问题2解决:
- 将获取的数据库连接绑定到线程上,一个线程一个连接,用JDBC的Connection控制当前线程的事务执行。
- 使用动态代理,管理Service层的事务
5. 代码改造
5.1手动添加自定义ioc容器
添加dom4j、xpath表达式的依赖
<!--dom4j依赖--><dependency><groupId>dom4j</groupId><artifactId>dom4j</artifactId><version>1.6.1</version></dependency><!--xpath表达式依赖--><dependency><groupId>jaxen</groupId><artifactId>jaxen</artifactId><version>1.1.6</version></dependency>
添加beans.xml,注册bean的全限定类名、属性等信息
<?xml version="1.0" encoding="UTF-8" ?> <beans><bean id = "transferService" class = "com.lb.service.impl.TransferServiceImpl"><property name = "AccountDao" ref = "accountDao"></property></bean><bean id = "accountDao" class="com.lb.dao.impl.JdbcAccountDaoImpl"></bean></beans>
添加BeanFactory类,工厂方式实现bean对象的创建
package com.lb.factory;import org.dom4j.Document; import org.dom4j.DocumentException; import org.dom4j.Element; import org.dom4j.io.SAXReader;import java.io.IOException; import java.io.InputStream; import java.lang.reflect.Method; import java.util.HashMap; import java.util.List; import java.util.Map;public class BeanFactory {//1、加载解析xml,读取xml中的bean信息,通过反射技术实例化bean对象,然后放⼊map待⽤//2、提供接⼝⽅法根据id从map中获取beanprivate 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 classPath = element.attributeValue("class");Class<?> aClass = Class.forName(classPath);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())){//获取依赖的beanObject propertyObject = map.get(ref);//反射调用set方法,注入beanmethod.invoke(parentObject, propertyObject);}}//维护依赖关系后,重新将bean放入mapmap.put(parentId, parentObject);}} catch (Exception e) {e.printStackTrace();}}public static Object getBean(String id){return map.get(id);} }
修改TransferServlet,从BeanFactory获取所需bean对象
package com.lb.servlet;import com.lb.factory.BeanFactory; import com.lb.pojo.Result; import com.lb.service.TransferService; import com.lb.service.impl.TransferServiceImpl; import com.lb.utils.JsonUtils;import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException;@WebServlet(name = "transferServlet", urlPatterns = "/transferServlet") public class TransferServlet extends HttpServlet {//private TransferService transferService = new TransferServiceImpl();private TransferService transferService = (TransferService) BeanFactory.getBean("transferService");@Overrideprotected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {this.doPost(req, resp);}@Overrideprotected 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 {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));} }
修改TransferServiceImpl,通过set方法实现dao层实例注入
package com.lb.service.impl;import com.lb.dao.AccountDao; import com.lb.dao.impl.JdbcAccountDaoImpl; import com.lb.pojo.Account; import com.lb.service.TransferService;public class TransferServiceImpl implements TransferService {private AccountDao accountDao;public void setAccountDao(AccountDao accountDao) {this.accountDao = accountDao;}@Overridepublic 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);} }
5.2 手动实现转账事务管理
创建ConnectionUtils工具类,获取与当前线程绑定的数据库连接,用来事务管理。若当前线程存在,则直接获取;若不存在,则从连接池获取一个和当前线程绑定。
package com.lb.utils;import java.sql.Connection; import java.sql.SQLException;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;} }
创建事务管理类,封装事务相关的开启、提交、回滚操作
package com.lb.utils;import java.sql.SQLException;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();}}
创建代理工厂类,获取目标对象的动态代理
package com.lb.factory;import com.lb.utils.TransactionManager;import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy;public class ProxyFactory {private TransactionManager transactionManager;public void setTransactionManager(TransactionManager transactionManager) {this.transactionManager = transactionManager;}public Object getProxy(Object target){return Proxy.newProxyInstance(this.getClass().getClassLoader(), target.getClass().getInterfaces(), new InvocationHandler() {@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {Object res = null;try{//开启事务transactionManager.beginTransaction();//调用原方法res = method.invoke(target, args);//提交事务transactionManager.commit();}catch (Exception e){e.printStackTrace();//回滚事务transactionManager.rollback();//向上抛出异常,便于Servlet捕获throw e.getCause();}return res;}});}}
修改TransferServlet,从bean工厂获取代理工厂的bean,从代理工厂获取transferService的jdk动态代理对象
package com.lb.servlet;import com.lb.factory.BeanFactory; import com.lb.factory.ProxyFactory; import com.lb.pojo.Result; import com.lb.service.TransferService; import com.lb.service.impl.TransferServiceImpl; import com.lb.utils.JsonUtils;import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException;@WebServlet(name = "transferServlet", urlPatterns = "/transferServlet") public class TransferServlet extends HttpServlet {//private TransferService transferService = new TransferServiceImpl();//private TransferService transferService = (TransferService) BeanFactory.getBean("transferService");private ProxyFactory proxyFactory = (ProxyFactory) BeanFactory.getBean("proxyFactory");private TransferService transferService = (TransferService) proxyFactory.getProxy(BeanFactory.getBean("transferService"));@Overrideprotected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {this.doPost(req, resp);}@Overrideprotected 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 {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));} }
修改JdbcAccountDaoImpl,注入ConnectionUtils依赖,从ConnectionUtils获取当前线程绑定的数据库连接。连接需要复用,取消连接关闭操作
package com.lb.dao.impl;import com.alibaba.druid.pool.DruidPooledConnection; import com.lb.dao.AccountDao; import com.lb.pojo.Account; import com.lb.utils.ConnectionUtils; import com.lb.utils.DruidUtils;import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet;public class JdbcAccountDaoImpl implements AccountDao {private ConnectionUtils connectionUtils;public void setConnectionUtils(ConnectionUtils connectionUtils) {this.connectionUtils = connectionUtils;}@Overridepublic Account queryAccountByCardNo(String cardNo) throws Exception {//Connection connection = DruidUtils.getInstance().getConnection();Connection connection = connectionUtils.getCurrentThreadConn();String sql = "select * from account where cardNo = ?";PreparedStatement preparedStatement = connection.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();//connection.close();return account;}@Overridepublic int updateAccountByCardNo(Account account) throws Exception {//Connection connection = DruidUtils.getInstance().getConnection();Connection connection = connectionUtils.getCurrentThreadConn();String sql = "update account set money = ? where cardNo = ?";PreparedStatement preparedStatement = connection.prepareStatement(sql);preparedStatement.setInt(1, account.getMoney());preparedStatement.setString(2, account.getCardNo());int i = preparedStatement.executeUpdate();preparedStatement.close();//connection.close();return i;} }
修改beans.xml,添加ConnectionUtils、TransactionManager、ProxyFactory三个bean,并将ConnectionUtils的bean注入JdbcAccountDaoImpl
<?xml version="1.0" encoding="UTF-8" ?> <beans><bean id = "transferService" class = "com.lb.service.impl.TransferServiceImpl"><property name = "AccountDao" ref = "accountDao"></property></bean><bean id = "accountDao" class="com.lb.dao.impl.JdbcAccountDaoImpl"><property name="ConnectionUtils" ref="connectionUtils"></property></bean><!--三个新增bean--><bean id="connectionUtils" class="com.lb.utils.ConnectionUtils"></bean><bean id="transactionManager" class="com.lb.utils.TransactionManager"><property name="ConnectionUtils" ref="connectionUtils"></property></bean><bean id="proxyFactory" class="com.lb.factory.ProxyFactory"><property name="TransactionManager" ref="transactionManager"></property></bean> </beans>
三、使用Spring实现IOC和AOP的案例改造
1. IOC基础
1.1 BeanFactory和ApplicationContext的区别
BeanFactory是Spring框架中IoC容器的顶层接口,它只是⽤来定义⼀些基础功能,定义⼀些基础规范,而ApplicationContext是它的⼀个⼦接⼝,所以ApplicationContext是具备BeanFactory提供的全部功能的。
通常,我们称BeanFactory为SpringIOC的基础容器,ApplicationContext是容器的⾼级接口,比 BeanFactory要拥有更多的功能,比如说国际化支持和资源访问(xml,java配置类)等。
1.2 基础使用
引入spring的jar包依赖
<dependency><groupId>org.springframework</groupId><artifactId>spring-context</artifactId><version>5.1.12.RELEASE</version> </dependency>
添加applicationContext.xml,配置bean的三种方式:
纯xml配置:
添加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/beanshttps://www.springframework.org/schema/beans/spring-beans.xsd">
启动IOC容器的方式:
javaSE启动IOC容器,通过ClassPathXmlApplicationContext
ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("classpath:applicationContext.xml");
web环境下启动IOC容器,在web.xml中配置监听器,当监听器启动时,classpath下applicationContext.xml会被加载,从而获取对应的bean存入容器
从xml下启动:
<!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 Application</display-name><!--配置Spring ioc容器的配置⽂件--><context-param><param-name>contextConfigLocation</param-name><param-value>classpath:applicationContext.xml</param-value></context-param><!--使⽤监听器启动Spring的IOC容器--><listener><listenerclass>org.springframework.web.context.ContextLoaderListener</listenerclass></listener> </web-app>
从配置类下启动:
<!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 Application</display-name><!--告诉ContextloaderListener知道我们使⽤注解的⽅式启动ioc容器--><context-param><param-name>contextClass</param-name><paramvalue>org.springframework.web.context.support.AnnotationConfigWebAppli cationContext</param-value></context-param><!--配置启动类的全限定类名--><context-param><param-name>contextConfigLocation</param-name><param-value>com.lagou.edu.SpringConfig</param-value></context-param><!--使⽤监听器启动Spring的IOC容器--><listener><listenerclass>org.springframework.web.context.ContextLoaderListener</listenerclass></listener> </web-app>
对象实例化:
使用无参构造函数创建对象,只需要添加对应bean和id、class信息
添加DemoBean类:
public class DemoBean {private String name;private int id;public String getName() {return name;}public void setName(String name) {this.name = name;}public int getId() {return id;}public void setId(int id) {this.id = id;}@Overridepublic String toString() {return "DemoBean{" +"name='" + name + '\'' +", id=" + id +'}';} }
applicationContext.xml中添加bean标签:
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://www.springframework.org/schema/beanshttps://www.springframework.org/schema/beans/spring-beans.xsd"><bean id="demoBean" class="com.lb.pojo.DemoBean"></bean></beans>
使用静态方法创建,创建工厂类的bean,添加生产bean对象的静态方法,在工厂bean配置中添加factory-method配置生产bean的静态方法
添加生产bean的工厂类:
public class CreateBeanFactory {public static DemoBean getInstance(){return new DemoBean();} }
applicationContext.xml添加工厂bean的配置信息:
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://www.springframework.org/schema/beanshttps://www.springframework.org/schema/beans/spring-beans.xsd"><!-- <bean id="demoBean" class="com.lb.pojo.DemoBean"></bean>--><!--静态方法获取bean--><bean id="demoBean" class="com.lb.factory.CreateBeanFactory" factory-method="getInstance"></bean></beans>
应用场景:
在实际开发中,尤其早期的项⽬没有使⽤Spring框架来管理对象的创建,但是在设计时使⽤了 ⼯⼚模式 解耦,那么当接⼊spring之后,⼯⼚类创建对象就具有和上述例⼦相同特征,即可采⽤ 此种⽅式配置。
使用实例化方法创建,用于非静态方法创建bean对象,由于是非静态方法,需要先创建生产bean的对象,然后再创建bean
CreateBeanFactory类添加普通生产bean方法:
public class CreateBeanFactory {public static DemoBean getInstance(){return new DemoBean();}public DemoBean getInstance2(){return new DemoBean();} }
修改applicationContext.xml信息:
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://www.springframework.org/schema/beanshttps://www.springframework.org/schema/beans/spring-beans.xsd"><!-- <bean id="demoBean" class="com.lb.pojo.DemoBean"></bean>--><!--静态方法获取bean--><!-- <bean id="demoBean" class="com.lb.factory.CreateBeanFactory" factory-method="getInstance"></bean>--><!--实例化工厂类,然后创建对应的bean--><bean id="createBeanFactory" class="com.lb.factory.CreateBeanFactory"></bean><bean id="demoBean" factory-bean="createBeanFactory" factory-method="getInstance2"></bean></beans>
应用场景:
在早期开发的项⽬中,⼯⼚类中的⽅法有可能是静态的,也有可能是非静态⽅法,当是非静态方法时,即可 采⽤此配置方式。
bean对象的其他标签属性:
scope:singleton单例(注入IOC容器,生命周期由容器管理),prototype(多例,生命周期不受容器管理)
创建SingletonBean类,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/beanshttps://www.springframework.org/schema/beans/spring-beans.xsd"><!-- <bean id="demoBean" class="com.lb.pojo.DemoBean"></bean>--><!--静态方法获取bean--><!-- <bean id="demoBean" class="com.lb.factory.CreateBeanFactory" factory-method="getInstance"></bean>--><!--实例化工厂类,然后创建对应的bean--><bean id="createBeanFactory" class="com.lb.factory.CreateBeanFactory"></bean><bean id="demoBean" factory-bean="createBeanFactory" factory-method="getInstance2"></bean><bean id="singletonBean" class="com.lb.pojo.SingletonBean" scope="singleton"/></beans>
init-method/destory-method:bean的初始化时 和 销毁时 执行的方法,需注意只有scope为singleton时才会执行destory属性指定的方法
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/beanshttps://www.springframework.org/schema/beans/spring-beans.xsd"><!-- <bean id="demoBean" class="com.lb.pojo.DemoBean"></bean>--><!--静态方法获取bean--><!-- <bean id="demoBean" class="com.lb.factory.CreateBeanFactory" factory-method="getInstance"></bean>--><!--实例化工厂类,然后创建对应的bean--><bean id="createBeanFactory" class="com.lb.factory.CreateBeanFactory"></bean><bean id="demoBean" factory-bean="createBeanFactory" factory-method="getInstance2"></bean><bean id="singletonBean" class="com.lb.pojo.SingletonBean" scope="singleton" init-method="init" destroy-method="destory"/></beans>
测试代码:
public class IocTest {@Testpublic void test(){ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("classpath:applicationContext.xml");DemoBean demoBean = (DemoBean) applicationContext.getBean("demoBean");System.out.println(demoBean);applicationContext.close();} }
测试结果:
bean对象的依赖注入方式
构造函数注入
创建bean对象,生成带参构造函数
public class ConstructBean {private int id;private String name;private DemoBean demoBean;public ConstructBean(int id, String name, DemoBean demoBean) {this.id = id;this.name = name;this.demoBean = demoBean;}@Overridepublic String toString() {return "ConstructBean{" +"id=" + id +", name='" + name + '\'' +", demoBean=" + demoBean +'}';} }
在xml文件中对应的bean配置内添加constructor-arg配置,name为set方法后的参数名,ref为当注入是bean对象时所对应的唯一id,value为注入的基本数据类型或String的值
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/beanshttps://www.springframework.org/schema/beans/spring-beans.xsd"><!-- <bean id="demoBean" class="com.lb.pojo.DemoBean"></bean>--><!--静态方法获取bean--><!-- <bean id="demoBean" class="com.lb.factory.CreateBeanFactory" factory-method="getInstance"></bean>--><!--实例化工厂类,然后创建对应的bean--><bean id="createBeanFactory" class="com.lb.factory.CreateBeanFactory"></bean><bean id="demoBean" factory-bean="createBeanFactory" factory-method="getInstance2"></bean><bean id="singletonBean" class="com.lb.pojo.SingletonBean" scope="singleton" init-method="init" destroy-method="destory"/><!--构造函数注入属性--><bean id="constructBean" class="com.lb.pojo.ConstructBean"><constructor-arg name="id" value="1"/><constructor-arg name="name" value="zhangsan"/><constructor-arg name="demoBean" ref="demoBean"/></bean></beans>
测试结果:
set方法注入
在xml文件中对应的bean配置内添加property配置,name为set方法后的参数名,ref为当注入是bean对象时所对应的唯一id,value为注入的基本数据类型或String的值
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/beanshttps://www.springframework.org/schema/beans/spring-beans.xsd"><!-- <bean id="demoBean" class="com.lb.pojo.DemoBean"></bean>--><!--静态方法获取bean--><!-- <bean id="demoBean" class="com.lb.factory.CreateBeanFactory" factory-method="getInstance"></bean>--><!--实例化工厂类,然后创建对应的bean--><bean id="createBeanFactory" class="com.lb.factory.CreateBeanFactory"></bean><bean id="demoBean" factory-bean="createBeanFactory" factory-method="getInstance2"></bean><bean id="singletonBean" class="com.lb.pojo.SingletonBean" scope="singleton" init-method="init" destroy-method="destory"/><!--构造函数注入属性--><bean id="constructBean" class="com.lb.pojo.ConstructBean"><constructor-arg name="id" value="1"/><constructor-arg name="name" value="zhangsan"/><constructor-arg name="demoBean" ref="demoBean"/></bean><!--set方法注入属性--><bean id="setBean" class="com.lb.pojo.SetBean"><property name="id" value="2"></property><property name="name" value="lisi"></property><property name="demoBean" ref="demoBean"></property></bean></beans>
测试结果:
注入复杂数据类型 --> 集合 类型,包括List 和 Map两种
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/beanshttps://www.springframework.org/schema/beans/spring-beans.xsd"><!-- <bean id="demoBean" class="com.lb.pojo.DemoBean"></bean>--><!--静态方法获取bean--><!-- <bean id="demoBean" class="com.lb.factory.CreateBeanFactory" factory-method="getInstance"></bean>--><!--实例化工厂类,然后创建对应的bean--><bean id="createBeanFactory" class="com.lb.factory.CreateBeanFactory"></bean><bean id="demoBean" factory-bean="createBeanFactory" factory-method="getInstance2"></bean><bean id="singletonBean" class="com.lb.pojo.SingletonBean" scope="singleton" init-method="init" destroy-method="destory"/><!--构造函数注入属性--><bean id="constructBean" class="com.lb.pojo.ConstructBean"><constructor-arg name="id" value="1"/><constructor-arg name="name" value="zhangsan"/><constructor-arg name="demoBean" ref="demoBean"/></bean><!--set方法注入属性--><bean id="setBean" class="com.lb.pojo.SetBean"><property name="id" value="2"></property><property name="name" value="lisi"></property><property name="demoBean" ref="demoBean"></property></bean><!--复杂数据类型注入--><bean id="complexDataBean" class="com.lb.pojo.ComplexDataBean"><property name="list"><array><value>1</value><value>2</value><value>3</value></array></property><property name="set"><set><value>set1</value><value>set2</value><value>set3</value></set></property><property name="map"><map><entry key="key1" value="1"></entry><entry key="key2" value="2"></entry></map></property><property name="properties"><props><prop key="prop1">value1</prop><prop key="prop2">value2</prop></props></property></bean></beans>
测试:
xml + 注解(springIOC容器的启动仍从加载xml开始;第三方jar中的bean定义在xml,比如德鲁伊数据库连接池,自定定义的bean用注解):
xml中标签与注解的对应关系;
xml标签 对应注解形式 bean标签 @Component(“accountDao”),注解加在类上 bean的id属性内容直接配置在注解后⾯如果不配置,默认定义个这个bean的id为类的类名首字母小写; 另外,针对分层代码开发提供了@Componenet的三种别名@Controller、 @Service、@Repository分别⽤于控制层类、服务层类、dao层类的bean定义,这 四个注解的用法完全⼀样,只是为了更清晰的区分⽽已 标签的 scope属 性 @Scope(“prototype”),默认单例,注解加在类上 标签的 initmethod 属性 @PostConstruct,注解加在⽅法上,该⽅法就是初始化后调⽤的⽅法 标签的 destorymethod 属性 @PreDestory,注解加在⽅法上,该⽅法就是销毁前调⽤的⽅法 创建一个使用注解的类:
@Component("demoBean1") @Scope("singleton") public class AnnoDemoBean {@PostConstructpublic void init(){System.out.println("PostConstuct...");}@PreDestroypublic void destory(){System.out.println("PreDestory...");} }
测试代码:
AnnotationConfigApplicationContext applicationContext1 = new AnnotationConfigApplicationContext("com.lb");DemoBean demoBean = (DemoBean) applicationContext1.getBean("demoBean"); System.out.println(demoBean); applicationContext1.close();
测试结果:
创建一个多例对象,测试每次生成实例是否为同一个。
多例类代码:
@Component("prototypeBean") @Scope("prototype") public class PrototypeBean {}
测试代码:
PrototypeBean prototypeBean1 = (PrototypeBean) applicationContext1.getBean("prototypeBean"); System.out.println(prototypeBean1); PrototypeBean prototypeBean2 = (PrototypeBean) applicationContext1.getBean("prototypeBean"); System.out.println(prototypeBean2);
测试结果:
依赖注入的实现方式1:@AutoWired --> 按照类型注入,若同一类型有多个bean值时,需要配合@Qualifier指定id来告诉Spring具体装配哪个对象。
创建接口类及其实现类
public interface FlyAble {public void fly(); }
@Component public class Bird implements FlyAble {@Overridepublic void fly() {System.out.println("鸟在天空飞...");} }
@Component public class Plane implements FlyAble {@Overridepublic void fly() {System.out.println("飞机在天空飞...");} }
创建demo类自动注入接口类,当不指定具体哪个实现时:
@Component public class AutoWiredDemo {@Autowiredpublic FlyAble flyAble;}
测试结果
当指定具体实现时:
@Component public class AutoWiredDemo {@Autowired@Qualifier("plane")public FlyAble flyAble;}
测试代码:
AutoWiredDemo autoWiredDemo = (AutoWiredDemo) applicationContext1.getBean("autoWiredDemo"); autoWiredDemo.flyAble.fly();
测试结果:
依赖注入的实现方式2:@Resource (在 Jdk 11中已经移除,如果要使⽤,需要单独引⼊jar包
)
- 如果同时指定了 name 和 type,则从Spring上下⽂中找到唯⼀匹配的bean进⾏装配,找不 到则抛出异常。
- 如果指定了 name,则从上下⽂中查找名称(id)匹配的bean进⾏装配,找不到则抛出异 常。
- 如果指定了 type,则从上下⽂中找到类似匹配的唯⼀bean进⾏装配,找不到或是找到多个, 都会抛出异常。
- 如果既没有指定name,⼜没有指定type,则⾃动按照byName⽅式进⾏装配。
测试类代码:
@Component public class ResourceDemo {@Resource(name = "bird", type = FlyAble.class)public FlyAble flyAble; }
测试代码:
ResourceDemo resourceDemo = (ResourceDemo) applicationContext1.getBean("resourceDemo"); resourceDemo.flyAble.fly();
测试结果:
纯注解方式:
- 从配置类启动容器,在web中需要使用监听器启动IOC容器,告诉监听器使用注解方式启动IOC容器,配置启动类的全限定类名
- 根据注解 和 xml中标签的对应关系,去除applicationContext.xml文件,改为注解配置
2. 案例改造
引入spring的jar包依赖
<!--引入Spring IoC容器功能-->
<dependency><groupId>org.springframework</groupId><artifactId>spring-context</artifactId><version>5.1.12.RELEASE</version>
</dependency>
<!--引入spring web功能-->
<dependency><groupId>org.springframework</groupId><artifactId>spring-web</artifactId><version>5.1.12.RELEASE</version>
</dependency>
2.1 xml改造
去除BeanFactory类,所有bean从Spring容器中获取
改造web.xml,使用监听器启动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 Application</display-name><!--配置SpringIoc容器的配置文件--><context-param><param-name>contextConfigLocation</param-name><param-value>classpath:applicationContext.xml</param-value></context-param><!--监听器启动IOC容器--><listener><listener-class>org.springframework.web.context.ContextLoaderListener</listener-class></listener></web-app>
将beans.xml 改名为 applicationContext.xml,并添加标签头
<?xml version="1.0" encoding="UTF-8" ?> <beans xmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://www.springframework.org/schema/beanshttps://www.springframework.org/schema/beans/spring-beans.xsd "><bean id = "transferService" class = "com.lb.service.impl.TransferServiceImpl"><property name = "AccountDao" ref = "accountDao"></property></bean><bean id = "accountDao" class="com.lb.dao.impl.JdbcAccountDaoImpl"><property name="ConnectionUtils" ref="connectionUtils"></property></bean><!--三个新增bean--><bean id="connectionUtils" class="com.lb.utils.ConnectionUtils"></bean><bean id="transactionManager" class="com.lb.utils.TransactionManager"><property name="ConnectionUtils" ref="connectionUtils"></property></bean><bean id="proxyFactory" class="com.lb.factory.ProxyFactory"><property name="TransactionManager" ref="transactionManager"></property></bean> </beans>
修改TransferServlet,重写init方法,web容器启动时,init方法会执行一次,此时可以从spring容器中获取TransferService实例。
@WebServlet(name = "transferServlet", urlPatterns = "/transferServlet")
public class TransferServlet extends HttpServlet {//private TransferService transferService = new TransferServiceImpl();//private TransferService transferService = (TransferService) BeanFactory.getBean("transferService");/* private ProxyFactory proxyFactory = (ProxyFactory) BeanFactory.getBean("proxyFactory");private TransferService transferService = (TransferService) proxyFactory.getProxy(BeanFactory.getBean("transferService"));*/private TransferService transferService;@Overridepublic void init() throws ServletException {WebApplicationContext webApplicationContext = WebApplicationContextUtils.getWebApplicationContext(this.getServletContext());transferService = (TransferService) webApplicationContext.getBean("transferService");}@Overrideprotected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {this.doPost(req, resp);}@Overrideprotected 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 {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));}
}
2.2 xml + 注解改造 (在纯xml配置的基础上做修改,第三方jar包中定义的bean使用xml,自定义bean使用注解形式)
开启注解扫描,扫描指定包下是否有springbean需要生成
<!--开启注解扫描,base-package指定扫描的包路径--><context:component-scan base-package="com.lb"/>
第三方jar包的bean只有Druid连接池,需要引入外部资源文件,定义数据库的相关属性,再将第三方jar中的bean定义在xml中
<!--引入外部资源文件--><context:property-placeholder location="classpath:jdbc.properties"/><!--第三方jar中的bean定义在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>
对应applicationContext.xml中自定义bean,添加注解
2.3 纯注解改造 (在xml + 注解 的工程上改造)
对应applicationContext.xml,创建配置类,配置第三方bean,然后删除applicationContext.xml
@Configuration @ComponentScan("com.lb") @PropertySource({"classpath:jdbc.properties"}) public class DataSourceConfig {@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 dataSource(){DruidDataSource dataSource = new DruidDataSource();dataSource.setDriverClassName(driverClassName);dataSource.setUrl(url);dataSource.setUsername(username);dataSource.setPassword(password);return dataSource;}}
修改web.xml,告诉监听器使用注解的方式启动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 Application</display-name><!--告诉ContextloaderListener知道我们使用注解的方式启动ioc容器--><context-param><param-name>contextClass</param-name><param-value>org.springframework.web.context.support.AnnotationConfigWebApplicationContext</param-value></context-param><!--配置启动类的全限定类名--><context-param><param-name>contextConfigLocation</param-name><param-value>com.lb.config.DataSourceConfig</param-value></context-param><!--监听器启动IOC容器--><listener><listener-class>org.springframework.web.context.ContextLoaderListener</listener-class></listener></web-app>
3. AOP基础
3.1 Aop基本介绍
在SpringAop中包含切入点,方位点和横切逻辑,此三部分可以锁定在哪个地方插入什么横切逻辑代码。
在xml配置文件中的对应关系
关于切入点表达式的说明(指横切逻辑需要插入的具体哪个方法)
表达式语法说明
示例:public * com.lb.dao.impl.UserDaoImpl.findUserById(java.lang.Integer))
访问修饰符 | 返回值 | 包名 | 类名和方法名 | 参数列表 |
---|---|---|---|---|
可省略 | 返回值可以使⽤*,表示任意返回值 | 1、 包名可以使⽤.表示任意包,但是有⼏级包,必须写⼏个点 2、 包名可以使⽤…表示当前包及其⼦包 | 类名和⽅法名,都可以使⽤.表示任意类,任意⽅法 | 参数列表,可以使⽤具体类型 基本类型直接写类型名称 : int 引⽤类型必须写全限定类名:java.lang.String 参数列表可以使⽤*,表示任意参数类型,但是必须有参数 参数还可以使用…,表示有无参数均可,有参数可以是任意类型 |
关于SpringAop的代理方式
Spring 实现AOP思想使⽤的是动态代理技术。默认情况下,Spring会根据被代理对象是否实现接口来选择使⽤JDK还是CGLIB。当被代理对象没有实现 任何接⼝时,Spring会选择CGLIB。当被代理对象实现了接⼝,Spring会选择JDK官⽅的代理技术,不过 我们可以通过配置的⽅式,让Spring强制使用CGLIB。
3.2 基础使用
引入aop相关jar包
纯xml实现AOP,创建测试相关代码
pojo类 及 查询User接口和查询实现类
User类
public class User {private int id; }
IUserDao接口
public interface IUserDao {public User findUserById(Integer id); }
UserDaoImpl实现类
public class UserDaoImpl implements IUserDao {@Overridepublic User findUserById(Integer id) {System.out.println("查找中...");//int i = 1/0;return new User();} }
横切逻辑类LogUtils
public class LogUtils {public void printLogBef(JoinPoint joinPoint){Object[] args = joinPoint.getArgs();System.out.println("前置通知,参数是:" + Arrays.toString(args));}public void printLogReturning(Object returnValue){System.out.println("正常返回通知,返回值是:" + returnValue);}public void printLogException(Throwable e){System.out.println("异常通知,异常是:" + e);}public void printLogFinally(){System.out.println("最终通知...");}//环绕通知可以随意在方法执行的任何时机定义横切逻辑public Object printLogAround(ProceedingJoinPoint proceedingJoinPoint){//定义返回值Object returnValue = null;try {System.out.println("前置通知...");//1、获取参数Object[] args = proceedingJoinPoint.getArgs();//2、执行切入点方法returnValue = proceedingJoinPoint.proceed();System.out.println("后置通知...");}catch (Throwable t){//异常通知System.out.println("异常通知");t.printStackTrace();}finally {//最终通知System.out.println("最终通知");}return returnValue;} }
applicationContext.xml配置文件编写
<beans xmlns="http://www.springframework.org/schema/beans"xmlns:context="http://www.springframework.org/schema/context"xmlns:aop="http://www.springframework.org/schema/aop"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://www.springframework.org/schema/beanshttps://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/contexthttps://www.springframework.org/schema/context/spring-context.xsdhttp://www.springframework.org/schema/aophttps://www.springframework.org/schema/aop/spring-aop.xsd"><bean id="userDao" class="com.lb.dao.impl.UserDaoImpl"></bean><!--注入横切逻辑类的bean--><bean id="logUtils" class="com.lb.utils.LogUtils"></bean><!--aop配置开始--><aop:config><!--配置横切逻辑类--><aop:aspect id="logAdvice" ref="logUtils"><!--配置前置通知--><aop:pointcut id="pt" expression="execution(public * com.lb.dao.impl.UserDaoImpl.findUserById(java.lang.Integer))"/><aop:before method="printLogBef" pointcut-ref="pt"></aop:before><!--配置正常返回通知--><aop:after-returning method="printLogReturning" returning="returnValue" pointcut-ref="pt"></aop:after-returning><!--配置异常通知--><aop:after-throwing method="printLogException" throwing="e" pointcut-ref="pt"></aop:after-throwing><!--配置返回前通知--><aop:after method="printLogFinally" pointcut-ref="pt"></aop:after><!--配置环绕通知--><!--<aop:around method="printLogAround" pointcut-ref="pt"></aop:around>--></aop:aspect></aop:config></beans>
测试代码
public class Aoptest {@Testpublic void test(){ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");IUserDao userDao = (IUserDao) applicationContext.getBean("userDao");userDao.findUserById(1);} }
正常测试
异常测试
环绕通知测试
spring在纯xml模式下配置强制使用cglib动态代理的方式(需导入cglib的jar包):
<aop:config proxy-target-class="true">
xml + 注解 实现AOP,修改相关代码
对应关系
在IUserDao中添加方法
public User findUserByIdXmlAnno(Integer id);
在UserDaoImpl中添加方法
@Override public User findUserByIdXmlAnno(Integer id) {System.out.println("查找中...");return new User(); }
添加横切逻辑类
@Component @Aspect public class LogUtilsXmlAnno {@Pointcut("execution(* com.lb.dao.impl.UserDaoImpl.findUserByIdXmlAnno(..))")public void pointcut(){}@Before("pointcut()")public void printLogBef(JoinPoint joinPoint){Object[] args = joinPoint.getArgs();System.out.println("前置通知,参数是:" + Arrays.toString(args));}@AfterReturning(value = "pointcut()", returning = "returnValue")public void printLogReturning(Object returnValue){System.out.println("正常返回通知,返回值是:" + returnValue);}@AfterThrowing(value = "pointcut()", throwing = "e")public void printLogException(Throwable e){System.out.println("异常通知,异常是:" + e);}@After("pointcut()")public void printLogFinally(){System.out.println("最终通知...");}//环绕通知可以随意在方法执行的任何时机定义横切逻辑@Around("pointcut()")public Object printLogAround(ProceedingJoinPoint proceedingJoinPoint){//定义返回值Object returnValue = null;try {System.out.println("前置通知...");//1、获取参数Object[] args = proceedingJoinPoint.getArgs();//2、执行切入点方法returnValue = proceedingJoinPoint.proceed();System.out.println("后置通知...");}catch (Throwable t){//异常通知System.out.println("异常通知");t.printStackTrace();}finally {//最终通知System.out.println("最终通知");}return returnValue;} }
XML 中开启 Spring 对注解 AOP 的⽀持,开启包扫描 ,注释掉其他配置,将UserDao用@Component注解标注
<!--开启spring对注解aop的⽀持--> <aop:aspectj-autoproxy/><context:component-scan base-package="com.lb"/>
测试代码:
@Test public void test2(){ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");IUserDao userDao = (IUserDao) applicationContext.getBean("userDao");userDao.findUserByIdXmlAnno(1); }
测试结果:
spring在xml+注解方式下配置强制使用cglib动态代理的方式(需导入cglib的jar包):
<aop:aspectj-autoproxy proxy-target-class="true"/>
纯注解 实现AOP,修改相关代码
使用纯注解开发,需要用注解替换掉applicationContext.xml中的注解支持的配置。
注释xml文件,创建SpringConfiguration 配置类
@Configuration @ComponentScan("com.lagou") @EnableAspectJAutoProxy //开启spring对注解AOP的⽀持 public class SpringConfiguration {}
测试代码:
@Testpublic void test3(){AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(SpringConfiguration.class);IUserDao userDao = (IUserDao) applicationContext.getBean("userDao");userDao.findUserByIdXmlAnno(1);}
测试结果:
spring在纯注解方式下配置强制使用cglib动态代理的方式(需导入cglib的jar包):
在配置类上@EnableAspectJAutoProxy注解后标注
@EnableAspectJAutoProxy(proxyTargetClass = true)
4. spring声明式事务
4.1 事务概念
事务指逻辑上的⼀组操作,组成这组操作的各个单元,要么全部成功,要么全部不成功。从⽽确保了数 据的准确与安全。
编程式事务:在业务代码中添加事务控制代码,这样的事务控制机制就叫做编程式事务
声明式事务:通过xml或者注解配置的⽅式达到事务控制的目的,叫做声明式事务
4.2 事务的四大特性
原子性(Atomicity):事务是一个不可分割的工作单位,事务中操作要么都发生,要么都不发生。
一致性(Consistency):事务必须使数据库从⼀个⼀致性状态变换到另外⼀个⼀致性状态
例如转账前A有1000,B有1000。转账后A+B也得是2000。
隔离性(Isolation): 事务的隔离性是多个⽤户并发访问数据库时,数据库为每⼀个⽤户开启的事务, 每个事务不能被其他事务的操作数据所干扰,多个并发事务之间要相互隔离。
比如:事务1给员⼯涨⼯资2000,但是事务1尚未被提交,员⼯发起事务2查询⼯资,发现⼯资涨了2000 块钱,读到了事务1尚未提交的数据(脏读)
持久性(Durability) :指⼀个事务⼀旦被提交,它对数据库中数据的改变就是永久性的,接下来即使数据库发⽣故障 也不应该对其有任何影响。
4.3 事务的隔离级别
错误情况
- 脏读:⼀个线程中的事务读到了另外⼀个线程中未提交的数据。
- 不可重复读:⼀个线程中的事务读到了另外⼀个线程中已经提交的update的数据(前后内容不⼀样)
- 虚读(幻读):⼀个线程中的事务读到了另外⼀个线程中已经提交的insert或者delete的数据(前后条数不⼀样)
对应的隔离级别
- Serializable(串⾏化): 都可避免
- Repeatable read(可重复读):可避免脏读、不可重复读情况的发⽣。
- Read committed(读已提交):可避免脏读情况发⽣。
- Read uncommitted(读未提交): 所有情况都无法避免
级别越高,效率越低
4.4 事务的传播行为
事务往往在service层进⾏控制,如果出现service层⽅法A调⽤了另外⼀个service层⽅法B,A和B⽅法本 身都已经被添加了事务控制,那么A调⽤B的时候,就需要进⾏事务的⼀些协商,这就叫做事务的传播行为。
A调⽤B,我们站在B的⻆度来观察来定义事务的传播⾏为
PROPAGATION_REQUIRED | 如果当前没有事务,就新建⼀个事务,如果已经存在⼀个事务中, 加⼊到这个事务中。这是最常⻅的选择。 |
---|---|
PROPAGATION_SUPPORTS | ⽀持当前事务,如果当前没有事务,就以⾮事务⽅式执⾏。 |
PROPAGATION_MANDATORY | 使⽤当前的事务,如果当前没有事务,就抛出异常。 |
PROPAGATION_REQUIRES_NEW | 新建事务,如果当前存在事务,把当前事务挂起。 |
PROPAGATION_NOT_SUPPORTED | 以⾮事务⽅式执⾏操作,如果当前存在事务,就把当前事务挂起。 |
PROPAGATION_NEVER | 以⾮事务⽅式执⾏,如果当前存在事务,则抛出异常。 |
PROPAGATION_NESTED | 如果当前存在事务,则在嵌套事务内执⾏。如果当前没有事务,则 执⾏与PROPAGATION_REQUIRED类似的操作。 |
4.5 spring声明式事务是什么
声明式事务要做的就是使⽤Aop(动态代 理)来将事务控制逻辑织⼊到业务代码
Spring不实现事务,只定义了事务实现的接口,具体实现由相应的jar包负责。
如下为Spring提供的事务接口:
public interface PlatformTransactionManager {TransactionStatus getTransaction(@Nullable TransactionDefinition var1) throws TransactionException;//提交事务void commit(TransactionStatus var1) throws TransactionException;//回滚事务void rollback(TransactionStatus var1) throws TransactionException;
}
举例:
- Spring有内置的一些具体策略:DataSourceTransactionManager , HibernateTransactionManager 等 等。
- 其他的策略:Spring JdbcTemplate(数据库操作⼯具)、Mybatis(mybatis-spring.jar)–> DataSourceTransactionManager
5. 案例改造(添加spring事务管理)
添加pom依赖
<!--spring aop的jar包支持-->
<dependency><groupId>org.springframework</groupId><artifactId>spring-aop</artifactId><version>5.1.12.RELEASE</version>
</dependency><!--第三方的aop框架aspectj的jar-->
<dependency><groupId>org.aspectj</groupId><artifactId>aspectjweaver</artifactId><version>1.8.13</version>
</dependency><!--引入spring声明式事务相关-->
<dependency><groupId>org.springframework</groupId><artifactId>spring-jdbc</artifactId><version>5.1.12.RELEASE</version>
</dependency>
<dependency><groupId>org.springframework</groupId><artifactId>spring-tx</artifactId><version>5.1.12.RELEASE</version>
</dependency>
5.1纯xml改造(在springioc的实现案例上改造)
删除ConnectionUtils、TransactionManager、ProxyFactory三个自定义事务相关的类
删除applicationContext.xml中相关的bean注入
从DruidUtils类中直接获取数据源dataSource的bean,使用静态方法获取
<bean id="dataSource" class="com.lb.utils.DruidUtils" factory-method="getInstance"></bean>
将accountDao中对ConnectionUtils的依赖注入 改为 直接注入dataSource
<bean id = "accountDao" class="com.lb.dao.impl.JdbcAccountDaoImpl"><property name="dataSource" ref="dataSource"></property> </bean>
修改JdbcAccountDaoImpl类代码,改为直接注入dataSource
public class JdbcAccountDaoImpl implements AccountDao {private DataSource dataSource;public void setDataSource(DataSource dataSource) {this.dataSource = dataSource;}@Overridepublic Account queryAccountByCardNo(String cardNo) throws Exception {Connection connection = dataSource.getConnection();String sql = "select * from account where cardNo = ?";PreparedStatement preparedStatement = connection.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();return account;}@Overridepublic int updateAccountByCardNo(Account account) throws Exception {//Connection connection = DruidUtils.getInstance().getConnection();Connection connection = dataSource.getConnection();String sql = "update account set money = ? where cardNo = ?";PreparedStatement preparedStatement = connection.prepareStatement(sql);preparedStatement.setInt(1, account.getMoney());preparedStatement.setString(2, account.getCardNo());int i = preparedStatement.executeUpdate();preparedStatement.close();//connection.close();return i;} }
applicationContext.xml中配置横切逻辑类(PlatformTransactionManager接口的实现类)、事务属性定义、然后配置aop属性,将事务的横切逻辑织入Service代码中做事务管理
<!--定义横切逻辑类--> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"><constructor-arg name="dataSource" ref="dataSource"></constructor-arg> </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.lb.service.impl.TransferServiceImpl.*(..))"/> </aop:config>
5.2 xml + 注解改造
在applicationContext.xml添加对注解事务的支持
<!--配置事务管理器的具体实现(横切逻辑类)--> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"><property name="dataSource" ref="dataSource"></property> </bean><!--开启spring对注解事务的支持--> <tx:annotation-driven transaction-manager="transactionManager"/>
在接口、类或者方法上添加@Transactional注解
@Transactional(readOnly = false, propagation = Propagation.REQUIRED)
5.3 纯注解改造
Spring基于注解驱动开发的事务控制配置,只需要把 xml 配置部分改为注解实现。只是需要⼀个注解替换掉xml配置⽂件中的配置。
在 Spring 的配置类上添加 @EnableTransactionManagement 注解即可
@EnableTransactionManagement//开启spring注解事务的⽀持
public class SpringConfiguration {}
总结
- Spring框架的核心思想就是IOC和AOP,主要解决了工程的对象创建和管理问题、横切逻辑代码重复问题。
- 本文从Spring的定义、基本特性、核心思想、简单案例实现、自定义IOC和AOP,再到Spring 的IOC和AOP使用介绍,最后到使用Spring进行案例改造,逐步推进,由浅入深,完成对Spring框架的学习。
- 关于spring的延迟加载、FactoryBean,可以查看下面两个博客:
spring的延迟加载介绍
spring关于FactoryBean的基本介绍
关于spring框架,看这一篇就够了~~~相关推荐
- Java 集合框架看这一篇就够了
点击上方 好好学java ,选择 星标 公众号 重磅资讯.干货,第一时间送达 今日推荐:都说了多少遍,不要再学 JSP 了!个人原创100W+访问量博客:点击前往,查看更多 话不多说,直接上图: Ja ...
- 学习Spring框架有这一篇就够了
目录 一.Spring框架 1.什么是Spring 2. 为什么使用spring 3.程序的耦合 4.解决办法IOC控制反转 二. 如何使用spring 1.Spring程序开发步骤 2.使用spri ...
- 不必东奔西走,Java 集合框架看这一篇就够了
Java 集合,也称作容器,主要是由两大接口 (Interface) 派生出来的:Collection 和 Map 顾名思义,容器就是用来存放数据的. 那么这两大接口的不同之处在于: Collecti ...
- 聊聊Java8之后的JDK升级内容(看这一篇就够了)
聊聊Java8之后的JDK升级内容(看这一篇就够了) 背景 从 JDK 8 到 JDK 17 的新特性 JDK8 回顾 JDK9 JDK10 JDK11 JDK12 JDK13 JDK14 JDK15 ...
- python装饰器功能是冒泡排序怎么做_传说中Python最难理解的点|看这完篇就够了(装饰器)...
https://mp.weixin.qq.com/s/B6pEZLrayqzJfMtLqiAfpQ 1.什么是装饰器 网上有人是这么评价装饰器的,我觉得写的很有趣,比喻的很形象 每个人都有的内裤主要是 ...
- 如何应对大数据分析工程师面试Spark考察,看这一篇就够了
作者丨斌迪.HappyMint 来源丨大数据与人工智能(ID:ai-big-data) [导读]本篇文章为大家带来spark面试指南,文内会有两种题型,问答题和代码题,题目大部分来自于网络上,有小部分 ...
- Fortran保姆级教学——考试所有知识点看这一篇就够了
Fortran保姆级教学--考试所有知识点看这一篇就够了 临近期末本人复习的同时将整个fortran课堂知识整理了下来,希望学弟学妹们今后学这门课的时候不至于在csdn找不到系统的教程,也希望能帮到需 ...
- 17万字 JUC 看这一篇就够了(三) (精华)
今天我们继续来学习Java并发编程 Juc框架 ,把剩余部分学习完 17万字 JUC 看这一篇就够了(一) (精华) 17万字 JUC 看这一篇就够了(二) (精华) 文章目录 非公原理 加锁 解锁 ...
- 面试被问到 ConcurrentHashMap答不出 ,看这一篇就够了!
本文汇总了常考的 ConcurrentHashMap 面试题,面试 ConcurrentHashMap,看这一篇就够了!为帮助大家高效复习,专门用"★ "表示面试中出现的频率,&q ...
- 阿里大师总结的Web安全超全知识点,看这一篇就够了
安全是互联网公司的生命,也是每一位网民的基本需求. 但根据<2021上半年中国互联网安全报告>,我国Web攻击.恶意爬虫攻击量连年翻倍增长,Web安全根本不能得到正常保障. 由此可见,We ...
最新文章
- 兵团教师计算机水平考试免考条件,兵团职称计算机考试政策.doc
- python循环生成二维数组_嵌套循环二维数组的计算与构造 - python
- python测试代码怎么写_Python 单元测试
- 如何学习开源系统有感(一)
- docker: docker安装和镜像下载
- java 手动线程调度_Java Thread 多线程 操作线程
- Docker仓库搭建
- 如何在 Eclipse 中使用命令行
- linux下进程调度模拟程序,linux认证辅导:linux进程调度模拟怎么做?
- leetCode 204. Count Primes 哈希 求素数
- 2021年中国超声波织物切割机市场趋势报告、技术动态创新及2027年市场预测
- 使用LIstView和自定义Adapter完成列表信息显示
- matlab中结构体使用方法
- osgEarth 加载矢量shp数据
- 基于微信小程序的记账系统
- JavaScript debugger调试
- [完全图解].NET Croe 使用JWT验证签名
- 小米路由器开启DDNS并支持二级路由
- Windows技巧:右键文件打开方式,该文件没有与之关联来执行该操作
- 3D-3D:ICP_SVD