期待JForum3的到来!!JForum3火速前瞻

来源:互联网 发布:talkingdata 数据科学 编辑:程序博客网 时间:2024/04/30 01:12
相信在项目中如果遇到必须使用java平台的论坛,很多人都会选择jforum。原因可简单归结为:
  1. 使用BSD许可证基于java平台,能在几乎所有的servlet容器上跑
  2. 支持多种类型数据库,还具有傻瓜式的安装方式
  3. 使用了缓存技术,表示层使用freemarker,运行速度相当快
  4. 支持SSO,可以自定义SSO实现,通过配置文件即可进行调整
为此我想我们应该感激Rafael Steil。因为项目需求,我开始了对jforum的二次开发和bug修改等工作,对它的框架、代码结构也在不断的理解。虽然项目暂时告一段落了,但是我还是很热衷关注jforum的发展,今天到官方网站上兜了几圈,找到了jforum3的svn代码库,最近2个月Rafael Steil的开发还是很活跃的http://fisheye2.atlassian.com/browse/jforum/trunk。为此虽然本人功力不深,但也很想写点什么表达自己看到jforum3的感受。

jforum3技术纵览
  • 使用VRaptor2实现WEB MVC
  • 页面渲染可能使用jsp+jstl+jforum-tags
  • bean管理终于用了spring
  • 持久层使用hibernat,原来的缓存技术暂时没有在代码中看到
  • 使用了流行的action/service/dao三层结构
从web.xml开始了解jforum3

<context-param>
    <param-name>org.vraptor.url.LogicLocator</param-name>
    <param-value>net.jforum.core.support.vraptor.DefaultLogicLocator</param-value>
</context-param

前面有提到过使用了VRaptor 2,关于这个框架,国内使用的人不多。但事实上,当我到它的官方网站去了解后,发现文档还是做的不错的,例子也很简单明了。LogicLocator是用来把request   URI进行特殊解析后,得到LogicMethod的类。因为VRaptor 2默认的uri是person.add.logic这种形式的,而jforum经典的形式是/forums/list.page这种以"/"分隔的。LogicLocator.locate()返回一个LogicMethod,拿个和struts2相对应的比喻来说,就是action中的一个方法,一个url请求过来,执行action里面的一个方法。

<context-param>
    <param-name>org.vraptor.url.ViewLocator</param-name>
    <param-value>net.jforum.core.support.vraptor.DefaultViewLocator</param-value>
</context-param>

这个是LogicMethod执行完成后,返回的视图控制器,以forward、redirect等形式返回的视图选择就在这里面进行具体实现。

<filter>
    <filter-name>hibernateSessionViewFilter</filter-name>
    <filter-class>net.jforum.core.support.hibernate.OpenSessionInViewFilter</filter-class>
</filter>

这是大家熟悉的hibernate session管理方式,在chain.doFilter()前让sessionFactory为当前线程分配一个session,打开事务,chain.doFilter()之后如果操作正常着提交事务,关闭session。在jforum2.18中,类似的功能由servlet来完成,既执行具体的command前获得connection,放到JForumExecutionContext中(其实就是一个ThreadLocal),执行command结束后,如果启用了事务,则关闭事务,关闭connection。

当然,sessionFactory是有spring来管理的,这是一个AnnotationSessionFactoryBean,也就没有了hibernate 的mapping文件了,mapping都使用Annotation写在entity类里面。

<bean id="sessionFactory" class="org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean">
    <property name="annotatedClasses">
        <list>
            <value>net.jforum.entities.Category</value>
                ......
        </list>
    </property>
    <property name="hibernateProperties">
        <props>
            <prop key="hibernate.dialect">org.hibernate.dialect.MySQLDialect</prop>
                .......
        </props>
    </property>
    <property name="eventListeners">
        <map>
            <entry key="load"><ref local="ForumRepositoryLoadListener"/></entry>
        </map>
    </property>
</bean>
<bean id="ForumRepositoryLoadListener" class="net.jforum.core.support.hibernate.ForumRepositoryLoadListener"/>

值得关注的是sessionFactory的eventListeners,这是为dao类注入sessionFactory属性的:

    @Override
    public void onLoad(LoadEvent event, LoadType type) throws HibernateException {
        super.onLoad(event, type);
       
        if (event.getEntityClassName().equals(Forum.class.getName())) {
            this.dao.setSessionFactory(event.getSession().getSessionFactory());
            ((Forum)event.getResult()).setRepository(dao);
        }
    }

jforum3使用了hibernate做持久层,那么原来繁琐的dao编码将少了很多,若是需要进行数据库迁移,也是相当容易的事情了。关于dao类的分析,其实没什么可说的,和平常大家看到的spring+hibernate的代码没什么区别。下面再回到web.xml这个文件来,说说jforum3的核心控制器:

<servlet>
    <servlet-name>jforum</servlet-name>
    <servlet-class>net.jforum.JForumServlet</servlet-class>
    <init-param>
        <param-name>configuration.directory</param-name>
        <param-value>/WEB-INF/jforumConfig</param-value>
    </init-param>
</servlet>
<servlet-mapping>
    <servlet-name>jforum</servlet-name>
    <url-pattern>*.page</url-pattern>
</servlet-mapping>

从配置上来说,乍一看和jforum2.18没什么,都是一个servlet。但读到它的源代码,你会发现原来forum2.18在启动过程中超级复杂而乱七八糟的关系,一下子变得明了多了:

public class JForumServlet extends VRaptorServlet {
    /**
     * 初始化springContext、jforum的其他配置,以及VRaptor视图配置
     * @see javax.servlet.GenericServlet#init(javax.servlet.ServletConfig)
     */
    @Override
    public void init(ServletConfig config) throws ServletException {
        super.init(config);
        JForumBootstrap bootstrap = new JForumBootstrap();
        bootstrap.run(this.getServletContext(), config);
    }
   
    @Override
    protected void service(HttpServletRequest request, HttpServletResponse response)
        throws ServletException, IOException {
        Introspector introspector = (Introspector)this.getServletContext().getAttribute(Introspector.class.getName());
        VRaptorServletRequest wrappedRequest = new VRaptorServletRequest(request, introspector);
       
        ApplicationContext springContext = (ApplicationContext)this.getServletContext()
            .getAttribute(ConfigKeys.SPRING_CONTEXT);
        ControllerUtils controllerUtils = (ControllerUtils)springContext.getBean(ControllerUtils.class.getName());
        UserSession userSession = controllerUtils.refreshSession(request, response);
        request.setAttribute("userSession", userSession);
        request.setAttribute(UserSession.class.getName(), userSession);
        request.setAttribute(ViewPropertyBag.class.getName(), new ViewPropertyBag(request));
        request.setAttribute(ViewService.class.getName(),
            new ViewService(request, response, (JForumConfig)springContext.getBean(JForumConfig.class.getName())));
        super.service(wrappedRequest, response);
        userSession.setRequestAndResponse(null, null);
    }
}

这里可以看到,这个servlet基础了VRaptor框架的VRaptorServlet。所有的jforum论坛相关配置完全由JForumBootstrap.run()完成,其实就是读取SystemGlobals.properties等重要的配置文件,所有的配置信息其实由JForumConfig来操作,然后JForumConfig又是有spring来管理的,那么JForumBootstrap.run()算是对JForumConfig的初始化吧。

在执行具体的Component(Action)前,servlet都会初始化UserSession对象,以供在Component需要判断用户权限等操作时使用。不管是cookie登陆还是SSO功能仍然由controllerUtils.refreshSession()负责完成。原来command的恐怖代码在使用了这些技术后,可以大大的减少,下面这个是TopicComponent.list()方法,既负责完成浏览主题:

    public void list(@Parameter(key = "topic_id") int topicId) {
        Topic topic = this.topicRepository.get(topicId);
        List<Post> posts = this.postRepository.getAllPosts(topic, 0,
            this.config.getInt(ConfigKeys.POST_PER_PAGE));    
        this.propertyBag.put("posts", posts);
        this.propertyBag.put("topic", topic);
        this.propertyBag.put("forum", topic.getForum());
        this.propertyBag.put("categories", this.categoryRepository.getAllCategories());
    }

可以看到技术在这个方法里面完成权限控制功能,代码也不会很多,不过由于VRaptor也有拦截器,估计Rafael Steil不会把代码写在每个方法里面。

后记:

希望大家对jforum3的开发也多多关注,让jforum3以更快的脚步到来。附上jforum3目录结构:

└─jforum
    ├─compile-lib
    ├─src
    │  ├─main
    │  │  ├─java
    │  │  │  └─net
    │  │  │      └─jforum
    │  │  │          ├─actions
    │  │  │          │  └─misc
    │  │  │          ├─api
    │  │  │          ├─bbcode
    │  │  │          ├─core
    │  │  │          │  ├─exceptions
    │  │  │          │  ├─hibernate
    │  │  │          │  ├─support
    │  │  │          │  │  ├─hibernate
    │  │  │          │  │  └─vraptor
    │  │  │          │  └─tags
    │  │  │          ├─entities
    │  │  │          │  └─util
    │  │  │          ├─repository
    │  │  │          ├─services
    │  │  │          ├─sso
    │  │  │          ├─startup
    │  │  │          └─util
    │  │  └─resources
    │  └─test
    │      ├─java
    │      │  └─net
    │      │      └─jforum
    │      │          ├─actions
    │      │          ├─bbcode
    │      │          ├─core
    │      │          │  ├─hibernate
    │      │          │  └─support
    │      │          │      └─vraptor
    │      │          ├─entities
    │      │          ├─services
    │      │          └─util
    │      └─resources
    │          └─i18n
    ├─target
    │  └─tests
    │      ├─i18n
    │      └─net
    │          └─jforum
    │              ├─actions
    │              ├─bbcode
    │              ├─core
    │              │  ├─hibernate
    │              │  └─support
    │              │      └─vraptor
    │              ├─entities
    │              ├─services
    │              └─util
    └─webapp
        ├─images
        │  ├─avatar
        │  └─smilies
        ├─META-INF
        ├─templates
        │  ├─agreement
        │  ├─default
        │  │  ├─admin
        │  │  ├─admin.old
        │  │  │  ├─images
        │  │  │  ├─js
        │  │  │  └─macros
        │  │  ├─adminBanning
        │  │  ├─adminCategories
        │  │  ├─adminForums
        │  │  ├─adminGroups
        │  │  ├─adminRankings
        │  │  ├─adminSmilies
        │  │  ├─adminUsers
        │  │  ├─forums
        │  │  ├─images
        │  │  │  ├─de_DE
        │  │  │  ├─en_US
        │  │  │  ├─es_ES
        │  │  │  ├─fr_FR
        │  │  │  ├─pl_PL
        │  │  │  ├─pt_BR
        │  │  │  ├─pt_PT
        │  │  │  ├─ru_RU
        │  │  │  ├─zh_CN
        │  │  │  └─zh_TW
        │  │  ├─js
        │  │  ├─posts
        │  │  ├─styles
        │  │  ├─topics
        │  │  └─user
        │  ├─macros
        │  └─mail
        │      ├─de_DE
        │      ├─hu_HU
        │      ├─pt_BR
        │      ├─pt_PT
        │      └─zh_TW
        └─WEB-INF
            ├─classes
            │  └─net
            │      └─jforum
            │          ├─actions
            │          │  └─misc
            │          ├─api
            │          ├─bbcode
            │          ├─core
            │          │  ├─exceptions
            │          │  ├─hibernate
            │          │  ├─support
            │          │  │  ├─hibernate
            │          │  │  └─vraptor
            │          │  └─tags
            │          ├─entities
            │          │  └─util
            │          ├─repository
            │          ├─services
            │          ├─sso
            │          ├─startup
            │          └─util
            ├─jforumConfig
            │  ├─database
            │  │  ├─generic
            │  │  └─mysql
            │  └─languages
            └─lib