1. 转账案例问题分析

转账案例问题分析.png

2. new关键字耦合分析

new关键字耦合问题分析.png

3. 手写IOC/DI

3.1 编写xml文件

  1. xml文件主要是用来把需要用的类配置成一个个bean,bean的属性有id:唯一标识;有class:类的全限定类名,用来通过反射获取类的实例。property的属性:name是用来匹配TransferServiceImpl这个类中需要注入值的方法,也就是setAccountDao()方法,ref:注入的值,通过这个id获取到JdbcAccountDaoImpl类来注入
  2. 代码如下 ```xml <?xml version=”1.0” encoding=”UTF-8” ?>

  1. <a name="twtJ6"></a>
  2. ## 3.2 编写BeanFactory
  3. 1. 通过dom4j和xpath来解析xml文件,获取每一个bean,然后再解析bean里的属性,获取id作为唯一标识,获取class的值来通过反射得到类的实例,然后id为key,类实例为value存入到Map中,该Map其实就是相当于IOC容器(准确来说是IOC容器里的单例池)。
  4. 2. 获取到每一个property标签,解析出name和ref,再通过父标签从map中获取到类实例,"set" + name就是类实例中需要注入值的方法,注入的值从map中获取,key就是ref的值。这就是依赖注入。
  5. 3. 在TransferServiceImpl类中注入JdbcAccountDaoImpl,只需要写一个setAccountDao(JdbcAccountDaoImpl accountDao)方法,BeanFactory就会解析到这个方法,然后通过动态代理把配置的ref标识对应的类注入进去。
  6. 4. 代码如下:
  7. ```java
  8. import org.dom4j.Document;
  9. import org.dom4j.DocumentException;
  10. import org.dom4j.Element;
  11. import org.dom4j.io.SAXReader;
  12. import java.io.InputStream;
  13. import java.lang.reflect.InvocationTargetException;
  14. import java.lang.reflect.Method;
  15. import java.util.HashMap;
  16. import java.util.List;
  17. import java.util.Map;
  18. public class BeanFactory {
  19. /**
  20. * ⼯⼚类的两个任务
  21. * 任务⼀:加载解析xml,读取xml中的bean信息,通过反射技术实例化bean对象,然后放⼊
  22. * map待⽤
  23. * 任务⼆:提供接⼝⽅法根据id从map中获取bean(静态⽅法)
  24. */
  25. private static Map<String,Object> map = new HashMap<>();
  26. static {
  27. InputStream resourceAsStream =
  28. BeanFactory.class.getClassLoader().getResourceAsStream("beans.xml");
  29. SAXReader saxReader = new SAXReader();
  30. try {
  31. Document document = saxReader.read(resourceAsStream);
  32. Element rootElement = document.getRootElement();
  33. List<Element> list = rootElement.selectNodes("//bean");
  34. // 实例化bean对象
  35. for (int i = 0; i < list.size(); i++) {
  36. Element element = list.get(i);
  37. String id = element.attributeValue("id");
  38. String clazz = element.attributeValue("class");
  39. Class<?> aClass = Class.forName(clazz);
  40. Object o = aClass.newInstance();
  41. map.put(id,o);
  42. }
  43. // 维护bean之间的依赖关系
  44. List<Element> propertyNodes = rootElement.selectNodes("//property");
  45. for (int i = 0; i < propertyNodes.size(); i++) {
  46. Element element = propertyNodes.get(i);
  47. // 处理property元素
  48. String name = element.attributeValue("name");
  49. String ref = element.attributeValue("ref");
  50. String parentId = element.getParent().attributeValue("id");
  51. Object parentObject = map.get(parentId);
  52. Method[] methods = parentObject.getClass().getMethods();
  53. for (int j = 0; j < methods.length; j++) {
  54. Method method = methods[j];
  55. if(("set" + name).equalsIgnoreCase(method.getName())) {
  56. // bean之间的依赖关系(注⼊bean)
  57. Object propertyObject = map.get(ref);
  58. method.invoke(parentObject,propertyObject);
  59. }
  60. }
  61. // 维护依赖关系后重新将bean放⼊map中
  62. map.put(parentId,parentObject);
  63. }
  64. } catch (DocumentException e) {
  65. e.printStackTrace();
  66. } catch (ClassNotFoundException e) {
  67. e.printStackTrace();
  68. } catch (IllegalAccessException e) {
  69. e.printStackTrace();
  70. } catch (InstantiationException e) {
  71. e.printStackTrace();
  72. } catch (InvocationTargetException e) {
  73. e.printStackTrace();
  74. }
  75. }
  76. public static Object getBean(String id) {
  77. return map.get(id);
  78. }
  79. }

4. 事务控制(AOP)

4.1 service层事务问题分析

service层添加事务控制思路分析.png

4.2 解决

  1. 让同个线程的jdbc操作都绑定到同一个connection连接,就可以控制属于同一个事务了,编写ConnectionUtil类,代码如下 ```java import java.sql.Connection; import java.sql.SQLException;

public class ConnectionUtils { //单例模式(懒汉) private volatile static ConnectionUtils INSTANCE;

  1. private ConnectionUtils() {};
  2. public static ConnectionUtils getInstance() {
  3. if (INSTANCE == null) {
  4. synchronized (ConnectionUtils.class) {
  5. if (INSTANCE == null) {
  6. INSTANCE = new ConnectionUtils();
  7. }
  8. }
  9. }
  10. return INSTANCE;
  11. }
  12. // 存储当前线程的连接
  13. private ThreadLocal<Connection> threadLocal = new ThreadLocal<>();
  14. /**
  15. * 从当前线程获取连接
  16. */
  17. public Connection getCurrentThreadConn() throws SQLException {
  18. /**
  19. * 判断当前线程中是否已经绑定连接,如果没有绑定,需要从连接池获取⼀个连接绑定到
  20. * 当前线程
  21. */
  22. Connection connection = threadLocal.get();
  23. if(connection == null) {
  24. // 从连接池拿连接并绑定到线程
  25. connection = DruidUtils.getInstance().getConnection();
  26. // 绑定到当前线程
  27. threadLocal.set(connection);
  28. }
  29. return connection;
  30. }

}

  1. 2. 把事务控制在service层,首先在service方法中获取到数据库连接将事务自动提交关闭,等全部执行完成手动提交,如果出现异常则进行事务回滚。
  2. <a name="FiBJk"></a>
  3. ## 4.3 优化
  4. ![使用代理对象进行事务控制.png](https://cdn.nlark.com/yuque/0/2022/png/13010374/1648027364107-2c78606e-b5f4-435f-8027-8b586795425e.png#clientId=u8a3adf87-92b8-4&crop=0&crop=0&crop=1&crop=1&from=ui&id=uf1355267&margin=%5Bobject%20Object%5D&name=%E4%BD%BF%E7%94%A8%E4%BB%A3%E7%90%86%E5%AF%B9%E8%B1%A1%E8%BF%9B%E8%A1%8C%E4%BA%8B%E5%8A%A1%E6%8E%A7%E5%88%B6.png&originHeight=955&originWidth=1694&originalType=binary&ratio=1&rotation=0&showTitle=false&size=172494&status=done&style=none&taskId=u25642cf4-22ad-44e3-b2f2-a596a763caf&title=)
  5. 1. 上一步中事务控制在service层的关闭事务自动提交、手动提交、事务回滚等逻辑在每一个需要控制事务的方法中都要执行,这段代码其实就可以横切出来,可以通过动态代理来实现。
  6. 2. 增加事务管理类TransactionManager
  7. ```java
  8. import java.sql.SQLException;
  9. public class TransactionManager {
  10. private ConnectionUtils connectionUtils;
  11. public void setConnectionUtils(ConnectionUtils connectionUtils) {
  12. this.connectionUtils = connectionUtils;
  13. }
  14. // 开启事务
  15. public void beginTransaction() throws SQLException {
  16. connectionUtils.getCurrentThreadConn().setAutoCommit(false);
  17. }
  18. // 提交事务
  19. public void commit() throws SQLException {
  20. connectionUtils.getCurrentThreadConn().commit();
  21. }
  22. // 回滚事务
  23. public void rollback() throws SQLException {
  24. connectionUtils.getCurrentThreadConn().rollback();
  25. }
  26. }
  1. 增加 ProxyFactory 代理⼯⼚类,用来获取代理对象 ```java 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 result = null; try{ // 开启事务 transactionManager.beginTransaction(); // 调⽤原有业务逻辑 result = method.invoke(target,args); // 提交事务 transactionManager.commit(); }catch(Exception e) { e.printStackTrace(); // 回滚事务 transactionManager.rollback(); // 异常向上抛出,便于servlet中捕获 throw e.getCause(); } return result; } }); } }

  1. 4. 修改beans.xml来管理各个类之间的依赖关系
  2. ```xml
  3. <?xml version="1.0" encoding="UTF-8" ?>
  4. <!--跟标签beans,⾥⾯配置⼀个⼜⼀个的bean⼦标签,每⼀个bean⼦标签都代表⼀个类的配置-->
  5. <beans>
  6. <!--id标识对象,class是类的全限定类名-->
  7. <bean id="accountDao"
  8. class="com.lagou.edu.dao.impl.JdbcAccountDaoImpl">
  9. <property name="ConnectionUtils" ref="connectionUtils"/>
  10. </bean>
  11. <bean id="transferService"
  12. class="com.lagou.edu.service.impl.TransferServiceImpl">
  13. <!--set+ name 之后锁定到传值的set⽅法了,通过反射技术可以调⽤该⽅法传⼊对应的值-->
  14. <property name="AccountDao" ref="accountDao"></property>
  15. </bean>
  16. <!--配置新增的三个Bean-->
  17. <bean id="connectionUtils"
  18. class="com.lagou.edu.utils.ConnectionUtils"></bean>
  19. <!--事务管理器-->
  20. <bean id="transactionManager"
  21. class="com.lagou.edu.utils.TransactionManager">
  22. <property name="ConnectionUtils" ref="connectionUtils"/>
  23. </bean>
  24. <!--代理对象⼯⼚-->
  25. <bean id="proxyFactory" class="com.lagou.edu.factory.ProxyFactory">
  26. <property name="TransactionManager" ref="transactionManager"/>
  27. </bean>
  28. </beans>
  1. 如图所示,TransferServlet调用的不再是直接的service实现类,而是调用它的代理类,通过ProxyFactory.getProxy()方法来获取,这样得到的就是方法增强后service,也就是实现了切面逻辑与业务的分离。
    1. public class TransferServlet extends HttpServlet {
    2. // 1. 最初版本是直接new一个service实现类
    3. //private TransferService transferService = new TransferServiceImpl();
    4. // 2. 然后改造成从BeanFactory来获取
    5. //private TransferService transferService =
    6. // (TransferService)BeanFactory.getBean("transferService");
    7. // 3.从⼯⼚获取委托对象(委托对象是增强了事务控制的功能)
    8. // ⾸先从BeanFactory获取到proxyFactory代理⼯⼚的实例化对象
    9. private ProxyFactory proxyFactory = (ProxyFactory)BeanFactory.getBean("proxyFactory");
    10. private TransferService transferService = (TransferService)
    11. proxyFactory.getJdkProxy(BeanFactory.getBean("transferService")) ;
    12. }