文章目录
一、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', //GET async:false, //或false,是否异步 data:{ fromCardNo:fromAccount.split(' ')[1], toCardNo:toAccount.split(' ')[1], money:money }, timeout:5000, //超时时间 dataType:'json', //返回的数据格式:json/xml/html/script/jsonp/text success: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; } @Override public 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; } @Override public 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 { @Override public 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; } @Override public 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(); @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); } }
-
创建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(); @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { this.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 { 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中获取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 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())){ //获取依赖的bean Object propertyObject = map.get(ref); //反射调用set方法,注入bean method.invoke(parentObject, propertyObject); } } //维护依赖关系后,重新将bean放入map map.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"); @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { this.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 { 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; } @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); } }
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() { @Override public 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")); @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { this.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 { 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; } @Override public 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; } @Override public 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/beans https://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; } @Override public 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/beans https://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/beans https://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/beans https://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/beans https://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/beans https://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 { @Test public 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; } @Override public 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/beans https://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/beans https://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/beans https://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 { @PostConstruct public void init(){ System.out.println("PostConstuct..."); } @PreDestroy public 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 { @Override public void fly() { System.out.println("鸟在天空飞..."); } }
@Component public class Plane implements FlyAble { @Override public void fly() { System.out.println("飞机在天空飞..."); } }
创建demo类自动注入接口类,当不指定具体哪个实现时:
@Component public class AutoWiredDemo { @Autowired public 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/beans https://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;
@Override
public void init() throws ServletException {
WebApplicationContext webApplicationContext = WebApplicationContextUtils.getWebApplicationContext(this.getServletContext());
transferService = (TransferService) webApplicationContext.getBean("transferService");
}
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
this.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 {
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 { @Override public 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/beans https://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd "> <bean id="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 { @Test public 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 { }
测试代码:
@Test public 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; } @Override public 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; } @Override public 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的基本介绍