大数据、Java EE 学习资料请关注 B 站:https://space.bilibili.com/204792350

Spring bean - 通过 annotaion 方式配置自动装配

基于注释的容器配置

隐式注解

注解的方式,看起来很明显就比 xml 这种方式简洁,不需要配置大量的自定义标签。

注解的使用很简单,只要在 xml 配置作少量的配置,然后在需要容器加载的类、方法、属性等地方的上面加上适用的注解即可。Spring 在运行的时候,会根据在 xml 配置的扫描范围进行查找需要注册的类、方法、属性。Spring 通常的做法是在必要功能的启动之后来注册并且实例化,这种方式是针对于 singleton 模式,原型的则由在具体程序地方使用的时候,在注册加载。

需要注意的是:注解的方式在 xml 之前,也就是说,在 xml 的配置会覆盖 annotaion 的配置。

随着 spring 版本的编程,实现的方式也不同。

<context:annotaion-config>:隐式注解


<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns="http://www.springframework.org/schema/beans"
       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/>

    <bean id="helloWorld" class="com.huasio.ssm.beans.HelloWorld"/>
    <bean id="student" class="com.huasio.ssm.beans.Student"/>

</beans>

HelloWorld 类之中,使用 @Autowired 实现自动装配。

@Component
public class HelloWorld {
    private String name;

    @Autowired
    private Student student;

    public HelloWorld() {}
    // ......
}

在一个测试方法中:

public class HelloTest {
    @Test
    public void testHello() {
        ClassPathXmlApplicationContext ioc = new ClassPathXmlApplicationContext("applicationContext.xml");
        HelloWorld helloWorld = ioc.getBean("helloWorld", HelloWorld.class);
        System.out.println(helloWorld); // HelloWorld{name='null', student=Student{id=null, name='null', age=null}}
    }
}

也就是说,结果是实现了自动装配,与在 bean 之中开启 autowire 一样的效果。

这种方式同样的也是在 ioc 容器寻找注册的 bean,没有找到报错,并且比起在 bean 属性中定义自动装配,需要事先按照什么类型,而隐式装配会自动切换装配类型。

隐式注解后的处理器:

  • AutowiredAnnotationBeanPostProcessor
  • CommonAnnotationBeanPostProcessor
  • PersistenceAnnotationBeanPostProcessor
  • RequiredAnnotationBeanPostProcessor

需要注意的是:<context:annotaion-config> 只作用于当前相同的应用程序的上下文中,在当前的范围寻找的 bean 的注解。

扫描包,托管组件

其实改来改去,底层的还是一回事,就像是这个扫描组件的配置,其底层还是包含了隐式注解,换句话来说就是它们本质都是一样,区别在于扫描组件的方式在这个本质上面丰富了配置,比如说:不用在实现定义自动装配的 bean,让 spring 根据某种过滤条件,将其自动注册并且按照需要自动装配。

通过查看 xml 源代码可以发现,单纯的隐式注解直接使用这个配置:

<xsd:element name="annotation-config">
	<xsd:annotation></xsd:annotation>
</xsd:element>

而扫描组件在这之前增加了许多属性:

<xsd:element name="component-scan">
	<xsd:annotation></xsd:annotation>
	<xsd:complexType>
		<xsd:sequence>
			<xsd:element name="include-filter" type="filterType"
				minOccurs="0" maxOccurs="unbounded">
				<xsd:annotation></xsd:annotation>
			</xsd:element>
			<xsd:element name="exclude-filter" type="filterType"
				minOccurs="0" maxOccurs="unbounded">
				<xsd:annotation></xsd:annotation>
			</xsd:element>
		</xsd:sequence>
		<xsd:attribute name="base-package" type="xsd:string"
			use="required">
			<xsd:annotation>
				
			</xsd:annotation>
		</xsd:attribute>
		<xsd:attribute name="resource-pattern" type="xsd:string">
			<xsd:annotation>
				
			</xsd:annotation>
		</xsd:attribute>
		<xsd:attribute name="use-default-filters" type="xsd:boolean"
			default="true">
			<xsd:annotation>
				
			</xsd:annotation>
		</xsd:attribute>
		<xsd:attribute name="annotation-config" type="xsd:boolean"
			default="true">
			<xsd:annotation>
			</xsd:annotation>
		</xsd:attribute>
		<xsd:attribute name="name-generator" type="xsd:string">
			<xsd:annotation>
				<xsd:appinfo>
					<tool:annotation>
						<tool:expected-type type="java.lang.Class"/>
						<tool:assignable-to type="org.springframework.beans.factory.support.BeanNameGenerator"/>
					</tool:annotation>
				</xsd:appinfo>
			</xsd:annotation>
		</xsd:attribute>
		<xsd:attribute name="scope-resolver" type="xsd:string">
			<xsd:annotation>
				<xsd:appinfo>
					<tool:annotation>
						<tool:expected-type type="java.lang.Class"/>
						<tool:assignable-to type="org.springframework.context.annotation.ScopeMetadataResolver"/>
					</tool:annotation>
				</xsd:appinfo>
			</xsd:annotation>
		</xsd:attribute>
		<xsd:attribute name="scoped-proxy">
			<xsd:annotation>
			</xsd:annotation>
			<xsd:simpleType>
				<xsd:restriction base="xsd:string">
					<xsd:enumeration value="no"/>
					<xsd:enumeration value="interfaces"/>
					<xsd:enumeration value="targetClass"/>
				</xsd:restriction>
			</xsd:simpleType>
		</xsd:attribute>
	</xsd:complexType>
</xsd:element>

贴上配置源代码,一看就知道那种方式的功能更丰富。

使用这个需要:xmlns:context="http://www.springframework.org/schema/context" 命名空间,一般 IDEA 会自动导入。

说了这么多,还是直接上实例:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns="http://www.springframework.org/schema/beans"
       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.huasio.spring"/>
</beans>

通过上面的简单一个配置,这个配置定义了要扫描包的范围,在这个范围内,只要是加了 @Component 类、都会自动被 ioc 容器注册,并且加载(若是单例的话)。

Spring 识别的组件有:

  • @Component
    • 基础组件,其它三个注解继承该注解。
    • 可以修饰一些没有特殊作用的 POJO。
  • @Controller
    • 控制层注解,在 spring 没有特殊作用。
    • 在 SpringMVC 中代表着控制层
  • @Service
    • 服务层,注解一些编写业务逻辑的类。
  • @Repository
    • 数据层注解,一般使用在 DAO 类。

后面三个继承第一个注解,所以它们能被 spring 识别。

并且使用了扫描包的方式,就不用配置隐式注解了,配置了也会被 spring 忽略。

在使用方面,扫描的有一些属性需要会的,还有子标签,例如说:

  • 自动识别注解:use-default-filters
    • true and false
    • 这个属性的一般与 include-filter 子标签搭配使用。
  • 子标签:<context:include-filter>
    • 只加载......
  • <context:exclude-filter>
    • 排除加载......

这两个子标签在整合 spring、springMVC 的时候非常有用,可用区分它们的负责范围。

两个子标签过滤类型都一样,不同的是过滤条件。

类型说明
annotation按注解的方式过滤加载
custom自定义
aspectjaspectj 语法
assignable指定 class 或 interface
regex正则表达式
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns="http://www.springframework.org/schema/beans"
       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">

	<!-- 只加载 org.springframework.stereotype.Controller 注解。 -->
	<!-- 不适用默认的过滤器 -->
    <context:component-scan base-package="com.huasio.spring" use-default-filter="false">
        <context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
    </context:component-scan>

	<!-- 排除 org.springframework.stereotype.Controller 注解。 -->
    <context:component-scan base-package="com.huasio.ssm">
        <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
    </context:component-scan>

</beans>

注解列表

@Required

使用这个注解的,方法,构造器,属性等,标识必须要显式定义 bean 或者实现自动装配。

使用在 setter 方法上面,标识着形参列表必须要,若是没有显式赋值或者自动装配,那么会抛出异常。

public class SimpleMovieLister {

    private MovieFinder movieFinder;

    @Required
    public void setMovieFinder(MovieFinder movieFinder) {
        this.movieFinder = movieFinder;
    }

}

通常对于一些在对象需要某些依赖的时候,必要在初始化方法检测,避免在后续使用时出现 NullPoniterException 异常。、

@Autowired

此注解作用与:属性、构造函数、方法等,表示由 ioc 容器自动装配,并且这个注解默认是强制必须的,同样没有的找到对应 bean,也会出现异常。

可用其 required 属性设置为:false,这样表示不是必须的。

public class SimpleMovieLister {

	@Autowired
    private MovieFinder movieFinder;

}

@Primary

使用自动装配的时候,其中有个模式的按照类型自动装配,这种情况很有可能会出现找到多个 bean,因为其可能有父类,同类等。

那么一旦出现多个 bean 的时候,若是没有特别处理,那么会抛出异常。

使用这个注解的作用就是将特定的结构标识为主要自动装配对象,避免在出现多个 bean 的时候没法处理。

public class SimpleMovieLister {

	@Autowired
    private MovieFinder movieFinder;

}

@Primary
class MovieFinder extends MovieFinderParent{}

class MovieFinderParent {}

像这个例子中,若是没有将 MovieFinder 标识为主要自动装配对象,那么就会可能将其父类也找到。

@Qualifier

自动装配的时候有多种模式,其中有一种是按照 tyName id 进行装配的,具体是根据标识名与 id 匹配。

拿属性来举例,有些时候,可能属性名与 id 不一致的时候,可用使用这个注解来指定这个属性装配 bean id。

public class SimpleMovieLister {

	@Autowired
	@Qualifier("MF")
    private MovieFinder movieFinder;

}

会在容器寻找名为:MF 的 bean。

@Resource

这个注解如同 @Autowired 一样的作用,同样都是要求属性方法自动装配。

区别在于 @Resource 可用两种方式找到,byName 和 byType。

@Resource 自动装配顺序:

  • 按照 name 属性值在容器中查找与之相同的 bean,自动装配。
  • 若是 name 为空,type 也为空,则按照 byType 的方式。
  • 若是指定 type,则按照 byType 在容器中匹配。
  • 若是同时指定 name 和 type 则在容器中查找满足两个条件的 bean。
  • 若是都没有找到,则抛出异常。
public class SimpleMovieLister {

	@Resource("mf")
    private MovieFinder movieFinder;

}

@Value

这个注解通常用于外部属性注入,例如说 *.properties、*.yml。

@PropertySource("classpath:application.properties")
@Component
public class MovieRecommender {

    private final String catalog;

    public MovieRecommender(@Value("${catalog.name}") String catalog) {
        this.catalog = catalog;
    }
}

在 application.properties 文件中:

catalog.name=MovieCatalog

在配置文件若是没有找到指定标识符的值,那么会将 $ 作为值。

若是要限制这种行为,声明一个方法:

@Configuration
public AppConfig {
	public static PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfgurer() {
		reutrn new PropertySourcesPlaceholderConfigurer();
	}
}

需要搭配 @Configuration 和 @Bean,这两个的是将在 spring 配置文件中增加 bean。

默认的 spring 会对于 String 和 Integer 自动转换支持,除了字面量,还支持数组,多个值以逗号 , 分割,就是形成数组。

Spring BeanPostProcessor 使用 CoversionService 将 @Value 进行转换类型,如果要定义类型,则提供 ConversionService bean。

@Configuration
public class AppConfig {

    @Bean
    public ConversionService conversionService() {
        DefaultFormattingConversionService conversionService = new DefaultFormattingConversionService();
        conversionService.addConverter(new MyCustomConverter());
        return conversionService;
    }
}

除了这些之外,还有小知识点需要了解的,在引用外部属性的时候是使用:${} 这个标识符号来表示使用外部的一个变量值。同样,各种符号可用方法自定义。

方法说明
setPlaceholderPrefix前缀
setPlaceholderSuffix后缀
setValueSeparator值之间的分割符号

这个几个方法是来自于 PropertySourcesPlaceholderConfigurer 类,设置了强制检测之后,一旦没有值报错,那么可以指定一个默认值。

@Component
public class MovieRecommender {

    private final String catalog;

    public MovieRecommender(@Value("${catalog.name:defaultCatalog}") String catalog) {
        this.catalog = catalog;
    }
}

在 @Value 之中也能使用 EL 表达式:

@Component
public class MovieRecommender {

    private final Map<String, Integer> countOfMoviesPerCatalog;
    private final String catalog;

    public MovieRecommender(@Value("#{systemProperties['user.catalog'] + 'Catalog' }") String catalog) {
        this.catalog = catalog;
    }

    public MovieRecommender(
            @Value("#{{'Thriller': 100, 'Comedy': 300}}") Map<String, Integer> countOfMoviesPerCatalog) {
        this.countOfMoviesPerCatalog = countOfMoviesPerCatalog;
    }
}

@PostConstruct 和 @PreDestroy

还记不记得,在 xml 配置 bean,其中有两个属性就是定义生命周期的方法。同样的,注解驱动的也有两个注解,代表着这两个阶段的生命周期。

前者是初始化阶段,后者是销毁阶段。

public class CachingMovieLister {

    @PostConstruct
    public void populateMovieCache() {
        // populates the movie cache upon initialization...
    }

    @PreDestroy
    public void clearMovieCache() {
        // clears the movie cache upon destruction...
    }
}

这两个注解和上面的 @Resource 一样,都是 jdk6 ~ jdk8 提供的,但是在 jdk9 的时候与核心部分分开了,在 jdk11 被移除。如果有需要,可以通过 Maven Central 获取 javax.annotation-api 工具,如同其它类库一样使用。

相关链接

# Java   Spring   框架  

评论

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×