通常的管理系统的菜单都是存储在数据库中,这样相当于写一遍代码同时还要操作一遍数据库,如果不同环境菜单表ID不相同那更是灾难。如果通过注解能自动生成菜单列表,这一切就简单的多。
扫描所有的action
我们可以通过Spring提供的SmartInitializingSingleton
接口来监听、处理Spring创建的bean。但似乎没有相应的url到action的映射的功能,我们可以通过WebMvcRegistrations
自己实现一个。
接口声明
public interface IMVCMappingHandler {
void afterMappingMethodsInstantiated(Map<RequestMappingInfo, HandlerMethod> handlerMethods);
}
处理类
@Component
public class WebMvcRegistrationsObserver implements WebMvcRegistrations {
private ApplicationContext context;
private MappingHandler handler = new MappingHandler();
public WebMvcRegistrationsObserver(ApplicationContext context) {
this.context = context;
}
@Override
public RequestMappingHandlerMapping getRequestMappingHandlerMapping() {
//返回我们自己的HandlerMapping
return handler;
}
private class MappingHandler extends RequestMappingHandlerMapping {
@Override
protected void handlerMethodsInitialized(Map<RequestMappingInfo, HandlerMethod> handlerMethods) {
super.handlerMethodsInitialized(handlerMethods);
//mvc已经处理完成,通知所有的bean
context.getBeansOfType(IMVCMappingHandler.class)
.forEach((name, bean) -> bean.afterMappingMethodsInstantiated(handlerMethods));
}
}
}
定义菜单
我们先声明一个菜单的注解
@Target({ ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MenuItem {
String label(); //菜单文字
String icon(); //菜单图标
}
再声明一个service
public interface IMenuUIService {
List<MenuVO> getAllMenu();
}
实现
@Component("menuUIService")
public class MenuUIServiceImpl implements IMVCMappingHandler, IMenuUIService {
private List<MenuVO> menus;
@Override
public void afterMappingMethodsInstantiated(Map<RequestMappingInfo, HandlerMethod> handlerMethods) {
List<MenuVO> arr = new LinkedList<>();
for (Map.Entry<RequestMappingInfo, HandlerMethod> entry : handlerMethods.entrySet()) {
HandlerMethod method = entry.getValue();
Class<?> beanType = method.getBeanType();
MenuItem menuItem =
AnnotatedElementUtils.getMergedAnnotation(method.getMethod(), MenuItem.class);
if (menuItem == null)
continue;
MenuVO vo = new MenuVO();
vo.setIcon(menuItem.icon());
vo.setLabel(menuItem.label());
//取第一个
vo.setUrl(entry.getKey().getPatternsCondition().getPatterns().iterator().next());
arr.add(vo);
}
menus = arr;
}
@Override
public List<MenuVO> getAllMenu() {
if (menus == null) {
throw new RuntimeException("菜单尚未初始化完成");
}
return menus;
}
}
主体功能就搭建完成了。
验证效果
这里用thymeleaf
做服务端渲染测试,bootstrap
菜单呈现。
<div class="left-menu">
<ul class="nav nav-pills nav-stacked">
<li role="presentation" th:each="menu : ${@menuUIService.getAllMenu()}"
th:classappend="${menu.getUrl().equals(#request.getRequestURI())} ? 'active'">
<a th:href="${menu.url}">
<span th:class="${menu.icon}" aria-hidden="true"></span>
<span th:text="${menu.label}"></span>
</a>
</li>
</ul>
</div>
声明两个菜单,图标用bootstrap
的Glyphicons
字体图标
@Controller
public class MenuController {
@GetMapping("/menu/menu1")
@MenuItem(label = "菜单1", icon = "glyphicon glyphicon-level-up")
public String menu1() {
return "view/menu/menu1";
}
@GetMapping("/menu/menu2")
@MenuItem(label = "菜单2", icon = "glyphicon glyphicon-log-in")
public String menu2() {
return "view/menu/menu2";
}
}
优点:简单省事
缺点:不支持参数,同一个action不能创建多个菜单
完整代码见 https://github.com/giafei/spring-security-token
下篇:https://www.jianshu.com/p/fbf91e347528