自定义注解:精简代码还是增加混乱?

大家好,我是香香。

Java 编程中,注解是一种元数据,它们提供了对程序元素(类、方法、字段等)进行标记和添加信息的机制。

除了 Java 本身提供的注解外,开发者还可以通过自定义注解来满足特定需求。

比如说我们在日常编写代码的时候,非常容易遇到某一些接口只允许已经登录的用户访问,而有的接口不需要登录也可以访问的需求。虽然有很多优秀的框架可以直接支持上述需求的实现,但是如果抛开框架不谈,有什么好的方法吗?

这时候有的同学可能就说了,我直接 if/else 判断,如果没有权限就不给他访问不就行了?是,确实是可以的,但是有没有发现,当随着业务慢慢复杂起来,这就会导致我们编写的代码重复度非常高,非常不利于维护,甚至于后期 Bug 不断,进而导致项目重构也不是没有可能。这就是不注重编码规范带来的苦果呀…

当然,我们今天的话题并不是教你如何写出更规范的代码(其实没有绝对的规范),而是除了框架,除了 if/else 之外的 自定义注解(重点加粗) 结合 AOP 实现权限校验,具体实现在文末会提到~

1. 自定义注解的适用场景

自定义注解在 Java 开发中具有广泛的应用场景,包括以下几个方面:

  1. 标记与配置:自定义注解可以用于标记特定的类、方法或字段,以便在后续的处理过程中进行识别和处理。比如,在 Spring 框架中,@Autowired@Component 等注解用于标记依赖注入和组件扫描的目标对象。

  2. 编译时检查:通过自定义注解,开发者可以在编译时对程序进行静态检查,以确保程序的正确性和健壮性。例如,可以定义一个 @NonNull 注解,用于标记方法参数或返回值不能为空,从而在编译时检查是否存在空指针异常的可能性。

  3. 运行时处理:自定义注解也可以在运行时进行处理,实现一些特定的功能或行为。比如,在 Spring 框架中,@Transactional 注解用于标记事务的边界,以实现事务管理。

  4. AOP:自定义注解与 AOP 结合,可以实现诸如日志记录、权限校验、性能监控等横切关注点的统一处理。通过定义切面,并在切面中使用自定义注解来标记目标方法或类,可以实现对这些横切关注点的集中管理和处理,这个场景对应文章开头的需求。

2. 自定义注解的优劣势

优势:

  1. 提高代码可读性和可维护性:通过使用自定义注解,可以使代码更具可读性,减少重复代码,并提高代码的可维护性。

  2. 降低耦合度:自定义注解可以将特定的功能与业务逻辑解耦,使代码更加灵活和可扩展。

  3. 增强代码的可靠性和安全性:通过自定义注解进行编译时或运行时的检查,可以提前发现潜在的问题,并确保程序的正确性和健壮性。

  4. 提高开发效率:自定义注解可以帮助开发者实现一些通用的功能或行为,从而节省开发时间和精力。

劣势:

  1. 学习成本:使用自定义注解需要对 Java 注解机制有一定的了解,可能需要一定的学习成本。

  2. 限制: 注解只能应用于类、方法、字段等程序元素上,因此有时候可能无法满足所有的业务需求。比如,如果需要在运行时动态改变注解的值,那么使用注解可能就不太适用。

  3. 滥用: 如果 过度 使用注解,可能会导致代码过于复杂和难以理解。特别是一些复杂的业务逻辑可能不适合使用注解,过度依赖注解可能会导致代码可读性变差和维护困难。

3. 自定义注解的实践指南

如果你决定在你的项目中使用自定义注解,以下是一些建议:

  1. 明确定义注解的作用和语义:在定义自定义注解时,要清晰地定义其作用和语义,避免歧义和误解。

  2. 遵循命名规范:自定义注解的命名应该符合 Java 命名规范,清晰简洁,方便理解和使用。

  3. 谨慎使用注解:不要滥用自定义注解,只在确实有必要的情况下使用,避免注解过多导致代码复杂和难以理解。

  4. 提供文档和示例:对于自定义注解,要提供详细的文档和示例,方便其他开发者理解和使用。

  5. 测试和验证:在使用自定义注解之前,要进行充分的测试和验证,确保其能够按照预期的方式工作。

4. 自定义注解实现权限校验

  1. 定义一个自定义注解,用于标记需要进行登录权限校验的方法或类。
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 {
}
  1. 创建一个拦截器(或者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 {
        // 在整个请求处理完成后执行,可以进行资源清理等操作
    }
}
  1. 在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/**"); // 可以排除不需要登录校验的接口
    }
}
  1. 在需要进行登录权限校验的接口上添加 @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

(0)
明月予我的头像明月予我bm

相关推荐

发表回复

登录后才能评论
极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!