Spring framework(4):IoC (2) Bean 装配

来源:互联网 发布:ubuntu删除搜狗输入法 编辑:程序博客网 时间:2024/05/15 15:23

Spring 装配 Bean 概述

Spring 容器启动的3个条件:
  • Spring 本身:Spring 框架的类包都已经放置在应用程序的类路径下;
  • Bean 配置信息:应用程序为 Spring 提供了完整的 Bean 配置信息;
  • Bean 实现类:Bean 实现类都已经放到应用程序的类路径下;

Bean 配置信息由以下4部分组成:
  • Bean 的实现类;
  • Bean 的属性信息;
  • Bean 的依赖关系;
  • Bean 的行为配置,如生命周期范围及生命周期各过程的回调函数等;

Spring 容器,Bean配置信息,Bean实现类,应用程序4部分的关系:


Spring 中对 Bean 进行装配目前有以下4种方式:
  • 通过 XML 配置文件装配(Spring 1.0 +)
  • 通过注解装配(Spring 2.0+)
  • 通过 JavaConfig 装配(Spring 3.0+)
  • 通过 Groovy DSL 装配(Spring 4.0+)
  • 通过 Kotlin DSL 装配(Spring 5.0+)

以下介绍前4种装配方式,对于大部分通用概念,以 xml 方式示例,以下的完整示例代码参见:
https://gitee.com/assad/springframework-test-beans




通过 XML 装配 


依赖注入的基本配置

Spring 支持3种依赖注入方式,分别是属性注入,构造函数注入,工厂方法注入
在实际使用中,一般使用属性注入方式会比较多,因为在配置上比较灵活,代码冗余度比较低;

1.属性注入

相关示例代码模块site.assad.ditype/User ; site.assad.ditype/beans.xml ; site.assad.ditype/ DiTypeTest
(分别指main/java, resources, test/java下的相关模块,下不赘述)
示例中创建一个默认的Bean为User:
 
package site.assad.ditype;
public class User {
    private String name;
    private int age;
    private String city;
    public User() {
    }
    //省略 getter,setter
}
beans.xml中对其进行相关配置:
 
  <bean id="user1" class="site.assad.ditype.User"
          p:name="Al-assad"
          p:age="20"
          p:city="Guangzhou" />
  <!-- 或者 -->
    <bean id="user1" class="site.assad.ditype.User">
      <property name="name" value="Al-assad" />
      <property name="age" value="20" />
      <property name="city" value="Al-Guangzhou" />
  </bean>
可以使用以上2种方式进行bean的属性注入,第一种是简便模式,第二种为完整模式;
对其该注入的bean的调用代码如下:
 
ApplicationContext factory = new ClassPathXmlApplicationContext("site/assad/ditype/beans.xml");
User user = factory.getBean("user1",User.class);

2.构造函数注入

相关示例代码模块site.assad.ditype/User, site.assad.ditype/beans.xml, site.assad.ditype/ DiTypeTest
构造函数注入一般用于保证一些必要的属性在Bean实例化是就得到设置,确保Bean在实例化就可以使用;
beans.xml中的相关设置如下:
 
   <bean id="user3" class="site.assad.ditype.User">
        <constructor-arg type="java.lang.String" value="Jhon" />  <!--设置构造函数参数-->
        <constructor-arg type="int" value="23" />
        <constructor-arg type="java.lang.String" value="BeiJing" />
    </bean>

3.工厂方法注入

相关示例代码模块site.assad.ditype/User,UserFactory ; site.assad.ditype/beans.xml ; site.assad.ditype/ DiTypeTest
Spring 除了支持DI概念中的属性注入,构造函数注入之外,还支持工厂方法注入;
示例中构建了一个 User 的工厂类 UserFactory,beans.xml 中的相关配置如下:
 
<!--配置工厂类bean-->
<bean id="userFactory" class="site.assad.ditype.UserFactory" />
<!--通过工厂类bean,调用不同的工厂方法,注入2个不同的 User bean-->
<bean id="user4" factory-bean="userFactory" factory-method="createUser" />
<bean id="user5" factory-bean="userFactory" factory-method="createGuangzhouUser" p:name="Jucka" p:age="34"/>
Spring 中还提供了一个 FactoryBean工厂类接口(org.springframework.beans.factory.FactoryBean),用户可以通过继承该接口实现工厂类,以定制实例化Bean的逻辑,;
FactoryBean 工厂类定义了3个接口方法:
T getObject()返回 Factory 创建的 Bean 实例,如果 isSingleton() 返回true,该示例会放置到 Spring 容器的单实例缓存池中boolean isSingleton()确定创建的 Bean 实例作用域为 singleton 或 protetypeClass<?> getObjectType()返回创建的 Bean 实例类型

示例中 site.assad.ditype/UserFactoryBean继承了 FactoryBean 接口实现了一个User的工厂类,如下:
 
public class UserFactoryBean implements FactoryBean{
    //实现通过逗号分隔的形式,一次性配置所有User的properties,简化配置;
    private String userInfo;
    public void setUserInfo(String userInfo) {  this.userInfo = userInfo; }
    @Override
    public Object getObject() throws Exception {
        User user = new User();
        String[] infos = userInfo.split(",");
        user.setName(infos[0]);
        user.setAge(Integer.parseInt(infos[1]));
        user.setCity(infos[2]);
        return user;
    }
    @Override
    public Class<?> getObjectType() {  return User.class; }
    @Override
    public boolean isSingleton() {  return true; }
}
beans.xml中相应的配置如下,注入的类为UserFactoryBean工厂类,调用时返回User类:
 
<bean id="user6" class="site.assad.ditype.UserFactoryBean" p:userInfo="Alex,30,San Francisco" />
调用该bean如下:
 
ApplicationContext factory = new ClassPathXmlApplicationContext("site/assad/ditype/beans.xml"); 
User user = factory.getBean("user6",User.class);


注入参数详解

1. 特殊字面量 和 null值

特殊字面量
xml文件中有5个特殊字符:&、<、>、"、‘  ,当配置文件中注入的字面量包含这些特殊字符时,处理的方式有2中:
  • 使用 <![CDATA[ ]]>包含该字面量,如字面量 Al-assad & Vancy  替换为<![CDATA[ Al-assad & Vancy ]]>
  • 使用转义序列代替特殊符号
<  → &lt;> → &gt;→ &amp;"  →  &quot;'  →  &apos;
null 值
如果某个属性插入值为 null 值,应该使用<null />标签放置在该属性节点下,如下:
 
<property name="city"><null /></property>

2. <bean>之间的关系

引用
如果一个bean引用另一个bean,可以使用 <ref bean=“beanId”> 配置该引用bean,如下:
示例代码模块:site.assad.ditype/User,Article ; site.assad.ditype/beans.xml ; site.assad.ditype/ DiTypeTest
 
<!--Article 中一个属性引用一个注入的 User-->
<bean id="user1" class="site.assad.ditype.User" p:name="Al-assad" p:age="20" p:city="Guangzhou" />
<!--简略模式,配置相关属性-ref-->
<bean id="article1" class="site.assad.ditype.Article"  p:title="How to Communicate with Cat"
      p:user-ref="user1"/> 
<!--或者-->
<bean id="article1" class="site.assad.ditype.Article">
     <property name="title" value="How to Communicate with Cat" />
     <property name="user">
            <ref bean="user1" />
      </property>
 </bean>
继承
bean配置支持继承关系,利用继承关系,可以节约同类型bean的重复注入配置,如要注入多个同类型bean中含有部分相同值的属性,可以将这些相同值的属性抽象实现一个抽象bean,再继承这个抽象bean实现多个同类型bean;
 
<!--定义抽象Bean-->
<bean id="abstructUser" class="site.assad.ditype.User" abstruct="true" 
      p:age="18" p:city="Guangzhou" />
<!--继承抽象Bean-->
<bean id="user8" parent="abstructBean" p:name="Alex" />
<bean id="user9" parent="abstructBean" p:name="Tim" />
依赖
如果一个bean的实例化依赖于另一个bean的实例化,可以通过设置该bean的<depends-on>属性来实现bean之间的依赖关系;
 
<!--在manager实例化前,会先实例化sysInt-->
<bean id="sysInt" class="..." />
<bean id="manager" class="..." depend-on="sysInt" />

3. 内部Bean

如果bean1中引用bean2时,bean2不需要被容器中的其他bean引用,可以将bean2以内部Bean的方式注入bean1中,类似匿名类的概念;
示例代码模块:site.assad.ditype/User,Article ; site.assad.ditype/beans.xml ; site.assad.ditype/ DiTypeTest
 
<!--article3以内部Bean的方式引用 User-->
<bean id="article3" class="site.assad.ditype.Article" p:title="Emmmmm....">
        <property name="user">
            <bean class="site.assad.ditype.User"
                  p:name="MoFila"
                  p:age="12"
                  p:city="ChongQing"/>
        </property>
</bean>

4.级联属性

Spring 支持级联属性的配置,对于上面的内部Bean引用的代码,可以使用以下级联属性的方式配置:
示例代码模块:site.assad.ditype/User,Article2 ; site.assad.ditype/beans.xml ; site.assad.ditype/ DiTypeTest
 
 <bean id="article4" class="site.assad.ditype.Article2" p:title="Are you OK?">
        <property name="user.name" value="LeiBuShi"/>
        <property name="user.age" value="999"/>
        <property name="user.city" value="BeiJing"/>
 </bean>
在使用某个bean的级联属性时,要为该级联属性申明一个初始化对象,如下:
 
public class Article2 {
    private String title;
    private User user = new User();  //在 Article2 使用 User 作为级联对象时,必须将user声明为一个初始化对象;
    .....
}

5.集合类型属性

Spring 为 java.util 中的集合类 List,Set,Map,Properties 提供了相应的标签支持;
示例代码模块:site.assad.ditype/User,Article,TaskList ; site.assad.ditype/beans.xml ; site.assad.ditype/ DiTypeTest
List
 
<bean id="taskList1" class="site.assad.ditype.TaskList">
       <property name="belongs">  <!--TaskList的belongs属性为List类型-->
            <list>
                <value>Al-assad</value>
                <value>Vancy</value>
                <value>Jone</value>
            </list>
        </property>
</bean>
set使用<set>标签,配置方法类似List;
Map
 
   <bean id="taskList2" class="site.assad.ditype.TaskList">
        <property name="items">   <!--TaskList的items属性为List类型-->
            <map>
                <entry key="9:00" value="go to work" />
                <entry key="21:00" value="go back home" />
            </map>
        </property>
    </bean>
Properties
Properties 本质上是 Map,不过他的key、value都只能是String类型
 
<bean id="taskList3" class="site.assad.ditype.TaskList">
        <property name="places"> <!--TaskList的places属性为List类型-->
            <props>
                <prop key="9:00">Company</prop>
                <prop key="21:00">Home maybe</prop>
            </props>
        </property>
 </bean>

6.集合合并

Spring 支持集合合并功能,允许子<bean>继承父<bean>的同名集合元素,并将子<bean>中的集合属性值合和父<bean>中的同名属性值合并起来作为最终 Bean 的属性值;
示例代码模块:site.assad.ditype/User,Article,TaskList ; site.assad.ditype/beans.xml ; site.assad.ditype/ DiTypeTest
 
    <bean id="taskListParent" abstract="true" class="site.assad.ditype.TaskList" >  <!--被合并的父 Bean-->
        <property name="belongs">
            <list>
                <value>Al-assad</value>
                <value>Vancy</value>
            </list>
        </property>
    </bean>
    <bean id="taskListChild" parent="taskListParent" >  <!--被合并的子Bean,指定父类Bean-->
        <property name="belongs">
            <list merge="true">   <!--和父类中的同名集合属性合并,taskListChild belongs属性中拥有 taskListParent belongs属性的所有元素值-->
                <value>Tom</value>
                <value>Jceke</value>
            </list>
        </property>
    </bean>

bean的作用域

bean 含有以下的作用域,作用域会对 Bean 的生命周期和创建方式产生影响;
singleton默认作用域,Bean 以单例的方式存在,Spring IoC 容器中只存在一个该 Bean 的实例;prototype每次从容器中调用 Bean 时,都会返回一个新的实例,即每次都会执行与一次 new XxxBean();request仅存在于 WebApplicationContext,每次 HTTP 请求都会创建一个新的 Bean;session仅存在于 WebApplicationContext,同一个 HTTP Session 共享一个Bean,不同 HTTP Session 使用不用的 Bean;globalSession仅存在于 WebApplicationContext,同一个全局 Session 共享一个Bean,一般适用于 Portlet 应用环境; 
可以用过bean的<scope>属性对一个bean的作用于进行配置,如下:
 
<bean id="user1" class="site.assad.ditype.User" p:name="Al-assad" p:age="20" p:city="Guangzhou"
      scope="prototype" />
对于 WebApplicationContext 相关的作用域(reques,session,globalSession),还要对Web容器进行额外的配置,可以利用HTTP请求监听器进行配置,如下:
web.xml
 
<web-app>
    ....
    <listener>
        <listener-class>org.springframework.web.context.request.RequestContextListener</listener-class>
    </listener>
    ....
</web-app>

合并多个配置文件

在一个大型的项目,可能会存在多个xml配置文件,在启动Spring容器时,可以通过一个数组来指定这些配置文件,也可以使用<import>标签将多个配置文件引入到一个文件中,在启动Spring容器时仅指定这个合并好的配置文件即可;
如在使用一个 beans.xml 将 site/assad/impt/bean1.xml, site/assad/impt/bean2.xml 这2个配置文件合并在一起,如下:
beans.xml
 
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
    <import resource="classpath:site/assad/impt/bean1.xml" />
    <import resource="classpath:site/assad/impt/bean2.xml" />
</beans>
  完整示例代码模块: site.assad.impt/beans.xml ,bean1.xml,bean2.xml ; site.assad.impt/ imptTest





通过 Annontation 注解装配


从Spring 2.0 开始引入了基于注解的配置方式,并在4.0时得到进一步增强,相比于基于xml配置文件的方式,基于注解的配置方式可以直接在Bean实现类上标注注解,以减少代码冗余;
完整示例代码模块site.assad.anno/User,Article ; site.assad.anno/beans.xml ; site.assad.anno/ AnnoTest

使用注解定义Bean

@Conpoment 注解用于定义一个Bean,同时Spring还提供同以下等效的注解用于对DAO,Service,Web层的Controller进行注解:
  • @Repository:用于注解DAO层bean;
  • @Service:用于注解Service层bean;
  • @Controller:用于注解Web层的Controller实现类bean;
一般注解的形式如下:
 
package site.assad.anno;
@Component
// 或者使用 @Component("user") 显式定义 beanId
public class User {
   .....
}
可以使用@Scope注解设置Bean的作用范围,如下:
 
@Scope("prototype")
@Component
public class User {
   .....
}

自动扫描注解定义的Bean

基本配置
在定义完Bean的注解后,需要配置一个xml配置文件用于扫描类包,以应用注解定义的Bean,类似如下:
beans.xml
 
<beans ...>
    <!--扫描注解定义的组件:默认扫描site.assad.anno 包下的所有class-->
    <context:component-scan base-package="site.assad.anno" />
</beans>
自定义扫描匹配
可以设置<context:component-scan>的 resource-pattern 属性进行自定义的扫描包匹配,用于匹配某个子包,或某个类型的文件,如下:
 
<beans ...>
    <!--扫描 site.assad 包下anno子包中的所有class -->
    <context:component-scan base-package="site.assad" resource-pattern="anno/*.class"/>
</beans>
添加包含或排除规则
可以使用子节点<context:include-filter><context:exclude-filter>进行包含和排除操作,如下:
 
<beans ...>
 <context:component-scan base-package="site.assad" use-deafult-filter="false" > 
        <context:include-filter  type="regex" expression="site\.assad\.anno.*" />   
        <context:exclude-filter type="aspectj" expression="site.assad..*Controller+*" />
    </context:component-scan>
</beans>
use-deafult-filter 属性设置是否开启默认过滤器,在没有设置为false的情况下,会默认扫描@Component、@Repository、@Service、@Controller的Bean,即使他们不在<context:include-filter>匹配的白名单中;
这两个过滤标签支持多种类型的表达式:
typeexpression sample annotationsite.assad.XxxAnnotation  匹配所有标注了XxxAnnotation注解的类,如 site.assad.Repository 匹配 site.assad 包下所有标注了 @Repository 的类;regexsite\.assad\.anno..*使用正则表达时进行匹配,示例中匹配 site.assad.anno 包下的所有文件assignablesite.assad.XxxService匹配所有继承或拓展 XxxService 的类;aspectjsite.assad..*Service+采用AspectJ表达式进行匹配,匹配所有类名以Service结尾的类,及继承和拓展他们的类;customsite.assad.XxxTypeFilter采用 XxxTypeFiler 代码方式实现过滤,该类必须实现 org.springframework.core.type.TypeFilter 接口

自动装配Bean

在代码中使用Bean时,可以通过@Autowired注解自动装配Bean,如下:
 
@Component
public class Article {
    
    //自动注入User
    @Autowired
    private User user;
    .....
}
也可以在方法上进行注解
@Component
public class Article {
    private User user;
    public Article() {
    }
    //在类方法上进行注解
    @Autowired
    public void setUser(User user) {
        this.user = user;
    }
}
使用@Qualifier注解可以指定注入Bean的名称(在存在多个同类型的Bean的情况下):
 
@Component
public class Article {
    
    @Autowired
    @Qualifier("user")
    private User user;
    .....
}
使用@Lazy注解可以实现延迟注入,如下:
 
@Lazy
@Repository
public class logDao(){
}
@Service 
public class LogonService implements BeanNameAware{
    @Lazy 
    @AutoWired
    public void setLogDao(LogDao logDao){ .....}
}

启动容器
启动容器部分的代码如下:
 
 ApplicationContext factory = new ClassPathXmlApplicationContext("site/assad/anno/beans.xml");
//“site/assad/anno/beans.xml” 为自动扫描的配置文件



通过 JavaConfig 类装配


JavaConfig 是Spring 的子项目,可以通过 Java 类的方式提供 Bean 的定义信息 ;
示例代码模块site.assad.conf,site.asad.conf1,site.asad.conf2, site.asad.conf3

使用JavaConfig类提供Bean的定义信息

使用@Configuration注解可以将一个 Java 类标注为 JavaConfig类,在 JavaConfig 类中,每一个标注了@Bean的类方法相当于提供了一个Bean的定义信息;
@Bean可以通过入参显示指定Bean名称,如:Bean(name="userDao");
 
package site.assad.conf1;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import site.assad.conf.LogDao;
import site.assad.conf.LogonService;
import site.assad.conf.UserDao;
@Configuration
public class AppConfig {
    @Bean
    public UserDao userDao(){
        return new UserDao();
    }
    @Bean
    public LogDao logDao(){
        return new LogDao();
    }
    @Bean
    public LogonService logonService(){
        LogonService logonService = new LogonService();
        logonService.setUserDao(userDao());
        logonService.setLogDao(logDao());
        return logonService;
    }
}
可以通过@Import导入另一个配置类的配置信息,将其组合到当前的配置类中:
 
@Configuration
@Import(DaoConfig.class)   //导入DaoConfig的配置信息,将其进行组合
public class ServiceConfig {
    @Autowired   
    private DaoConfig daoConfig;
    @Bean
    public LogonService logonService(){
        LogonService logonService = new LogonService();
        logonService.setLogDao(daoConfig.logDao());
        logonService.setUserDao(daoConfig.userDao());
        return logonService;
    }
}

使用基于JavaConfig类的配置信息启动Spring容器

使用@Configuration标注类为配置类后,启动Spring容器有以下3种方式:

1. 直接通过 @Configuration 类启动 Spring 容器

示例代码模块site.assad.conf/*.class,site.asad.conf1/*.class ; ;site.asad.conf/ConfigTest
启动代码如下:
 
//方式1:通过构造函数加载配置类
AnnotationConfigApplicationContext factory = new AnnotationConfigApplicationContext(AppConfig.class);
//方式2:通过编码的方式注注册配置类
AnnotationConfigApplicationContext factory = new AnnotationConfigApplicationContext();
factory.register(DaoConfig.class);    //注册 DaoConfig
factory.register(ServiceConfig.class);   //注册 ServiceConfig
factory.refresh();                   //刷新上下文

2. 通过 XML 配置文件引用 @Configuration 的配置

示例代码模块site.assad.conf/*.class,site.asad.conf2/*.class ;site.asad.conf/beanConf2.xmlsite.asad.conf/ConfigTest
添加一个扫描包的xml配置文件:
 
<beans ...>
    <!--扫描site.assad.conf2.AppConfig配置类,并初始化其中定义的bean-->
    <context:component-scan base-package="site.assad.conf2" resource-pattern="AppConfig.class" />
</beans>
启动容器的代码:
 
 ApplicationContext factory = new ClassPathXmlApplicationContext("site/assad/conf/beanConf2.xml");

3. 通过 @Configuration 配置类引用 XML 的配置信息

示例代码模块site.assad.conf/*.class,site.asad.conf3/LogonServiceConfigsite.asad.conf/beanConf3.xml site.asad.conf/ConfigTest
xml配置文件信息如下:
 
<beans ...>
    <!--定义2个bean-->
    <bean id="userDao" class="site.assad.conf.UserDao" />
    <bean id="logDao" class="site.assad.conf.LogDao" />
</beans>
其中在配置类添加@ImportReource标注,导入xml配置文件:
 
@Configuration
@ImportResource("classpath:site/assad/conf/beanConf3.xml")  //引入XML配置文件
public class LogonServiceConfig {
    @Bean
    @Autowired
    public LogonService logonService(UserDao userDao, LogDao logDao){
        LogonService logonService = new LogonService();
        logonService.setLogDao(logDao);
        logonService.setUserDao(userDao);
        return logonService;
    }
}



通过 Groovy DSL 装配 


在 Spring 4.0 引入额使用 Groovy DSL 脚本配置bean的方式,并提供了 GenericGroovyApplicationContext 用于启动 Spring 容器;
示例代码模块site.assad.groovy/*.class ;site.asad.groovy/spring-context.groovysite.asad.groovy/GroovyTest

使用 Groovy DSL 提供Bean的定义信息

示例中用于定义bean信息的 groovy脚本 spring-context.groovy 如下:
 
import site.assad.groovy.DbUserDao
import site.assad.groovy.XmlUserDao
import site.assad.groovy.LogDao
import site.assad.groovy.LogonService
import org.springframework.core.io.ClassPathResource
beans {
    //声明 context 命名空间
    xmlns context: "http://www.springframework.org/schema/context"
    //与注解混合使用,定义注解Bean的扫描包
    context.'component-scan'('base-package': "com.smart.groovy") {
        'exclude-filter'('type': "aspectj", 'expression': "com.smart.xml.*")  //排除不需要的包
    }
    //读取 app-conf.properties 配置文件
    def stream
    def config = new Properties()
    try{
        stream = new ClassPathResource('conf/app-conf.properties').inputStream
        config.load(stream)
    }finally{
        if(stream != null)
            stream.close()
    }
    //根据条件注入Bean
    if(config.get('dataProvider') == 'db'){
        userDao(DbUserDao)
    }else{
        userDao(XmlUserDao)
    }
    //配置无参构造函数注入Bean
    logDao(LogDao){
        bean ->
            bean.scope = "prototype"
            bean.initMethod="init"
            bean.destroyMethod="destory"
            bean.lazyInit = true
    }
    //配置由参数构造函数注入Bean,对应 LogonService 以 UserDao作为参数的构造函数
    logonService(LogonService,userDao){
        logDao = ref('logDao')   //配置属性注入,引用Groovy定义的Bean
    }
}

使用 GenericGroovyApplicationContext 启动Spring容器

 
//加载 Groovy Bean 配置文件
ApplicationContext cxt = new GenericGroovyApplicationContext("classpath:site/assad/groovy/spring-context.groovy");



4 种装配方式的适用场景


  • 基于XML的配置方式
① Bean 实现类来源于第三方类库,如:DataSource,JdbcTemplate等(无法在类中使用注解标注);
② 命名空间的配置,如aop,context等,只能采用基于xml的配置;
  • 基于注解的配置方式
Bean 的实现类是当前项目开发的,可以直接在 Java 类中使用基于注解的配置;
  • 基于JavaConfig类的配置方式
优势在于可以通过代码的方法控制 Bean 初始化的整体逻辑,如果实例化 Bean 比较复杂,使用 JavaConfig 会比较方便;
  • 基于Groovy DSL的配置方式
优势在于可以同构 groovy 脚本灵活地控制 Bean 初始化的过程;

一般项目开发采用地比较多地是“xml+注解”的方式,能用注解的地方尽量使用注解,以减少代码冗余;