Tauren framework 是一个轻量级Java Web框架,它提供了类似Spring framework的IoC和AOP功能。tauren具有如下特点:
- 基于Servlet 3.0规范,可部署于Tomcat容器,或其它Servlet容器
- 客户端不使用XML配置,完全使用注解进行开发
- 应用基于面向服务编程(SOA),可进行分布式部署
- 灵活性高,易于扩展
Tauren没有Spring framework那么强大,客户端在使用tauren过程中默认满足下列条件:
-
被taurenIoc容器接管的类都有无参的构造方法且构造方法不是私有的
-
@Bean只应用于类,应用于接口或抽象类将无效
-
每个Controller被*@RequestMapping*标注的方法,其参数必须是HttpServletRequest
ClassScanner是类扫描器,提供了三种方法,都是根据条件,递归扫描客户端文件夹所有的类。但是getClassesByAnnotation和getClassesBySuper方法不会返回接口和抽象类
Bean工厂中首先通过ClassScanner获取所有带有 @Bean 注解的类,被 @Bean 注解的类说明需要被IoC容器接管。通过Class.newInstance()
方法实例化类,并分别放入nameContainer和typeContainer这两个Map中。IoC容器在具体实现起来是通过Map来实现的。
Bean注入器完成的工作是遍历BeanFactory中实例化的类,再依次遍历各个类的字段,找出被 @Inject 注解的字段,默认通过名称的方式注入对象;如果通过名称注入失败,则改用通过类型注入的方式。
-
几点说明
-
如果一个接口有多个实现类,则在使用 @Bean 标注时必须填入类的名称,在注入的地方 @Inject 也必须使用名称,即这种情况必须使用名称注入方式
-
如果一个类实现了多个接口,在使用
BeanFactory
获取Bean时,存在以下几种情况:
-
//假设 UserServiceImpl implements UserService, StudentService
Object bean1 = factory.getBean("userServiceImpl");
Assert.assertNotNull(bean1);
Object bean2 = factory.getBean(UserService.class);
Assert.assertNotNull(bean2);
Object bean3 = factory.getBean("userServiceImpl", UserService.class);
Assert.assertNotNull(bean3);
Object bean4 = factory.getBean("userServiceImpl", StudentService.class);
Assert.assertNotNull(bean4);
循环依赖就是循环引用,两个或多个Bean相互之间持有对方的引用,比如A类引用B类,B类引用C类,C类又引用A类,它们最终反映为一个环。
Spring容器循环依赖包括构造器循环依赖和setter循环依赖。
构造器循环依赖表示通过构造器注入构成循环依赖,此依赖是无法解决的,只能抛出 BeanCurrentlyInCreationException 异常表示循环依赖。在创建A类时,构造器需要B类,那将去创建B类,在创建B类时又发现需要C类,那将去创建C类,最终在创建C类又发现需要A类,从而形成一个环,无法创建。
Spring容器将每一个正在创建的Bean标识符放在一个“当前创建Bean池”中,Bean标识符在Bean创建过程中一直保存在这个池中(Map保存),因此在创建Bean过程中发现自己已经在这个池中时将抛出 BeanCurrentlyInCreationException 异常表示循环依赖;而对于创建完毕的Bean则从池中清除。
setter循环依赖表示通过setter注入方式构成的循环依赖。对于setter循环依赖,spring是通过先无参构造方法创建一个实例提前把A的引用暴露出来并缓存,并且只能解决单例作用域的Bean循环依赖,而对于prototype
作用域的Bean,由于Spring不缓存,无法提前暴露一个创建中的Bean,故不能解决。
对于构造器循环依赖,tauren同样无法解决,因为这本身就是无解的。对于setter循环依赖,由于tauren只有注解模式,没有xml模式,且在注入字段表示的对象时,该字段所在的类已经被实例化了,因此setter循环依赖在tauren中并不存在。
Tauren使用CGLib来实现代理类的生成
Tauren的AOP中暂时不支持对类的批量拦截,目前只做到了对指定类所有方法的拦截。首先定义了拦截模板类ProxyInterceptor,这个类定了模板方法,有前置增强before(),后置增强after()及异常增强exception()。客户端的拦截器需要继承此类并选择性覆盖这三个方法。ProxyInterceptor同时负责代理类的创建。
BeanFactory中在实例化Bean时,如果发现某个类被@Intercept修饰,则说明该类将会被框架拦截并增强,@Intercept 指定了增强类的名称或类型,二者不能同时为空。BeanFactory通过名称或者类型找到了目标类的增强类,通过创建代理类实例,取代容器中目标类的实例,这样从BeanFactory中取出的实例就是增强之后的对象。
Tauren框架只有一个Servlet——DispatcherServlet,其作用是拦截所有客户端的请求,将请求分发给各个Controller。
一个客户端的请求,包含了请求方式(POST、GET等)、URI、参数等内容,我们用请求方式:URI来唯一标识一个请求,例如GET:/longin/check,而这个新字符串将会对应某个Controller的一个方法,这个方法有可能返回一个页面,或返回一个JSON字符串。
基于上述的思想,我们在对类进行初始化时,如果遇到一个Controller,则遍历Controller中声明的被*@RequestMapping标记的方法,将@RequestMapping*中定义的值组成请求方式:URI作为key并放入Map,value为一个Action类。Action封装了该URI要访问的对象、方法及参数。
当客户端请求过来时,通过客户端的请求组成的key去Map中查询对应的Action,如果查到了,就通过反射执行对应Controller的方法,没查到则返回404。
Tauren框架提供了抽象类BaseDao
,子类DAO继承BaseDao
后即可使用基本的增、删、改、查方法。子类DAO还可以自行扩展数据库操作方法。BaseDao
是通过ThreadLocal
来获取获取数据库连接,这一点很重要,因为这一设计是实现事务的关键。BaseDao
对数据库的操作最终是委托给了apache DBUtil
。
Tauren
框架利用apache commons-pool
实现了一个数据库连接池,所有的数据库连接都是从这个连接池中产生。客户端不必关闭连接,连接池会维护关闭操作。
关于事务,Tauren提供了事务注解@Transaction
,标注于类,表示该类所有方法执行事务操作;标注于类的方法,表示该方法执行事务操作。Tauren
框架是通过代理类的方式实现事务的,即在IoC过程中,生成类的事务代理类。
@Bean
public class UserService {
//do something
}
为Login
注入UserService
@Bean
public class Login {
@Inject
private UserService userService;
//do something
}
以用户服务类UserServiceImpl
为例,需要当这个类中所有方法增加日志功能,先写好LogProxy
,并继承ProxyInterceptor
:
@Bean
public class LogProxy extends ProxyInterceptor {
@Override
protected void before() {
System.out.println("log from before");
}
@Override
protected void after() {
System.out.println("log from after");
}
}
然后在UserServiceImpl
上打上@Intercept
注解:
@Bean
@Intercept(type = LogProxy.class)
public class UserServiceImpl implements UserService {
// do something
}
然后正常调用UserService
中的方法,原方法即得到增强。
在客户端,一个Controller的典型写法是这样的:
@Controller
public class LoginController {
@Inject
private LoginService loginService;
/** 返回loginRet.jsp页面 */
@RequestMapping(value = "/login/userLogin")
public String login(HttpServletRequest request) {
loginService.login();
return "loginRet";
}
/** 返回JSON对象 */
@RequestMapping(value = "/login/check", responseMethod = ResponseMethod.JSON)
public User check(HttpServletRequest request) {
User user = new User(1, "宋江");
return user;
}
}
@Controller
的作用和@Bean
的作用是一样的,只不过名称不一样而已。每个被@RequestMapping
标记的方法,其参数只能是HttpServletRequest,框架负责将该参数注入。方法的返回值是一个字符串,该字符串实际代表的是loginRet.jsp。
@RequestMapping
注解有三个值:value表示uri;requestMethod表示http请求方式,默认同时支持GET和POST;responseMethod表示返回类型,可以是页面也可以是JSON字符串。
在客户端,一个DAO的典型写法是这样的:
@Bean
public class BizPayOrderDao extends BaseDao {
@Transaction
public int updateOrder(String sql, Object... params) throws SQLException {
int cnt = super.update(sql, params);
return cnt;
}
如果在Service层使用事务,一般是这样:
@Bean
public class UserServiceImpl extends UserService {
@Inject
private UserDao userDao;
@Inject
private OrderDao orderDao;
@Transaction
public updateUserInfo(User user){
userDao.update(...);
orderDao.update(...);
}
}