IoC
什么是IoC?
IoC (Inversion of Control) 被称之为 控制反转 或 反转控制,是一种技术思想(理论),而不是一种技术实现,只不过spring 在技术层面上完美地实现了而已
描述的事情:Java 开发领域 对象的创建、管理的问题
传统的开发方式:比如类A依赖于类B,往往会在类A中 new 一个B的对象
而在IoC思想中,我们不需要自己手动new对象,而是由IoC容器去帮助我们实例化并管理,我们需要使用哪个对象,就问IoC容器要即可
此时我们丧失了一个权力:创建和管理对象的权力,但是我们得到了一个福利:不用考虑对象的创建和管理等一系列的事情
而这个IoC容器,就是Spring框架实现的
为什么叫控制反转?
- 控制:对象创建(实例化、管理)的权力
- 反转:控制权交给外部环境(IoC容器)
IoC解决了什么问题?
IoC解决了对象之间的耦合问题
假设我们拥有一个Dao层和一个Service层
Dao:UserDao、UserDaoImpl
Service:UserService、UserServiceImpl
在UserServiceImpl中处理业务,必然要使用到UserDaoImpl,此时需要先构建其对象:UserDao userDao = new UserDaoImpl();
创建对象完成后,注意,此时的UserSerivceImpl 就和 UserDaoImpl 耦合在一起了,耦合性太高,一方面,二者其中之一发生异常,两个服务都将无法正常执行,另一方面,在UserDaoImpl的业务发生修改,UserServiceImpl也必然要一起发生修改。在任一业务发生修改,另一业务的变化会随着修改而变大时,这就称之为耦合度过高,而代码就是追求低耦合高内聚
IoC和DI的区别?
DI:Dependancy Injection(依赖注入)
IoC 和 DI 描述的是同一件事(对象创建和依赖关系维护),只是角度不一样
IoC角度:站在对象角度,对象实例化及其管理的权力交给了容器
DI角度:站在容器角度,容器会把对象依赖的其他对象注入(实例化A对象过程中,因为声明了B类型属性,就需要容器把B对象注入给A)
AOP 思想:切面编程
什么是AOP?
AOP:Aspect oriented Programming 面向切面编程/面向方面编程
AOP 是 OOP 的延续
OOP:面向对象三大特征:封装、继承、多态
OOP 解决的是多个类中 相同位置出现相同代码的问题,但是要是在方法中 相同位置出现相同代码,OOP就无法解决,而OOP的衍生 AOP 就是用来解决这个问题的(方法中同位置出现重复代码叫做横切逻辑代码)
AOP解决的什么问题?
在原有业务不变的情况下,增强横切逻辑代码,从根本上解耦合,避免横切逻辑代码重复
为什么叫面向切面编程?
切:横切逻辑,原有业务逻辑不能动,只能操作横切逻辑代码,所以面向切面逻辑
面:横切逻辑往往要影响的是多个方法,每一个方法如同一个点,多个点则构成面,有一个面的概念在其中
手写实现 IoC 和 AOP
分析
在项目中,最大的问题实际上是出现在new 关键字上,service层的实现类 ServiceImpl 和 Dao 层的具体实现类 DaoImpl 耦合在了一起,当需要切换Dao层实现类的时候必须要修改service 代码,不符合面向接口开发的最优原则
一、 除了 new 以外的技术还有什么技术可以实例化对象?
反射
Class.forName(“全限定名”);
全限定名:xxx.xxx.xxx.xxxDaoImpl
反射通过查找全限定名来实例化对象,但是DaoImpl修改了,全限定名又要一个一个重新指定,因此,全限定名应该被设定在 xml 中进行统一管理,更改 xml 一个全限定名即可更改所有反射的全限定名
二、 当需要实例化大量相同的对象时,可以使用工厂设计模式来通过反射技术生产对象
工厂模式是解耦合非常好的一种方式
工厂类只需要 读取解析 xml,然后通过反射技术实例化对象给外部提供获取对象的接口方法,而ServiceImpl直接向工厂类索要Dao层的实现类对象即可
实现对象解耦合
实现 自定义 xml:
<?xml version="1.0" encoding="UTF-8" ?>
<!--根标签beans,里面配置若干个bean子标签,每个bean子标签都代表一个类的配置-->
<beans>
<!--在将来解析xml时,我们只需要将bean的class名解析为全限定名即可-->
<!--在Service实现类索要对象时,需要有能够分辨对象的一个标识,我们将bean的id解析为标识即可-->
<!--id用来给出去,class用来拿进来实例化-->
<!--简单来讲:id标识对象,class锁定全限定名-->
<bean id="标识" class="xxx.xxx.xxxImpl"></bean>
</beans>
实现工厂类:
/**
*工厂类 生产对象(使用反射技术)
*实际上也是工厂模式的一个应用
*/
public class BeanFactory{
//1.首先要读取解析 xml,通过反射技术实例化对象并且存储待用(map集合)(饿汉模式实现,需要JVM加载时就执行)
//2.对外提供获取实例对象的接口(根据 id 获取)
private static Map<String,Object> map = new HashMap<>();//存储对象
//饿汉式实现读取解析xml
static{
//1.读取xml
InputStream resourceAsStream = BeanFactory.class.getClassLoader().getResourceAsStream("beans.xml");
//2.解析xml(dom4j依赖实现,xpath表达式依赖快速定位xml元素)
SAXReader saxReader = new SAXReader();
try{
Document document = saxReader.read(resourceAsStream);//将xml文件以流的方式读取
Element rootElement = document.getRootElement();//获取xml流的根节点
List<Element> beanList = rootElement.selectNodes("//bean");//查找具体的节点列表,这里查找所有的bean节点
for(int i = 0;i < beanList.size(); i++){
Element element = beanList.get(i);//<bean id="标识" class="xxx.xxx.xxxDaoImpl">
//处理每一个bean元素,获取到元素的id和class
String id = element.attributeValue("id");
String clazz = element.attributeValue("class");
//通过反射技术获取全限定名(clazz)来创建对象
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="setxxxDao" ref="accountDao"></property>
String name = element.attributeValue("name");
String ref = element.attributeValue("ref");
//找到当前需要处理依赖关系的bean,实际上就是父节点
Element parent = element.getParent();
//调用父元素对象的反射功能
String parentId = parent.attributeVlaue("id");
Object parentObject = map.get(parentId);
//遍历父对象中的方法,找到"set" + name属性的方法
Method[] methods = parent.getClass().getMethods();
for(int j = 0;j < methods.length;j++){
Method method = methods[j];
if(method.getName().equalsIgnoreCase("set" + name)){//该方法就是set方法
method.invoke(parentObject,map.get(ref));
}
}
//注意:把处理之后的parentObject重新放到map中
map.put(parentId,parentObject);
}
}catch(DocumentException e){
e.printStackTrace();
}
//对外提供获取实例对象的接口(根据id获取)
public static Object getBean(String id){
return map.get(id);
}
}
}
}
spring主要的注入方式之一:set注入,也叫构造器注入:
我们在xml中定义新的标签, 在bean标签内定义一个property标签,使工厂类捕捉到property标签的name,以name来作为对应bean对象的set方法,具体实现如下:
<?xml version="1.0" encoding="UTF-8" ?>
<!--根标签beans,里面配置若干个bean子标签,每个bean子标签都代表一个类的配置-->
<beans>
<!--在将来解析xml时,我们只需要将bean的class名解析为全限定名即可-->
<!--在Service实现类索要对象时,需要有能够分辨对象的一个标识,我们将bean的id解析为标识即可-->
<!--id用来给出去,class用来拿进来实例化-->
<!--简单来讲:id标识对象,class锁定全限定名-->
<bean id="标识" class="xxx.xxx.xxxDaoImpl">
<!--在bean标签下追加property子标签-->
<!--set + name之后锁定了set方法,通过反射可以调用对应方法传入对应值-->
<property name="xxxDao" ref="accountDao"></property>
</bean>
</beans>
Spring事务控制
当存在一个业务时:
puiblic void treansfer(String fromCardNo,String toCardNo,int money) throw Exception{
Account from = accountDao.queryAccountByCardNo(fromCardNo);
Account to = accountDao.queryAccountByCardNo(toCardNo);
form.setMoney(from.getMoney()-money);
to.setMoney(to.getMoney()+money);
accountDao.updateAccountByCardNo(to);
int c = 1/0;
accountDao.updateAccountByCardNo(from);
}
在为两条数据进行更新时发生异常,此时增加金额的数据正常执行,但是减少金额的数据由于异常被终止了
数据库事务归根结底是 Connection 的事务
connection.commit();提交事务
connection.rollback();回滚事务
在任何代码都没有使用connection.commit()时,JDBC依旧能够提交事务,是因为JDBC默认自动提交,可以通过setAutoCommit(Boolean)来决定是否自动提交
Connection con = DruidUnitls.getInstance().getConnection();//阿里的德鲁伊连接池
con.setAutoCommit(false)
此时就完成了关闭自动提交
此时衍生出来一些问题:
- 增加金额和减少金额是两个事务,等同于两个Connection,要怎么样统一控制呢?
- 事务控制目前是在Dao层,没有控制在service层
解决思路:
- 让两次事务使用同一个Connection
- 线程解决:两次事务均属于同一个线程内的执行调用,我们可以给当前线程绑定一个Connection,和当前线程有关系的数据库操作都去使用这个Connection(从当前线程中拿)
- 把事务添加在service层
思路实现
原先的JdbcDao层:
public class JdbcAccountDaoImpl implements AccountDao{
@Override
public Account queryAccountByCardNo(String cardNo) throws Exception{
//从连接池获取连接
Connection con = DruidUtils.getInstance().getConnection();
String sql = "select * from xxx where cardNo=?";
PreparedStatement perparedStatement = con.preparStatement(sql);
preparedStatement.getString(1,cardNo);
ResultSet resultSet = preparedStatement.executeQuery();
Account account = new Account();
while(resultSet.next()){
account.setCardNo(resultSet.getString("cardNo"));
account.setName(result.getString("name"));
account.setMoney(resultSet.getInt("money"));
}
resultSet.close();
preparedStatement.close();
con.close();
return account;
}
@Override
public int updateAccountByCardNo(Account account)throws Exception{
//从连接池获取连接
//改造为:从当前线程中获取绑定的connection连接
Connection con = DruidUtils.getInstance().getConnection();
String sql = "update account set money=? where cardNo=?"
PreparedStatement preparedStatement = con.preparedStatement(sql);
preparedStatement.setInt(1,account.getMoney());
preparedStatement.setString(2,account.getCardNo());
int i = preparedStatement.executeUpdate();
preparedStatement.close();
con.close();
return i;
}
}
创建一个工具类,用于获取线程中的connection连接
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(){
//判断当前线程是否已经绑定连接,没有绑定就需要从连接池中获取一个连接绑定到当前连接
Connection connection = threadLocal.get();
if(connection == null){
//从连接池拿连接并绑定到当前线程
connection = DruidUtils.getInstace().getConnection();
//绑定到当前线程
threadLocal.set(connection);
}
return connection;
}
}
然后对JdbcDao层进行修改:
public class JdbcAccountDaoImpl implements AccountDao{
@Override
public Account queryAccountByCardNo(String cardNo) throws Exception{
//从连接池获取连接
//Connection con = DruidUtils.getInstance().getConnection();//修改为调用连接工具类的当前线程
Connection con = ConnectionUtils.getInstance().getCurrentThreadConn();//修改后
String sql = "select * from xxx where cardNo=?";
PreparedStatement perparedStatement = con.preparStatement(sql);
preparedStatement.getString(1,cardNo);
ResultSet resultSet = preparedStatement.executeQuery();
Account account = new Account();
while(resultSet.next()){
account.setCardNo(resultSet.getString("cardNo"));
account.setName(result.getString("name"));
account.setMoney(resultSet.getInt("money"));
}
resultSet.close();
preparedStatement.close();
//con.close();//取消关闭线程,让线程一直存在
return account;
}
@Override
public int updateAccountByCardNo(Account account)throws Exception{
//从连接池获取连接
//改造为:从当前线程中获取绑定的connection连接
//Connection con = DruidUtils.getInstance().getConnection();//修改
Connection con = ConnectionUtils.getInstance().getCurrentThreadConn();//修改后
String sql = "update account set money=? where cardNo=?"
PreparedStatement preparedStatement = con.preparedStatement(sql);
preparedStatement.setInt(1,account.getMoney());
preparedStatement.setString(2,account.getCardNo());
int i = preparedStatement.executeUpdate();
preparedStatement.close();
//con.close();//取消释放线程,让线程一直存在
return i;
}
}
最后我们来完成事务控制,即开启事务、提交事务和回滚事务,首先拿到我们最开始的业务,对这个业务进行修改:
puiblic void treansfer(String fromCardNo,String toCardNo,int money) throw Exception{
try{
//开启事务(关闭事务的自动提交)
ConnectionUtils.getInstance().getCurrentThreadConn().getAutoCommit(false);//将放入事务管理器中
//不动的逻辑
Account from = accountDao.queryAccountByCardNo(fromCardNo);
Account to = accountDao.queryAccountByCardNo(toCardNo);
form.setMoney(from.getMoney()-money);
to.setMoney(to.getMoney()+money);
accountDao.updateAccountByCardNo(to);
int c = 1/0;
accountDao.updateAccountByCardNo(from);
//提交事务
ConnectionUtils.getInstance().getCurrentThreadConn().commit();//将放入事务管理器中
}catch (Exception e){
e.printStackTrace();
//回滚事务
ConnectionUtils.getInstance().getCurrentThreadConn().rollback();//将放入事务管理器中
throw e;//捕获了异常一定要抛出,便于上层捕获该异常
}
}
拓展代码思路:开启事务、提交事务和回滚事务实际上可以封装为一个类,作为一个管理器,实际上就是应用了面向对象思想
//事务管理器:负责手动事务的开启、提交、回滚
public class TransactionManager{
private TransactionManager();
private static TransactionManager transactionManager = new TransactionaManager();
public static TransactionManager getInstance(){
return transactionManager;
}
//开启事务
public void beginTransaction() throws SQLException {
ConnectionUtils.getInstance().getCurrentThreadConn().getAutoCommit(false);//放入了管理器中
}
//提交事务
public void commit() throws SQLException {
ConnectionUtils.getInstance().getCurrentThreadConn().commit();//放入了管理器中
}
//回滚事务
public void rollback() throws SQLException {
ConnectionUtils.getInstance().getCurrentThreadConn().rollback();//放入了管理器中
}
}
此时业务层变成了:
puiblic void treansfer(String fromCardNo,String toCardNo,int money) throw Exception{
try{
//开启事务(关闭事务的自动提交)变更
TransactionManager.getInstance().beginTransaction();
//不动的逻辑
Account from = accountDao.queryAccountByCardNo(fromCardNo);
Account to = accountDao.queryAccountByCardNo(toCardNo);
form.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();
throw e;//捕获了异常一定要抛出,便于上层捕获该异常
}
}
下一篇:AOP-代理模式-动态代理