Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

56 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Tauren Framework

License

目录

一. 简介

Tauren framework 是一个轻量级Java Web框架,它提供了类似Spring frameworkIoCAOP功能。tauren具有如下特点:

  • 基于Servlet 3.0规范,可部署于Tomcat容器,或其它Servlet容器
  • 客户端不使用XML配置,完全使用注解进行开发
  • 应用基于面向服务编程(SOA),可进行分布式部署
  • 灵活性高,易于扩展

二. 约定

Tauren没有Spring framework那么强大,客户端在使用tauren过程中默认满足下列条件:

  • taurenIoc容器接管的类都有无参的构造方法且构造方法不是私有

  • @Bean只应用于类,应用于接口或抽象类将无效

  • 每个Controller被*@RequestMapping*标注的方法,其参数必须是HttpServletRequest

三. 实现方式

框架实现的类图如上图所示。下面分别说明各个模块的实现方式。

3.1 IoC(Inversion of Control)

3.1.1 ClassScanner

ClassScanner是类扫描器,提供了三种方法,都是根据条件,递归扫描客户端文件夹所有的类。但是getClassesByAnnotationgetClassesBySuper方法不会返回接口抽象类

3.1.2 BeanFactory

Bean工厂中首先通过ClassScanner获取所有带有 @Bean 注解的类,被 @Bean 注解的类说明需要被IoC容器接管。通过Class.newInstance()方法实例化类,并分别放入nameContainertypeContainer这两个Map中。IoC容器在具体实现起来是通过Map来实现的。    

3.1.3 BeanInjector

   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);
    

   

3.2 循环依赖问题

3.2.1 什么是循环依赖?

循环依赖就是循环引用,两个或多个Bean相互之间持有对方的引用,比如A类引用B类,B类引用C类,C类又引用A类,它们最终反映为一个环。

3.2.2 Spring是如何解决循环依赖?

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,故不能解决。

3.2.3 Tauren是如何解决循环依赖?

对于构造器循环依赖,tauren同样无法解决,因为这本身就是无解的。对于setter循环依赖,由于tauren只有注解模式,没有xml模式,且在注入字段表示的对象时,该字段所在的类已经被实例化了,因此setter循环依赖在tauren中并不存在。

3.3 AOP(Aspect Oriented Programming)

Tauren使用CGLib来实现代理类的生成

Tauren的AOP中暂时不支持对类的批量拦截,目前只做到了对指定类所有方法的拦截。首先定义了拦截模板类ProxyInterceptor,这个类定了模板方法,有前置增强before(),后置增强after()及异常增强exception()。客户端的拦截器需要继承此类并选择性覆盖这三个方法。ProxyInterceptor同时负责代理类的创建。

BeanFactory中在实例化Bean时,如果发现某个类被@Intercept修饰,则说明该类将会被框架拦截并增强,@Intercept 指定了增强类的名称或类型,二者不能同时为空。BeanFactory通过名称或者类型找到了目标类的增强类,通过创建代理类实例,取代容器中目标类的实例,这样从BeanFactory中取出的实例就是增强之后的对象。

3.4 MVC

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。

3.5 ORM

Tauren框架提供了抽象类BaseDao,子类DAO继承BaseDao后即可使用基本的增、删、改、查方法。子类DAO还可以自行扩展数据库操作方法。BaseDao是通过ThreadLocal来获取获取数据库连接,这一点很重要,因为这一设计是实现事务的关键。BaseDao对数据库的操作最终是委托给了apache DBUtil

Tauren框架利用apache commons-pool实现了一个数据库连接池,所有的数据库连接都是从这个连接池中产生。客户端不必关闭连接,连接池会维护关闭操作。

关于事务,Tauren提供了事务注解@Transaction,标注于类,表示该类所有方法执行事务操作;标注于类的方法,表示该方法执行事务操作。Tauren框架是通过代理类的方式实现事务的,即在IoC过程中,生成类的事务代理类。

四. 使用方式

4.1 IoC使用

@Bean
public class UserService {
    //do something
}

Login注入UserService

@Bean
public class Login {

    @Inject
    private UserService userService;
    
    //do something
}

4.2 AOP使用

以用户服务类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中的方法,原方法即得到增强。

4.3 MVC使用

在客户端,一个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字符串。

4.4 ORM使用

在客户端,一个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(...);
        }
    }

Releases

Packages

Contributors

Languages