SSM整合04:注解及整合

197 阅读13分钟

Spring 注解编程

1. 注解基础概念

2. Spring 基础注解(Spring 2.x)

使注解生效

配置 applicationContext.xml:让 Spring 框架在设置包及其⼦包中扫描对应的注解,使其⽣效。

<context:component-scan base-package="com.hihanying"/>

2.1 对象相关注解

@Component 注解

作用:替换原有 Spring 配置⽂件中的 bean 标签

import org.springframework.stereotype.Component;
@Component("u")
public class User {...}

细节:

  • 通过反射的方式获取 class 属性
  • 默认 id 属性是类名的首字母小写,如果想要指定 id ,在注解后面标上新 id, 例如:@Component(“u”)
  • Spring 配置⽂件中配置 bean 标签可以覆盖注解配置内容,但 id 和 class 的值应该和注解的配置一致

@Component的衍⽣注解

本质:衍⽣注解就是 @Component 注解,作用与 @Component 一致

目的:更加准确的表达⼀个类型的作⽤

类型:

  • @Repository:主要应用在 XxxDAO
  • @Service:主要应用在 XxxService
  • @Controller:主要应用在 XxxController

注意:Spring 整合 Mybatis 开发过程中不使⽤ @Repository @Component,因为 不需要创建 DAO 对象,由 Spring 提供

@Scope注解

作⽤:控制简单对象创建次数,等效于 bean 标签中的 scope 属性

import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;
@Component
@Scope("prototype")
public class Customer {
}

属性值:

  • singleton:表示对象随工厂创建而创建,且只创建一次,每次 getBean 拿到的是同一个对象
  • prototype:表示 对象随 getBean 调用而创建,且每次创建的对象都是新的

注意:

  • 不添加 @Scope,Spring 提供默认值 singleton

@Lazy注解

作⽤:延迟创建单实例对象,Spring 会在 getBean 对象时,进⾏这个对象的创建,等效于 bean 标签设置 lazy=“false”

import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Component;
@Component
@Lazy
public class Account {
    public Account() {
        System.out.println("Account.Account");
    }
}

生命周期相关注解

  • @PostConstruct:标记初始化方法,用于替换配置文件中 <bean init-method=""/>
  • @PreDestroy:标记销毁方法,用于替换配置文件中 <bean destory-method=""/>
package com.hihanying.life;

import org.springframework.stereotype.Component;

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

@Component
public class Product {
    @PostConstruct
    public void myInit() {
        System.out.println("Product.myInit");
    }
    @PreDestroy
    public void myDestroy() {
        System.out.println("Product.myDestroy");
    }
}

注意:

  1. 想要测试销毁方法,打印出"Product.myDestroy",需要在测试方法中使用 ClassPathXmlApplicationContext 接受工厂,并使用 close 方法关闭工厂

    @Test
    public void test1() {
        ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
        ctx.close();
    }
    
  2. JDK 8 以上的 JDK 使用了新的 module 系统,javax.annotation 默认不可见,因此 @PostConstruct 和 @PreDestroy 的方法并不执行

2.2 注入相关注解

自定义类型的注入

@Autowired(重点)

  • @Autowired 默认基于类型进行注入,注入对象类型必须与被注入对象的成员变量类型相同或是其子类(实现类),推荐使用。
  • @Autowired 基于名字进行注入,需要引入 @Qualifier 注解,并指定注入对象的 id 值。

位置:

  • 将 @Autowired 放置在对应成员变量的 set ⽅法上
  • 将 @Autowired 放置在对应成员变量上,Spring 通过反射直接对成员变量进⾏注⼊(推荐)

测试代码:当工厂创建 UserServiceImpl 时,会调用 setUserDAO 注入 UserDAO 对象

package com.hihanying.injection;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Service;
@Service
public class UserServiceImpl implements UserService {
    private UserDAO userDAO;
    public UserDAO getUserDAO() {
        return userDAO;
    }
    @Autowired
    @Qualifier("userDAOImpl")
    public void setUserDAO(UserDAO userDAO) {
        System.out.println("UserServiceImpl.setUserDAO");
        this.userDAO = userDAO;
    }
    @Override
    public void register() {
        userDAO.save();
    }
}

@Resouce

由 JSR250 提供

  • 默认基于名字进⾏注⼊,如果未指定 name 属性,则按照类型进⾏注⼊。

    @Resouce(name="userDAOImpl")

  • 放置在对应成员变量上

@Inject

由 JSR330 提供,应用在 EJB3.0,功能与 @Autowired 完全⼀致,基于类型进行注入

需要引入依赖

<dependency>
    <groupId>javax.inject</groupId>
    <artifactId>javax.inject</artifactId>
    <version>1</version>
</dependency>

JDK 类型注入

@Value

使用:

  1. 配置 properties 文件,使用键值对保存需要注入的 JDK 类型的成员变量的名字和要注入的值

  2. 配置 applicationContext.xml 文件,让 Spring 的⼯⼚读取这个配置⽂件

    <context:property-placeholder location=""/>
    
  3. 在对应的 JDK 类型成员变量上加上:@Value("${key}")

测试:

  • com.hihanying.injection.Category

    package com.hihanying.injection;
    import org.springframework.beans.factory.annotation.Value;
    import org.springframework.stereotype.Component;
    @Component
    public class Category {
        @Value("${id}")
        private Integer id;
        @Value("${name}")
        private String name;
        public String getName() {        return name;    }
        public void setName(String name) {        this.name = name;    }
        public Integer getId() {        return id;    }
        public void setId(Integer id) {        this.id = id;    }
    }
    
  • src\main\resources\init.properties

    id = 10
    name =  suns
    
  • src\main\resources\applicationContext.xml

    <context:property-placeholder location="classpath:init.properties"/>
    
  • 测试方法

    @Test
    public void test2() {
        ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
        Category category = (Category) ctx.getBean("category");
        System.out.println("category.getId() = " + category.getId());
        System.out.println("category.getName() = " + category.getName());
    }
    

细节:

  1. @Value注解不能应⽤在静态(static)成员变量上,如果应⽤,赋值(注⼊)失败
  2. @Value注解+Properties 配置文件这种⽅式,不能注⼊集合类型
    • Spring 提供新的配置形式 YAML YML (SpringBoot)

@PropertySource

作用:替换上述第 2 步的 applicationContext.xml 文件配置

代码:

@Component
@PropertySource("classpath:init.properties")
public class Category {
    @Value("${id}")
    private Integer id;
    @Value("${name}")
    private String name;
 	// Getter and Setter   
}

2.3 注解扫描详解

在 applicationContext.xml 文件中配置,表示扫描当前包及其子包:

<context:component-scan base-package="com.hihanying"/>

如果想表示更加复杂的包路径呢?有两种方式。

1. 排除方式

使用子标签 context:exclude-filte 进行排除设置

<context:component-scan base-package="com.hihanying">
    <context:exclude-filter type="" expression=""/>
</context:component-scan>

其中 type 可以设置以下的值:

  • assignable:表示排除特定的类型,不进⾏扫描

    <context:exclude-filter type="assignable" expression="com.hihanying.bean.User"/>
    
  • annotation:排除特定的注解,不进⾏扫描

    <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Service"/>
    

    注意:如果设置为 Component,会连同 Service,Repository 等相关注解一并排除

  • aspectj:排除切⼊点表达式所包含的包或者类,只能用包切⼊点和类切入点表达式

    • 包切⼊点:

      <context:exclude-filter type="aspectj" expression="com.hihanying.lazy..*"/>
      
    • 类切⼊点:

      <context:exclude-filter type="aspectj" expression="*..Product"/>
      

    注意:需要引入 aspectjweaver 依赖

    <dependency>
      <groupId>org.aspectj</groupId>
      <artifactId>aspectjweaver</artifactId>
      <version>1.9.6</version>
    </dependency>
    
  • regex(了解):正则表达式

  • custom(了解):⾃定义排除策略,主要应用在框架底层开发

注意:排除策略可以叠加使⽤

<context:component-scan base-package="com.hihanying">
    <context:exclude-filter type="assignable" expression="com.hihanying.bean.User"/>
    <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Service"/>
    <context:exclude-filter type="aspectj" expression="com.hihanying.lazy..*"/>
</context:component-scan>

测试:

@Test
public void test1() {
    ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
    String[] beanDefinitionNames = ctx.getBeanDefinitionNames();
    for (String beanDefinitionName : beanDefinitionNames) {
        System.out.println("beanDefinitionName = " + beanDefinitionName);
    }
}

2. 包含方式

使用子标签 context:include-filte 进行排除设置

<context:component-scan base-package="com.hihanying" use-defaultfilters="false">
    <context:include-filter type="" expression=""/>
</context:component-scan>
  • use-defaultfilters="false":表示让 Spring 的默认注解扫描⽅式(扫描 base-package 及其子包)失效。

  • type 和 expression 使用方式和上面讲的一样。

  • 包含的⽅式同样⽀持叠加

2.4 对于注解开发的思考

  1. 配置互通:Spring注解配置和 xml 配置⽂件的配置是互通的
  2. 什么情况下使⽤注解?什么情况下使⽤配置⽂件?
    • 程序员开发的类可以加⼊对应注解来进⾏对象的创建,例如上面的 User、UserService、UserDAO、 UserAction
    • 应⽤其他⾮程序员开发的类型时,还是需要使⽤ bean 标签进⾏配置,例如:SqlSessionFactoryBean

2.5 SSM整合开发(半注解开发)

搭建开发环境

1. 引入依赖

<dependencies>
  <!--使用切入点表达式需要引用 aspectjweaver 依赖-->
  <dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjweaver</artifactId>
    <version>1.9.6</version>
  </dependency>
  <!--@PostConstruct的方法并不执行,原因是jdk8以上的jdk使用了新的module系统,javax.annotation默认不可见。-->
  <dependency>
    <groupId>javax.annotation</groupId>
    <artifactId>javax.annotation-api</artifactId>
    <version>1.3.2</version>
  </dependency>
  <!--Spring 整合 Struts2-->
  <dependency>
    <groupId>javax.servlet</groupId>
    <artifactId>javax.servlet-api</artifactId>
    <version>3.1.0</version>
    <scope>provided</scope>
  </dependency>
  <dependency>
    <groupId>javax.servlet</groupId>
    <artifactId>jstl</artifactId>
    <version>1.2</version>
  </dependency>
  <dependency>
    <groupId>javax.servlet.jsp</groupId>
    <artifactId>javax.servlet.jsp-api</artifactId>
    <version>2.3.3</version>
    <scope>provided</scope>
  </dependency>
  <dependency>
    <groupId>org.apache.struts</groupId>
    <artifactId>struts2-spring-plugin</artifactId>
    <version>2.5.25</version>
  </dependency>
  <!--整合 MyBatis-->
  <dependency>
    <groupId>org.mybatis</groupId>
    <artifactId>mybatis</artifactId>
    <version>3.5.5</version>
  </dependency>
  <dependency>
    <groupId>org.mybatis</groupId>
    <artifactId>mybatis-spring</artifactId>
    <version>2.0.5</version>
  </dependency>
  <dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>5.1.49</version>
  </dependency>
  <dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid</artifactId>
    <version>1.1.13</version>
  </dependency>
  <!--Spring 组件-->
  <dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-tx</artifactId>
    <version>5.2.8.RELEASE</version>
  </dependency>
  <dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-web</artifactId>
    <version>5.2.8.RELEASE</version>
  </dependency>
  <dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-jdbc</artifactId>
    <version>5.2.8.RELEASE</version>
  </dependency>
  <dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-core</artifactId>
    <version>5.2.8.RELEASE</version>
  </dependency>
  <dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-beans</artifactId>
    <version>5.2.8.RELEASE</version>
  </dependency>
  <dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-aop</artifactId>
    <version>5.2.8.RELEASE</version>
  </dependency>
  <dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
    <version>5.2.8.RELEASE</version>
  </dependency>
  <!--整合 日志-->
  <dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-log4j12</artifactId>
    <version>1.7.25</version>
  </dependency>
  <dependency>
    <groupId>org.apache.logging.log4j</groupId>
    <artifactId>log4j-to-slf4j</artifactId>
    <version>2.13.3</version>
  </dependency>
  <dependency>
    <groupId>log4j</groupId>
    <artifactId>log4j</artifactId>
    <version>1.2.17</version>
  </dependency>
  <!--测试-->
  <dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>4.12</version>
    <scope>test</scope>
  </dependency>
</dependencies>

2. 引入相关配置文件

src\main\resources\applicationContext.xml

<?xml version="1.0" encoding="UTF-8"?>
<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">

</beans>

src\main\resources\log4j.properties

###### 配置根
log4j.rootLogger = debug,console
###### ?志输出到控制台显示
log4j.appender.console=org.apache.log4j.ConsoleAppender
log4j.appender.console.Target=System.out
log4j.appender.console.layout=org.apache.log4j.PatternLayout
log4j.appender.console.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss} %-5p %c{1}:%L - %m%n

src\main\resources\struts.xml

<?xml version="1.0" encoding="UTF-8"?>

<!DOCTYPE struts PUBLIC
        "-//Apache Software Foundation//DTD Struts Configuration 2.5//EN"
        "http://struts.apache.org/dtds/struts-2.5.dtd">
<struts>
</struts>

xxxMapper.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="">

</mapper>

3. 初始化配置

src\main\webapp\WEB-INF\web.xml

<!DOCTYPE web-app PUBLIC
        "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
        "http://java.sun.com/dtd/web-app_2_3.dtd" >

<web-app version="2.4"
         xmlns="http://java.sun.com/xml/ns/j2ee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee
                             http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">
    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>
    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>classpath:applicationContext.xml</param-value>
    </context-param>

    <filter>
        <filter-name>struts2</filter-name>
        <filter-class>org.apache.struts2.dispatcher.filter.StrutsPrepareAndExecuteFilter</filter-class>
    </filter>
    <filter-mapping>
        <filter-name>struts2</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>

</web-app>

代码编写

1. 结合 MyBatis 开发 DAO 层

在 applicationContext.xml 中配置数据源:使用 alibaba 提供的 druid 连接池

<!--配置数据源信息,通过 druid 提供-->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
    <property name="driverClassName" value="com.mysql.jdbc.Driver"/>
    <property name="url" value="jdbc:mysql://localhost:3306/javaee"/>
    <property name="username" value="root"/>
    <property name="password" value="root"/>
</bean>

在 applicationContext.xml 中配置 SqlSessionFactoryBean,用于创建 SqlSessionFactory

<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
    <property name="dataSource" ref="dataSource"/>
    <property name="typeAliasesPackage" value="com.hihanying.entity"/>
    <property name="mapperLocations">
        <list>
            <value>classpath:com.hihanying.mapper/*Mapper.xml</value>
        </list>
    </property>
</bean>

在 applicationContext.xml 中配置 MapperScannerConfigurer,在其中从指定的 basePackage 的目录递归搜索接口并生成映射器。

<bean id="configure" class="org.mybatis.spring.mapper.MapperScannerConfigurer">
    <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>
    <property name="basePackage" value="com.hihanying.dao"/>
</bean>

编写实体类

package com.hihanying.entity;
public class User {
    private Integer id;
    private String name;
    private String password;

编写 DAO 接口

package com.hihanying.dao;
import com.hihanying.entity.User;
public interface UserDAO {
    public void save(User user);
}

编写接口映射文件:src\main\resources\com.hihanying.mapper\UserDAOMapper.xml

<mapper namespace="com.hihanying.dao.UserDAO">
    <insert id="save" parameterType="User">
        insert into user (name, password) values (#{name}, #{password})
    </insert>
</mapper>

注意:(#{name}, #{password}) 里面是大括号

2. 使用 Spring AOP 为 Service 添加事务

编写业务接口:

package com.hihanying.service;
import com.hihanying.entity.User;
public interface UserService {
    public void register(User user);
}

编写业务类(目标对象)

  • 使用 @Service 创建 UserServiceImpl Bean,使用 @Autowired 为其注入 userDAO
  • 使用 @Transactional 注解为 UserServiceImpl 添加事务
package com.hihanying.service;
import com.hihanying.dao.UserDAO;
import com.hihanying.entity.User;
@Transactional
@Service
public class UserServiceImpl implements UserService {
    @Autowired
    private UserDAO userDAO;
    public UserDAO getUserDAO() { return userDAO; }
    public void setUserDAO(UserDAO userDAO) { this.userDAO = userDAO; }
    @Override
    public void register(User user) {
        userDAO.save(user);
    }
}

在 applicationContext.xml 中配置

  • 开启注解扫描,路径为 base-package 包及其子包
  • 配置 DataSourceTransactionManager 进行事务管理,为其注入 dataSource(注意是 ref)
  • 通过注解的方式组装切面
<context:component-scan base-package="com.hihanying"></context:component-scan>
<bean id="dataSourceTransactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <property name="dataSource" ref="dataSource"/>
</bean>
<tx:annotation-driven transaction-manager="dataSourceTransactionManager"/>

3. Controller 层开发 (使用Spring+Struts2)

编写注册控制器 RegAction:

  • 使用 @Controller 注解创建 RegAction Bean
  • 使用 @Scope("prototype") 注解配置 RegAction Bean 可以创建多次
  • 使用 @Autowired 为 RegAction Bean 注入 UserService 类对象
package com.hihanying.action;
import com.hihanying.entity.User;
import com.hihanying.service.UserService;
import com.opensymphony.xwork2.Action;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Controller;
@Controller
@Scope("prototype")
public class RegAction implements Action {
    private User user;
    @Autowired
    private UserService userService;
    public User getUser() {        return user;    }
    public void setUser(User user) {        this.user = user;    }
    public UserService getUserService() {        return userService;    }
    public void setUserService(UserService userService) {
        this.userService = userService;
    }
    @Override
    public String execute() throws Exception {
        userService.register(user);
        return Action.SUCCESS;
    }
}

编写 struts2 配置文件:struts.xml

<struts>
    <package name="ssm" extends="struts-default">
        <action name="reg" class="regAction">	<!-- 默认 id -->
            <result name="success">/regOK.jsp</result> <!--结果视图-->
        </action>
    </package>
</struts>

4. 编写网页

编写注册页面:src\main\webapp\reg.jsp

<%@ page contentType="text/html;charset=UTF-8" pageEncoding="utf-8" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<html>
    <head>
        <title>注册</title>
    </head>
    <body>
        <form method="post" action="${pageContext.request.contextPath}/reg.action">
            UserName<input type="text" name="user.name"/><br/>
            PassWord<input type="password" name="user.password"/><br/>
            <input type="submit" value="reg">
        </form>
    </body>
</html>

编写注册成功跳转页面:src\main\webapp\regOK.jsp

<%@ page contentType="text/html;charset=UTF-8" pageEncoding="utf-8" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<html>
    <head>
        <title>成功</title>
    </head>
    <body>
        <h1>regOK</h1>
    </body>
</html>

3. Spring 高级注解

3.1 logback 日志整合

基于注解开发不能集成 Log4j,推荐使用 logback

引入相关依赖

<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-api</artifactId>
    <version>1.7.30</version>
</dependency>
<dependency>
    <groupId>org.apache.logging.log4j</groupId>
    <artifactId>log4j-to-slf4j</artifactId>
    <version>2.13.3</version>
</dependency>
<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>jcl-over-slf4j</artifactId>
    <version>1.5.6</version>
</dependency>
<dependency>
    <groupId>ch.qos.logback</groupId>
    <artifactId>logback-classic</artifactId>
    <version>1.2.3</version>
</dependency>
<dependency>
    <groupId>ch.qos.logback</groupId>
    <artifactId>logback-core</artifactId>
    <version>1.2.3</version>
</dependency>
<dependency>
    <groupId>org.logback-extensions</groupId>
    <artifactId>logback-ext-spring</artifactId>
    <version>0.1.5</version>
</dependency>

引入配置文件:logback.xml

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
    <!-- 控制台输出 -->
    <appender name="STDOUT"
              class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <!--格式化输出:%d表示⽇期 %thread表示线程名 %-5level级别从左显示5个字符宽度 %msg⽇志消息 %n是换⾏符-->
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
        </encoder>
    </appender>
    <root level="DEBUG">
        <appender-ref ref="STDOUT" />
    </root>
</configuration>

3.2 @Configuration

Spring在3.x提供的 @Configuration 注解,⽤于替换 applicationContext.xml 配置⽂件。

创建配置 Bean

package com.hihanying;
import org.springframework.context.annotation.Configuration;
@Configuration
public class AppConfig {
}

创建 ApplicationContext 工厂

ApplicationContext ctx = new AnnotationConfigApplicationContext(AppConfig.class);//指定配置bean的Class对象
ApplicationContext ctx = new AnnotationConfigApplicationContext("com.hihanying");//指定配置bean所在的路径

日志输出:

Creating shared instance of singleton bean 'appConfig'

注意:@Configuration 是 @Component 注解的衍⽣注解

3.3 @Bean

@Bean 注解在配置 Bean (AppConfig )中进⾏使⽤,等同于原来 applicationContext.xml 配置⽂件中的 bean 标签

创建 Bean

在配置 Bean 中使用 @Bean 注解标记方法,表示创建这个方法返回值类型的 Bean

  • 默认方法名就是 Bean 的 id,可以使用 @Bean("id") 指定 id
  • 控制对象创建次数使用 @Scope("prototype"),默认是singleton
package com.hihanying;
import com.hihanying.bean.User;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
@Configuration
public class AppConfig {
    @Bean
    public User user() {
        return new User();
    }
    @Bean
    public Connection conn() {
        Connection conn = null;
        try {
            Class.forName("com.mysql.jdbc.Driver");
            conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/javaee?userSSL=false",
                    "root", "root");
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (SQLException throwables) {
            throwables.printStackTrace();
        }
        return conn;
    }
}
  • 简单对象:通过 new 创建对象返回。
  • 复杂对象:将创建对象的代码卸载方法体中,使用 return 返回创建的复杂对象。

针对复杂对象,Spring 建议使用实现 FactoryBean 的方式进行创建,那如何与 @Bean 注解整合在一起呢?

实现 FactoryBean 的方式进行创建:

package com.hihanying.bean;
import org.springframework.beans.factory.FactoryBean;
import java.sql.Connection;
import java.sql.DriverManager;
public class ConnectionFactoryBean implements FactoryBean {
    @Override
    public Object getObject() throws Exception {
        Class.forName("com.mysql.jdbc.Driver");
        Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/javaee?userSSL=false", "root", "root");
        return conn;
    }
    @Override
    public Class<?> getObjectType() {
        return Connection.class;
    }
    @Override
    public boolean isSingleton() {
        return false;
    }
}

使用 @Bean 注解整合到配置 Bean 中

@Bean
public Connection conn() {
    Connection conn = null;
    ConnectionFactoryBean factoryBean = new ConnectionFactoryBean();
    try {
        conn = (Connection) factoryBean.getObject();
    } catch (Exception e) {
        e.printStackTrace();
    }
    return conn;
}

主要应用于遗留系统整合。

注入 Bean

  1. 注入⽤户⾃定义类型

    xml 方式:通过 ref 引用自定义 Bean 的 id 实现注入

    <bean id="userDAO" class="com.hihanying.bean.UserDAOImpl"></bean>
    <bean id="userService" class="com.hihanying.bean.UserServiceImpl">
        <property name="userDAO" ref="userDAO"/>
    </bean>
    

    注解的方式:通过方法的参数传递给目标 Bean 的 set 方法实现注入

    @Bean
    public UserDAO userDAO() {
        return new UserDAOImpl();
    }
    @Bean
    public UserService userService(UserDAO userDAO) {
        UserServiceImpl userService = new UserServiceImpl();
        userService.setUserDAO(userDAO);
        return userService;
    }
    

    注解的方式:不通过形参的简化写法

    @Bean
    public UserService userService() {
        UserServiceImpl userService = new UserServiceImpl();
        userService.setUserDAO(userDAO());
        return userService;
    }
    

    通过日志可以发现创建 userService Bean 的同时将 userDAO Bean 注入到 userService 中。

    Creating shared instance of singleton bean 'userDAO'
    Creating shared instance of singleton bean 'userService'
    Autowiring by type from bean name 'userService' via factory method to bean named 'userDAO'
    
  2. 注入 JDK 类型

    可以通过 set 方法直接进行注入

    @Bean
    public User user() {
        User user = new User();
        user.setId(1111);
        user.setName("han");
        return user;
    }
    

    如果直接在代码中进⾏ set ⽅法的调⽤,会存在耦合的问题,如何解决呢?

    可以通过 @PropertySource 注解引入配置文件 init.properties 来解决

    • src\main\resources\init.properties

      id = 2222
      name = suns
      
    • com.hihanying.AppConfig

      @Configuration
      @PropertySource("classpath:/init.properties")
      public class AppConfig {
          @Value("${id}")
          private Integer id;
          @Value("${name}")
          private String name;
          @Bean
          public User user() {
              User user = new User();
              user.setId(id);
              user.setName(name);
              return user;
          }
      }
      

3.4 @ComponentScan

@ComponentScan 注解等同于 applicationContext.xml 配置⽂件中的 context:component-scan 标签。

1. 基本使用

  • 位置:配置 Bean 上,进行相关注解的扫描。

  • 属性:basePackages 表示扫描注解的包及其子包

  • 代码:

    在 com.hihanying.scan 包中创建 Scan 类,加上 @Component 注解

    编写配置 Bean,加上 @ComponentScan 注解,指定 basePackages = "com.hihanying.scan"

    @Configuration
    @ComponentScan(basePackages = "com.hihanying.scan")
    public class AppConfig2 {
    }
    

    测试,可以得到创建的 Bean 的 id

    @Test
    public void test2() {
        ApplicationContext ctx = new AnnotationConfigApplicationContext(AppConfig2.class);
        String[] beanDefinitionNames = ctx.getBeanDefinitionNames();
        for (String beanDefinitionName : beanDefinitionNames) {
            System.out.println("beanDefinitionName = " + beanDefinitionName);
        }
    }
    

2. 排除和包含的使用

  • 排除:通过设置 @ComponentScan 注解的 excludeFilters 属性,该属性是一个列表,表示可以设置多种排除策略同时生效

    @Configuration
    @ComponentScan(basePackages = "com.hihanying.scan", excludeFilters = {
            @ComponentScan.Filter(type = FilterType.ANNOTATION, value = {Service.class}),
            @ComponentScan.Filter(type = FilterType.ASPECTJ, pattern = "*..Scan2"),
    })
    public class AppConfig2 {
    }
    
    • @ComponentScan.Filter 的 type 属性:表示过滤器的类型,是一个枚举 FilterType,其中有5种类型,分别是:

      ANNOTATION、ASSIGNABLE_TYPE、ASPECTJ、REGEX、CUSTOM

    • @ComponentScan.Filter 的 type 属性:表示某种滤波器的表达式形式,分别对应 value、value、pattern、pattern、value

  • 包含:通过设置 @ComponentScan 注解的 useDefaultFilters 属性为 false,并设置 excludeFilters 属性来指定包含策略

    @Configuration
    @ComponentScan(basePackages = "com.hihanying.scan", useDefaultFilters = false, includeFilters = {
            @ComponentScan.Filter(type = FilterType.ANNOTATION, value = {Service.class})
    })
    public class AppConfig2 {
    }
    
    • @ComponentScan.Filter 使用与上述排除策略一致。

3.5 创建 Bean 的多种⽅式

1. 多种配置⽅式的应⽤场景

目前创建 Bean 的方式包括:@Component 及其衍生注解、配置 Bean 中的 @Bean 注解、配置文件的 bean 标签。应用场景:

  • @Component 及其衍生注解:应用于程序员自己开发的类上,例如:Service、DAO、Controller
  • @Bean 注解:应用于框架提供的类以及其他程序员开发的类,或者说没有源码的类,例如:SqlSessionFactoryBean
  • bean 标签:主要用于遗留系统的整合
  • @Import 注解:用于配置 Bean 上,导入类的 class 对象可以创建 对应的 Bean。应用场景:
    • 一般用于 Spring 框架的底层
    • 多配置 Bean 的整合

2. 配置优先级

优先级关系:@Component及其衍⽣注解 < @Bean 注解 < 配置⽂件 bean 标签

作用:优先级⾼的配置会覆盖优先级低的配置

注意:对应的 id 值必须保持⼀致

应用:注解配置方式的解耦

对于配置 Bean 中使用 @Bean 注解创建的 Bean,如果想更换实现方式,可用通过配置文件 bean 标签的方式进行覆盖。

  • 原配置 Bean:最初的配置

    @Configuration
    public class AppConfig4 {
        @Bean
        public UserDAO userDAO() {
            return new UserDAOImpl();
        }
    }
    
  • 新配置 bean:不需要修改原来的 配置 Bean ,只需要新写一个配置 Bean 用于引入配置文件

    <bean id="userDAO" class="com.hihanying.injection.UserDAOImplNew"/>
    
    @Configuration
    @ImportResource("applicationContext.xml")
    public class AppConfig5{
    }
    
  • 测试:两种实现方式

    ApplicationContext ctx = new AnnotationConfigApplicationContext(AppConfig4.class, AppConfig4.class);
    ApplicationContext ctx = new AnnotationConfigApplicationContext(“com.hihanying”);
    

3.6 整合多个配置信息

将配置 Bean 按照功能拆分多个配置 Bean 的开发方式,体现了模块化开发的思想,也体现了⾯向对象各司其职的设计思想。

此外,除了配置 Bean之外,还包括 applicationContext.xml 配置⽂件等配置信息。因此,多配置信息整合的方式主要有:

  • 整合多个功能不同的配置 Bean。
  • 整合配置 Bean 与 @Component 及其衍生注解创建的 Bean
  • 整合配置 Bean 与 Spring 的 XML 配置⽂件,主要用于覆盖配置、整合遗留系统

整合多种配置需要关注的要点包括:

  • 如何使多配置的信息 汇总成⼀个整体
  • 如何实现跨配置的注⼊

1. 整合多个配置 Bean

我们在 com.hihanying 中创建一个新的包 config ,并在包中创建两个配置 Bean,分别创建两个不同的 Bean

package com.hihanying.config;
import com.hihanying.injection.UserDAO;
import com.hihanying.injection.UserDAOImpl;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class AppConfig1 {
    @Bean
    public UserDAO userDAO() {
        return new UserDAOImpl();
    }
}
package com.hihanying.config;
import com.hihanying.injection.UserService;
import com.hihanying.injection.UserServiceImpl;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class AppConfig2 {
    @Bean
    public UserService userService() {
        UserServiceImpl userService = new UserServiceImpl();
        return userService;
    }
}

可以在创建 ApplicationContext 时引入 base-package 参数,表示通过包导入配置 Bean.

 ApplicationContext context = new AnnotationConfigApplicationContext("com.hihanying.config");

也可以在创建 ApplicationContext 时指定多个配置 Bean 的 Class 对象(了解)。

 ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig1.class, AppConfig2.class);

还可以在第 1 个配置 Bean 上通过 @Import 注解引入第 2 个配置 Bean 的 class 对象。

@Configuration
@Import(AppConfig2.class)
public class AppConfig1 {
    @Bean
    public UserDAO userDAO() {
        return new UserDAOImpl();
    }
}
 ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig1.class);

注意:在应⽤配置Bean的过程中,不管使⽤哪种⽅式进⾏配置信息的汇总,都可以通过在成员变量上加⼊**@Autowired** 注解完成。

@Configuration
@Import(AppConfig1.class)
public class AppConfig2 {
    @Autowired
    private UserDAO userDAO;
    @Bean
    public UserService userService() {
        UserServiceImpl userService = new UserServiceImpl();
        userService.setUserDAO(userDAO);
        return userService;
    }
}

如果在 AppConfig2 上通过 @Import 注解引入了 AppConfig1.class,就不能在 AppConfig1 上再通过 @Import 注解引入 AppConfig2.class,否则会报错

2. 整合配置 Bean 与 @Component 及其衍生注解创建的 Bean

我们为 com.hihanying.injection.UserDAOImpl 类加上 @Repository 注解来为其创建 Bean

package com.hihanying.injection;
import org.springframework.stereotype.Repository;
@Repository
public class UserDAOImpl implements UserDAO {
    @Override
    public void save() {
        System.out.println("UserDAOImpl.save");
    }
}

如果配置 Bean 中需要使用该 Bean,我们可以通过为配置 Bean 加上 @ComponentScan("com.hihanying.injection") 注解,通过设置扫描位置的方式引入该 Bean

@Configuration
@ComponentScan("com.hihanying.injection")
public class AppConfig2 {
    @Autowired
    private UserDAO userDAO;
    @Bean
    public UserService userService() {
        UserServiceImpl userService = new UserServiceImpl();
        userService.setUserDAO(userDAO);
        return userService;
    }
}

3. 整合配置 Bean 与 Spring 的 XML 配置⽂件

我们在 applicationContext.xml 中为 com.hihanying.injection.UserDAOImpl 创建 Bean

<bean id="userDAO" class="com.hihanying.injection.UserDAOImpl"/>

如果配置 Bean 中需要使用该 Bean,我们可以通过为配置 Bean 加上 @ImportResource("applicationContext.xml") 注解,为其引入配置文件即可,尽管此时 userDAO 会报错(IDEA无法联想到配置文件中创建的 Bean),但不影响执行。

@Configuration
@ImportResource("applicationContext.xml")
public class AppConfig2 {
    @Autowired
    private UserDAO userDAO;
    @Bean
    public UserService userService() {
        UserServiceImpl userService = new UserServiceImpl();
        userService.setUserDAO(userDAO);
        return userService;
    }
}

3.7 配置Bean底层实现原理

当我们为配置 Bean 加上 @Configuration 注解后,底层就会通过 Cglib 的代理⽅式,来进⾏对象相关的配置、处理、创建,我们得到的是代理对象,而不是我们在配置 Bean 中所直接 new 出的对象。因此,可以在代理对象中加入一些额外的功能,比如说控制创建对象的次数。

image-20201021175632456

3.8 纯注解 AOP 编程

开发步骤

  1. 创建目标类

    com.hihanying.aop.UserService

    package com.hihanying.aop;
    public interface UserService {
        public void login();
        public void register();
    }
    

    com.hihanying.aop.UserServiceImpl

    package com.hihanying.aop;
    import org.springframework.stereotype.Service;
    @Service("userService")							// 使用 @Service 注解创建 userService Bean
    public class UserServiceImpl implements UserService {
        @Override
        public void login() {
            System.out.println("UserServiceImpl.login");
        }
        @Override
        public void register() {
            System.out.println("UserServiceImpl.register");
        }
    }
    
  2. 创建切面类:com.hihanying.aop.MyAspect

    package com.hihanying.aop;
    import org.aspectj.lang.ProceedingJoinPoint;
    import org.aspectj.lang.annotation.Around;
    import org.aspectj.lang.annotation.Aspect;
    import org.aspectj.lang.annotation.Pointcut;
    import org.springframework.stereotype.Component;
    @Aspect							// 使用 @Aspect 注解组装切面
    @Component						// 使用 @Component 注解创建 myAspect Bean
    public class MyAspect {
        @Pointcut("execution(* com.hihanying.aop..*.*(..))")	// 使用 @Pointcut 注解指定切入点
        public void pointCut(){}
        @Around("pointCut()")									// 使用 @Around 注解添加附加功能
        public Object arround(ProceedingJoinPoint joinPoint) throws Throwable {
            System.out.println("---------Log---------");		// 附加功能
            Object proceed = joinPoint.proceed();				// 使用参数传递的方式注入 pointCut Bean
            return proceed;
        }
    }
    
  3. 创建配置类:com.hihanying.aop.AppConfig

    package com.hihanying.aop;
    import org.springframework.context.annotation.ComponentScan;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.context.annotation.EnableAspectJAutoProxy;
    @Configuration						// 使用 @Configuration 注解指定配置 Bean
    @ComponentScan("com.hihanying.aop") // 使用 @ComponentScan 注解指定扫描的包路径
    @EnableAspectJAutoProxy				// 使用 @EnableAspectJAutoProxy 注解替换<aop:aspectj-autoproxy />
    public class AppConfig {
    }
    
  4. 创建测试类:com.hihanying.TestConfig

    package com.hihanying;
    import com.hihanying.aop.UserService;
    import org.junit.Test;
    import org.springframework.context.ApplicationContext;
    import org.springframework.context.annotation.AnnotationConfigApplicationContext;
    public class TestConfig {
        @Test
        public void test () {
            ApplicationContext context = new AnnotationConfigApplicationContext("com.hihanying.aop");
            UserService userService = (UserService) context.getBean("userService");
            userService.login();
            userService.register();
        }
    }
    
  5. 运行产生的日志和输出:

    Creating shared instance of singleton bean 'appConfig'
    Found AspectJ method: public java.lang.Object com.hihanying.aop.MyAspect.arround(org.aspectj.lang.ProceedingJoinPoint) throws java.lang.Throwable
    Creating shared instance of singleton bean 'myAspect'
    Creating shared instance of singleton bean 'userService'
    ---------Log---------
    UserServiceImpl.login
    ---------Log---------
    UserServiceImpl.register
    

注解AOP细节分析

  1. 如何切换为 Cglib 代理创建方式,默认是 JDK 方式
    • xml 形式开发中:<aop:aspectj-autoproxy proxy-target-class=true/>
    • 注解开发中:将 @EnableAspectjAutoProxy(proxyTargetClass = true) 加在配置 Bean上
  2. 在 SpringBoot AOP 的开发⽅式中,默认代理实现是 Cglib,且不需要使用 @EnableAspectjAutoProxy 注解,其他的同上

3.9 纯注解 Spring+MyBatis

Spring 与 MyBatis 整合开发步骤如下:

  1. 基础配置:连接池、SqlSessionFactoryBean、MapperScannerConfigure
  2. 代码开发:创建实体及其在数据库中对应的表、创建DAO接口、创建 Mapper 文件

1. 基础配置

xml方式

<!-- 创建 连接池 对象-->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
    <property name="driverClassName" value="com.mysql.jdbc.Driver"/>
    <property name="url" value="jdbc:mysql://localhost:3306/javaee"/>
    <property name="username" value="root"/>
    <property name="password" value="root"/>
</bean>
<!-- sqlSqlSessionFactoryBean 注入,创建 sqlSqlSessionFactory 对象-->
<bean id="sqlSessionFactoryBean" class="org.mybatis.spring.SqlSessionFactoryBean">
    <property name="dataSource" ref="dataSource"/>
    <property name="typeAliasesPackage" value="com.hihanying.mybatis.origin.entity"/>
    <property name="mapperLocations">
        <list>
            <value>classpath:com.hihanying.mybatis.origin.dao/*Mapper.xml</value>
        </list>
    </property>
</bean>
<!-- MapperScannerConfigure 注入,创建 DAO 对象-->
<bean id="scanner" class="org.mybatis.spring.mapper.MapperScannerConfigurer">
    <property name="sqlSessionFactoryBeanName" value="sqlSessionFactoryBean"/>
    <property name="basePackage" value="com.hihanying.mybatis.origin.dao"/>
</bean>

注解方式

package com.hihanying.mybatis;
import com.alibaba.druid.pool.DruidDataSource;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.core.io.support.ResourcePatternResolver;
import javax.sql.DataSource;
import java.io.IOException;
@Configuration
@ComponentScan(basePackages = "com.hihanying.mybatis")
@MapperScan(basePackages = "com.hihanying.mybatis")
public class MyBatisAutoConfiguration {
    @Bean
    public DruidDataSource dataSource() {
        DruidDataSource dataSource = new DruidDataSource();
        dataSource.setDriverClassName("com.mysql.jdbc.Driver");
        dataSource.setUrl("jdbc:mysql://localhost:3306/javaee?useSSL=false");
        dataSource.setUsername("root");
        dataSource.setPassword("root");
        return dataSource;
    }
    @Bean
    public SqlSessionFactoryBean sqlSessionFactoryBean(DataSource dataSource) {
        SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
        sqlSessionFactoryBean.setDataSource(dataSource);
        sqlSessionFactoryBean.setTypeAliasesPackage("com.hihanying.mybatis");
        try {
            ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
            Resource[] resources = resolver.getResources("com.hihanying.mapper/*Mapper.xml");
            sqlSessionFactoryBean.setMapperLocations(resources);
        } catch (IOException e) {
            e.printStackTrace();
        }
        return sqlSessionFactoryBean;
    }
}

2. 代码开发

创建实体类:com.hihanying.mybatis.User

package com.hihanying.mybatis;
public class User {
    private Integer id;
    private String name;
    private String password;

创建DAO接口:com.hihanying.mybatis.UserDAO

package com.hihanying.mybatis;
public interface UserDAO {
    public void save(User user);
}

编写 Mapper 文件:com.hihanying.mapper.UserDAOMapper.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.hihanying.mybatis.UserDAO">
    <insert id="save" parameterType="User">
        insert into user (name, password) values (#{name}, #{password})
    </insert>
</mapper>

测试:

@Test
public void test1 () {
    ApplicationContext context = new AnnotationConfigApplicationContext(MyBatisAutoConfiguration.class);
    UserDAO userDAO = (UserDAO) context.getBean("userDAO");
    User user = new User();
    user.setName("suns");
    user.setPassword("suns");
    userDAO.save(user);
}

日志:

DEBUG o.s.b.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'myBatisAutoConfiguration'
DEBUG o.s.b.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'dataSource'
DEBUG o.s.b.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'sqlSessionFactoryBean'
DEBUG o.s.b.factory.support.DefaultListableBeanFactory - Autowiring by type from bean name 'sqlSessionFactoryBean' via factory method to bean named 'dataSource'
DEBUG org.mybatis.spring.SqlSessionFactoryBean - Property 'configuration' or 'configLocation' not specified, using default MyBatis Configuration
DEBUG org.mybatis.spring.SqlSessionFactoryBean - Parsed mapper file: 'class path resource [UserDAOMapper.xml]'
DEBUG o.s.b.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'userDAO'
DEBUG org.mybatis.spring.SqlSessionUtils - Creating a new SqlSession
DEBUG org.mybatis.spring.SqlSessionUtils - SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@26d10f2e] was not registered for synchronization because synchronization is not active
DEBUG o.springframework.jdbc.datasource.DataSourceUtils - Fetching JDBC Connection from DataSource
INFO  com.alibaba.druid.pool.DruidDataSource - {dataSource-1} inited
DEBUG o.m.spring.transaction.SpringManagedTransaction - JDBC Connection [com.mysql.jdbc.JDBC4Connection@411291e5] will not be managed by Spring
DEBUG com.hihanying.mybatis.UserDAO.save - ==>  Preparing: insert into user (name, password) values (?, ?)
DEBUG com.hihanying.mybatis.UserDAO.save - ==> Parameters: suns(String), suns(String)
DEBUG com.hihanying.mybatis.UserDAO.save - <==    Updates: 1
DEBUG org.mybatis.spring.SqlSessionUtils - Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@26d10f2e]

3. 细节分析:配置Bean数据耦合的问题

创建配置文件:src\main\resources\mybatis.properties

mybatis.driverClassName = com.mysql.jdbc.Driver
mybatis.url = jdbc:mysql://localhost:3306/javaee?useSSL=false
mybatis.username = root
mybatis.password = root
mybatis.typeAliasesPackages = com.hihanying.mybatis
mybatis.mapperLocations = com.hihanying.mapper/*Mapper.xml

创建对应的配置文件类:com.hihanying.mybatis.MyBatisProperties

package com.hihanying.mybatis;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.PropertySource;
import org.springframework.stereotype.Component;
@Component
@PropertySource("classpath:mybatis.properties")
public class MyBatisProperties {
    @Value("${mybatis.driverClassName}")
    private String driverClassName;
    @Value("${mybatis.url}")
    private String url;
    @Value("${mybatis.username}")
    private String username;
    @Value("${mybatis.password}")
    private String password;
    @Value("${mybatis.typeAliasesPackages}")
    private String typeAliasesPackages;
    @Value("${mybatis.mapperLocations}")
    private String mapperLocations;
	// Getter and Setter method
}

在配置 Bean 中创建配置文件对象并注入:

package com.hihanying.mybatis;
import com.alibaba.druid.pool.DruidDataSource;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.core.io.support.ResourcePatternResolver;
import javax.sql.DataSource;
import java.io.IOException;
@Configuration
@ComponentScan(basePackages = "com.hihanying.mybatis")
@MapperScan(basePackages = "com.hihanying.mybatis")
public class MyBatisAutoConfiguration {
    @Autowired		// 使用 @Autowired 注解注入
    private MyBatisProperties myBatisProperties;
    @Bean
    public DruidDataSource dataSource() {
        DruidDataSource dataSource = new DruidDataSource();
        dataSource.setDriverClassName(myBatisProperties.getDriverClassName());
        dataSource.setUrl(myBatisProperties.getUrl());
        dataSource.setUsername(myBatisProperties.getUsername());
        dataSource.setPassword(myBatisProperties.getPassword());
        return dataSource;
    }
    @Bean
    public SqlSessionFactoryBean sqlSessionFactoryBean(DataSource dataSource) {
        SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
        sqlSessionFactoryBean.setDataSource(dataSource);
        sqlSessionFactoryBean.setTypeAliasesPackage(myBatisProperties.getTypeAliasesPackages());
        try {
            ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
            Resource[] resources = resolver.getResources(myBatisProperties.getMapperLocations());
            sqlSessionFactoryBean.setMapperLocations(resources);
        } catch (IOException e) {
            e.printStackTrace();
        }
        return sqlSessionFactoryBean;
    }
}

3.10 纯注解事务编程

1. 创建目标类并加入事务

com.hihanying.mybatis.UserService

package com.hihanying.mybatis;
public interface UserService {
    public void register(User user);
}

com.hihanying.mybatis.UserServiceImpl

package com.hihanying.mybatis;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
@Transactional	// 使用 @Transactional 注解为目标类 UserServiceImpl 加入事务
public class UserServiceImpl implements UserService {
    @Autowired	// 使用 @Autowired 注入 userDAO Bean
    private UserDAO userDAO;
    public UserDAO getUserDAO() {        return userDAO;    }
    public void setUserDAO(UserDAO userDAO) {        this.userDAO = userDAO;    }
    @Override
    public void register(User user) {
        userDAO.save(user);
    }
}

2. 创建事务配置 Bean

com.hihanying.mybatis.TransactionAutoConfiguration

package com.hihanying.mybatis;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import javax.sql.DataSource;
@Configuration
// 不需要使用 @ComponentScan 注解进行扫描,因为在之前的 MyBatisAutoConfiguration 配置 Bean 中已经扫描
public class TransactionAutoConfiguration {
    @Autowired	// 使用 @Autowired 注解注入已经在 MyBatisAutoConfiguration 创建的 dataSource Bean
    private DataSource dataSource;
    @Bean
    public DataSourceTransactionManager dataSourceTransactionManager() {
        DataSourceTransactionManager dstm = new DataSourceTransactionManager();
        dstm.setDataSource(dataSource);
        return dstm;
    }
}

3. 基于Schema的事务配置

在 MyBatisAutoConfiguration 配置 Bean 上加 @EnableTransactionManagement 注解,开始事务管理

4. 测试

@Test
public void test () {
    // 注意通过包路径导入配置 Bean,这是 Spring Boot 的实现思想
    ApplicationContext context = new AnnotationConfigApplicationContext("com.hihanying.mybatis");
    // 注意返回值的类型是 UserService,id 是 userServiceImpl
    UserService userServiceImpl = (UserService) context.getBean("userServiceImpl");
    User user = new User();
    user.setName("suns");
    user.setPassword("suns");
    userServiceImpl.register(user);
}

4. Spring 框架中 YML 的使⽤

4.1 概述

image-20201021233114206

Properties 进⾏配置的问题

  1. Properties 表达过于繁琐,⽆法表达数据的内在联系.
  2. Properties ⽆法表达集合类型的对象

YML语法简介

  1. 定义yml⽂件:xxx.ymlxxx.yaml
  2. 语法
    • 基本语法:通过冒号表示键值对

      name: suns
      password: 12345
      
    • 通过缩进表达对象概念

      account:
          id: 1
          password: 123456
      
    • 表示 list 集合

      service:
      	- 11111
      	- 22222
      

4.2 Spring与YML集成编码

1. 环境搭建

<dependency>
    <groupId>org.yaml</groupId>
    <artifactId>snakeyaml</artifactId>
    <version>1.26</version>
</dependency>

2. 准备 yml 配置⽂件

src\main\resources\mybatis.yml

mybatis:
    driverClassName : "com.mysql.jdbc.Driver"
    url : "jdbc:mysql://localhost:3306/javaee?useSSL=false"
    username : root
    password : root
    typeAliasesPackages : "com.hihanying.mybatis"
    mapperLocations : "com.hihanying.mapper/*Mapper.xml"

3. 创建读取 YML 文件的配置 Bean

com.hihanying.mybatis.YmlAutoConfiguration

package com.hihanying.mybatis;

import org.springframework.beans.factory.config.YamlPropertiesFactoryBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.support.PropertySourcesPlaceholderConfigurer;
import org.springframework.core.io.ClassPathResource;

import java.util.Properties;

@Configuration
public class YmlAutoConfiguration {
    @Bean
    public PropertySourcesPlaceholderConfigurer configurer() {
        YamlPropertiesFactoryBean yamlPropertiesFactoryBean = new YamlPropertiesFactoryBean();
        yamlPropertiesFactoryBean.setResources(new ClassPathResource("mybatis.yml"));
        Properties properties = yamlPropertiesFactoryBean.getObject();
        PropertySourcesPlaceholderConfigurer configurer = new PropertySourcesPlaceholderConfigurer();
        configurer.setProperties(properties);
        return configurer;
    }
}

4. 创建对应的属性文件类,并通过@Value注入相关属性

com.hihanying.mybatis.MyBatisProperties

package com.hihanying.mybatis;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.PropertySource;
import org.springframework.stereotype.Component;
@Component
public class MyBatisProperties {
    @Value("${mybatis.driverClassName}")
    private String driverClassName;
    @Value("${mybatis.url}")
    private String url;
    @Value("${mybatis.username}")
    private String username;
    @Value("${mybatis.password}")
    private String password;
    @Value("${mybatis.typeAliasesPackages}")
    private String typeAliasesPackages;
    @Value("${mybatis.mapperLocations}")
    private String mapperLocations;
	// Getter and Setter method
}

5. 在主配置 Bean 中注入 MyBatisProperties

4.3 Spring与YML集成问题

  1. 集合处理的问题:SpringEL 表达式解决 @Value("#{'${list}'.split(',')}")

  2. 对象类型的YAML进⾏配置时过于繁琐,使用时必须带上对象名 @Value("${account.name}")

解决:SpringBoot 的 @ConfigurationProperties 注解

end