自定义IoC&AOP框架
一、Spring概述
1. Spring简介
Spring 是一个分层的轻量级的开源框架,以Ioc和Aop为内核,提供了展现层、业务层等众多的企业级应用技术,还能整合各种开源框架,已经成为使用最多的java EE 企业应用框架。
官方网站:http://spring.io
我们经常说的spring实际上指的是spring framework
2. Spring发展历程
- 1997年提出EJB思想,1998年发布EJB1.0版本,直到2006年发布EJB3.0规范
- Rod Johnson 在2004年发布了J2EE开发不使用EJB的解决方式,提出了Spring的雏形
- 2017年9月Spring 发布了5.0的版本
3. Spring的优势
- 方便解耦,简化开发
通过Spring提供的Ioc容器,可以将对象之间的关系交由Spring进行控制,避免硬编码导致的国度耦合。同时用户也不需要为单例、配置文件解析等操作进行编码,只需要关注其上层的应用 - Aop编程的支持
通过Spring的Aop功能,方便对切面进行编程,可以解决传统OOP不易实现的功能 - 声明式事务的支持
可以将开发人员从烦闷的事务管理代码中解脱出来,通过声明方式灵活的进行事务的管理,提高开发效率 - 方便程序的测试
可以使用非容器依赖的方式进行几乎所有的测试工作,使测试变得随手可做 - 可以集成各种优秀框架
Spring可以降低其他框架的使用难度,提供了对很多框架的支持 - 降低Java EE API的使用难度
Spring对javaEE Api进行了封装,使用这些api的难度下降 - 源码是经典的java学习范例
Spring的代码设计精妙、结构清晰,处处体现着⼤师对Java设计模式灵活运⽤以及对
Java技术的⾼深造诣。它的源代码⽆疑是Java技术的最佳实践的范例
3.Spring的核心结构
主要包括⼏个⼤模块:数据处理模块、 Web模块、 AOP(Aspect Oriented Programming) /Aspects模块、 Core Container模块和 Test 模块
- Spring核⼼容器(Core Container) 容器是Spring框架最核⼼的部分,它管理着Spring应⽤中
bean的创建、配置和管理。在该模块中,包括了Spring bean⼯⼚,它为Spring提供了DI的功能。基于bean⼯⼚,我们还会发现有多种Spring应⽤上下⽂的实现。所有的Spring模块都构建于核⼼容器之上。 - ⾯向切⾯编程(AOP) /Aspects Spring对⾯向切⾯编程提供了丰富的⽀持。这个模块是Spring应⽤系统中开发切⾯的基础,与DI⼀样, AOP可以帮助应⽤对象解耦
- 据访问与集成(Data Access/Integration)
Spring的JDBC和DAO模块封装了⼤量样板代码,这样可以使得数据库代码变得简洁,也可以更专注于我们的业务,还可以避免数据库资源释放失败⽽引起的问题。 另外, Spring AOP为数据访问提供了事务管理服务,同时Spring还对ORM进⾏了集成,如Hibernate、 MyBatis等。该模块由JDBC、 Transactions、 ORM、 OXM 和 JMS 等模块组成 - Web 该模块提供了SpringMVC框架给Web应⽤,还提供了多种构建和其它应⽤交互的远程调⽤⽅案。 SpringMVC框架在Web层提升了应⽤的松耦合⽔平
- Test 为了使得开发者能够很⽅便的进⾏测试, Spring提供了测试模块以致⼒于Spring应⽤的测试。 通过该模块, Spring为使⽤Servlet、 JNDI等编写单元测试提供了⼀系列的mock对象实现
4.Spring框架版本
Spring版本 | 所需JDK版本 |
---|---|
3.x | JDK 5+ |
4.x | JDK 6+ |
5.x | JDK 8+ |
二、核心思想
1.Ioc
1.1 什么是Ioc
Ioc (Inversion of Control) 控制翻转,它是一种思想,不是一种技术实现。它描述的是对象的创建以及管理的问题。
传统开发中:A依赖于B,我们就会在A中new 一个B对象
Ioc模式下:我们不需要手动去创建对象,由Ioc容器代替我们去创建和管理对象。我们在需要的时候向容器要即可。
1.2 Ioc解决的什么问题
它解决的是对象之间的耦合问题。在传统中模式下,我们需要使用一个接口会new一个它的实现类,如果这个新增一个实现类,要在所有调用的地方都进行替换,耦合严重。
1.3 Ioc和DI的区别
DI : 依赖注入
DI和Ioc都是跟对象实例化和依赖关系维护相关,Ioc是站在对象的角度,说明对象的创建以及管理交由容器管理。DI是站在容器的角度,在创建对象时要把依赖的其他对象注入。
2.Aop
2.1 什么是Aop
Aop是面向切面编程,它是oop的延续,oop具有三大特性:封装、继承、多态,对于重复的代码,可以通过继承向上提取。所以oop是一种垂直继承体系。所以一般的代码重复问题oop都可以很好的解决,但是对于多个方法中相同位置出现了相同的代码,就无法解决。Aop可以通过横向的抽取机制
2.2 Aop解决的是什么问题
解决的是oop不能解决的代码重复问题,即在不改变原有业务逻辑的情况下,增强横切代码逻辑
,将业务代码与监控、日志等功能进行解耦,避免横切代码的重复。
2.3 为什么叫做面向切面编程
「切」:不改变原有逻辑,把横切逻辑 切入进入
「面」:切入的点一般很多,多个点形成一个面,如果只对一个地方进行切入,横向逻辑反而麻烦
三、手写实现Ioc和Aop
1.给定一个案例及关键代码
需求:A账户向B账户进行转账,数据存在数据库的表中
关键代码:
TransferService接口
public interface TransferService {
/**
* 定义一个 转账的方法
*/
void transfer(String fromCardNo, String toCardNo, Integer money) throws Exception;
}
TransferServiceImpl
public class TransferServiceImpl implements TransferService {
private AccountDao accountDao = new AccountDaoImpl();
@Override
public void transfer(String fromCardNo, String toCardNo, Integer money) throws Exception {
Account from = accountDao.queryAccountCardNo(fromCardNo);
Account to = accountDao.queryAccountCardNo(toCardNo);
from.setMoney(from.getMoney() - money);
to.setMoney(to.getMoney() + money);
accountDao.updateAccountByCardNo(from);
accountDao.updateAccountByCardNo(to);
}
}
AccountDao接口
public interface AccountDao {
// 在接口中,定义一个 根据 cardNo 查询 账户的 方法
Account queryAccountCardNo(String cardNo) throws SQLException, Exception;
// 在接口中,定义一个 根据 cardNo 修改 money 的方法
int updateAccountByCardNo(Account account) throws Exception;
}
JdbcAccountDaoImpl
public class AccountDaoImpl implements AccountDao {
// 在接口 实现类中 实现根据 cardNo查询账户的方法
@Override
public Account queryAccountCardNo(String cardNo) throws Exception {
DruidPooledConnection connection = DruidUtils.getInstance().getConnection();
String sql = "select * from account where cardNo = ?";
PreparedStatement preparedStatement = connection.prepareStatement(sql);
preparedStatement.setString(1,cardNo);
ResultSet resultSet = preparedStatement.executeQuery();
Account account = new Account();
while (resultSet.next()){
account.setMoney(resultSet.getInt("money"));
account.setCardNo(resultSet.getString("cardNo"));
account.setName(resultSet.getString("name"));
}
resultSet.close();
preparedStatement.close();
connection.close();
return account;
}
// 在接口 实现类中 实现 根据 cardNo 修改 money 的方法
@Override
public int updateAccountByCardNo(Account account) throws Exception{
DruidPooledConnection connection = DruidUtils.getInstance().getConnection();
String sql = "update account set money = ? where cardNo = ?";
PreparedStatement preparedStatement = connection.prepareStatement(sql);
preparedStatement.setInt(1,account.getMoney());
preparedStatement.setString(2,account.getCardNo());
int i = preparedStatement.executeUpdate();
preparedStatement.close();
connection.close();
return i;
}
}
2.代码存在的问题分析
- 问题一:service调用Dao的时候,是在实现类中直接new一个JdbcAccountDaoImpl,这个操作将这两个类耦合了在一起,如果后续使用其他Dao层进行数据库操作,将会对所有new的地方进行替换,成本比较大
- 问题二:service在处理业务逻辑的时候没有进行事务管理,如果代码执行过程中发生错误,可能会导致数据库的数据错乱,问题很严重。
3.解决问题
对于问题一,我们可以使用反射来获取对象,通过把类的全路径配置在xml中,因为类会有很对,所以可以再工厂类中进行反射,并存储对象,工厂类还应该提供获取实例的方法。
- 建立beans.xml,在里面配置需要加载的类的全路径
<?xml version="1.0" encoding="UTF-8" ?>
<beans>
<!-- class 指定类的全路径,id用于获取实例对象时的唯一标识-->
<bean id="accountDao" class="com.lagou.dao.Impl.AccountDaoImpl"/>
<bean id="transferService" class = "com.lagou.service.Impl.TransferServiceImpl"/>
</beans>
- 创建BeanFactory类
package com.lagou.factory;
import org.dom4j.Document;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import java.io.InputStream;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class BeanFactory {
//存储实例化的对象
private static Map<String, Object> objectMap = new HashMap<>();
static {
//1.加载并解析xml配置
InputStream resource = BeanFactory.class.getClassLoader().getResourceAsStream("beans.xml");
SAXReader saxReader = new SAXReader();
try {
Document document = saxReader.read(resource);
//获取根元素
Element rootElement = document.getRootElement();
//获取所有bean标签
List<Element> elements = rootElement.selectNodes("//bean");
elements.forEach(e -> {
String id = e.attributeValue("id");
String clazz = e.attributeValue("class");
//2.运用反射实例化对象
try {
Class<?> objectClass = Class.forName(clazz);
Object object = objectClass.newInstance();
objectMap.put(id, object);
} catch (Exception e1) {
e1.printStackTrace();
}
}
);
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 获取bean方法
*/
public static Object getBean(String id){
return objectMap.get(id);
}
}
- 修改原先的实现类
public class TransferServiceImpl implements TransferService {
// private AccountDao accountDao = new AccountDaoImpl();
private AccountDao accountDao = (AccountDao) BeanFactory.getBean("accountDao");
}
虽然通过反射实现了类的实例化,但是类中的属性仍然是人为的从beanFactory中获取,没有实现依赖自动的注入,而且还会因为类实例化的顺序,导致有依赖关系的类的属性为null。所以我们提出了方案二
- 修改beans.xml
<!-- <bean id="transferService" class = "com.lagou.service.Impl.TransferServiceImpl"/>-->
<bean id="transferService" class = "com.lagou.service.Impl.TransferServiceImpl">
<property name="accountDao" ref ="accountDao"/>
</bean>
- 修改beanFactory,在实例化对象后继续注入属性
//注入依赖
List<Element> property = rootElement.selectNodes("//property");
property.forEach(p -> {
String name = p.attributeValue("name");
String ref = p.attributeValue("ref");
//获取property标签的父bean ,然后获取实例化对象,利用反射调用set方法进行注入
Element bean = p.getParent();
String parentId = bean.attributeValue("id");
Object parent = objectMap.get(parentId);
Method[] methods = parent.getClass().getMethods();
for (int i = 0; i < methods.length; i++) {
if(methods[i].getName().equalsIgnoreCase("set"+ name)){
try {
methods[i].invoke(parent,objectMap.get(ref));
} catch (Exception e) {
e.printStackTrace();
}
}
}
objectMap.put(parentId,parent);
});
- 修改实现类
public class TransferServiceImpl implements TransferService {
// private AccountDao accountDao = new AccountDaoImpl();
// private AccountDao accountDao = (AccountDao) BeanFactory.getBean("accountDao");
private AccountDao accountDao;
public void setAccountDao(AccountDao accountDao) {
this.accountDao = accountDao;
}
}
对于问题二,事务的管理,最终是通过connection的commit和rollback方式实现的,我们发现在dao层每次执行方法的时候,都是去获取一个新的连接,这必然导致service中的两次更新方法,不在同一个连接(不在同一个事务中),所以我们可以让当前线程获取到相同的连接。
- 创建ConnectionUtil工具类
package com.lagou.util;
import java.sql.Connection;
import java.sql.SQLException;
public class ConnectionUtil {
//使用单例,如果每次new一个util,会导致threadLocal 也每次是新的,每次就都是第一次获取数据库连接
private ConnectionUtil() {
}
private static ConnectionUtil connectionUtil = new ConnectionUtil();
public static ConnectionUtil getInstance() {
return connectionUtil;
}
//存储线程持有的connection
private ThreadLocal<Connection> threadLocal = new ThreadLocal<>();
public Connection getCurrentThreadConnect() throws SQLException {
Connection connection = threadLocal.get();
if (connection == null) {
//第一次获取连接
connection = DruidUtils.getInstance().getConnection();
threadLocal.set(connection);
}
return connection;
}
}
- 修改到层实现,修改connection获取方式,并去掉connection的关闭
//Connection connection = DruidUtils.getInstance().getConnection();
Connection connection = ConnectionUtil.getInstance().getCurrentThreadConnect();
......
//connection.close();
- 创建事务工具类TransactionManager
package com.lagou.util;
import java.sql.SQLException;
public class TransactionManager {
private TransactionManager(){
}
private static TransactionManager transactionManager = new TransactionManager();
public static TransactionManager getInstance(){
return transactionManager;
}
public void beginTransation() throws SQLException {
ConnectionUtil.getInstance().getCurrentThreadConnect().setAutoCommit(false);
}
public void commit() throws SQLException {
ConnectionUtil.getInstance().getCurrentThreadConnect().commit();
}
public void rollback() throws SQLException {
ConnectionUtil.getInstance().getCurrentThreadConnect().rollback();
}
}
- 修改service实现
@Override
public void transfer(String fromCardNo, String toCardNo, Integer money) throws Exception {
try {
//开启事务
TransactionManager.getInstance().beginTransation();
Account from = accountDao.queryAccountCardNo(fromCardNo);
Account to = accountDao.queryAccountCardNo(toCardNo);
from.setMoney(from.getMoney() - money);
to.setMoney(to.getMoney() + money);
accountDao.updateAccountByCardNo(from);
accountDao.updateAccountByCardNo(to);
//提交事务
TransactionManager.getInstance().commit();
} catch (Exception e) {
e.printStackTrace();
//回滚事务
TransactionManager.getInstance().rollback();
}
}
4. 回顾代理方式
代理一共分为两种类型,静态代理和动态代理,动态代理又有两种实现,jdk动态代理和cglib动态代理。
- 静态代理
- 创建一个接口
package com.lagou.poxy;
public interface IRentingHouse {
void rentHouse();
}
- 创建实现类
package com.lagou.poxy;
public class RentingHouseImpl implements IRentingHouse{
@Override
public void rentHouse() {
System.out.println("我要组一个房租");
}
}
- 创建一个静态代理类
package com.lagou.poxy.statics;
import com.lagou.poxy.IRentingHouse;
import com.lagou.poxy.RentingHouseImpl;
public class StaticRentingHouseProxy implements IRentingHouse {
private IRentingHouse rentingHouse;
public StaticRentingHouseProxy(IRentingHouse rentingHouse) {
this.rentingHouse = rentingHouse;
}
@Override
public void rentHouse() {
System.out.println("房屋代理带你去看房子");
rentingHouse.rentHouse();
System.out.println("中介把你的信息卖了3毛钱");
}
}
- 测试
public static void main(String[] args) {
IRentingHouse myRentingHouse = new RentingHouseImpl();
StaticRentingHouseProxy proxy = new StaticRentingHouseProxy(myRentingHouse);
proxy.rentHouse();
}
- JDK动态代理
public static void main(String[] args) {
IRentingHouse myRentingHouse = new RentingHouseImpl();
IRentingHouse proxyRentingHouse = (IRentingHouse) Proxy.newProxyInstance(myRentingHouse.getClass().getClassLoader(), myRentingHouse.getClass().getInterfaces(), new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("房屋代理带你去看房子");
Object object = method.invoke(myRentingHouse, args);
System.out.println("中介把你的信息卖了3毛钱");
return object;
}
});
proxyRentingHouse.rentHouse();
}
- cglib动态代理
- 引入jar包
<!-- cglib -->
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.2.6</version>
</dependency>
- 实现
public static void main(String[] args) {
RentingHouseImpl myRentingHouse = new RentingHouseImpl();
RentingHouseImpl proxy = (RentingHouseImpl) Enhancer.create(myRentingHouse.getClass(), new MethodInterceptor() {
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
System.out.println("房屋代理带你去看房子");
Object object = method.invoke(myRentingHouse, objects);
System.out.println("中介把你的信息卖了3毛钱");
return object;
}
});
proxy.rentHouse();
}
- 两种代理的比较
- Cglib和jdk动态代理的区别?
1、Jdk动态代理:利用拦截器(必须实现InvocationHandler)加上反射机制生成一个代理接口的匿名类,在调用具体方法前调用InvokeHandler来处理
2、Cglib动态代理:利用ASM框架,对代理对象类生成的class文件加载进来,通过修改其字节码生成子类来处理
- 什么时候用cglib什么时候用jdk动态代理?
1、如果目标对象实现了接口,既可以使用JDK也可以使用cglib
2、如果目标对象没有实现接口,只能使用cglib