前面我们提到过:按照注入的配置元数据来讲,Spring的配置开发一般分为三类:基于 XML 的配置文件,基于注解的配置,基于 Java 的配置【不推荐】,之前我们所有的概念和实践都是基于XML配置实现的,今天这篇Blog我们来基于注解进行Spring的开发,了解下注解的开发模式,这是一种可以大量减少XML配置的开发方式;同时顺带了解下基于Java类的配置开发方式,这种方式则完全不使用配置文件,同时以上两种模式我们对于注入内容目前只考虑属性注入,暂不考虑构造函数注入。
基于注解进行配置开发
从 Spring 2.5 开始就可以使用注解来配置依赖注入。而不是采用 XML 来描述一个 bean ,可以使用相关类,属性声明的注解,将 bean 配置移动到组件类本身,这样可以减少大量配置。当然我们需要完成两个前提条件
applicationContext.xml配置文件中引入注解约束
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
引入后配置文件头变为:
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p" xmlns:c="http://www.springframework.org/schema/c" 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">
applicationContext.xml声明使用注解并添加包扫描器
如果没有包扫描器,而类本身又没有在xml中显式的配置为bean,那么会报错,报找不到该bean的错,相当于该扫描器会自动扫描目标区域使用注解定义的类bean并把它们装入IOC容器中。
<context:annotation-config/> <context:component-scan base-package="com.example.Spring.model"/>
属性注入的Bean注解
我们首先来看下如何在类中直接使用注解定义Bean。
注解处理属性注入
这里需要重点关注三个注解@Componen、@Value
以及@scope
,在model包下新增类People:
package com.example.Spring.model; import lombok.Data; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Scope; import org.springframework.stereotype.Component; import javax.annotation.Resource; @Data @Component(value = "people") @Scope("prototype") // 相当于配置文件中 <bean id="people" class="当前注解的类" scope="prototype"/> public class People { @Value("tml") private String name; // 相当于配置文件中 <property name="name" value="tml"/> @Value("30") private int age; @Resource private Address address; public void show(){ System.out.println("name: "+name); System.out.println("age: "+age); System.out.println("address: "+address); } }
然后使用该类依赖应用的Address属性,这个我们之前就创建好了
package com.example.Spring.model; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; @Data @AllArgsConstructor @NoArgsConstructor public class Address { private String cityName; private String cityCode; }
这个Address是在XML中配置的
<bean id="address" class="com.example.Spring.model.Address" > <property name="cityCode" value="100031"/> <property name="cityName" value="北京"/> </bean>
测试类如下:
import com.example.Spring.model.People; import org.junit.jupiter.api.Test; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; public class PeopleTest { @Test public void PeopleTest(){ //解析beans.xml文件,生成管理相应的bean对象 ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml"); People people = (People) context.getBean("people"); people.show(); } }
测试结果如下:
正常输出,说明了我们通过注解和xml混合开发也是可以的,甚至大多数场景下简单的用注解,复杂的用xml可以达到很好的效果,事实上,我们这些注解,就是替代了在配置文件当中配置步骤而已
Spring @Componen衍生注解
@Component有三个衍生注解,它们的具体实现时一样的,都是代表了一个Component,不同的命名只不过为了区分不同的层,为了更好的进行分层,Spring可以使用其它三个注解,目前使用哪一个功能都一样。
@Controller
:Controller层,也就是控制层,接口请求的入口@Service
:service层,业务逻辑层,具体的业务逻辑实现层@Repository
:dao层,持久化层,和数据库交互的层,事实上,这个层的实现我们上一个系列MyBatis刚介绍过。
写上这些注解,就相当于将这个类交给Spring管理装配了
自动装配的注解
上一篇Blog关于自动装配我们介绍过,Spring是如何通过自动装备将依赖自动进行装配的,具体实现细节是byName和byType,这里的自动装配注解也是为自动装配服务的。
Spring @Autowired 注解
需要注意的是,@Autowired
是按类型byType自动转配的,不支持id也就是byName匹配:
package com.example.Spring.model; import lombok.Data; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Scope; import org.springframework.stereotype.Component; @Data @Component(value = "people") @Scope("prototype") // 相当于配置文件中 <bean id="people" class="当前注解的类" scope="prototype"/> public class People { @Value("tml") private String name; // 相当于配置文件中 <property name="name" value="tml"/> @Value("30") private int age; @Autowired private Address address; //虽然属性名不一致 public void show(){ System.out.println("name: "+name); System.out.println("age: "+age); System.out.println("address: "+address); } }
XML中的配置为:
<bean id="address2" class="com.example.Spring.model.Address" > <property name="cityCode" value="100031"/> <property name="cityName" value="上海"/> </bean>
结果还是能正常打印出:
Spring @Qualifier 注解
@Autowired
是根据类型自动装配的,加上@Qualifier
则可以根据byName的方式自动装配,@Qualifier
不能单独使用,在使用@Qualifier
之前,如果有两个类型一样的:
<bean id="address2" class="com.example.Spring.model.Address" > <property name="cityCode" value="100031"/> <property name="cityName" value="北京"/> </bean> <bean id="address1" class="com.example.Spring.model.Address" > <property name="cityCode" value="100031"/> <property name="cityName" value="上海"/> </bean>
那么会报类型发现多个的错误:
Caused by: org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type 'com.example.Spring.model.Address' available: expected single matching bean but found 2: address2,address1 at org.springframework.beans.factory.config.DependencyDescriptor.resolveNotUnique(DependencyDescriptor.java:220) at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1358) at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1300)
配置上Qualifier 注解后,也可以使用多个相同类型了,每次可以指定一个使用:
package com.example.Spring.model; import lombok.Data; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Scope; import org.springframework.stereotype.Component; @Data @Component(value = "people") @Scope("prototype") // 相当于配置文件中 <bean id="people" class="当前注解的类" scope="prototype"/> public class People { @Value("tml") private String name; // 相当于配置文件中 <property name="name" value="tml"/> @Value("30") private int age; @Autowired @Qualifier(value="address2") private Address address; public void show(){ System.out.println("name: "+name); System.out.println("age: "+age); System.out.println("address: "+address); } }
测试结果如下:
Spring @Resource 注解
@Resource
如有指定的name属性,先按该属性进行byName方式查找装配;其次再进行默认的byName方式进行装配;如果以上都不成功,则按byType的方式自动装配。都不成功,则报异常。也就是@Resource的作用相当于@Autowired
加上@Qualifier(value="address2")
,我们设计XML配置如下:
<bean id="address2" class="com.example.Spring.model.Address" > <property name="cityCode" value="100031"/> <property name="cityName" value="指定name"/> </bean> <bean id="address" class="com.example.Spring.model.Address" > <property name="cityCode" value="100031"/> <property name="cityName" value="默认name"/> </bean> <bean id="address1" class="com.example.Spring.model.Address" > <property name="cityCode" value="100031"/> <property name="cityName" value="类型"/> </bean>
然后People使用Resource:
package com.example.Spring.model; import lombok.Data; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Scope; import org.springframework.stereotype.Component; import javax.annotation.Resource; @Data @Component(value = "people") @Scope("prototype") // 相当于配置文件中 <bean id="people" class="当前注解的类" scope="prototype"/> public class People { @Value("tml") private String name; // 相当于配置文件中 <property name="name" value="tml"/> @Value("30") private int age; @Resource(name="address2") private Address address; public void show(){ System.out.println("name: "+name); System.out.println("age: "+age); System.out.println("address: "+address); } }
首先按照指定name装配,打印结果如下:
然后我们调整配置,干掉指定name的bean:
<bean id="address" class="com.example.Spring.model.Address" > <property name="cityCode" value="100031"/> <property name="cityName" value="默认name"/> </bean> <bean id="address1" class="com.example.Spring.model.Address" > <property name="cityCode" value="100031"/> <property name="cityName" value="类型"/> </bean>
同时调整Resource注解为:
@Resource private Address address;
此时的打印结果如下:
然后我们把默认name的bean干掉,只留下类型bean在这里插入代码片
:
<bean id="address1" class="com.example.Spring.model.Address" > <property name="cityCode" value="100031"/> <property name="cityName" value="类型"/> </bean>
打印结果如下:
当然Resource注解不光能自动装配来自xml配置的bean,也能装配容器中管理的基于注解配置的bean,例如我们定义个model下该xml会扫描到的类:
package com.example.Spring.model; import lombok.Data; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; @Data @Component //将这个类标注为Spring的一个组件,放到容器中! public class Room { @Value("豪宅") public String name; }
然后再加上实现类配置:
package com.example.Spring.model; import com.example.Spring.config.Zoom; import lombok.Data; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Scope; import org.springframework.stereotype.Component; import javax.annotation.Resource; @Data @Component(value = "people") @Scope("prototype") // 相当于配置文件中 <bean id="people" class="当前注解的类" scope="prototype"/> public class People { @Value("tml") private String name; // 相当于配置文件中 <property name="name" value="tml"/> @Value("30") private int age; @Resource private Address address; @Resource private Room room; public void show(){ System.out.println("name: "+name); System.out.println("age: "+age); System.out.println("address: "+address); System.out.println("room: "+room); } }
实现结果如下:
所以Resource是万金油,推荐使用这个配置而不是Autowired
基于Java类进行配置开发
到目前为止,我们已经看到如何使用 XML 配置文件以及注解方式来配置 Spring Bean,可以发现即使是通过注解方式注入其实也是通过配置文件进行包扫描注入的,也就是说注解开发模式还是离不开XML配置,但其实我们接下来要说的这种基于Java类进行配置开发不需要任何XML配置文件,事实上,Config类代替了XML配置文件
1 JavaConfig类编写
我们首先编写一个JavaConfig类用来注册各种Bean,在包config下
package com.example.Spring.config; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration //代表这是一个配置类 public class JavaConfig { @Bean //通过方法注册一个bean,这里的返回值就Bean的类型,方法名就是bean的id! public Zoom zoom(){ return new Zoom(); } @Bean //通过方法注册一个bean,这里的返回值就Bean的类型,方法名就是bean的id! public School school(){ return new School(); } }
2 Bean的封装类编写
然后我们分别添加Bean的注册封装类:
Zoom类编写
package com.example.Spring.config; import lombok.Data; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; @Data @Component //将这个类标注为Spring的一个组件,放到容器中! public class Zoom { @Value("动物园") public String name; }
School 类编写
package com.example.Spring.config; import lombok.Data; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; @Data @Component //将这个类标注为Spring的一个组件,放到容器中! public class School { @Value("学校") public String name; }
注意这里我们的配置类和封装实现类都放置在config包下,并没有放到xml扫描的model包下,也就是说我们这里的实现方式和xml没有半毛钱关系。
3 测试实现
然后我们来测试下实现,注意我们这里不使用xml获取上下文,而是注解:AnnotationConfigApplicationContext
import com.example.Spring.config.JavaConfig; import com.example.Spring.config.School; import com.example.Spring.config.Zoom; import org.junit.jupiter.api.Test; import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.AnnotationConfigApplicationContext; public class JavaConfigTest { @Test public void JavaConfigTest(){ ApplicationContext applicationContext = new AnnotationConfigApplicationContext(JavaConfig.class); School school = (School) applicationContext.getBean("school"); System.out.println(school.name); Zoom zoom = (Zoom) applicationContext.getBean("zoom"); System.out.println(zoom.name); } }
打印结果如下:
总结一下
到这篇Blog为止,终于全面了解了基于元数据的IOC注入模式:XML配置、注解模式、Java配置类,这几种其实可以混合使用,在合适的场景使用合适的元数据注入模式,XML比较清晰,注解模式比较快捷,Java配置类模式完全不依赖配置文件,各有各的好吧,总的来说对于Spring我感觉还是以配置为主,因为没有很好的支持注解的实现方式。到后边的Spring Boot就是大量注解了,因为支持的比较好吧,逐渐的开始约定大于配置了。