实现自己的BeanFactory、AOP以及声明式事务

实现自己的BeanFactory                                                                  

在使用spring时,我们很少用"new"关键字创建对象,而是通过spring容器BeanFactory提供的getBean()方法得到对象:

BeanFactory ctx = new ClassPathXmlApplicationContext();

通过spring容器统一管理bean的创建,避免了代码中四处散落的"new"关键字,使我们能够统一管理对象的创建与销毁,本节将模仿spring创建一个自己版本的BeanFactory。首先我们定义一个Dao接口和实现,该接口提供一个方法用于保存Student:

public interface StudentDao {
	void saveStudent();
}

public class StudentDaoImpl implements StudentDao {
	public void saveStudent(){
		System.out.println("save success!");
	}
}

  然后我们再创建一个service,该service将调用上面定义的Dao用于存储Student:

public class StudentService {
	private StudentDao stuDao;

	public void saveStudent() {
		stuDao.saveStudent();
	}

	public StudentDao getStuDao() {
		return stuDao;
	}

	public void setStuDao(StudentDao stuDao) {
		this.stuDao = stuDao;
	}
}

  和spring一样,我们也需要一个xml文件用于定义bean对象的创建规则,该xml文件即为beans.xml,其内容如下:

<beans>
	<bean id="stuDao" class="dao.impl.StudentDaoImpl" />
	<bean id="stuService" class="service.StudentService">
		<property name="stuDao" bean="stuDao"/>
	</bean>
</beans>

  现在,两个JavaBean对象已经定义好了,分别是:StudentDaoImpl和StudentService,beans.xml文件也定义好了,现在我们需要定义一个工厂(Factory),该Factory将根据beans.xml定义的对象创建规则创建JavaBean,然后把创建的JavaBean保存起来,并提供一个getBean()方法以便用户获得这些JavaBean,这个工厂的接口与实现如下:

public interface BeanFactory {
	public Object getBean(String name);
}

public class ClassPathXmlApplicationContext implements BeanFactory {
	private Map<String, Object> beans = new HashMap<String, Object>();

	public ClassPathXmlApplicationContext() {
		try {
			SAXBuilder sb = new SAXBuilder();
			Document doc = (Document) sb.build(this.getClass().getClassLoader()
					.getResourceAsStream("beans.xml"));
			Element root = doc.getRootElement();

			List<Element> list = (List<Element>) root.getChildren("bean");
			for (int i = 0; i < list.size(); i++) {
				Element element = (Element) list.get(i);
				String id = element.getAttributeValue("id");
				String clazz = element.getAttributeValue("class");
				Object o = Class.forName(clazz).newInstance();
				beans.put(id, o);

				for (Element element2 : (List<Element>) element
						.getChildren("property")) {
					String name = element2.getAttributeValue("name");
					String bean = element2.getAttributeValue("bean");
					Object beanObject = beans.get(bean);
					String methodName = "set"
							+ name.substring(0, 1).toUpperCase()
							+ name.substring(1);
					Method m = o.getClass().getMethod(methodName,
							beanObject.getClass().getInterfaces()[0]);
					m.invoke(o, beanObject);
				}
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	@Override
	public Object getBean(String name) {
		return beans.get(name);
	}
}

  可以看到,在ClassPathXmlApplicationContext的构造函数中,我们读取并解析xml文件,然后创建对象,并把对象保存在一个HashMap中,ClassPathXmlApplicationContext类的getBean方法传入一个bean name,然后我们在map中查找name对应对象并返回。

最后,我们创建一个测试类,用于测试我们编写的代码是否正确:

public class Test {
	public static void main(String[] args) {
		BeanFactory ctx = new ClassPathXmlApplicationContext();
		StudentService s = (StudentService) ctx.getBean("stuService");
		s.saveStudent();
	}
}

  至此,一个简单的BeanFactory实现了,这个BeanFactory的实现使用到了xml解析技术和反射技术。

实现自己的AOP                                                                           

AOP,即面向方面编程,主要用于把日志记录,性能统计,异常处理等非业务逻辑代码从业务逻辑代码中分离出来。下面我们通过Java动态代理实现自己的AOP功能,这个例子会在方法启动前和启动后打印当前时间,并计算方法耗时。首先我们定义一个Advice接口和实现,该接口定义了方法调用前和方法调用后的行为:

public interface Advice {
	void beforeMethod(Method method);
	void afterMethod(Method method);
}

public class MyAdvice implements Advice {
	long beginTime = 0;
	public void beforeMethod(Method method) {
		System.out.println("before time: " + System.currentTimeMillis());
		beginTime = System.currentTimeMillis();
	}

	public void afterMethod(Method method) {
		System.out.println("after time: " + System.currentTimeMillis());
		long endTime = System.currentTimeMillis();
		System.out.println(method.getName() + " running time of " + (endTime - beginTime));

	}
}

然后我们定义一个xml文件,在该xml文件中定义了一个bean,这个bean有一个属性"advice":

<beans>
	<bean id="testObject" class="java.util.ArrayList">
		<property advice="aoptest.MyAdvice"/>
	</bean>
</beans>

  最后我们还是定义一个BeanFactory,该BeanFactory会解析这个xml文件:

public class BeanFactory {
	private Map<String, Object> beans = new HashMap<String, Object>();

	public BeanFactory() {
		try {
			SAXBuilder sb = new SAXBuilder();
			Document doc = (Document) sb.build(this.getClass().getClassLoader()
					.getResourceAsStream("aop.xml"));
			Element root = doc.getRootElement();

			List<Element> list = (List<Element>) root.getChildren("bean");
			for (int i = 0; i < list.size(); i++) {
				Element element = (Element) list.get(i);
				String id = element.getAttributeValue("id");
				String clazz = element.getAttributeValue("class");
				Object target = Class.forName(clazz).newInstance();

				for (Element element2 : (List<Element>) element
						.getChildren("property")) {
					String adviceStr = element2.getAttributeValue("advice");
					MyAdvice advice = (MyAdvice) Class.forName(adviceStr)
							.newInstance();
					beans.put(id, getProxy(advice, target));
				}
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	public Object getProxy(final MyAdvice advice, final Object target) {
		Object result = Proxy.newProxyInstance(target.getClass()
				.getClassLoader(), target.getClass().getInterfaces(),
				new InvocationHandler() {

					public Object invoke(Object proxy, Method method,
							Object[] args) throws Throwable {
						advice.beforeMethod(method);
						Object retVal = method.invoke(target, args);
						advice.afterMethod(method);
						return retVal;
					}
				});
		return result;
	}

	public Object getBean(String name) {
		return beans.get(name);
	}
}

  注意,这个beanFactory的实现,在最后调用beans.put(id, getProxy(advice, target))方法时,存入map中的是一个代理对象,并不是xml中定义的原生方法。最后,我们编写一个测试类:

public class Test {
	public static void main(String[] args) throws Exception {
		Object bean = new BeanFactory().getBean("testObject");
		((Collection) bean).add(12);
	}
}

  在该测试类中,我们先从BeanFactory中得到bean,再调用bean上的add方法,该测试类的输出如下:

before time: 1416066155411
after time: 1416066155411
add running time of 0

  可以看到,如同我们预想的,在方法开始前打印了一下当前时间,在方法结束后又打印了时间,最后计算出了方法耗时,使用AOP的方法统计计算耗时,可以避免把统计代码与业务代码耦合在一起,可以方便统计代码的复用。

实现自己的声明式事务                                                                  

声明式事务可以让我们从复杂的事务处理中得到解脱,使我们再也不需要在与事务相关的方法中处理大量的try...catch...finally代码,这章我们将实现自己的声明式事务,使用到的技术是上一章节介绍的aop技术。首先我们定义一个JdbcUtils类,该类有一个方法:getConnection,该方法会返回一个JDBC连接:

public final class JdbcUtils {
	public static Connection conn = null;
	public static boolean autoCommit = true;

	static {
		try {
			Class.forName("com.mysql.jdbc.Driver");
			conn = DriverManager.getConnection(
					"jdbc:mysql://localhost:3306/temp", "root", "");
		} catch (Exception e) {
			throw new ExceptionInInitializerError(e);
		}
	}

	private JdbcUtils() {
	}

	public static Connection getConnection() throws SQLException {
		conn.setAutoCommit(autoCommit);
		return conn;
	}
}

  注意,该类还包含一个autoCommit的静态布尔属性,在返回Connection之前会用该属性定义是否自动提交。然后,我们定义一个类用于数据库操作:

public interface UserDao {
	void save1() throws Exception;
	void save2() throws Exception;
}

public class UserDaoImpl implements UserDao {
	public void save1() throws Exception {
		Connection conn = JdbcUtils.getConnection();
		Statement stmt = conn.createStatement();
	    stmt.executeUpdate("insert into user(name, birthday, money) values(‘save1‘, ‘1984-10-11‘, 87446)");
	}

	public void save2() throws Exception {
		Connection conn = JdbcUtils.getConnection();
		Statement stmt = conn.createStatement();
	    stmt.executeUpdate("insert into user(name, birthday, money) values(‘save2‘, ‘1984-10-11‘, 87446)");
		throw new RuntimeException("qq");
	}
}

接着,我们定义一个Advice,该Advice在方法调用前把autoCommit设置为false,方法执行完成之后commit方法,如果捕捉到异常就回滚事务,最后再把autoCommit设置为true:

public class MyAdvice{
	public void beforeMethod(Method method) {
		JdbcUtils.autoCommit = false;
	}

	public void afterMethod(Method method) throws Exception {
		JdbcUtils.conn.commit();
	}

	public void finallyMethod(Method method) {
		JdbcUtils.autoCommit = true;
	}

	public void onException(Method method) throws SQLException {
		JdbcUtils.conn.rollback();
	}
}

  然后,我们定义一个xml文件,把bean和advice关系注册一下:

<beans>
	<bean id="testObject" class="test.UserDaoImpl">
		<property advice="aopframework.MyAdvice"/>
	</bean>
</beans>

  最后,定义BeanFactory解析xml文件,这段代码的内容和第二节代码十分相似,只有一点区别,在创建代理时候套上了try-catch-finally以便进行事务回滚:

public class BeanFactory {
	private Map<String, Object> beans = new HashMap<String, Object>();

	public BeanFactory() {
		try {
			SAXBuilder sb = new SAXBuilder();
			Document doc = (Document) sb.build(this.getClass().getClassLoader()
					.getResourceAsStream("aop.xml"));
			Element root = doc.getRootElement();

			List<Element> list = (List<Element>) root.getChildren("bean");
			for (int i = 0; i < list.size(); i++) {
				Element element = (Element) list.get(i);
				String id = element.getAttributeValue("id");
				String clazz = element.getAttributeValue("class");
				Object target = Class.forName(clazz).newInstance();

				for (Element element2 : (List<Element>) element
						.getChildren("property")) {
					String adviceStr = element2.getAttributeValue("advice");
					MyAdvice advice = (MyAdvice) Class.forName(adviceStr)
							.newInstance();
					beans.put(id, getProxy(advice, target));
				}
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	public Object getProxy(final MyAdvice advice, final Object target) {
		Object result = Proxy.newProxyInstance(target.getClass()
				.getClassLoader(), target.getClass().getInterfaces(),
				new InvocationHandler() {

					public Object invoke(Object proxy, Method method,
							Object[] args) throws Throwable {
						Object retVal = null;
						try {
							advice.beforeMethod(method);
							retVal = method.invoke(target, args);
							advice.afterMethod(method);
						} catch (Exception e) {
							advice.onException(method);
						} finally {
							advice.finallyMethod(method);
						}
						return retVal;
					}
				});
		return result;
	}

	public Object getBean(String name) {
		return beans.get(name);
	}
}

  测试代码与第二节代码一致:

public class AopFrameworkTest {
	public static void main(String[] args) throws Exception {
		Object bean = new BeanFactory().getBean("testObject");
		((UserDao) bean).save1();
		((UserDao) bean).save2();
	}
}

  运行后,在数据库查看,可以发现只有save1方法插入的数据生效了,save2未能插入数据。回头看看我们的设计,我们发现,我们把事务处理相关的代码放到了统一的地方,避免了与业务代码耦合,只需在配置文件中配置哪些方法需要事务支持,哪些不需要事务支持,大大简化了代码复杂度。

时间: 2024-08-24 20:29:10

实现自己的BeanFactory、AOP以及声明式事务的相关文章

Spring注解驱动开发(四)-----aop、声明式事务

AOP 概念 指在程序运行期间动态的将某段代码切入到指定方法指定位置进行运行的编程方式:-----基于动态代理 一个aop示例 1.导入aop模块:Spring AOP:(spring-aspects)-----导入相关jar包 2.MathCalculator-----一个业务逻辑的类-----在业务逻辑运行的时候将日志进行打印(方法之前.方法运行结束.方法出现异常,xxx) package com.atguigu.aop; public class MathCalculator { publ

27Spring_的事务管理_银行转账业务加上事务控制_基于tx.aop进行声明式事务管理

上一篇文章中,银行转账业务没有使用事务,会出现问题,所以这篇文章对上篇文章出现的问题进行修改. 事务 依赖 AOP , AOP需要定义切面, 切面由Advice(通知) 和 PointCut(切点) 组成 ! 项目结构图: 这个案例和前一篇文章的案例是一样的,我们修改的其实只是ApplicationContext.xml文件,其他代码不会变所以这里就不多做解释了. 直接给出ApplicationContext.xml代码.里面的注释好好看一下. <?xml version="1.0&quo

AOP:声明式事务管理流程

1. 注册BeanFactoryTransactionAttributeSourceAdvisor @EnableTransactionManagement --> @Import(TransactionManagementConfigurationSelector.class) --> 注册ProxyTransactionManagementConfiguration @Configuration public class ProxyTransactionManagementConfigur

Spring AOP声明式事务异常回滚

转:http://hi.baidu.com/iduany/item/20f8f8ed24e1dec5bbf37df7 近日测试用例,发现这样一个现象:在业务代码中,有如下两种情况,比如:throw new RuntimeException("xxxxxxxxxxxx"); 事物回滚throw new Exception("xxxxxxxxxxxx"); 事物没有回滚 自以为很了解事物,或许时间久远的缘故,没分析出来何故,遂查阅了下资料,写下了如下的内容,供参考: 1

spring的学习____12 声明式事务(AoP的横向织入)

1.事务的介绍: 事务涉及到数据的一致性问题. 事务:要么都成功,要么都不成功! 事务的四大特性: ACID :原子性:一致性:隔离性:持久性. 编程中遇到的实际问题: 在如下的实现类(UserDaoImpl)中,执行了:先添加一个user,再删除一个user的操作,最后打印出所有的用户列表. 当我们人为的在删除代码写错时(即就不能成功执行删除操作),发现程序还是执行了添加和打印用户列表的其余两步,这就不符合事物的一致性. public class UserDaoImpl implements

Spring4 -12 -声明式事务及完整的XML配置文件信息 -声明式事务中的相关属性(tx:advice的标签)

1.编程式事务: 1.1由程序员编程事务控制代码. 1.2OpenSessionInView 就属于编程式事务: session.commit()和rollback() 2.声明式事务: 2.1事务控制代码已经由spring 写好.程序员只需要声明出哪些方法需要进行事务控制和如何进行事务控制. 3.声明式事务都是针对于ServiceImpl 类下方法的. 4.事务管理器基于通知(advice)的. 5.在spring 配置文件中配置声明式事务 完整地XML配置文件信息: <context:pro

spring aop 声明式事务管理

Spring使用AOP来完成声明式的事务管理   有annotation和xml两种形式 代码和上一篇基本相近,再贴一遍 两个实体类 package com.ouc.wkp.model; import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.Id; @Entity(name = "t_log") public class Log { priva

Spring AOP声明式事务的缺陷

Spring AOP声明式事务的缺陷 今天项目验收时遇到的一个问题,记录一下 转载:http://liuu.iteye.com/blog/422810 [问题]        Spring的声明式事务,我想就不用多介绍了吧,一句话“自从用了Spring AOP啊,事务管理真轻松啊,真轻松:事务管理代码没有了,脑不酸了,手不痛了,一口气全配上了事务:轻量级,测试起来也简单,嘿!”.不管从哪个角度看,轻量级声明式事务都是一件解放生产力的大好事.所以,我们“一直用它”. 不过,最近的一个项目里,却碰到

spring mvc + mybatis + spring aop声明式事务管理没有作用

在最近的一个项目中,采用springMVC.mybatis,发现一个很恼人的问题:事务管理不起作用!!网上查阅了大量的资料,尝试了各种解决办法,亦未能解决问题! spring版本:3.0.5 mybatis版本:3.2.2 1.applicationContext.xml配置: mvc + mybatis + spring aop声明式事务管理没有作用" title="spring mvc + mybatis + spring aop声明式事务管理没有作用">2.spr