Spring学习笔记(三) - 注解

知识分子没文化
2021-11-10 / 0 评论 / 196 阅读 / 4,966 字数 / 正在检测是否收录...

目录:

一、通过注解定义Bean

1.1、启用注解支持

<context:annotation-config />

若要以注解方式注入 Bean,则需要开启扫描包中的组件,并用 base-package 属性指定扫描的基本包路径:

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

使用 <context:annotation-scan> 标签时,默认将隐式启用对注解的支持

1.2、定义Bean

在 XML 配置中如果要将一个 Bean 交给 Spring IoC 容器管理,需要进行如下配置:

<bean id="userService" class="com.wlplove.service.impl.UserServiceImpl" />

若用注解要实现相同目的,只需要在要被管理的 Java 类上添加 @Component 注解,并在后面的括号中指定 Bean 的名称:

// 注解后面的括号里是 bean 的名称
@Component("userServiceImpl")
public class UserServiceImpl implements UserService {
    // ...
}

@Component 注解也可以不指定 Bean 名称,Spring 会自动为该 Bean 生成一个默认的名称,通常是类名的首字母小写形式:

@Component
// 可以用名称“userServiceImpl”来获取这个Bean
public class UserServiceImpl implements UserService {
    // ...
}

所以只需要在 Java 类上加入 @Component 注解,用于标识这个类是 Spring 容器中的组件(即 Bean),就可以实现与 XML 配置中的 <bean> 标签相同的作用。

1.3、@Component的派生注解

此外,Spring 还提供了 @Component 的几个派生注解:@Repository@Service@Controller@Configuration,这几个注解的功能和用法与 @Component 相同,但它们在使用场景上有所不同。

1.3.1、@Repository

用于数据访问层(Dao 层)中与数据库交互的类。

Spring 会对使用 @Repository 注解的类进行特定的异常处理(将数据访问层的异常,如 SQLException 等转换为 Spring 的 DataAccessException 异常,方便统一处理和异常传播)。

@Repository
public interface UserMapper {
    // ...
}
1.3.2、@Service

用于标识业务逻辑层(Service 层)中处理核心业务逻辑的类。

使用 @Service 注解可以将一个类声明为业务逻辑组件,将其对象存入 Spring 容器中,以便在其他组件(如Controller)中通过注入该 service 类的实例来使用其业务逻辑。

@Service
public class UserServiceImpl implements UserService {
    // ...
}
1.3.3、@Controller

用于标识控制层(Controller 层)中处理响应和请求的类。

@Controller 标记的类实际上就是一个 Spring MVC Controller 对象,它可以处理请求,并通过 @RequestMapping 等注解将不同的请求分发到对应的方法上。

@Controller
public class UserController {
    // ...
}
1.3.4、@Configuration

用于标记替换 XML 配置文件的配置类,这个注解的用法将在 1.4 中详细说明。

1.4、用注解代替XML配置文件

Spring 支持完全去除 XML 配置文件实现纯注解开发,实现的方式就是使用一个 Java 类来代替原本的 XML 配置文件。

1.4.1、配置类

创建一个 Java 类,类上面添加 @Configuration 注解标识这是一个配置类:

@Configuration
public class SpringConfig {
    // ...
}

在 XML 配置文件中会用到 <context:annotation-config base-package=""> 标签来设置扫描注解的路径,在配置类上添加 @ComponentScan 注解也可以实现相同的效果:

@Configuration
+@ComponentScan("com.xxx")
public class SpringConfig {
    // ...
}

如果有多个包路径,可以用数组格式:

@ComponentScan({"com.xxx.dao","com.xxx.service"})
1.4.2、初始化IoC容器

当创建出一个配置类之后,初始化 Spring IoC 容器的方式也需要改变。

原来是通过 ClassPathXmlApplicationContext 类加载 XML 配置文件初始化得到 IoC 容器:

ApplicationContext ctx = new ClassPathXmlApplicationContext("spring-config.xml");

现在改用 AnnotationConfigApplicationContext 类来加载配置类得到 Spring IoC 容器:

ApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig.class);

二、通过注解使用Bean

2.1、自动装配

2.1.1、@Autowired

在要引用的依赖上面使用 @Autowired 注解开启自动装配注入该依赖:

@Service
public class BookServiceImpl implements BookService {
    @Autowired
    private BookDao bookDao;

    public void save() {
        System.out.println("book service save ...");
        bookDao.insert();
    }
}

@Autowired 注解默认按照类型(byType)进行自动装配

2.1.2、@Qualifier

如果 Spring IoC 容器中存在多个类型相同的 Bean 时,仅仅使用 @Autowired 根据类型自动装配时就导致 Spring 无法确定应该自动注入哪一个 Bean,便会抛出 NoUniqueBeanDefinitionException 异常。那么此时使用 @Qualifier 注解来指定要装配的 Bean 名称:

@Repository
public class BookDao1 implements BookDao {
    // ...
}

@Repository
public class BookDao2 implements BookDao {
    // ...
}

@Service
public class BookServiceImpl implements BookService {
    @Autowired
    // 存在两个BookDao类型的Bean,这里指定注入名称为bookDao1的Bean
    @Qualifier("bookDao1")
    private BookDao bookDao; 
}

需要注意的是,@Qualifier 不能单独使用,必须与 @Autowired 一起使用。因为 @Autowired 主要是根据类型进行自动装配,而 @Qualifier 则在此基础上匹配 Bean 名称,从而进行更精确的限制。

2.1.3、@Resource

还有一个与 @Autowired 类似的注解是 @Resource默认按照名称(byName)进行自动装配,通过其 name 属性指定要注入的 Bean 名称:

@Service
public class BookServiceImpl implements BookService 

    @Resource(name = "bookDao2")
    private BookDao bookDao;
}

不像 @Autowired 是 Spring 框架提供的用于按类型(byType)自动装配的注解,@Resource 是 JavaEE 标准中(javax.annotation.Resource)提供的用于按名称(byName)自动装配的注解,并非 Spring 框架所特有的。

2.1.4、@Primary

@Qualifier 类似,@Primary 注解也可以用来解决当存在多个相同类型的 Bean 时应该注入哪一个 Bean 的问题。

如果 Spring IoC 容器中存在多个类型相同的 Bean,给该 Bean 上面添加 @Primary 注解,则 Spring 会优先使用标注了 @Primary 的 Bean。

2.1.5、@Value

使用 @Value 注解实现简单类型的注入:

@Service
public class UserServiceImpl implements UserService {
    @Value(30)
    private int age;

    @Value("root")
    private String userName;
}

@Value 注解除了用于给简单类型的成员注入固定的值以外,也可以用于将配置文件中的属性注入给简单类型的成员,配置文件中的属性名写在占位符 ${} 里面:

@Service
public class UserServiceImpl implements UserService {    
    @Value("${user.name}")
    private String userName;
}

2.2、读取properties文件配置

src/main/resources 目录下新建一个配置数据库连接信息的配置文件 jdbc.properties

jdbc.driver=com.mysql.cj.jdbc.Driver
jdbc.url=jdbc:mysql://127.0.0.1:3306/spring_db?useSSL=false&setUnicode=true&characterEncoding=utf8
jdbc.username=root
jdbc.password=root

使用 @PropertySource 注解在 Spring 配置类上加载外部的 properties 文件,在要注入值的成员上添加 @Value 注解指定注入哪个属性:

/**
 * Spring配置类
 */
@Configuration
@ComponentScan({"com.xxx"})
@PropertySource("classpath:jdbc.properties")
public class SpringConfig {
    // ...
}

/**
 * 使用属性
 */
public class DataBaseConnInfo {
    // 注入配置文件中的属性
    @Value("${jdbc.driverName}")
    private String driverName;

    @Value("${jdbc.url}")
    private String url;
}

若要加载多个配置文件应使用数组:

@PropertySource({"classpath:jdbc.properties","classpath:appConfig.properties"})

2.3、作用域

使用注解 @Scope 定义 Bean 的作用域是 prototype(原型/非单例)或者 singleton(单例)。

public class AppConfig {
    @Bean
    // @Scope("prototype")
    @Scope("singleton")
    public MyBean myBean() {
        return new MyBean();
    }
}

2.4、自定义Bean初始化/销毁操作

使用 @PostConstruct 注解定义初始化方法(方法名不限),将在构造方法之后执行该方法。

使用 @PreDestroy 注解定义销毁方法(方法名不限),在容器销毁前执行该方法,一般用来释放占用的资源等。

public class UserServiceImpl implements UserService {
    @PostConstruct
    publid void init() {
        System.out.println("init ....");
    }

    @PreDestroy
    public void destory() {
        System.out.println("destory ...");
    }
}

2.5、总结

05

三、整合框架

3.1、数据源对象(以Druid为例)

导入 Druid 数据源的依赖坐标:

<!-- https://mvnrepository.com/artifact/com.alibaba/druid -->
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid</artifactId>
    <version>1.2.22</version>
</dependency>

新建一个配置数据库连接的类 JdbcConfig,里面引入配置文件中的数据库信息,然后用一个方法构造并返回一个 DataSource 对象,最后用 @Bean 注解设置这个方法的返回值成为 Spring IoC 容器的一个对象:

public class JdbcConfig {
    @Value("${jdbc.driver}")
    private String driver;

    @Value("${jdbc.url}")
    private String url;

    @Value("${jdbc.username}")
    private String username;

    @Value("${jdbc.password}")
    private String password;

    /**
     * 数据源
     * @return
     */
    @Bean
    public DataSource dataSource() {
        DruidDataSource ds = new DruidDataSource();
        ds.setDriverClassName(driver);
        ds.setUrl(url);
        ds.setUsername(username);
        ds.setPassword(password);
        return ds;
    }
}

Spring 配置类中用 Import 注解引入 JdbcConfig 这个类:

@Configuration
@ComponentScan("com.xxx")
+@Import({JdbcConfig.class})
public class SpringConfig {
    // ...
}

3.2、整合Mybatis

导入 MyBatis 的依赖坐标:

<!-- spring-jdbc -->
<!-- https://mvnrepository.com/artifact/org.springframework/spring-jdbc -->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-jdbc</artifactId>
    <version>6.1.4</version>
</dependency>

<!-- mybatis -->
<!-- https://mvnrepository.com/artifact/org.mybatis/mybatis -->
<dependency>
    <groupId>org.mybatis</groupId>
    <artifactId>mybatis</artifactId>
    <version>3.5.16</version>
</dependency>

<!-- mybatis-spring -->
<!-- https://mvnrepository.com/artifact/org.mybatis/mybatis-spring -->
<dependency>
    <groupId>org.mybatis</groupId>
    <artifactId>mybatis-spring</artifactId>
    <version>3.0.3</version>
</dependency>

<!-- mysql -->
<!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>8.0.33</version>
</dependency>

新建一个 MyBatis 配置类 MybatisConfig,配置 SqlSessionFactoryBeanMapperScannerConfigurer 这两个 Bean:

public class MybatisConfig {
    @Bean
    public SqlSessionFactoryBean sqlSessionFactory(DataSource dataSource) {
        SqlSessionFactoryBean ssfb = new SqlSessionFactoryBean();
        ssfb.setDataSource(dataSource);
        ssfb.setTypeAliasesPackage("com.xxx.xxx");
        return ssfb;
    }

    @Bean
    public MapperScannerConfigurer mapperScannerConfigurer() {
        MapperScannerConfigurer msc = new MapperScannerConfigurer();
        // Mapper接口所在的包路径
        msc.setBasePackage("com.xxx.mapper");
        return msc;
    }
}

Spring 配置类中用 Import 注解引入 MybatisConfig 这个类:

@Configuration
@ComponentScan("com.xxx")
+@Import({JdbcConfig.class, MybatisConfig.class})
public class SpringConfig {
    // ...
}

3.3、整合Log4j

导入 Log4j 的依赖坐标:

<!-- https://mvnrepository.com/artifact/org.apache.logging.log4j/log4j-core -->
<dependency>
    <groupId>org.apache.logging.log4j</groupId>
    <artifactId>log4j-core</artifactId>
    <version>2.23.1</version>
</dependency>

"src/main/java/resources" 下新建一个 Log4j 的配置文件 Log4j2.xml

<?xml version="1.0" encoding="UTF-8"?>
<!--  Configuration 具有 Appenders 和 Loggers 这两种子节点,每个子节点可以定义多个 -->
<configuration>

    <!-- Appender节点,具有 Console(控制台)、File(文件)、RoolingFile(滚动文件)这三种类型的子节点 -->
    <Appenders>
        <!-- 输出日志信息到控制台 -->
        <Console name="console" target="SYSTEM_OUT">
            <!--指定控制日志输出的格式-->
            <PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss SSS} [%t] %-3level %logger{1024} - %msg%n"/>
        </Console>

        <!-- File 节点用来定义输出到指定位置的文件的 Appender,会将所有内容写入到同一个文件中 -->
        <!-- append属性设置写入新的日志时是追加在原内容后面,还是清除所有内容之后再写入 -->
<!--        <File name="allLog" fileName="logs/AlliInOne.log" append="true">-->
<!--            <ThresholdFilter level="ALL" onMatch="ACCEPT" onMismatch="DENY"/>-->
<!--            <PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %-5p %c{9.9.9.1}(%L) %m%n"/>-->
<!--        </File>-->

        <!-- RollingFile 节点,将日志写入文件,但是允许日志文件根据时间或大小进行滚动,从而避免单个文件过大 -->
        <!-- fileName 生成的初始日志文件  -->
        <RollingFile name="rollingFileInfo" fileName="logs/${date:yyyy-MM}/log-info-${date:yyyy-MM-dd}.log" filePattern="logs/${date:yyyy-MM}/log-info-%d{yyyy-MM-dd}-%i.log">
            <!-- ThresholdFilter 只输出level及以上级别的信息(onMatch),其他的直接拒绝(onMismatch) -->
            <ThresholdFilter level="DEBUG" onMatch="ACCEPT" onMismatch="DENY"/>
            <!-- PatternLayout 指定控制日志输出的格式,不设置默认为:%m%n -->
            <PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss SSS} [%t] %-3level %logger{1024} - %msg%n"/>
            <!-- Policies 滚动策略 -->
            <Policies>
                <!-- 按时间滚动 -->
                <TimeBasedTriggeringPolicy interval="1" modulate="true"/>
                <!-- 按大小滚动 -->
                <SizeBasedTriggeringPolicy size="10MB"/>
            </Policies>
            <!-- DefaultRolloverStrategy 设置一个文件下保存的日志文件数量,不设置则默认为同一文件夹下7个文件,超过这个数量后,最老的文件将被删除 -->
            <DefaultRolloverStrategy max="20"/>
        </RollingFile>
    </Appenders>

    <!-- 在 Loggers 中引入上面定义好的 Appender -->
    <loggers>
        <!-- level指定日志级别,从低到高的优先级:
                ALL < TRACE < DEBUG < INFO < WARN < ERROR < FATAL < OFF
        -->
        <!-- 设置org.mybatis包下的日志只打印WARN及以上级别 -->
        <Logger name="org.mybatis" level="WARN" additivity="false">
            <appender-ref ref="console"/>
            <appender-ref ref="rollingFileInfo"/>
        </Logger>
        <!-- 设置org.springframework包下的日志只打印WARN及以上级别 -->
        <Logger name="org.springframework" level="WARN" additivity="false">
            <appender-ref ref="console"/>
            <appender-ref ref="rollingFileInfo"/>
        </Logger>

        <root level="DEBUG">
            <appender-ref ref="console"/>
            <appender-ref ref="rollingFileInfo"/>
        </root>
    </loggers>

</configuration>

Log4j 配置文件详解:彻底掌握Log4j2 - 蚂蚁小哥 - 博客园

修改 Mybatis 配置类中的 SqlSessionFactoryBean,设置其 Configuration 属性:

public class MybatisConfig {
    // ...

    @Bean
    public SqlSessionFactoryBean getSqlSessionFactory(DataSource dataSource) {
        SqlSessionFactoryBean ssfb = new SqlSessionFactoryBean();
        ssfb.setTypeAliasesPackage("com.xxx.xxx");
        ssfb.setDataSource(dataSource);
+        Configuration configuration = new Configuration();
+        // 修改MyBatis使用的日志框架为Log4j2
+        configuration.setLogImpl(Log4j2Impl.class);
+        ssfb.setConfiguration(configuration);
        return ssfb;
    }

    // ...
}

3.4、整合Junit

导入 Junit 和 spring-test 的依赖坐标:

<!-- https://mvnrepository.com/artifact/org.junit.jupiter/junit-jupiter-api -->
<dependency>
    <groupId>org.junit.jupiter</groupId>
    <artifactId>junit-jupiter-api</artifactId>
    <version>5.10.2</version>
    <scope>test</scope>
</dependency>

<!-- https://mvnrepository.com/artifact/org.springframework/spring-test -->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-test</artifactId>
    <version>5.2.10.RELEASE</version>
</dependency>

新建一个测试类:

// 在 JUnit 5 中集成 Spring 功能,如果是Junit4则换成@RunWith(SpringJunit4ClassRunner.class)
@ExtendWith(SpringExtension.class)
@ContextConfiguration(classes = SpringConfig.class)
public class SpringTest {

    @Autowired
    private UserMapper userMapper;

    @Test
    public void find() {
        User user = userMapper.select();
    }
}

四、Spring事务

导入 Spring 事务的依赖坐标:

<!-- https://mvnrepository.com/artifact/org.springframework/spring-tx -->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-tx</artifactId>
    <version>5.2.10.RELEASE</version>
</dependency>

在 Spring 的配置类上添加注解 @EnableTransactionManagement 开启 Spring 的事务管理功能:

@Configuration
@ComponentScan("com.xxx")
@Import({JdbcConfig.class, MybatisConfig.class})
+@EnableTransactionManagement
public class SpringConfig {
    // ...
}

配置类中添加 PlatformTransactionManager 这个 Bean:

@Bean
public PlatformTransactionManager getTransactionManager(DataSource dataSource) {
    DataSourceTransactionManager dstm = new DataSourceTransactionManager();
    dstm.setDataSource(dataSource);
    return dstm;
}

最后在 service 方法或类上面用 @Transactional 注解设置添加事务管理,在执行该方法或类中的方法时便会开启事务管理,如果程序执行时发生异常就回滚之前的数据库操作。

需要注意的是,当在同一类中调用有 @Transactional 注解的方法时,可能不会触发事务管理,因为它绕过了 Spring 的代理机制,解决办法是将这个方法也标记为 @Transactional

1

评论 (0)

取消