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 编写BeanFactory
1. 通过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. 代码如下:
```java
import 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 优化
![使用代理对象进行事务控制.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=)
1. 上一步中事务控制在service层的关闭事务自动提交、手动提交、事务回滚等逻辑在每一个需要控制事务的方法中都要执行,这段代码其实就可以横切出来,可以通过动态代理来实现。
2. 增加事务管理类TransactionManager
```java
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();
}
}
- 增加 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")) ;
}