1. 转账案例问题分析
2. new关键字耦合分析
3. 手写IOC/DI
3.1 编写xml文件
- xml文件主要是用来把需要用的类配置成一个个bean,bean的属性有id:唯一标识;有class:类的全限定类名,用来通过反射获取类的实例。property的属性:name是用来匹配TransferServiceImpl这个类中需要注入值的方法,也就是setAccountDao()方法,ref:注入的值,通过这个id获取到JdbcAccountDaoImpl类来注入
- 代码如下
```xml
<?xml version=”1.0” encoding=”UTF-8” ?>
<a name="twtJ6"></a>## 3.2 编写BeanFactory1. 通过dom4j和xpath来解析xml文件,获取每一个bean,然后再解析bean里的属性,获取id作为唯一标识,获取class的值来通过反射得到类的实例,然后id为key,类实例为value存入到Map中,该Map其实就是相当于IOC容器(准确来说是IOC容器里的单例池)。2. 获取到每一个property标签,解析出name和ref,再通过父标签从map中获取到类实例,"set" + name就是类实例中需要注入值的方法,注入的值从map中获取,key就是ref的值。这就是依赖注入。3. 在TransferServiceImpl类中注入JdbcAccountDaoImpl,只需要写一个setAccountDao(JdbcAccountDaoImpl accountDao)方法,BeanFactory就会解析到这个方法,然后通过动态代理把配置的ref标识对应的类注入进去。4. 代码如下:```javaimport org.dom4j.Document;import org.dom4j.DocumentException;import org.dom4j.Element;import org.dom4j.io.SAXReader;import java.io.InputStream;import java.lang.reflect.InvocationTargetException;import java.lang.reflect.Method;import java.util.HashMap;import java.util.List;import java.util.Map;public class BeanFactory {/*** ⼯⼚类的两个任务* 任务⼀:加载解析xml,读取xml中的bean信息,通过反射技术实例化bean对象,然后放⼊* map待⽤* 任务⼆:提供接⼝⽅法根据id从map中获取bean(静态⽅法)*/private static Map<String,Object> map = new HashMap<>();static {InputStream resourceAsStream =BeanFactory.class.getClassLoader().getResourceAsStream("beans.xml");SAXReader saxReader = new SAXReader();try {Document document = saxReader.read(resourceAsStream);Element rootElement = document.getRootElement();List<Element> list = rootElement.selectNodes("//bean");// 实例化bean对象for (int i = 0; i < list.size(); i++) {Element element = list.get(i);String id = element.attributeValue("id");String clazz = element.attributeValue("class");Class<?> aClass = Class.forName(clazz);Object o = aClass.newInstance();map.put(id,o);}// 维护bean之间的依赖关系List<Element> propertyNodes = rootElement.selectNodes("//property");for (int i = 0; i < propertyNodes.size(); i++) {Element element = propertyNodes.get(i);// 处理property元素String name = element.attributeValue("name");String ref = element.attributeValue("ref");String parentId = element.getParent().attributeValue("id");Object parentObject = map.get(parentId);Method[] methods = parentObject.getClass().getMethods();for (int j = 0; j < methods.length; j++) {Method method = methods[j];if(("set" + name).equalsIgnoreCase(method.getName())) {// bean之间的依赖关系(注⼊bean)Object propertyObject = map.get(ref);method.invoke(parentObject,propertyObject);}}// 维护依赖关系后重新将bean放⼊map中map.put(parentId,parentObject);}} catch (DocumentException e) {e.printStackTrace();} catch (ClassNotFoundException e) {e.printStackTrace();} catch (IllegalAccessException e) {e.printStackTrace();} catch (InstantiationException e) {e.printStackTrace();} catch (InvocationTargetException e) {e.printStackTrace();}}public static Object getBean(String id) {return map.get(id);}}
4. 事务控制(AOP)
4.1 service层事务问题分析
4.2 解决
- 让同个线程的jdbc操作都绑定到同一个connection连接,就可以控制属于同一个事务了,编写ConnectionUtil类,代码如下 ```java import java.sql.Connection; import java.sql.SQLException;
public class ConnectionUtils { //单例模式(懒汉) private volatile static ConnectionUtils INSTANCE;
private ConnectionUtils() {};public static ConnectionUtils getInstance() {if (INSTANCE == null) {synchronized (ConnectionUtils.class) {if (INSTANCE == null) {INSTANCE = new ConnectionUtils();}}}return INSTANCE;}// 存储当前线程的连接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;}
}
2. 把事务控制在service层,首先在service方法中获取到数据库连接将事务自动提交关闭,等全部执行完成手动提交,如果出现异常则进行事务回滚。<a name="FiBJk"></a>## 4.3 优化1. 上一步中事务控制在service层的关闭事务自动提交、手动提交、事务回滚等逻辑在每一个需要控制事务的方法中都要执行,这段代码其实就可以横切出来,可以通过动态代理来实现。2. 增加事务管理类TransactionManager```javaimport 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();}}
- 增加 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; } }); } }
4. 修改beans.xml来管理各个类之间的依赖关系```xml<?xml version="1.0" encoding="UTF-8" ?><!--跟标签beans,⾥⾯配置⼀个⼜⼀个的bean⼦标签,每⼀个bean⼦标签都代表⼀个类的配置--><beans><!--id标识对象,class是类的全限定类名--><bean id="accountDao"class="com.lagou.edu.dao.impl.JdbcAccountDaoImpl"><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><!--配置新增的三个Bean--><bean id="connectionUtils"class="com.lagou.edu.utils.ConnectionUtils"></bean><!--事务管理器--><bean id="transactionManager"class="com.lagou.edu.utils.TransactionManager"><property name="ConnectionUtils" ref="connectionUtils"/></bean><!--代理对象⼯⼚--><bean id="proxyFactory" class="com.lagou.edu.factory.ProxyFactory"><property name="TransactionManager" ref="transactionManager"/></bean></beans>
- 如图所示,TransferServlet调用的不再是直接的service实现类,而是调用它的代理类,通过ProxyFactory.getProxy()方法来获取,这样得到的就是方法增强后service,也就是实现了切面逻辑与业务的分离。
public class TransferServlet extends HttpServlet {// 1. 最初版本是直接new一个service实现类//private TransferService transferService = new TransferServiceImpl();// 2. 然后改造成从BeanFactory来获取//private TransferService transferService =// (TransferService)BeanFactory.getBean("transferService");// 3.从⼯⼚获取委托对象(委托对象是增强了事务控制的功能)// ⾸先从BeanFactory获取到proxyFactory代理⼯⼚的实例化对象private ProxyFactory proxyFactory = (ProxyFactory)BeanFactory.getBean("proxyFactory");private TransferService transferService = (TransferService)proxyFactory.getJdkProxy(BeanFactory.getBean("transferService")) ;}
