XML与注解结合的方式

需要注意

  • 实际企业开发中,纯 XML 模式已经很少使用了
  • 引入注解功能,不需要引入额外的 Jar
  • XML+注解结合模式,XML 文件依然存在,所以,Spring IoC 容器仍然是从加载 XML 开始
  • 哪些 Bean 定义需要写到 XML 中,哪些 Bean 的定义使用注解

简单来说:第三方的 Jar 中的 Bean 定义在 XML 中,自己开发的 Bean 使用注解的方式。


XML中标签与注解的对应

XML 标签注解
<bean>@Component@Repository@Service@Controller
<property>@Value
<constructor-arg>构造器注入

DI 依赖注入的注解实现方式

推荐使用 @Autowired,该注解是 Spring 提供的注解,采取的策略为按照类型注入

public class WzkTransferServiceImpl {
    @Autowired
    private WzkAccountDao wzkAccountDao;
}

如上代码所示,这样装配回去 Spring 容器中找到类型为 AccountDao 的类,然后将其注入进来。这样会产生一个问题,当一个类型有多个 Bean 的值的时候,会造成无法选择具体注入哪一个的情况,这个时候我们需要配合着使用 @Qualifier 注解。

@Qualifier 会告诉 Spring 用哪个对象来注入:

public class WzkTransferServiceImpl {
    @Autowired
    @Qualifier(name="jdbcWzkAccountDaoImpl")
    private WzkAccountDao wzkAccountDao;
}

当然,我们也可以使用 JDK 提供的 @Resource 注解,该注解默认按照 byName 自动注入:

public class WzkTransferService {

    @Resource
    private AccountDao accountDao;

    @Resource(name="studentDao")
    private StudentDao studentDao;

    @Resource(type="TeacherDao")
    private TeacherDao teacherDao;

    @Resource(name="manDao",type="ManDao")
    private ManDao manDao;

}

需要注意

  • 如果同时指定了 name 和 type,则从 Spring 上下文中寻找唯一匹配的 Bean 进行装配
  • 如果指定了 name,则从上下文中查找名称(id)匹配的 bean 进行装配,找不到则抛出异常
  • 如果指定了 type,则从上下文找到类型匹配的唯一 bean 进行装配,找不到或是找到多个,都会抛出异常
  • 如果既没有指定 name,又没有指定 type,则自动按照 byName 方式进行装配

重要:Resource 在 JDK11 中已经移除了,如果要使用的话,需要单独引入 Jar 包:

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

applicationContext.xml 配置

写入内容如下:

<?xml version="1.0" encoding="UTF-8"?>
<beans  xmlns="http://www.springframework.org/schema/beans"
        xmlns:context="http://www.springframework.org/schema/context"
        xmlns:aop="http://www.springframework.org/schema/aop"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="
        http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        https://www.springframework.org/schema/context/spring-context.xsd
        http://www.springframework.org/schema/aop
        https://www.springframework.org/schema/aop/spring-aop.xsd
">

</beans>

web.xml 配置

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
         version="3.1">
    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>classpath:applicationContext.xml</param-value>
    </context-param>
    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>
</web-app>

配置改造

接下来我们将刚才的纯 XML 的模式的代码,改造成 注解 + XML 的方式,解放出一定的 XML 来。

比如在纯 XML 模式下,我们写的:

<!-- 配置 Bean -->
<bean id="wzkTransferService" class="wzk.service.impl.WzkTransferServiceImpl">
    <constructor-arg name="wzkAccountDao" ref="wzkAccountDao"></constructor-arg>
</bean>

这个目的是让 Spring 接管我们的 Bean,而此时,我们可以将这一系列的 Bean 配置都移除出去,使用注解的方式来满足我们的 Bean 管理。

此时,我们将 Bean 的 XML 配置移除,并在需要交由 Spring 管理的 Bean 上加入注解、加入 DI 的管理。但是此时,Spring 是不知道哪些对象要处理的,所以我们需要告诉 Spring 哪些需要进行扫描:

<context:annotation-config/>
<context:component-scan base-package="wzk 代码改造示例

### JdbcW"/>

##zkAccountDaoImpl

这里加入注解和 ID 的管理方式,部分如下:

@Component
public class JdbcWzkAccountDaoImpl implements WzkAccountDao {

    @Autowired
    private WzkConnectionUtils wzkConnectionUtils;

}

WzkTransferServiceImpl

@Component
public class WzkTransferServiceImpl implements WzkTransferService {

    @Autowired
    private WzkAccountDao wzkAccountDao;

}

WzkServlet(重要!!!)

这里需要注意,按理来说,我们应该在 WzkServlet 上也写 @Component@Autowired 来注入我们的 WzkTransferService,但是实际上呢,@Component 和 @Servlet 注解是冲突的

  • @WebServlet 是 Servlet 的注解,交由给 Tomcat 管理
  • @Component 是 Spring 的注解,交由给 Spring 管理

此时要求我们不能在这里使用 Component 注解,我们需要用 WebApplicationContext 来进行处理:

@WebServlet(name="wzkServlet", urlPatterns = "/wzkServlet")
public class WzkServlet extends HttpServlet {

    private WzkTransferService wzkTransferService;

    @Override
    public void init() throws ServletException {
        ApplicationContext context = WebApplicationContextUtils
                .getWebApplicationContext(getServletContext());
        this.wzkTransferService = context.getBean(WzkTransferService.class);
    }

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp)
            throws ServletException, IOException {
        try {
            wzkTransferService.transfer("1", "2", 100);
        } catch (Exception e) {
            e.printStackTrace();
            System.out.println("=== transfer error ====");
        }
        resp.setContentType("application/json;charset=utf-8");
        resp.getWriter().print("=== WzkServlet doGet ===");
    }
}

工具类

比如用到的一些工具,我们有依赖关系,所以也交给 Spring 进行管理:

@Component
public class WzkConnectionUtils {

    private final ThreadLocal<Connection> threadLocal = new ThreadLocal<>();

}

总结

通过 XML 与注解结合的方式,我们可以:

  1. 减少 XML 配置:将自定义的 Bean 交给注解管理
  2. 保留 XML 配置:用于第三方 Bean 的管理
  3. 组件扫描:使用 <context:component-scan> 指定要扫描的包
  4. Servlet 特殊处理:由于 @Component@WebServlet 冲突,需要使用 WebApplicationContextUtils 手动获取 Bean