Spring 基于注解装配Bean

Spring 基于注解装配Bean

前言

​ 在 Spring 中,尽管可以使用 XML 配置文件实现 Bean 的装配工作,但如果应用中 Bean 的数量较多,会导致 XML 配置文件过于臃肿,从而给维护和升级带来一定的困难。

​ Java 从 JDK 5.0 以后,提供了 Annotation(注解)功能,Spring 2.5 版本开始也提供了对 Annotation 技术的全面支持,我们可以使用注解来配置依赖注入。

​ Spring 默认不使用注解装配 Bean,因此需要在配置文件中添加 <context:annotation-config/>,启用注解。

  • 注解:就是一个类,使用@注解名称
  • 开发中:使用注解 取代 xml配置文件。

什么是注解

Annontation是Java5开始引入的新特征,中文名称叫注解

​ 它提供了一种安全的类似注释的机制,用来将任何的信息或元数据(metadata)与程序元素(类、方法、成员变量等)进行关联。为程序的元素(类、方法、成员变量)加上更直观更明了的说明,这些说明信息是与程序的业务逻辑无关,并且供指定的工具或框架使用。Annontation像一种修饰符一样,应用于包、类型、构造方法、方法、成员变量、参数及本地变量的声明语句中。
  Java注解是附加在代码中的一些元信息,用于一些工具在编译、运行时进行解析和使用,起到说明、配置的功能。注解不会也不能影响代码的实际逻辑,仅仅起到辅助性的作用。包含在 java.lang.annotation 包中。

注解的用处:

1、生成文档。这是最常见的,也是java 最早提供的注解。常用的有@param @return 等
2、跟踪代码依赖性,实现替代配置文件功能。比如Dagger 2依赖注入,未来java开发,将大量注解配置,具有很大用处;
3、在编译时进行格式检查。如@override 放在方法前,如果你这个方法并不是覆盖了超类方法,则编译时就能检查出。

更多注解信息点击这里-JAVA 注解的基本原理 - Single_Yam - 博客园 (cnblogs.com)

为什么要使用注解装配Bean

传统的Spring做法是使用.xml文件来对bean进行注入或者是配置aop、事物,这么做有两个缺点:

  1. 如果所有的内容都配置在.xml文件中,那么.xml文件将会十分庞大;如果按需求分开.xml文件,那么.xml文件又会非常多,总之这将导致配置文件的可读性与可维护性变得很低。
  2. 在开发中在.java文件和.xml文件之间不断切换,是一件麻烦的事,同时这种思维上的不连贯也会降低开发的效率。

为了解决这两个问题,Spring引入了注解,通过"@XXX"的方式,让注解与Java Bean紧密结合,大大减少了配置文件的体积,又增加了Java Bean的可读性与内聚性。

使用Spring注解

0.启用组件扫描

1)使用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:component-scan base-package="com.spring.learn2" />
</beans>

配置属性<context:component-scan base-package="com.spring.learn2" />

其中`base-package

2) @ComponentScan注解启用了组件扫描

package com.spring.learn2.config;

import org.springframework.context.annotation.ComponentScan;

@ComponentScan
public class BeanConfig {}
package com;

import com.spring.learn2.config.BeanConfig;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class MainApp {
    public static void main(String[] args) {
        ApplicationContext applicationContext =
                new AnnotationConfigApplicationContext(BeanConfig.class);

        String[] beanDefinitionNames = applicationContext.getBeanDefinitionNames();
        for (String beanName : beanDefinitionNames) {
            System.out.println("beanName: " + beanName);
        }
    }
}

运行效果

beanName: org.springframework.context.annotation.internalConfigurationAnnotationProcessor
beanName: org.springframework.context.annotation.internalAutowiredAnnotationProcessor
beanName: org.springframework.context.annotation.internalCommonAnnotationProcessor
beanName: org.springframework.context.event.internalEventListenerProcessor
beanName: org.springframework.context.event.internalEventListenerFactory
beanName: beanConfig

除了 spring 本身注册的一些 bean 之外,可以看到最后一行,已经将 BeanConfig 这个类注册进容器中了。

创建一个配置类,在配置类上添加 @ComponentScan 注解。该注解默认会扫描该类所在的包下所有的配置类,相当于之前的 <context:component-scan>

类BeanConfig通过Java代码定义了Spring的装配规则。观察可知,BeanConfig类并没有显式地声明任何bean,只不过它使用了@ComponentScan注解,这个注解能够在Spring中启用组件扫描

如果没有其他配置的话,@ComponentScan默认会扫描与配置类相同的包。因为BeanConfig类位于com.spring.learn2.config包中,因此Spring将会扫描这个包以及这个包下的所有子包,查找带有@Component注解的类。这样的话,就能发现使用了注解的类,并且会在Spring中自动为其创建一个Bean

3)指定要扫描的包

(使用@ComponentScan 的 valule 属性来配置)

@ComponentScan(value = "com.spring.learn2")
public class BeanConfig {

}
excludeFilters 和 includeFilters 的使用
使用 excludeFilters 来按照规则排除某些包的扫描。
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.ComponentScan.Filter;
import org.springframework.context.annotation.FilterType;
import org.springframework.stereotype.Controller;

@ComponentScan(value = "com.spring.learn2",
        excludeFilters = {@Filter(type = FilterType.ANNOTATION,
        value = {Controller.class})})
public class BeanConfig {

}

excludeFilters 的参数是一个 Filter[] 数组,然后指定 FilterType 的类型为 ANNOTATION,也就是通过注解来过滤,最后的 value 则是Controller 注解类。

​ 配置之后,在 spring 扫描的时候,就会跳过 com.spring.learn2 包下,所有被 @Controller 注解标注的类

使用 includeFilters 来按照规则只包含某些包的扫描。
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.ComponentScan.Filter;
import org.springframework.context.annotation.FilterType;
import org.springframework.stereotype.Controller;

@ComponentScan(value = "com.spring.learn2", includeFilters = {@Filter(type = FilterType.ANNOTATION, classes = {Controller.class})})
public class BeanConfig {

}

spring 默认会自动发现被 @Component、@Repository、@Service 和 @Controller 标注的类,并注册进容器中。要达到只包含某些包的扫描效果,就必须将这个默认行为给禁用掉(在 @ComponentScan 中将 useDefaultFilters 设为 false 即可)。

@ComponentScan 的 useDefaultFilters 属性,该属性默认值为 true。

1. 定义Bean@Component

需要在类上使用注解@Component,该注解的value属性用于指定该bean的id值。

@Component(value = "role")
public class Role {
    private Long id;
    private String roleName;
    private String note;
}

2. Bean的作用域@Scope

需要在类上使用注解@Scope,其value属性用于指定作用域。默认为singleton。

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;

@Scope("prototype")
@Component("RoleService")
public class RoleServiceImpl implements RoleService {
    ...
}

3. 基本类型属性注入@Value

需要在属性上使用注解@Value,该注解的value属性用于指定要注入的值。
使用该注解完成属性注入时,类中无需setter。当然,若属性有setter,则也可将其加到setter上。

package com.spring.learn2;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

@Component(value = "role")
public class Role {
    @Value("1")
    private Long id;
    @Value("role_name_1")
    private String roleName;
    @Value("role_note_1")
    private String note;
}

4.按类型注入域属性@Autowired

@Autowired是spring的注解

需要在域属性上使用注解@Autowired,该注解默认使用按类型自动装配Bean的方式。
使用该注解完成属性注入时,类中无需setter。当然,若属性有setter,则也可将其加到setter上。

package com.spring.learn2;


import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;

@Scope("prototype")
@Component("RoleService")
public class RoleServiceImpl implements RoleService {
    @Autowired
    private Role role;

    /**** setter and getter ****/
    public Role getRole() {
        return role;
    }

    public void setRole(Role role) {
        this.role = role;
    }
}

5. 按名称注入域属性@Autowired与@Qualifier

需要在域属性上联合使用注解@Autowired与@Qualifier。

@Qualifier的value属性用于指定要匹配的Bean的id值。同样类中无需setter,也可加到setter上。

package com.spring.learn2;


import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;

@Scope("prototype")
@Component("RoleService")
public class RoleServiceImpl implements RoleService {
    @Autowired(required =false)
 	@Qualifier("role")
    private Role role;

    /**** setter and getter ****/
    public Role getRole() {
        return role;
    }

    public void setRole(Role role) {
        this.role = role;
    }
}

@Autowired还有一个属性required,默认值为true,表示当匹配失败后,会终止程序运行。若将其值设置为false,则匹配失败,将被忽略,未匹配的属性值为null。

6. 域属性注解@Resource

@Resource是java自带的注解

Spring提供了对JSR-250规范中定义@Resource标准注解的支持。@Resource注解既可以按名称匹配Bean,也可以按类型匹配Bean。使用该注解,要求JDK必须是6及以上版本。

1. 按类型注入域属性

@Resource注解若不带任何参数,则会按照类型进行Bean的匹配注入。

import org.springframework.stereotype.Component;

import javax.annotation.Resource;

@Component("RoleService")
public class RoleServiceImpl implements RoleService {
    @Resource
    private Role role;

    /**** setter and getter ****/
    public Role getRole() {
        return role;
    }

    public void setRole(Role role) {
        this.role = role;
    }
}

2. 按名称注入域属性

@Resource注解指定其name属性,则name的值即为按照名称进行匹配的Bean的id。

package com.spring.learn2;


import org.springframework.stereotype.Component;
import javax.annotation.Resource;


@Component("RoleService")
public class RoleServiceImpl implements RoleService {
    @Resource(name="role")
    private Role role;

    /**** setter and getter ****/
    public Role getRole() {
        return role;
    }

    public void setRole(Role role) {
        this.role = role;
    }
}

7. Bean的生命始末@PostConstruct与@PreDestroy

在方法上使用@PostConstruct,与原来的init-method等效。在方法上使用@PreDestroy,与destroy-method等效。

package com.spring.learn2;


import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import javax.annotation.Resource;


@Component("RoleService")
public class RoleServiceImpl implements RoleService {
    @Resource(name="role")
    private Role role;

    @PostConstruct
    public void init(){
        System.out.println("Bean初始化后执行");
    }

    @PreDestroy
    public void destroy(){
        System.out.println("Bean销毁前执行");
    }

    /**** setter and getter ****/
    public Role getRole() {
        return role;
    }

    public void setRole(Role role) {
        this.role = role;
    }
}

Spring 中常用的注解

@Component取代<bean class="">

@Component("id") 取代 <bean id="" class="">

web开发,提供3个@Component注解衍生注解(功能一样)取代<bean class="">

@Repository :dao层 。

@Service:service层 。

@Controller:web层。

@Component

可以使用此注解描述 Spring 中的 Bean,但它是一个泛化的概念,仅仅表示一个组件(Bean),并且可以作用在任何层次。使用时只需将该注解标注在相应类上即可。

import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

@Component(value = "role")
public class Role {
    @Value("1")
    private Long id;
    @Value("role_name_1")
    private String roleName;
    @Value("role_note_1")
    private String note;
}
  • 注解@Component代表Spring IoC会把这个类扫描生成Bean实例,而其中的value属性代表这个类在Spring中的id,这就相当于XML方式定义的Bean的id,也可以简写成@Component("role"),甚至直接写成@Component,对于不写的,Spring IoC容器就默认类名,但是以首字母小写的形式作为id,为其生成对象,配置到容器中。
  • 注解@Value代表的是值的注入,这里只是简单注入一些值,其中id是一个long型,注入的时候Spring会为其转化类型。

@Repository

用于将数据访问层(DAO层)的类标识为 Spring 中的 Bean,其功能与 @Component 相同。

@Service

通常作用在业务层(Service 层),用于将业务层的类标识为 Spring 中的 Bean,其功能与 @Component 相同。

@Controller

通常作用在控制层(如 Struts2 的 Action、SpringMVC 的 Controller),用于将控制层的类标识为 Spring 中的 Bean,其功能与 @Component 相同。

@Autowired

自动装配

​ 通过学习Spring IoC容器,我们知道Spring是先完成Bean的定义和生成,然后寻找需要注入的资源。也就是当Spring生成所有的Bean后,如果发现这个注解,他就会在Bean中查找,然后找到对应的类型,将其注入进来,这样就完成依赖注入了。

​ 所谓自动装配技术是一种由Spring自己发现对应的Bean,自动完成装配工作的方式,它会应用到一个十分常用的注解@Autowired上,这个时候Spring会根据类型去寻找定义的Bean然后将其注入,这里需要留意按类型(Role)的方式。

​ 可以应用到 Bean 的属性变量、属性的 setter 方法、非 setter 方法及构造函数等,配合对应的注解处理器完成 Bean 的自动配置工作。默认按照 Bean 的类型进行装配。

  • 示例

    role

    package com.spring.learn2;
    
    import org.springframework.beans.factory.annotation.Value;
    import org.springframework.stereotype.Component;
    
    @Component(value = "role")
    public class Role {
        @Value("1")
        private Long id;
        @Value("role_name_1")
        private String roleName;
        @Value("role_note_1")
        private String note;
    
        public Long getId() {
            return id;
        }
    
        public void setId(Long id) {
            this.id = id;
        }
    
        public String getRoleName() {
            return roleName;
        }
    
        public void setRoleName(String roleName) {
            this.roleName = roleName;
        }
    
        public String getNote() {
            return note;
        }
    
        public void setNote(String note) {
            this.note = note;
        }
    }
    

    RoleService

    package com.spring.learn2;
            
    public interface RoleService {
        public void printRoleInfo();
    }
    
    package com.spring.learn2;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Component;
    
    @Component("RoleService")
    public class RoleServiceImpl implements RoleService {
      @Autowired
      private Role role;
    
      @Override
      public void printRoleInfo() {
          System.out.println("id =" + role.getId());
          System.out.println("roleName =" + role.getRoleName());
          System.out.println("note =" + role.getNote());
      }
    
      /**** setter and getter ****/
      public Role getRole() {
          return role;
      }
    
      public void setRole(Role role) {
          this.role = role;
      }
    }
    
    

    MainApp

    package com;
    
    
    import com.spring.learn2.RoleService;
    import org.springframework.context.ApplicationContext;
    import org.springframework.context.support.ClassPathXmlApplicationContext;
    
    public class MainApp {
        public static void main(String[] args) {
            ApplicationContext ctx = new ClassPathXmlApplicationContext("Beans.xml");
            RoleService uc = (RoleService) ctx.getBean("RoleService");
            uc.printRoleInfo();
        }
    }
    
    

    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:component-scan base-package="com.spring.learn2" />
    </beans>
    

    ​ 这里的@Autowired注解,表示在Spring IoC定位所有的Bean后,这个字段需要按类型注入,这样IoC容器就会寻找资源,然后将其注入。。比如代码Role和代码RoleServiceImpl的两个Bean,假设将其定义,那么Spring IoC容器会为它们先生成对应的实例,然后依据@Autowired注解,按照类型找到定义的实例,将其注入。

     IoC容器有时候会寻找失败,在默认的情况下寻找失败它就会抛出异常,也就是说默认情况下,Spring IoC容器会认为一定要找到对应的Bean来注入这个字段,有些时候这并不是一个真实的需要,比如日志,有时候我们会觉得这是可有可无的,这个时候可以通过@Autowired的配置项required来改变它,比如@Autowired(required=false)。

    ​ 正如之前所谈到的在默认情况下是必须注入成功的,所以这里的required的默认值为true。当把配置修改为了false时,就告诉Spring IoC容器,假如在已经定义好的Bean中找不到对应的类型,允许不注入,这样也就没有了异常抛出,只是这样这个字段可能为空,读者要自行校验,以避免发生空指针异常。在大部分的情况下,都不需要这样修改。
    ​ @Autowired除可以配置在属性之外,还允许方法配置,常见的Bean的setter方法也可以使用它来完成注入。

@Resource

​ 作用与 Autowired 相同,区别在于 @Autowired 默认按照 Bean 类型装配,而 @Resource 默认按照 Bean 实例名称进行装配。

​ @Resource 中有两个重要属性:name 和 type。

​ Spring 将 name 属性解析为 Bean 的实例名称,type 属性解析为 Bean 的实例类型。如果指定 name 属性,则按实例名称进行装配;如果指定 type 属性,则按 Bean 类型进行装配。如果都不指定,则先按 Bean 实例名称装配,如果不能匹配,则再按照 Bean 类型进行装配;如果都无法匹配,则抛出 NoSuchBeanDefinitionException 异常。

​ 另外要注意,@Resource是java自带的注解,不是Spring中的注解。@Resource注解完整的包路径为

import  javax.annotation.Resource;

示例

@Resource(name = "userServiceImpl")
private UserService userService;

@Qualifier

与 @Autowired 注解配合使用,会将默认的按 Bean 类型装配修改为按 Bean 的实例名称装配,Bean 的实例名称由 @Qualifier 注解的参数指定。

 @Autowired
 @Qualifier("userServiceImp")
 private UserSerevice userService;

@Service@Controller 这些注解要放在接口的实现类上,而不是接口上面。

@Autowired@Resource是用来修饰字段,构造函数,或者设置方法,并做注入的。

@Service@Controller@Repository@Component则是用来修饰类,标记这些类要生成bean。

注解使用前提,添加命名空间,让spring扫描含有注解类

示例

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:component-scan base-package="com.spring.learn1" />
</beans>

UserDao

package com.spring.learn1;

public interface UserDao {
    /**
     * 输出方法
     */
    public void outContent();
}

UserDaoImpl

package com.spring.learn1;

import org.springframework.stereotype.Repository;
@Repository("userDao")
public class UserDaoImpl implements UserDao {
    @Override
    public void outContent() {
        System.out.println("dao方法");
    }
}
package com.spring.learn1;

public interface UserService {
    /**
     * 输出方法
     */
    public void outContent();
}

UserServiceImpl

package com.spring.learn1;

import javax.annotation.Resource;
import org.springframework.stereotype.Service;
@Service("userService")
public class UserServiceImpl implements UserService{

    @Resource(name="userDao")
    private UserDao userDao;

    public UserDao getUserDao() {
        return userDao;
    }
    public void setUserDao(UserDao userDao) {
        this.userDao = userDao;
    }
    @Override
    public void outContent() {
        userDao.outContent();
        System.out.println("service");
    }
}

UserController

package com.spring.learn1;

import javax.annotation.Resource;
import org.springframework.stereotype.Controller;
@Controller("userController")
public class UserController {
    @Resource(name = "userService")
    private UserService userService;
    public UserService getUserService() {
        return userService;
    }
    public void setUserService(UserService userService) {
        this.userService = userService;
    }
    public void outContent() {
        userService.outContent();
        System.out.println("content");
    }
}

MainApp

package com;


import com.spring.learn1.UserController;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class MainApp {
    public static void main(String[] args) {
        ApplicationContext ctx = new ClassPathXmlApplicationContext("Beans.xml");
        UserController uc = (UserController) ctx.getBean("userController");
        uc.outContent();
    }
}

运行时以下错误

org.springframework.context.support.AbstractApplicationContext refresh

警告: Exception encountered during context initialization - cancelling refresh attempt: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'userController' defined in file [。。。]: Post-processing of merged bean definition failed; nested exception is java.lang.NoSuchMethodError: javax.annotation.Resource.lookup()Ljava/lang/String;
Exception in thread "main" org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'userController' defined in file [。。。]: Post-processing of merged bean definition failed; nested exception is java.lang.NoSuchMethodError: javax.annotation.Resource.lookup()Ljava/lang/String;
。。。。
Caused by: java.lang.NoSuchMethodError: javax.annotation.Resource.lookup()Ljava/lang/String;

检查发现不存在javax.annotation这个包

在pom.xml文件中引入

<dependency>
    <groupId>javax.annotation</groupId>
    <artifactId>javax.annotation-api</artifactId>
    <version>1.3.1</version>
</dependency>

运行如下

dao方法
service
content

@Resource vs @Autowired

@Resource的装配顺序如下:

  1. @Resource后面没有任何内容,默认通过name属性去匹配bean,找不到再按type去匹配;
  2. 指定了name或者type则根据指定的类型去匹配bean;
  3. 指定了name和type则根据指定的name和type去匹配bean,任何一个不匹配都将报错。

然后,区分一下@Autowired和@Resource两个注解的区别:

  1. @Autowired默认按照byType方式进行bean匹配,@Resource默认按照byName方式进行bean匹配
  2. @Autowired是Spring的注解,@Resource是J2EE的注解,这个看一下导入注解的时候这两个注解的包名就一清二楚了

Spring属于第三方的,J2EE是Java自己的东西,因此,建议使用@Resource注解,以减少代码和Spring之间的耦合。

总结一下:

@Resource根据name和type,是先Name后Type,@Autowired是Type,一般情况下我们最好使用@Resource。

​ 文中详细讲解了@Service、@Autowired、@Resource和@Qualifier的用法,其中重点讲述了@Autowired、@Resource的区别,那么对于@Component、@Repository、@Controller这3个注解,文中也就开头提到,这3个注解其实和@Service一个含义,只是我们在写代码时,会进行分层,比如DAO层、Service层、Action层,分别可以用@Repository、@Service、@Controller表示,其实也就字面含义不一样,效果其实是一样的,然后@Component可以作用在任何层次。所以看起来有7个注解,其实你可以理解只有4个。

上一篇:嵌入式相关概念杂谈


下一篇:Spring学习(五)Spring 基于注解装配Bean