大家好,我是香香。
在 Java 编程中,注解是一种元数据,它们提供了对程序元素(类、方法、字段等)进行标记和添加信息的机制。
除了 Java 本身提供的注解外,开发者还可以通过自定义注解来满足特定需求。
比如说我们在日常编写代码的时候,非常容易遇到某一些接口只允许已经登录的用户访问,而有的接口不需要登录也可以访问的需求。虽然有很多优秀的框架可以直接支持上述需求的实现,但是如果抛开框架不谈,有什么好的方法吗?
这时候有的同学可能就说了,我直接 if/else 判断,如果没有权限就不给他访问不就行了?是,确实是可以的,但是有没有发现,当随着业务慢慢复杂起来,这就会导致我们编写的代码重复度非常高,非常不利于维护,甚至于后期 Bug 不断,进而导致项目重构也不是没有可能。这就是不注重编码规范带来的苦果呀…
当然,我们今天的话题并不是教你如何写出更规范的代码(其实没有绝对的规范),而是除了框架,除了 if/else 之外的 自定义注解(重点加粗) 结合 AOP 实现权限校验,具体实现在文末会提到~
1. 自定义注解的适用场景
自定义注解在 Java 开发中具有广泛的应用场景,包括以下几个方面:
-
标记与配置:自定义注解可以用于标记特定的类、方法或字段,以便在后续的处理过程中进行识别和处理。比如,在 Spring 框架中,
@Autowired
和@Component
等注解用于标记依赖注入和组件扫描的目标对象。 -
编译时检查:通过自定义注解,开发者可以在编译时对程序进行静态检查,以确保程序的正确性和健壮性。例如,可以定义一个
@NonNull
注解,用于标记方法参数或返回值不能为空,从而在编译时检查是否存在空指针异常的可能性。 -
运行时处理:自定义注解也可以在运行时进行处理,实现一些特定的功能或行为。比如,在 Spring 框架中,
@Transactional
注解用于标记事务的边界,以实现事务管理。 -
AOP:自定义注解与 AOP 结合,可以实现诸如日志记录、权限校验、性能监控等横切关注点的统一处理。通过定义切面,并在切面中使用自定义注解来标记目标方法或类,可以实现对这些横切关注点的集中管理和处理,这个场景对应文章开头的需求。
2. 自定义注解的优劣势
优势:
-
提高代码可读性和可维护性:通过使用自定义注解,可以使代码更具可读性,减少重复代码,并提高代码的可维护性。
-
降低耦合度:自定义注解可以将特定的功能与业务逻辑解耦,使代码更加灵活和可扩展。
-
增强代码的可靠性和安全性:通过自定义注解进行编译时或运行时的检查,可以提前发现潜在的问题,并确保程序的正确性和健壮性。
-
提高开发效率:自定义注解可以帮助开发者实现一些通用的功能或行为,从而节省开发时间和精力。
劣势:
-
学习成本:使用自定义注解需要对 Java 注解机制有一定的了解,可能需要一定的学习成本。
-
限制: 注解只能应用于类、方法、字段等程序元素上,因此有时候可能无法满足所有的业务需求。比如,如果需要在运行时动态改变注解的值,那么使用注解可能就不太适用。
-
滥用: 如果 过度 使用注解,可能会导致代码过于复杂和难以理解。特别是一些复杂的业务逻辑可能不适合使用注解,过度依赖注解可能会导致代码可读性变差和维护困难。
3. 自定义注解的实践指南
如果你决定在你的项目中使用自定义注解,以下是一些建议:
-
明确定义注解的作用和语义:在定义自定义注解时,要清晰地定义其作用和语义,避免歧义和误解。
-
遵循命名规范:自定义注解的命名应该符合 Java 命名规范,清晰简洁,方便理解和使用。
-
谨慎使用注解:不要滥用自定义注解,只在确实有必要的情况下使用,避免注解过多导致代码复杂和难以理解。
-
提供文档和示例:对于自定义注解,要提供详细的文档和示例,方便其他开发者理解和使用。
-
测试和验证:在使用自定义注解之前,要进行充分的测试和验证,确保其能够按照预期的方式工作。
4. 自定义注解实现权限校验
-
定义一个自定义注解,用于标记需要进行登录权限校验的方法或类。
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface RequiresLogin {
}
-
创建一个拦截器(或者AOP切面),在其中对标记了 @RequiresLogin
注解的方法进行登录权限校验。
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
public class LoginInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
if (handler instanceof HandlerMethod) {
HandlerMethod handlerMethod = (HandlerMethod) handler;
RequiresLogin requiresLogin = handlerMethod.getMethod().getAnnotation(RequiresLogin.class);
if (requiresLogin != null) {
// 执行登录权限校验逻辑,比如检查当前用户是否已登录
// 如果未登录,则可以跳转到登录页面或返回未登录提示
// 如果已登录,则放行,继续执行后续操作
// 示例代码中的逻辑为简化示例,实际校验逻辑可能会更复杂
if (!userIsLoggedIn(request)) {
response.sendRedirect("/login");
return false; // 终止请求
}
}
}
return true; // 放行请求
}
private boolean userIsLoggedIn(HttpServletRequest request) {
// 检查用户是否已登录的逻辑,这里用一个简化的示例代替
return request.getSession().getAttribute("user") != null;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
// 在处理完请求后执行,可以对响应结果进行处理
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
// 在整个请求处理完成后执行,可以进行资源清理等操作
}
}
-
在Spring MVC配置中注册拦截器,将拦截器应用到需要进行登录权限校验的接口上。
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new LoginInterceptor())
.addPathPatterns("/**") // 对所有请求进行拦截
.excludePathPatterns("/public/**"); // 可以排除不需要登录校验的接口
}
}
-
在需要进行登录权限校验的接口上添加 @RequiresLogin
注解,这样在请求到达控制器方法之前,拦截器会先检查是否有该注解,从而执行登录权限校验逻辑。
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class ExampleController {
@GetMapping("/api/private")
@RequiresLogin
public String privateApi() {
// 私有API,需要登录才能访问
return "Private API";
}
@GetMapping("/api/public")
public String publicApi() {
// 公共API,不需要登录即可访问
return "Public API";
}
}
通过以上步骤,我们就实现了一个简单的登录权限校验功能。拦截器会在请求到达控制器方法之前进行拦截,并检查是否有 @RequiresLogin
注解,如果有则执行登录权限校验逻辑,否则放行请求。这样就可以确保只有已登录用户才能访问需要登录的接口,而不需要在每个方法中都手动编写登录校验逻辑,大大提高了代码的简洁性和可维护性。
5. Spring Security 实现按需权限校验
在 Spring Security 中,你可以通过配置 HttpSecurity
来定义安全规则,包括哪些接口需要进行登录权限校验,哪些接口不需要。例如:
@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/public/**").permitAll() // 允许公开访问的接口
.anyRequest().authenticated() // 其他接口需要登录权限校验
.and()
.formLogin()
.loginPage("/login") // 配置登录页面的路径
.permitAll(); // 允许所有用户访问登录页面
}
}
在上面的示例中,/public/**
路径下的接口被配置为允许公开访问,而其他路径下的接口则需要进行登录权限校验。登录页面的路径被配置为 /login
。
6. Apache Shiro 实现按需权限校验
在 Apache Shiro 中,你可以通过配置 ShiroFilterFactoryBean
来定义安全规则,同样可以指定哪些接口需要进行登录权限校验,哪些接口不需要。例如:
@Configuration
public class ShiroConfig {
@Bean
public ShiroFilterFactoryBean shiroFilterFactoryBean(DefaultWebSecurityManager securityManager) {
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
shiroFilterFactoryBean.setSecurityManager(securityManager);
Map<String, String> filterChainDefinitionMap = new LinkedHashMap<>();
filterChainDefinitionMap.put("/public/**", "anon"); // 允许公开访问的接口
filterChainDefinitionMap.put("/**", "authc"); // 其他接口需要登录权限校验
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
return shiroFilterFactoryBean;
}
@Bean
public DefaultWebSecurityManager securityManager() {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
// 设置 Realm 等安全组件
return securityManager;
}
}
在上面的示例中,/public/**
路径下的接口被配置为允许公开访问,而其他路径下的接口则需要进行登录权限校验。
Spring Security 、 Apache Shiro 以及自定义注解等方法都可以实现对接口的登录权限校验,并可以根据需求灵活配置哪些接口需要进行校验,哪些接口不需要。在实际开发中可以按需选择,文章只是介绍自定义注解,利用了权限校验的场景而已,有方便且合适的框架也是可以选择使用的。
原文始发于微信公众号(Coder香):自定义注解:精简代码还是增加混乱?
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/280692.html