分类
spring

Spring Bean 注解

Spring Bean Annotations

1. 概述

本文中我们将讨论Spring中用于定义Bean的几种注解。

在Spring中有多种定义Bean的方式:比如可以使用xml文件来配置bean、在配置类中使用@Bean注解来标识某个方法,或者使用来自于 org.springframework.stereotype 包中的注解来标识当前应用包中的某个类。

2. 扫描组件

扫描组件功能被开启的情况下,Spring能够自动的扫描特定包(包含子包)下的定义的所有bean。

在@Configuration的类上同时使用@ComponentScan注解可以自定义Spring在启动时扫描的包

@ComponentScan(basePackages = "club.codedemo.outpackage")

如上代码上,basePackages属性指明了要扫描的包的名称为club.codedemo.outpackage,则Spring应用在启动时会自动扫描该包下所有的可用Bean。

其实正是由于Spring Boot应用中的@SpringBootApplication包含了@ComponentScan注解,所以Spring Boot项目在启动时会扫描项目启动类所在包以及子包中的所有可用的Bean。

在Spring Boot项目中,往往使用@ComponentScan注解来定义扫描当前Spring Boot项目以外的包

同样的道理,如果我们想在项目启动时扫描某个类(该类必须使用@Configuration标识)中的Bean,则可以使用basePackageClasses属性:

@ComponentScan(basePackageClasses = OutClass.class)

通过观察属性名(basePackages,basePackageClasses)可以猜想出,该属性对应的值是可以接收数组类型的。当有多个包或是类需要被定义时可以如下使用:

@ComponentScan(basePackages = {"club.codedemo.outpackage", "xxx.xxx.xxx"})
@ComponentScan(basePackageClasses = {OutClass.class, Xxxx.class})

如果我们未给@ComponentScan注解传入任何参数则表示:扫描当前文件所在包以及子包下的所有Bean。

自java8开发,允许我们在同一个类上重复使用某一注解,比如我们可以重复使用@ComponentScan注解来标识ComponentScanConfig类:

@ComponentScan(basePackages = "club.codedemo.outpackage")
@ComponentScan(basePackageClasses = OutClass.class)
public class ComponentScanConfig {

如果你不喜欢这种方式,还可以使用@ComponentScans来合并多个@ComponentScan

@ComponentScans({
        @ComponentScan(basePackages = "club.codedemo.outpackage"),
        @ComponentScan(basePackageClasses = OutClass.class)
})
public class ComponentScanConfig {

当使用xml配置时,代码也很简单:

<context:component-scan base-package="club.codedemo.outpackage" />

注意:受限于笔者水平,使用xml文件进行配置的方法并未在示例代码中体现。

3. @Component 注解

@Component注解作用于类中。Spring在进行Bean扫描时,能够检测到使用@Component注解的类。

比如:

@Component
public class Student {
}

默认情况下Spring在实例化一个Student作为Bean放置到自己管理的容器中,并且使用Student的首字小写(student)来作用bean的名称。如果你想自定义该bean的名称,则可以设置@Component注解的value属性:

@Component(value = "student")
public class Student {
}

而由于@Repository@Service@Configuration 以及 @Controller均是@Component的元注解,所以上述注解拥有@Component的特性及"待遇"。Spring应用在进行组件扫描时,也将扫描上述注解并按相同的命名规则来命名相应的bean。

4. @Repostiory 注解

在Spring应用中,一般使用DAO或是数据仓库Repository来充当数据访问层,进而完成与数据库的交互功能。我们往往使用@Repository注解来标识属于该层的类:

@Repository
public class StudentRepository {

@Repository注解可以自动对其内部发生的异常进行转换。比如当我们使用Hibernate作用JPA的实现时, Hibernate在数据操作中发生的异常被自动被捕获并转换为Spring中的DataAccessException异常子类被抛出。这种方式使我们能够使用统一的方式来处理数据访问层的异常。

预开启上述异常转换,则还需要声明一个PersistenceExceptionTranslationPostProcessor bean:

    @Bean
    public PersistenceExceptionTranslationPostProcessor exceptionTranslation() {
        return new PersistenceExceptionTranslationPostProcessor();
    }

XML配置如下:

<bean class=
  "org.springframework.dao.annotation.PersistenceExceptionTranslationPostProcessor"/>

5. @Service 注解

一般情况下,我们会使用@Service来标识一个负责处理业务逻辑的service layer服务层。

@Service
public class CourseServiceImpl implements CourseService {
}

6. @Controller 注解

@Controller用于标识Spring MVC中的控制器:

@Controller
public class StudentController {
}

7. @Configuration 注解

当某个类想在方法中中使用@Bean注解定义bean时,则需要在该类上启用@Configuration注解

@Configuration
public class ComponentScanConfig {
 
    @Bean
    public PersistenceExceptionTranslationPostProcessor exceptionTranslation() {
        return new PersistenceExceptionTranslationPostProcessor();
    }
}

8. Stereotype固有注解以及AOP

当我们使用Spring的固有注解时,我们可以非常轻松地创建一个针对Spring固有注解的切点。

比如在实际的生产项目中我们需要获取用户在使用过程中的慢查询。则可以配合@AspectJ注解如下实现:

@Aspect
@Component
public class PerformanceAspect {

    @Pointcut("within(@org.springframework.stereotype.Repository *)")
    public void repositoryClassMethods() {}

    @Around("repositoryClassMethods()")
    public Object measureMethodExecutionTime(ProceedingJoinPoint joinPoint) throws Throwable {
        // 获取切点方法执行的起始、结束时间
        long start = System.nanoTime();
        Object returnValue = joinPoint.proceed();
        long end = System.nanoTime();

        // 获取切点方法的类名、方法名并打印执行时间
        String className = joinPoint.getTarget().getClass().getName();
        String methodName = joinPoint.getSignature().getName();
        Long costTime = TimeUnit.NANOSECONDS.toMillis(end - start);
        System.out.println("执行:" + className + "->" + methodName +
                "耗时:" + costTime  + "ms");

        if (costTime > 1000) {
            // 耗时大于1秒的认为是慢查询,根据实际情况进行后续操作
            // 比如可以实时的短信预警、钉钉预警、邮件预警、推送到日志服务器等
        }

        return returnValue;
    }
}

上述代码中我们创建了一个切点,该切点的作用范围是:以@Repository为注解的类下的所有方法。然后使用了@Around 注解来关联该切点并拦截对应的方法、记录方法执行的时间等。

如上述代码注释所示,当执行花费的时间大于设定的上限时,我们则可以根据实现的需求发送相应的预警信息。

9. 总结

本文介绍了定义Bean的几种注解。同时介绍了自定义扫描包、类的方法。

在文章的最后以@Repository为例,定义了AOP切面并完成了获取慢查询的方法。通过该方法不难看出:数据访问层可以完全的专注了数据操作,而AOP切面则可以完全关注于查询时间。这或许就是程序开发时关注点分离的具体体现吧。