MK
摩柯社区 - 一个极简的技术知识社区
AI 面试

Java注解在Spring框架中的应用

2021-12-085.7k 阅读

Java注解基础回顾

在深入探讨Java注解在Spring框架中的应用之前,我们先来回顾一下Java注解的基础知识。

Java注解是一种元数据形式,它提供了一种安全的类似注释的机制,用于将额外的信息与程序元素(如类、方法、字段等)关联起来。注解在编译期、类加载期以及运行期都可以发挥作用。

定义一个简单的注解非常容易,以下是一个自定义注解的示例:

// 定义一个简单的注解
public @interface MyAnnotation {
    String value() default "";
}

在上述代码中,我们使用@interface关键字定义了一个名为MyAnnotation的注解。这个注解有一个名为value的元素,并且为其提供了默认值""

我们可以将这个注解应用到类、方法或字段上,例如:

// 使用自定义注解
@MyAnnotation("This is a test")
public class MyClass {
    @MyAnnotation
    public void myMethod() {
        // 方法实现
    }
}

注解在Java中有不同的保留策略,分别是RetentionPolicy.SOURCERetentionPolicy.CLASSRetentionPolicy.RUNTIME

  • RetentionPolicy.SOURCE:注解只在源码阶段保留,编译时就会被丢弃。
  • RetentionPolicy.CLASS:注解在编译时会被保留在字节码文件中,但在运行时JVM不会保留,这是默认的保留策略。
  • RetentionPolicy.RUNTIME:注解不仅在编译时保留,在运行时JVM也会保留,这样我们就可以在运行时通过反射机制来读取注解信息。
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;

// 定义一个运行时保留的注解
@Retention(RetentionPolicy.RUNTIME)
public @interface RuntimeAnnotation {
    String message();
}

通过反射获取运行时注解的示例代码如下:

import java.lang.reflect.Method;

@RuntimeAnnotation(message = "This is a runtime annotation")
public class AnnotationReflectionExample {
    @RuntimeAnnotation(message = "Method level annotation")
    public void myAnnotatedMethod() {
        // 方法体
    }

    public static void main(String[] args) {
        try {
            Class<AnnotationReflectionExample> clazz = AnnotationReflectionExample.class;
            // 获取类上的注解
            RuntimeAnnotation classAnnotation = clazz.getAnnotation(RuntimeAnnotation.class);
            if (classAnnotation != null) {
                System.out.println("Class annotation message: " + classAnnotation.message());
            }

            Method method = clazz.getMethod("myAnnotatedMethod");
            // 获取方法上的注解
            RuntimeAnnotation methodAnnotation = method.getAnnotation(RuntimeAnnotation.class);
            if (methodAnnotation != null) {
                System.out.println("Method annotation message: " + methodAnnotation.message());
            }
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        }
    }
}

上述代码通过反射获取了类和方法上的RuntimeAnnotation注解,并打印出注解中的message信息。

Spring框架中的核心注解

Spring框架广泛使用注解来简化配置和开发流程。下面我们来介绍一些Spring框架中的核心注解。

@Component及其衍生注解

@Component是Spring框架中最基本的组件注解,用于将一个类标记为Spring容器管理的组件。它可以被应用到任何需要Spring管理的类上,例如:

import org.springframework.stereotype.Component;

@Component
public class MyComponent {
    // 组件逻辑
}

Spring还提供了一些@Component的衍生注解,以提高代码的可读性和语义性。

  • @Service:通常用于标记业务逻辑层的服务类,例如:
import org.springframework.stereotype.Service;

@Service
public class UserService {
    // 用户服务逻辑
}
  • @Repository:主要用于标记数据访问层(DAO层)的类,它不仅将类标记为组件,还能让Spring自动处理数据库异常。例如:
import org.springframework.stereotype.Repository;

@Repository
public class UserRepository {
    // 用户数据访问逻辑
}
  • @Controller:用于标记Web层的控制器类,处理HTTP请求。例如:
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;

@Controller
@RequestMapping("/user")
public class UserController {
    @RequestMapping("/list")
    public ModelAndView listUsers() {
        // 处理用户列表请求逻辑
        ModelAndView mav = new ModelAndView("userList");
        return mav;
    }
}

@Autowired

@Autowired注解用于自动装配Spring容器中的Bean。它可以应用在字段、方法或构造函数上。

  1. 字段注入
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class OrderService {
    @Autowired
    private ProductService productService;

    // 订单服务逻辑,可能会使用productService
}

在上述代码中,Spring容器会自动查找类型为ProductService的Bean,并将其注入到OrderServiceproductService字段中。

  1. 方法注入
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class OrderService {
    private ProductService productService;

    @Autowired
    public void setProductService(ProductService productService) {
        this.productService = productService;
    }

    // 订单服务逻辑,可能会使用productService
}

这里通过setProductService方法进行注入,Spring会调用该方法并传入匹配的ProductService Bean。

  1. 构造函数注入
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class OrderService {
    private final ProductService productService;

    @Autowired
    public OrderService(ProductService productService) {
        this.productService = productService;
    }

    // 订单服务逻辑,可能会使用productService
}

构造函数注入在创建OrderService实例时就完成了依赖注入,并且由于productService被声明为final,可以确保其不可变。

@Qualifier

当Spring容器中存在多个相同类型的Bean时,@Autowired可能无法确定要注入哪个Bean。这时就需要使用@Qualifier注解来指定具体要注入的Bean。

假设我们有两个ProductService的实现类:

import org.springframework.stereotype.Service;

@Service
public class DefaultProductService implements ProductService {
    // 默认产品服务实现
}

import org.springframework.stereotype.Service;

@Service
public class PremiumProductService implements ProductService {
    // 高级产品服务实现
}

OrderService中,如果要明确注入PremiumProductService,可以这样做:

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Service;

@Service
public class OrderService {
    private ProductService productService;

    @Autowired
    @Qualifier("premiumProductService")
    public void setProductService(ProductService productService) {
        this.productService = productService;
    }

    // 订单服务逻辑,可能会使用productService
}

@Qualifier中指定的名称是Bean在Spring容器中的默认名称,默认名称是类名首字母小写,即premiumProductService

@RequestMapping及其相关注解

@RequestMapping是Spring MVC中用于映射HTTP请求到控制器方法的注解。它可以应用在类或方法上。

  1. 类级别的@RequestMapping
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;

@Controller
@RequestMapping("/user")
public class UserController {
    // 控制器方法
}

上述代码将UserController类的所有请求映射到/user路径下。

  1. 方法级别的@RequestMapping
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;

@Controller
@RequestMapping("/user")
public class UserController {
    @RequestMapping("/list")
    public ModelAndView listUsers() {
        // 处理用户列表请求逻辑
        ModelAndView mav = new ModelAndView("userList");
        return mav;
    }
}

@RequestMapping("/list")listUsers方法映射到/user/list路径。

Spring MVC还提供了一些@RequestMapping的变体注解,如@GetMapping@PostMapping@PutMapping@DeleteMapping等,用于处理特定HTTP方法的请求。例如:

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.servlet.ModelAndView;

@Controller
@RequestMapping("/user")
public class UserController {
    @GetMapping("/list")
    public ModelAndView listUsers() {
        // 处理GET请求的用户列表逻辑
        ModelAndView mav = new ModelAndView("userList");
        return mav;
    }

    @PostMapping("/save")
    public ModelAndView saveUser() {
        // 处理POST请求的用户保存逻辑
        ModelAndView mav = new ModelAndView("userSaved");
        return mav;
    }
}

Spring Boot中的注解增强

Spring Boot在Spring的基础上进一步简化了开发,它大量使用注解来实现自动配置和约定优于配置的理念。

@SpringBootApplication

@SpringBootApplication是Spring Boot应用的核心注解,它是一个组合注解,包含了@Configuration@EnableAutoConfiguration@ComponentScan

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class MySpringBootApp {
    public static void main(String[] args) {
        SpringApplication.run(MySpringBootApp.class, args);
    }
}
  • @Configuration:表明该类是一个配置类,等同于XML配置文件。
  • @EnableAutoConfiguration:开启自动配置,Spring Boot会根据类路径下的依赖自动配置Spring应用。例如,如果类路径下有spring - jdbch2 - database依赖,Spring Boot会自动配置一个内存数据库的数据源。
  • @ComponentScan:自动扫描指定包及其子包下的所有组件,并将其注册到Spring容器中。默认扫描与主应用类相同的包及其子包。

@ConfigurationProperties

@ConfigurationProperties注解用于将配置文件中的属性绑定到Java对象上。假设我们有一个application.properties文件:

myapp.username=admin
myapp.password=secret

我们可以创建一个配置类来绑定这些属性:

import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;

@Component
@ConfigurationProperties(prefix = "myapp")
public class MyAppProperties {
    private String username;
    private String password;

    // getters and setters
    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }
}

在上述代码中,@ConfigurationProperties(prefix = "myapp")表示将application.properties中以myapp为前缀的属性绑定到MyAppProperties类的对应字段上。

@PathVariable@RequestParam

在Spring Boot的Web开发中,@PathVariable@RequestParam注解用于获取HTTP请求中的参数。

  1. @PathVariable:用于获取URL路径中的变量。例如,我们有一个请求路径/user/{id},可以这样获取id
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class UserController {
    @GetMapping("/user/{id}")
    public String getUserById(@PathVariable Long id) {
        // 根据id获取用户逻辑
        return "User with id " + id;
    }
}
  1. @RequestParam:用于获取查询参数。例如,对于请求/user?name=john,可以这样获取name参数:
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class UserController {
    @GetMapping("/user")
    public String getUserByName(@RequestParam String name) {
        // 根据name获取用户逻辑
        return "User with name " + name;
    }
}

@RequestParam的参数是可选的,我们可以通过设置required = false来表示该参数不是必需的,并且还可以设置默认值:

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class UserController {
    @GetMapping("/user")
    public String getUserByName(@RequestParam(name = "name", required = false, defaultValue = "anonymous") String name) {
        // 根据name获取用户逻辑
        return "User with name " + name;
    }
}

自定义Spring注解

在Spring开发中,有时候我们需要定义自己的注解来满足特定的业务需求。下面我们来看看如何自定义Spring注解并结合AOP(面向切面编程)来实现一些功能。

定义自定义注解

假设我们要定义一个用于权限检查的注解@RequirePermission,示例代码如下:

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface RequirePermission {
    String value();
}

上述注解@RequirePermission被定义为运行时保留,并且只能应用在方法上。value元素用于指定所需的权限。

使用AOP实现注解逻辑

我们可以通过AOP来实现@RequirePermission注解的权限检查逻辑。首先,添加Spring AOP的依赖,例如在Maven项目中添加:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter - aop</artifactId>
</dependency>

然后创建一个切面类:

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;

@Aspect
@Component
public class PermissionAspect {
    @Around("@annotation(requirePermission)")
    public Object checkPermission(ProceedingJoinPoint joinPoint, RequirePermission requirePermission) throws Throwable {
        String requiredPermission = requirePermission.value();
        // 这里模拟权限检查逻辑
        boolean hasPermission = checkIfUserHasPermission(requiredPermission);
        if (hasPermission) {
            return joinPoint.proceed();
        } else {
            throw new RuntimeException("No permission to access this method");
        }
    }

    private boolean checkIfUserHasPermission(String permission) {
        // 实际的权限检查逻辑,这里简单返回true
        return true;
    }
}

在上述切面类中,@Around("@annotation(requirePermission)")表示对所有标注了@RequirePermission注解的方法进行环绕增强。在checkPermission方法中,首先获取所需的权限,然后进行权限检查。如果用户有相应权限,则执行原方法joinPoint.proceed();否则,抛出异常。

在控制器类中使用自定义注解:

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class AdminController {
    @GetMapping("/admin/operation")
    @RequirePermission("admin:operation")
    public String performAdminOperation() {
        return "Admin operation performed successfully";
    }
}

当调用/admin/operation接口时,会先经过权限检查,如果权限不足,会抛出No permission to access this method异常。

注解与Spring的配置文件

虽然Spring Boot推崇注解驱动的开发方式,但在一些复杂场景下,配置文件仍然有其用武之地。Spring可以将注解配置和XML配置文件混合使用。

从注解配置导入XML配置

在Spring Boot应用中,可以通过@ImportResource注解来导入XML配置文件。假设我们有一个beans.xml文件:

<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema - instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
                           http://www.springframework.org/schema/beans/spring - beans.xsd">
    <bean id="legacyBean" class="com.example.LegacyBean">
        <!-- 配置属性等 -->
    </bean>
</beans>

在Spring Boot的主应用类中,可以这样导入:

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.ImportResource;

@SpringBootApplication
@ImportResource("classpath:beans.xml")
public class MySpringBootApp {
    public static void main(String[] args) {
        SpringApplication.run(MySpringBootApp.class, args);
    }
}

这样beans.xml中定义的LegacyBean就会被注册到Spring容器中。

从XML配置导入注解配置

在传统的Spring XML配置中,也可以导入基于注解的配置。假设我们有一个基于注解的配置类AppConfig

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class AppConfig {
    @Bean
    public NewBean newBean() {
        return new NewBean();
    }
}

beans.xml中可以这样导入:

<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema - instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
                           http://www.springframework.org/schema/beans/spring - beans.xsd
                           http://www.springframework.org/schema/context
                           http://www.springframework.org/schema/context/spring - context.xsd">
    <context:annotation - config/>
    <context:component - scan base - package="com.example"/>
    <import resource="classpath:app - config.xml"/>
</beans>

其中,context:annotation - config开启对注解的支持,context:component - scan扫描指定包下的组件,import导入基于注解的配置文件(这里假设AppConfig被包含在app - config.xml相关配置中,实际可能不需要额外配置文件来包含注解配置类)。

注解在Spring Cloud中的应用

Spring Cloud基于Spring Boot构建,进一步拓展了Spring在微服务架构中的应用。在Spring Cloud中,注解也发挥着重要作用。

@EnableEurekaServer@EnableEurekaClient

在基于Eureka的服务注册与发现机制中,@EnableEurekaServer用于将一个Spring Boot应用标记为Eureka服务端,示例代码如下:

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;

@SpringBootApplication
@EnableEurekaServer
public class EurekaServerApp {
    public static void main(String[] args) {
        SpringApplication.run(EurekaServerApp.class, args);
    }
}

@EnableEurekaClient用于将一个Spring Boot应用标记为Eureka客户端,使其能够注册到Eureka服务端并获取服务列表,例如:

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;

@SpringBootApplication
@EnableEurekaClient
public class UserServiceApp {
    public static void main(String[] args) {
        SpringApplication.run(UserServiceApp.class, args);
    }
}

@FeignClient

@FeignClient是Spring Cloud OpenFeign中的注解,用于声明式地调用远程服务。假设我们有一个用户服务,需要调用订单服务,订单服务提供了一个获取订单列表的接口。

首先,添加OpenFeign的依赖:

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring - cloud - starter - openfeign</artifactId>
</dependency>

然后创建一个Feign客户端接口:

import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;

@FeignClient(name = "order - service")
public interface OrderFeignClient {
    @GetMapping("/orders")
    String getOrders();
}

在上述代码中,@FeignClient(name = "order - service")指定了要调用的远程服务名称为order - service。接口中的方法通过Spring MVC风格的注解来定义请求路径和方法。

在服务实现类中使用这个Feign客户端:

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class UserService {
    @Autowired
    private OrderFeignClient orderFeignClient;

    public String getUserOrders() {
        return orderFeignClient.getOrders();
    }
}

这样就可以通过UserService间接调用order - service/orders接口,而不需要手动处理HTTP请求等细节。

通过上述对Java注解在Spring框架及其相关技术(如Spring Boot、Spring Cloud)中的应用介绍,我们可以看到注解在简化开发、提高代码可读性和实现特定功能方面发挥了巨大作用。无论是基础的组件管理、依赖注入,还是复杂的AOP、微服务相关功能,注解都提供了一种简洁高效的实现方式。