Skip to content

Latest commit

 

History

History
241 lines (174 loc) · 11.6 KB

README.md

File metadata and controls

241 lines (174 loc) · 11.6 KB

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(...);
        }
    }