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切面则可以完全关注于查询时间。这或许就是程序开发时关注点分离的具体体现吧。