spring--第一部分:自定义IoC&AOP框架
Spring 概述
Spring 简介
Spring 的优势
- ⽅便解耦,简化开发
- AOP编程的⽀持
- 声明式事务的⽀持
- ⽅便程序的测试
- ⽅便集成各种优秀框架
- 降低JavaEE API的使⽤难度
- 源码是经典的 Java 学习范例
Spring 的核⼼结构
核⼼思想
IoC
什么是IoC?
为什么叫做控制反转?
IoC解决了什么问题
IoC和DI的区别
AOP
什么是AOP
1)横切逻辑代码
2)横切逻辑代码存在什么问题
- 横切代码重复问题
- 横切逻辑代码和业务代码混杂在⼀起,代码臃肿,维护不⽅便
AOP在解决什么问题
为什么叫做⾯向切⾯编程
⼿写实现 IoC 和 AOP
转账工程案例:
后台:
1)Servlet:
package com.lagou.edu.servlet; import com.lagou.edu.factory.BeanFactory; import com.lagou.edu.pojo.Result; import com.lagou.edu.service.TransferService; import com.lagou.edu.service.impl.TransferServiceImpl; import com.lagou.edu.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; /** * @author */ @WebServlet(name="transferServlet",urlPatterns = "/transferServlet") public class TransferServlet extends HttpServlet { // 1. 实例化service层对象 private TransferService transferService = new TransferServiceImpl(); @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { doPost(req,resp); } @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { // 设置请求体的字符编码 req.setCharacterEncoding("UTF-8"); String fromCardNo = req.getParameter("fromCardNo"); String toCardNo = req.getParameter("toCardNo"); String moneyStr = req.getParameter("money"); int money = Integer.parseInt(moneyStr); Result result = new Result(); try { // 2. 调用service层方法 transferService.transfer(fromCardNo,toCardNo,money); result.setStatus("200"); } catch (Exception e) { e.printStackTrace(); result.setStatus("201"); result.setMessage(e.toString()); } // 响应 resp.setContentType("application/json;charset=utf-8"); resp.getWriter().print(JsonUtils.object2Json(result)); } }
2)Service、ServiceImpl:
package com.lagou.edu.service; /** * @author */ public interface TransferService { void transfer(String fromCardNo, String toCardNo, int money) throws Exception; }
package com.lagou.edu.service.impl; import com.lagou.edu.dao.AccountDao; import com.lagou.edu.dao.impl.JdbcAccountDaoImpl; import com.lagou.edu.factory.BeanFactory; import com.lagou.edu.pojo.Account; import com.lagou.edu.service.TransferService; import java.lang.reflect.AccessibleObject; /** * @author */ 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(to); // int c = 1/0; accountDao.updateAccountByCardNo(from); } }
3)Dao、DaoImpl:
package com.lagou.edu.service; /** * @author */ public interface TransferService { void transfer(String fromCardNo, String toCardNo, int money) throws Exception; }
package com.lagou.edu.service.impl; import com.lagou.edu.dao.AccountDao; import com.lagou.edu.dao.impl.JdbcAccountDaoImpl; import com.lagou.edu.factory.BeanFactory; import com.lagou.edu.pojo.Account; import com.lagou.edu.service.TransferService; import java.lang.reflect.AccessibleObject; /** * @author */ 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(to); // int c = 1/0; accountDao.updateAccountByCardNo(from); } }
4)pojo:
public class Account { private String cardNo; private String name; private int money; } public class Account { private String cardNo; private String name; private int money; }
5)util:
package com.lagou.edu.utils; import com.alibaba.druid.pool.DruidDataSource; /** * @author */ public class DruidUtils { private DruidUtils(){ } private static DruidDataSource druidDataSource = new DruidDataSource(); static { druidDataSource.setDriverClassName("com.mysql.jdbc.Driver"); druidDataSource.setUrl("jdbc:mysql://localhost:3306/bank"); druidDataSource.setUsername("root"); druidDataSource.setPassword("root"); } public static DruidDataSource getInstance() { return druidDataSource; } }
package com.lagou.edu.utils; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.JavaType; import com.fasterxml.jackson.databind.ObjectMapper; import java.util.List; /** * JSON工具类(使用的是jackson实现的) * @author */ public class JsonUtils { private static final ObjectMapper MAPPER = new ObjectMapper(); /** * 将对象转换成json字符串。 * @param data * @return */ public static String object2Json(Object data) { try { String string = MAPPER.writeValueAsString(data); return string; } catch (JsonProcessingException e) { e.printStackTrace(); } return null; } /** * 将json结果集转化为对象 * * @param jsonData json数据 * @param beanType 对象中的object类型 * @return */ public static <T> T json2Pojo(String jsonData, Class<T> beanType) { try { T t = MAPPER.readValue(jsonData, beanType); return t; } catch (Exception e) { e.printStackTrace(); } return null; } /** * 将json数据转换成pojo对象list * @param jsonData * @param beanType * @return */ public static <T>List<T> json2List(String jsonData, Class<T> beanType) { JavaType javaType = MAPPER.getTypeFactory().constructParametricType(List.class, beanType); try { List<T> list = MAPPER.readValue(jsonData, javaType); return list; } catch (Exception e) { e.printStackTrace(); } return null; } }
前台
web.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> </web-app>
index.html:
<!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>
数据库:
mysql
1、IOC实现
(1)问题⼀:在上述案例实现中,
service 层实现类在使⽤ dao 层对象时,直接在TransferServiceImpl 中通过 AccountDao accountDao = new JdbcAccountDaoImpl()
获得了 dao层对象,然⽽⼀个 new 关键字却将 TransferServiceImpl 和 dao 层具体的⼀个实现类JdbcAccountDaoImpl 耦合在了⼀起,
如果说技术架构发⽣⼀些变动,dao 层的实现要使⽤其它技术,
1)代码实现(工厂的方式):
bean.xml
<?xml version="1.0" encoding="UTF-8" ?> <!--跟标签beans,里面配置一个又一个的bean子标签,每一个bean子标签都代表一个类的配置--> <beans> <!--id标识对象,class是类的全限定类名--> <bean id="accountDao" class="com.lagou.edu.dao.impl.JdbcTemplateDaoImpl"> <property name="ConnectionUtils" ref="connectionUtils"/> </bean> <bean id="transferService" class="com.lagou.edu.service.impl.TransferServiceImpl"> </bean> </beans>
BeanFactory:
/* 工厂类,生产对象(使用反射技术) */ public class BeanFactory { /** * 任务一:读取解析xml,通过反射技术实例化对象并且存储待用(map集合) * 任务二:对外提供获取实例对象的接口(根据id获取) */ private static Map<String,Object> map = new HashMap<>(); // 存储对象 static { // 任务一:读取解析xml,通过反射技术实例化对象并且存储待用(map集合) // 加载xml InputStream resourceAsStream = BeanFactory.class.getClassLoader().getResourceAsStream("beans.xml"); // 解析xml SAXReader saxReader = new SAXReader(); try { Document document = saxReader.read(resourceAsStream); Element rootElement = document.getRootElement(); List<Element> beanList = rootElement.selectNodes("//bean"); for (int i = 0; i < beanList.size(); i++) { Element element = beanList.get(i); // 处理每个bean元素,获取到该元素的id 和 class 属性 String id = element.attributeValue("id"); // accountDao String clazz = element.attributeValue("class"); // com.lagou.edu.dao.impl.JdbcAccountDaoImpl // 通过反射技术实例化对象 Class<?> aClass = Class.forName(clazz); Object o = aClass.newInstance(); // 实例化之后的对象 // 存储到map中待用 map.put(id,o); } } catch (DocumentException e) { e.printStackTrace(); } catch (ClassNotFoundException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } catch (InstantiationException e) { e.printStackTrace(); } catch (InvocationTargetException e) { e.printStackTrace(); } } // 任务二:对外提供获取实例对象的接口(根据id获取) public static Object getBean(String id) { return map.get(id); } }
2)代码实现(代码中能否只声明所需实例的接⼝类型,不出现 new 也不出现⼯⼚类的字眼)
bean.xml:
<?xml version="1.0" encoding="UTF-8" ?> <!--跟标签beans,里面配置一个又一个的bean子标签,每一个bean子标签都代表一个类的配置--> <beans> <!--id标识对象,class是类的全限定类名--> <bean id="accountDao" class="com.lagou.edu.dao.impl.JdbcTemplateDaoImpl"> <property name="ConnectionUtils" ref="connectionUtils"/> </bean> <bean id="transferService" class="com.lagou.edu.service.impl.TransferServiceImpl"> <!--set+ name 之后锁定到传值的set方法了,通过反射技术可以调用该方法传入对应的值--> <property name="AccountDao" ref="accountDao"></property> </bean> </beans>
Beanfactory:
/** * * 工厂类,生产对象(使用反射技术) */ public class BeanFactory { /** * 任务一:读取解析xml,通过反射技术实例化对象并且存储待用(map集合) * 任务二:对外提供获取实例对象的接口(根据id获取) */ private static Map<String,Object> map = new HashMap<>(); // 存储对象 static { // 任务一:读取解析xml,通过反射技术实例化对象并且存储待用(map集合) // 加载xml InputStream resourceAsStream = BeanFactory.class.getClassLoader().getResourceAsStream("beans.xml"); // 解析xml SAXReader saxReader = new SAXReader(); try { Document document = saxReader.read(resourceAsStream); Element rootElement = document.getRootElement(); List<Element> beanList = rootElement.selectNodes("//bean"); for (int i = 0; i < beanList.size(); i++) { Element element = beanList.get(i); // 处理每个bean元素,获取到该元素的id 和 class 属性 String id = element.attributeValue("id"); // accountDao String clazz = element.attributeValue("class"); // com.lagou.edu.dao.impl.JdbcAccountDaoImpl // 通过反射技术实例化对象 Class<?> aClass = Class.forName(clazz); Object o = aClass.newInstance(); // 实例化之后的对象 // 存储到map中待用 map.put(id,o); } // 实例化完成之后维护对象的依赖关系,检查哪些对象需要传值进入,根据它的配置,我们传入相应的值 // 有property子元素的bean就有传值需求 List<Element> propertyList = rootElement.selectNodes("//property"); // 解析property,获取父元素 for (int i = 0; i < propertyList.size(); i++) { Element element = propertyList.get(i); //<property name="AccountDao" ref="accountDao"></property> String name = element.attributeValue("name"); String ref = element.attributeValue("ref"); // 找到当前需要被处理依赖关系的bean Element parent = element.getParent(); // 调用父元素对象的反射功能 String parentId = parent.attributeValue("id"); Object parentObject = map.get(parentId); // 遍历父对象中的所有方法,找到"set" + name Method[] methods = parentObject.getClass().getMethods(); for (int j = 0; j < methods.length; j++) { Method method = methods[j]; if(method.getName().equalsIgnoreCase("set" + name)) { // 该方法就是 setAccountDao(AccountDao accountDao) method.invoke(parentObject,map.get(ref)); } } // 把处理之后的parentObject重新放到map中 map.put(parentId,parentObject); } } catch (DocumentException e) { e.printStackTrace(); } catch (ClassNotFoundException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } catch (InstantiationException e) { e.printStackTrace(); } catch (InvocationTargetException e) { e.printStackTrace(); } } // 任务二:对外提供获取实例对象的接口(根据id获取) public static Object getBean(String id) { return map.get(id); } }
2、AOP实现
ConnectionUtils :从线程中获取连接,进行事务控制
package com.lagou.edu.utils; import java.sql.Connection; import java.sql.SQLException; /** */ public class ConnectionUtils { /*private ConnectionUtils() { } private static ConnectionUtils connectionUtils = new ConnectionUtils(); public static ConnectionUtils getInstance() { return 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; } }
import java.sql.SQLException; /** * * 事务管理器类:负责手动事务的开启、提交、回滚 */ public class TransactionManager { private ConnectionUtils connectionUtils; public void setConnectionUtils(ConnectionUtils connectionUtils) { this.connectionUtils = connectionUtils; } /*private TransactionManager(){ } private static TransactionManager transactionManager = new TransactionManager(); public static TransactionManager getInstance() { return transactionManager; }*/ // 开启手动事务控制 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(); } }
serviceImpl上添加事务
private AccountDao accountDao; // 构造函数传值/set方法传值 public void setAccountDao(AccountDao accountDao) { this.accountDao = accountDao; } @Override public void transfer(String fromCardNo, String toCardNo, int money) throws Exception { try{ // 开启事务(关闭事务的自动提交) TransactionManager.getInstance().beginTransaction(); Account from = accountDao.queryAccountByCardNo(fromCardNo); Account to = accountDao.queryAccountByCardNo(toCardNo); from.setMoney(from.getMoney()-money); to.setMoney(to.getMoney()+money); accountDao.updateAccountByCardNo(to); int c = 1/0; accountDao.updateAccountByCardNo(from); // 提交事务 TransactionManager.getInstance().commit(); }catch (Exception e) { e.printStackTrace(); // 回滚事务 TransactionManager.getInstance().rollback(); // 抛出异常便于上层servlet捕获 throw e; } }
问题:
servicelmpl许多 横切代码,如何保证代码的不重复?
动态代理
不使用代理: public interface Izufang { void zufang(); } @Override public void zufang() { System.out.println("我要租用一室一厅的房子"); }
JDK动态代理
public class ProxyFactory { //单例模式 private ProxyFactory() { } private static ProxyFactory proxyFactory = new ProxyFactory(); public static ProxyFactory getInstance(){ return proxyFactory; } /** * @Author: denghy * @Description: JDK动态代理 * @Param: [obj] * @return: porxy.ProxyFactory * @Date: 2021/3/1 */ public Object getJdkProxy(Object obj){ return Proxy.newProxyInstance(obj.getClass().getClassLoader(), obj.getClass().getInterfaces(), new InvocationHandler() { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { Object result = null; System.out.println("JDK动态代理前..........."); result = method.invoke(obj,args); System.out.println("JDK动态代理后..........."); return result; } }); } }
CGLIB动态代理
public class ProxyFactory { //单例模式 private ProxyFactory() { } private static ProxyFactory proxyFactory = new ProxyFactory(); public static ProxyFactory getInstance(){ return proxyFactory; } /** * @Author: denghy * @Description: JDK动态代理 * @Param: [obj] * @return: porxy.ProxyFactory * @Date: 2021/3/1 */ public Object getJdkProxy(Object obj){ return Proxy.newProxyInstance(obj.getClass().getClassLoader(), obj.getClass().getInterfaces(), new InvocationHandler() { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { Object result = null; System.out.println("JDK动态代理前..........."); result = method.invoke(obj,args); System.out.println("JDK动态代理后..........."); return result; } }); } /** * @Author: denghy * @Description: cgLib动态代理 * @Param: [obj] * @return: java.lang.Object * @Date: 2021/3/1 */ public Object getCglibProxy(Object obj){ return Enhancer.create(obj.getClass(), new MethodInterceptor() { @Override public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable { Object result = null; System.out.println("cglib前..........."); result = method.invoke(obj, objects); System.out.println("cglib后..........."); return result; } }); } }
JDK和CGLIB区别
java动态代理是利用反射机制生成一个实现代理接口的匿名类,在调用具体方法前调用InvokeHandler来处理。
而cglib动态代理是利用asm开源包,对代理对象类的class文件加载进来,通过修改其字节码生成子类来处理。
1、如果目标对象实现了接口,默认情况下会采用JDK的动态代理实现AOP
2、如果目标对象实现了接口,可以强制使用CGLIB实现AOP
3、如果目标对象没有实现了接口,必须采用CGLIB库,spring会自动在JDK动态代理和CGLIB之间转换
如何强制使用CGLIB实现AOP?
(1)添加CGLIB库,SPRING_HOME/cglib/*.jar
(2)在spring配置文件中加入<aop:aspectj-autoproxy proxy-target-class="true"/>
JDK动态代理和CGLIB字节码生成的区别?
(1)JDK动态代理只能对实现了接口的类生成代理,而不能针对类
(2)CGLIB是针对类实现代理,主要是对指定的类生成一个子类,覆盖其中的方法
因为是继承,所以该类或方法最好不要声明成final