简化版Spring框架
!!!
这只是我为了学习Spring而写的内容,和真正的Spring比起来少了很多很多,只是使用了Spring的一些思想去模拟他得到一些功能,大家可以看懂我的实现方式,然后再去看Spring的源码会简单很多。
IOC容器
我们先写一个配置文件,定义两个实体类,其中一个类中引用了另外一个类作为属性
public class MySpring {
private Myreference reference;
public void setReference(Myreference reference) {
this.reference = reference;
}
}
public class Myreference {
public void show(){
System.out.println("我是注入进来的依赖");
}
}
<?xml version = "1.0" encoding="UTF-8" ?>
<beans>
<bean id="myspring" class="com.william.entity.MySpring">
<property name ="Reference" ref="myreference"></property>
</bean>
<bean id="myreference" class="com.william.entity.Myreference"></bean>
</beans>
好了我们现在开始写工厂类,工厂类中会实现xml文件的读取和解析,bean的实例化以及存储,还有bean中的依赖注入
第一步,读取xml文件,为我们需要导入两个工具类,jaxen和dom4j
<dependency>
<groupId>dom4j</groupId>
<artifactId>dom4j</artifactId>
<version>1.6.1</version>
</dependency>
<dependency>
<groupId>jaxen</groupId>
<artifactId>jaxen</artifactId>
<version>1.1.6</version>
</dependency>
创建一个工厂包,建立一个BeanFactory类
/**
* 工厂类 生产对象 使用反射技术
*/
public class BeanFactory {
private static Map<String, Object> map = new HashMap<String, Object>();//存储对象
/*
类加载的时候就执行
*/
static {
//将配置文件读取成流
InputStream inputStream = BeanFactory.class.getClassLoader().getResourceAsStream("beans.xml");
//开始解析xml
SAXReader saxReadeer = new SAXReader();
try {
Document document = saxReadeer.read(inputStream);
Element rootElement = document.getRootElement();//获取根元素
List<Element> beanList = rootElement.selectNodes("//bean");//获取所有bean标签
for (Element element : beanList) {
String id = element.attributeValue("id"); //myspring
String claszz = element.attributeValue("class"); //com.william.entity.MySpring
Class<?> aClass = Class.forName(claszz);
Object o = aClass.newInstance(); //获取一个实例
map.put(id,o);//将这个id和类对象放到对象池中,类似于spring中的单例池
}
//实例化完成之后维护对象依赖
List<Element> propertyList = rootElement.selectNodes("//property");
//解析property,获取父元素
for (int i = 0; i < propertyList.size(); i++) {
Element element = propertyList.get(i); //<property name ="Reference" ref="myreference"></property>
String name = element.attributeValue("name");
String ref = element.attributeValue("ref");
//找到这个元素的上级,即需要注入依赖的元素
Element parent = element.getParent();
//调用父元素反射
String parentId = parent.attributeValue("id");
Object parentObject = map.getOrDefault(parentId, null);//获取到这个父类
Method[] methods = parentObject.getClass().getMethods();
for (Method method : methods) {//定位到set方法,使用set注入
if(method.getName().equalsIgnoreCase("set"+name)){
method.invoke(parentObject,map.get(ref));
}
}
//把父元素更新
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.getOrDefault(id,null);//获取bean的静态方法
}
}
好,我们接来下来试一试有没有注入进去,注意我的MySpring类中的属性是没有实例化的,如果没有注入,在我调用这个方法的时候肯定会有空指针异常
public class test {
public static void main(String[] args) {
MySpring mySpring = (MySpring) BeanFactory.getBean("myspring");
mySpring.getReference().show();
}
}
输出结果
注入成功!一个简单的IOC容器就实现了,这是最简单的版本,之后会进行升级
事务管理
我们先来简单的模拟一个场景,两个顾客之间进行转账
我们先建立一个实体类
public class Customer {
private int money;
public Customer() {
this.money = 100;
}
public int getMoney() {
return money;
}
public void setMoney(int money) {
this.money = money;
}
}
在写一个转钱方法
public class transfer {
public void transferMoney(Customer cus1, Customer cus2, int money) {
cus1.setMoney(cus1.getMoney() - money);
cus2.setMoney(cus2.getMoney() + money);
}
}
正常情况下这样的逻辑没有问题,但是如果在cus1减完前后发生了异常导致系统中断,那么cus1的钱就不见了,我们来看看,使用1/0来模拟异常
public class transfer {
public void transferMoney(Customer cus1, Customer cus2, int money) {
cus1.setMoney(cus1.getMoney()-money);
int i = 1/0;
cus2.setMoney(cus2.getMoney()+money);
}
}
我们来写一个测试类
public class test {
public static void main(String[] args) {
Customer a = new Customer();
Customer b = new Customer();
transfer transfer = new transfer();
transfer.transferMoney(a,b,50);
System.out.println(b.getMoney());
System.out.println(a.getMoney());
}
}
这样子的执行结果是直接报异常,我们看不到输出的每个顾客的前,我们通过调试来看
我们到这步的时候,一切正常,往下走一步
这一步1号顾客钱已经减了,按理说2号顾客接下来要加钱了,可是这里出现了异常
导致了系统奔溃
然后我们就需要去写事务管理,一般来说最终还是使用jdbc事务进行管理,在dao层中执行,如果我们同时使用了两个transferMoney的话,就算开启了事务,也会有问题,因为两个transfer用的不是同一个connection,因此我们需要让他们绑定同一个connection,让他们能够在service层中进行事务控制
private ThreadLocal<Connection> threadLocal = new ThreadLocal<>();
/**
* 从当前线程获取connection链接
*/
public Connection getCurrentThreadConn() throws SQLException {
Connection connection = threadLocal.get();
if(connection == null){
connection= (Connection) DruidUtils.getInstance().getConnection();
threadLocal.set(connection);
}
return connection;
}
这样子同一个线程使用的就是同一个connection对象了。
然后我们在service层中加入事务
public class transfer {
public void transferMoney(Customer cus1, Customer cus2, int money) throws SQLException {
try {
ConnectionUtils.getInstance().getCurrentThreadConn().setAutoCommit(false);
cus1.setMoney(cus1.getMoney() - money);
int i = 1 / 0;
cus2.setMoney(cus2.getMoney() + money);
ConnectionUtils.getInstance().getCurrentThreadConn().commit();
}catch (Exception e){
e.printStackTrace();
ConnectionUtils.getInstance().getCurrentThreadConn().rollback();
}
}
}
我们把这些方法抽取出来,放到一个专门的事务管理器类中, 这里最好用单例模式去写,但我懒得改了
public class transactionManager {
public void beginTransaction() throws SQLException {
ConnectionUtils.getInstance().getCurrentThreadConn().setAutoCommit(false);//开启事务
}
public void commit() throws SQLException {
ConnectionUtils.getInstance().getCurrentThreadConn().commit();//提交事务
}
public void rollback() throws SQLException {
ConnectionUtils.getInstance().getCurrentThreadConn().rollback();//回滚事务
}
}
AOP动态代理
在上面事务管理的代码中我们可以看到,每一个service方法我们都需要写这个控制语句,而且这些事务语句和我们的业务逻辑混合在了一起,这就是我们为什么要使用AOP动态代理来处理这个问题
我们先写一个通用的JDK动态代理类
public class JDKProxy {
public Object getJDKProxy(Object obj) {
return Proxy.newProxyInstance(obj.getClass().getClassLoader(), obj.getClass().getInterfaces(), new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object result = null;
result = method.invoke(obj, args);
return result;
}
});
}
}
我们把事务管理加到这里来
public class JDKProxy {
public Object getJDKProxy(Object obj) {
return Proxy.newProxyInstance(obj.getClass().getClassLoader(), obj.getClass().getInterfaces(), new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object result = null;
try {
ConnectionUtils.getInstance().getCurrentThreadConn().setAutoCommit(false);
result = method.invoke(obj, args); //执行原有业务逻辑
ConnectionUtils.getInstance().getCurrentThreadConn().commit();
}catch (Exception e){
e.printStackTrace();
ConnectionUtils.getInstance().getCurrentThreadConn().rollback();
}
return result;
}
});
}
}
这时候我们就可以把原来业务逻辑中的事务代码给删了,当我们在servlet层使用service代码时,我们调用的时这个service类的代理类对象,这样就能实现业务增强了,注意如果使用的是JDK的动态代理,那么被代理类需要实现接口,CGlib的就不用。