在Java的开发生态中,aop也叫面向切面编程是一个很重要的开发思想,它的意思是程序在运行时,动态地将用反射将代码切入到类的指定方法、指定位置执行,从而可以实现类似事务管理/记录日志/细粒度权限控制…等。其实就是类似servlet技术中的Filter拦截器,但是filter只能运行在Servlet之外,具体到我们的业务代码就不通用了。面向切面就是我们拦截自定义方法对它执行前后做一些我们需要的操作。aop有两种实现方式静态代理(AspectJ)、动态代理(基于反射对实现了同样接口的类),本文讲的是针对事务管理使用动态代理的方式实现aop。
首先事务管理我们肯定在service层处理,因为业务逻辑层可能调用多个dao层操作数据库。这个时候我们会发现不是所有的service层方法都需要进行事物控制,所以我们在这层做个区分:
定义注解类,作为是否需要开启的Tag
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Tran {
}
如果需要就带上:
public interface OrderService extends Service{
/**
* 增加订单
* @param order 订单bean
*/
@Tran
void addOrder(Order order);
然后为了控制事物我们需要写个事物工具类来改造数据源,内部应该做到:
- 提供获取数据源方法,外部统一通过这个工具类拿到数据源(唯一的DataSource)
- 应该根据当前是否有上述的Tag,对数据源进行改造,没有就返回普通数据源,有就返回改造的数据源(拦截修改getConnection方法 )->返回经过改造的Connection(改造close方法)
- 提供开启事物的方法,如果开启过就保存改造的Connection(因为要执行多条sql就必须改造close方法,使他不能关闭连接)
- 提供提交回滚事物的方法
- 提供释放资源的方法
事物管理工具类
public class TransactionManager {
private TransactionManager() {
}
//--数据源,整个程序中都只有这一个数据源
private static DataSource source = new ComboPooledDataSource();
//--是否开启事务的标记
private static ThreadLocal<Boolean> isTran_local = new ThreadLocal<Boolean>(){
@Override
protected Boolean initialValue() {
return false;//--最开始false,表明默认不开启事务
}
};
//--保存真实连接的代理连接,改造过close方法
private static ThreadLocal<Connection> proxyConn_local = new ThreadLocal<Connection>(){};
//--保存真实连接
private static ThreadLocal<Connection> realconn_local = new ThreadLocal<Connection>(){};
/**
* 开启事务的方法
* @throws SQLException
*/
public static void startTran() throws SQLException{
isTran_local.set(true);//--设置事务标记为true
final Connection conn = source.getConnection();//--创建连接,所有当前线程中的数据库操作都基于这个conn
conn.setAutoCommit(false);//--开启事务
realconn_local.set(conn);//--为了方便后续关闭连接,将这个连接保存起在当前线程中
//--由于一个事务需要执行多条sql,每个sql执行过后都关闭连接,这样一来后续的sql没法执行,所以这个地方法改造close方法,使他不能关闭连接
Connection proxyConn = (Connection) Proxy.newProxyInstance(conn.getClass().getClassLoader(), conn.getClass().getInterfaces()
, new InvocationHandler(){
public Object invoke(Object proxy, Method method,
Object[] args) throws Throwable {
if("close".equals(method.getName())){
return null;
}else{
return method.invoke(conn, args);
}
}
});
proxyConn_local.set(proxyConn);
}
/**
* 提交事务
*/
public static void commit(){
DbUtils.commitAndCloseQuietly(proxyConn_local.get());
}
/**
* 回滚事务
*/
public static void rollback(){
DbUtils.rollbackAndCloseQuietly(proxyConn_local.get());
}
/**
* 这个方法应该做到:
* 如果没有开启过事务,则返回最普通的数据源
* 如果开启过事务,则返回一个改造过getConnection方法的数据源,这个方法改造后每次都返回同一个开启过事务的Connection
* @return
* @throws SQLException
*/
public static DataSource getSource() throws SQLException{
if(isTran_local.get()){
//--如果开启过事务,则返回改造的DataSource,
//改造为每次调用getConnection都返回同一个开启过事务的Conn
return (DataSource) Proxy.newProxyInstance(source.getClass().getClassLoader(), source.getClass().getInterfaces()
,new InvocationHandler(){
public Object invoke(Object proxy, Method method,
Object[] args) throws Throwable {
if("getConnection".equals(method.getName())){
return proxyConn_local.get();
}else{
return method.invoke(source, args);
}
}
});
}else{//--没有开启过事务,返回普通的数据源
return source;
}
}
/**
* 释放资源
*/
public static void release(){
//--之前连接是没有关闭的在release的时候真正的关闭连接
DbUtils.closeQuietly(realconn_local.get());
realconn_local.remove();
proxyConn_local.remove();
isTran_local.remove();
}
}
最后当我们没使用spring那些注解配置注入service,dao对象的时候,我们自己实现一个类似的工厂类来创建对象。
工厂类创建service,dao对象
/**
* BasicFactory
* 工厂类实现层与层接耦1.写接口dao 和 service层中
* 2.在config.properties中写配置文件
* 3.写工厂类实现方法基于配置文件生成对象
*/
public class BasicFactory {
private static BasicFactory factory= new BasicFactory();
private static Properties prop =null;
private BasicFactory(){} //使其没法new对象
static{
try{
prop=new Properties();
prop.load(new FileReader(BasicFactory.class.getClassLoader().getResource("config.properties").toURI().getPath()));
}catch (Exception e) {
// TODO: handle exception
e.printStackTrace();
throw new RuntimeException(e);
}
}
public static BasicFactory getFactory(){
return factory;
}
public <T extends Dao> T getDao(Class<T> clazz){//其他方法调用这个方法的时候,需要传入一个UserDao.class或者UserService.class的名称
try {
String InfName = clazz.getSimpleName();//接口的名字UserDao/UserService
String ImplName = prop.getProperty(InfName);//通过接口名字获取配置文件中实现类的全路径名com.itheima.dao.UserDaoImpl、com.itheima.service.UserServiceImpl
return (T)Class.forName(ImplName).newInstance();//产生一个新对象并强制转换为T类型,其实就是获得接口的实例对象。
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException(e);
} }
/**
* 获取Service的方法
*/
public <T extends Service> T getService(Class<T> clazz){
try{
//--根据配置文件创建具体的Service
String infName = clazz.getSimpleName();
String implName = prop.getProperty(infName);
final T service = (T) Class.forName(implName).newInstance();
//--为了实现AOP,生成service代理,根据注解确定在Service方法执行之前和之后做一些操作,比如:事务管理/记录日志/细粒度权限控制....
@SuppressWarnings("unchecked")
T proxyService = (T) Proxy.newProxyInstance(service.getClass().getClassLoader(), service.getClass().getInterfaces()
, new InvocationHandler(){
//根据注解控制事务
public Object invoke(Object proxy, Method method,Object[] args) throws Throwable {
if(method.isAnnotationPresent(Tran.class)){//如果有注解,则管理事务:
try{
TransactionManager.startTran();//--开启事务
Object obj = method.invoke(service, args);//--真正执行方法
TransactionManager.commit();//--提交事务
return obj;
}catch (InvocationTargetException e) {
TransactionManager.rollback();//--回滚事务
throw new RuntimeException(e.getTargetException());
} catch (Exception e) {
TransactionManager.rollback();//--回滚事务
throw new RuntimeException(e);
}finally{
TransactionManager.release();//--释放资源
}
}else{//如果没有注解,则不管理事务,直接执行方法
return method.invoke(service, args);
}
}
});
return proxyService;
}catch (Exception e) {
e.printStackTrace();
throw new RuntimeException(e);
}
}
}
dao层我们就不用处理了,处理Service层就行。