前端时间把struts-menu扩展成动态从数据库中读取的方式,遇到了一些问题,归纳如下:
从上面的资料不难看出要动态构造sturts-menu菜单只需要构造出符合
struts-menu中需要用的menuComponent,并把这些menuComponen添加到
MenuRepository中,设置MenuRepository的displayers,并把MenuRepository
存入到容器中,那么只要在jsp中指定MenuRepository和displayer以及是否需要
权限控制即可以打印出菜单了。路子已经有了,第一部要做的就是构造这个list,
官方例子为了方便演示将持久层和业务层放在一起了,我是分开做的:构造自定义
的Menu对象,如下为所需的属性,struts-menu的menuComponent的属性要比这些多很多,需要的可以自己查查看。
Model层
Menu对象所需属性:
private String parentName;
private String name;
private String title;
private String description;
private String location;
private String roles;
service层:
1)interface:
public interface MenuManager {
/**
* 通过menuList构造MenuRepository来生成struts-menu
* @return MenuRepository
*/
public MenuRepository getMenuRepository();
}
2)impl
public class MenuManagerImpl implements MenuManager {
private AcegiCacheManager acegiCacheManager;
private ResourcesDao resourcesDao;
public AcegiCacheManager getAcegiCacheManager() {
return acegiCacheManager;
}
public void setAcegiCacheManager(AcegiCacheManager acegiCacheManager) {
this.acegiCacheManager = acegiCacheManager;
}
public ResourcesDao getResourcesDao() {
return resourcesDao;
}
public void setResourcesDao(ResourcesDao resourcesDao) {
this.resourcesDao = resourcesDao;
}
/**
* 从数据库中读取资源构造Menu的list对象
* @return menuList
*/
private List getMenuList(){
List menuList = new ArrayList();
Menu menu = null;
Resources res = null;
String roles;
String parentId;
String parentName;
List ressList = resourcesDao.getResourcesByType(com.wonder.cdc.oa.service.security.Constants.RESOURCE_URL);
if(ressList != null){
for(int i = 0; i < ressList.size(); i++){
res = (Resources)ressList.get(i);
menu = new Menu();
if(!StringUtils.isBlank(res.getResource())){
roles = acegiCacheManager.getPrivsByResources(res.getResource());
}else{
roles = "";
}
menu.setDescription(res.getDescription());
if(!StringUtils.isBlank(res.getResource())){
menu.setLocation(res.getResource());
}
menu.setName(res.getName());
//如果为子类的情况下需存入父类的名字
if(res.getId().length() > 3){
parentId = res.getId().substring(0,res.getId().length() - 3);
parentName = resourcesDao.findNameById(parentId);
if(!StringUtils.isBlank(parentName)){
menu.setParentName(parentName);
}
}
menu.setRoles(roles);
menu.setTitle(res.getName());
menuList.add(menu);
}
}
return menuList;
}
/**
* @see com.wonder.cdc.oa.service.MenuManager#getMenuRepository()
*/
public MenuRepository getMenuRepository(){
MenuRepository repository = new MenuRepository();
List list = getMenuList();
for (int i=0; i < list.size(); i++) {
MenuComponent menuComponent = new MenuComponent();
Menu menu = (Menu)list.get(i);
String name = menu.getName();
menuComponent.setName(name);
String parent = (String) menu.getParentName();
if (parent != null) {
MenuComponent parentMenu = repository.getMenu(parent);
if (parentMenu == null) {
parentMenu = new MenuComponent();
parentMenu.setName(parent);
repository.addMenu(parentMenu);
}
menuComponent.setParent(parentMenu);
}
String title = (String) menu.getTitle();
menuComponent.setTitle(title);
String description = (String) menu.getDescription();
menuComponent.setDescription(description);
String location = (String) menu.getLocation();
if(!StringUtils.isBlank(location)){
menuComponent.setLocation("/" + Constants.APP_NAME + location);
}
String roles = (String) menu.getRoles();
if(!StringUtils.isBlank(roles)){
menuComponent.setRoles(roles);
}
repository.addMenu(menuComponent);
}
return repository;
}
}
需要注意的,如果location为空则菜单项不显示,如果菜单项没有对应权限那么就不要设置setRoles,如果
setRoles为空字符串,而当前用户又没有空字符串的权限的话那么此菜单项也不显示。
现在数据都准备好了,在何处构造MenuRepository对象合适那?
我想到了3处,但是权衡利弊后我选择了第一项。
1.系统启动时
优点:响应快速
缺点:如果修改resources以及用户相关信息需重新启动系统后修改才可以生效或者修改resources的同时更新application范围内的
MenuRepository,后者导致增加代码量,增加日后系统维护、扩展的难度
疑问:如果在系统正式发布后菜单项不变的情况下是可行的,系统启动MenuRepository对象会常驻内存,仅需要1次访问数据库
2.用户登陆时
优点:菜单灵活度提高,菜单相关信息改变后的响应时间缩短,只需用户再次登陆就可以生效
缺点:每个用户登陆都要构造用户相对应的MenuRepository对象存在会话中,过度占用系统资源
3.定指标签,每个页面包含
优点:菜单灵活度提高,只需刷新页面即可获得最新的菜单项
缺点:每次刷新页面都会访问一次数据库,导致系统性能降低,当然可以通过cache来解决过渡访问数据库的问题,但同时也增加了代码量
我想针对不同的应用可以采取如上3种不同的解决方案(我认为的3种,应该会有更好的),针对我们目前的项目感觉采取第一种方案比较适合
启动时实现就非常的简单了,在实现了ServletContextListener的类的contextInitialized(ServletContextEvent event)中添加如下代码:
MenuRepository defaultRepository = (MenuRepository) context.getAttribute(MenuRepository.MENU_REPOSITORY_KEY);
MenuRepository mr = menuManager.getMenuRepository();
mr.setDisplayers(defaultRepository.getDisplayers());
context.setAttribute("repository",mr);
这里我遇到了一个问题,context.getAttribute(MenuRepository.MENU_REPOSITORY_KEY)这句返回的是个空
到网上查了一下(
http://struts-menu.sourceforge.net/faq.html)
发现menu-config.xml文件是通过MenuContextListener加载的,查看了一下web.xml确实声明了这个listener.
我怀疑这里是否能加载上.继续看那个网页发现如果version在2.3以上(我的是2.4)可以通过spring的ioc容器来管理。
修改一下,重起,问题解决了。但是为什么通过listener的方式加载不了我还没有研究,正常来讲应该也是可以的。
剩下的就是页面了:
包含这句就可以了
java 代码
- <menu:useMenuDisplayer name="Velocity" config="WEB-INF/classes/cssHorizontalMenu.vm" permissions="rolesAdapter"
- repository="repository">
- <ul id="primary-nav" class="menuList">
- <li class="pad"> </li>
- <c:if test="${empty pageContext.request.remoteUser}"><li><a href="<c:url value="/login.jsp"/>"
- class="current"><fmt:message key="login.title"/></a></li></c:if>
- <c:forEach var="menu" items="${repository.topMenus}">
- <menu:displayMenu name="${menu.name}"/>
- </c:forEach>
- </ul>
- </menu:useMenuDisplayer>