自定义struts(扩展)--将注解从action上移到service中

以前做的一个项目,没用用框架,最近在学习框架,所以练手的时候就把以前的项目用框架写了一次。


这里用的是mybatis与struts2框架。没用到spring(因为不会)。

在处理事务的时候出现了一些问题:

action

public class PendingDocAction {


	private List<CheckedAccount> checkedAccounts = new ArrayList<CheckedAccount>();

	public String submitCheckDoc() {
		PendingDocDAO dao = DAOFactory.getDAO(PendingDocDAOImpl.class);
		dao.updateCheckedAccounts(checkedAccounts);
		dao.updatePendingDoc(pendingDocId);
		return "index";
	}

dao

public class PendingDocDAOImpl implements PendingDocDAO {

	
	public void updateCheckedAccounts(List<CheckedAccount> checkedAccounts) {
		SqlSession session = SessionFactory.getSession();
		session.update("PendingDoc.submitCheckedAccount", checkedAccounts);
		session.commit();
		session.close();
	}

	public void updatePendingDoc(Integer pendingDocId) {
		SqlSession session = SessionFactory.getSession();
		session.update("PendingDoc.updatePendingDoc", pendingDocId);
		session.commit();
		session.close();
	}
所以这里就出现了问题,dao写好了,两步独立的操作,事务无法控制了。


主要原因还是这个action不归我管 ,所以要做事务就得改dao,这当然不是我想要的。

我的目标是dao不动,一个操作对应一个方法。

然后再另外想办法。。。。。


真是因为这里碰到的问题,加上对struts实现原理的好奇,所以有了在前几篇中提到的自己写个简单功能的struts。

的确,简单的struts功能完成了,action的事务也实现了。


现在要做的就是将action中的事务移到service中,这样在不是自己写的struts2中使用service来控制事务。


一、方法:

把之前产生action对象的方法套在产生service方法上。

检测service中的方法:

如果无transaction注解:正常执行

如果有……………:代理改方法,将原先的方法移到TransactionManager的控制之下。

因为service和action一样都是没有接口的,所以还是用cglib


二、先看效果:

action

public class TestAction {
	public String execute() {
		TestService service = ServiceManager.getService(TestService.class);
		service.doSth1();
		service.doSth2();
		return null;
	}
}

service

public class TestService {
	@Transaction
	public void doSth1() {
		System.out.println("执行事务业务");
	}

	public void doSth2() {
		System.out.println("执行无事务业务");
	}
}

结果:

/未检测到事务注解
执行无事务业务
//检测到事务注解
------------------开启事务--------------------
执行事务业务
------------------提交事务--------------------

因为用dao操作数据库还得看数据库数据变化,贴起来东西太多了,这里就模拟一下,但是它的确是好用的。


三、核心代码

service获取源:serviceManager

public class ServiceManager {

	/**
	 * 通过该方法得到写好的service对象。
	 * */
	@SuppressWarnings("unchecked")
	public static <T> T getService(Class<T> clazz) {
		Enhancer enhancer = new Enhancer();
		enhancer.setSuperclass(clazz);
		enhancer.setCallback(new ServiceTransactionInterceptor());
		return (T) enhancer.create();
	}
}
核心方法代理类:ServiceTransactionInterceptor
public class ServiceTransactionInterceptor implements MethodInterceptor {

	public Object intercept(Object obj, Method method, Object[] args,
			MethodProxy methodProxy) throws Throwable {

		if (method.isAnnotationPresent(Transaction.class)) {
			System.out.println("//检测到事务注解");
			TransactionManager manager = TransactionManager.getInstance();
			manager.startTranscation();
			Object result = null;
			try {
				result = methodProxy.invokeSuper(obj, args);
				manager.commit();
			} catch (Exception e) {
				manager.rollback();
			}
			return result;
		}
		System.out.println("/未检测到事务注解");
		return methodProxy.invokeSuper(obj, args);
	}

}

四、再看一下TransactionManager

先说下我的想法:

这里用的是mybatis数据库,dao就与mybatis相关了,要实现mybatis的事务,我就写了mybatis的事务管理器MyBatisTransactionManager,mybatis用的是sqlSession,这里我也写好了SessionFactory,里面的sqlSession也是被做过手脚的。

总之就是MyBatisTransactionManager与SessionFactory的耦合性有点高,所以就放一起写好了。

等以后要是用其他持久层框架的话,那就再写一个XXXTransactionManager以及SessionFactory。


所以在工程的classPath上就有一个配置文件,里面设置TransactionManager的类名,可以自定义TransactionManager,然后去修改配置文件。

serviceSupport.properties

TransactionManager=com.aii.struts.service.transaction.mybatis.MyBatisTransactionManager


因为考虑到通用性,所以这里就用TransactionManager抽象类,具体的实例去配置中读(没读到就默认我写好的这个)。

在jdk8中接口中就能使用带static或者default的已实现方法,但是为了通用,就不用接口了。

TransactionManager

public abstract class TransactionManager {

	public abstract void startTranscation();

	public abstract void commit();

	public abstract void rollback();

	@SuppressWarnings("rawtypes")
	public static TransactionManager getInstance() {
		String className = PropertiesReader.read("TransactionManager");
		if (className == null) {
			className = "com.aii.struts.service.transaction.mybatis.MyBatisTransactionManager";
		}
		try {
			Class clazz = Class.forName(className);
			Field instance = clazz.getDeclaredField("manager");
			instance.setAccessible(true);
			return (TransactionManager) instance.get(null);
		} catch (NoSuchFieldException e) {
			throw new RuntimeException(
					"there should a manager filed in manager");
		} catch (Exception e) {
			e.printStackTrace();
			throw new RuntimeException(e);
		}
	}
}
对了,这个transaction中得有一个属性叫做manager,不然我去new实例不好把。


具体的实现类:

public class MyBatisTransactionManager extends TransactionManager{

	private static MyBatisTransactionManager manager = new MyBatisTransactionManager();

	private MyBatisTransactionManager() {
	};

	private ThreadLocal<SqlSession> sessions = new ThreadLocal<SqlSession>();
	private ThreadLocal<Boolean> states = new ThreadLocal<Boolean>();
	 static MyBatisTransactionManager get(){
		return manager;
	}
	public void startTranscation() {
		System.out.println("------------------开启事务--------------------");
		SqlSession sqlSession = SessionFactory.getOriginalSession();
		sessions.set(sqlSession);
		states.set(true);
	}

	public void commit() {
		System.out.println("------------------提交事务--------------------");
		SqlSession sqlSession = sessions.get();
		sqlSession.commit();
		sqlSession.close();
		sessions.remove();
		states.remove();
	}

	public void rollback() {
		System.out.println("------------------回滚事务--------------------");
		SqlSession sqlSession = sessions.get();
		sqlSession.rollback();
		sqlSession.close();
		sessions.remove();
		states.remove();
	}

	Boolean getState() {
		return states.get();
	}

	SqlSession getSession() {
		return sessions.get();
	}
}
这在 FakeStruts实现@Transaction 注解事务控制中也有,基本没变。


五、SessionFactory

public class SessionFactory {
	public static SqlSessionFactory factory = null;
	private static final String CONFIGURATION_PATH = "configuration.xml";
	static {
		SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();

		factory = builder.build(Thread.currentThread().getContextClassLoader()
				.getResourceAsStream(CONFIGURATION_PATH));
	}

	public static SqlSession getSession() {
		MyBatisTransactionManager manager = MyBatisTransactionManager.get();
		Boolean state = manager.getState();
		// 否则自己产生,直接返回
		if (state == null || !state) {
			System.out.println("未开启事务,正常执行");
			return getOriginalSession();
		}
		// 如果有事务处理,则从manager中拿
		System.out.println("开启事务,返回代理过的sqlSession");
		return (SqlSession) Proxy.newProxyInstance(Thread.currentThread()
				.getContextClassLoader(), new Class[] { SqlSession.class },
				new ProxySqlSessionHandler(manager.getSession()));
	}

	static SqlSession getOriginalSession() {
		return factory.openSession();
	}

	static class ProxySqlSessionHandler implements InvocationHandler {
		private SqlSession sqlSession;

		public ProxySqlSessionHandler(SqlSession sqlSession) {
			this.sqlSession = sqlSession;
		}

		public Object invoke(Object proxy, Method method, Object[] args)
				throws Throwable {
			// 查看transactionManager状态,根据状态确定session.commit,session.close
			Boolean state = MyBatisTransactionManager.get().getState();
			if (method.getName().equals("commit") && state) {
				return null;
			}
			if (method.getName().equals("close") && state) {
				return null;
			}
			return method.invoke(sqlSession, args);
		}

	}
}

也是 FakeStruts实现@Transaction 注解事务控制中有的。

可以看到这两个类相互依赖,无法拆开。。。


到这里,service的事务就实现了,没有spring事务也能这么玩了。


测试用例、源码下载地址:

点击打开链接

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值