Spring注解

一、基于注解的IoC

1. 快速入门

需求描述
  • 有dao层:UserDaoUserDaoImpl
  • 有service层:UserServiceUserServiceImpl
  • 使用注解配置bean,并注入依赖
需求分析
  1. 准备工作:创建Maven项目,导入依赖坐标

  2. 编写代码并注解配置:

    编写dao层、service层代码,使用注解@Component配置bean:代替xml里的bean标签

    使用注解@Autowired依赖注入:代替xml里的propertyconstructor-arg标签

  3. 在配置文件中开启组件扫描

  4. 测试

需求实现
1) 准备工作
  • 创建Maven项目,导入依赖坐标
<dependencies>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context</artifactId>
        <version>5.0.2.RELEASE</version>
    </dependency>
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.12</version>
    </dependency>
</dependencies>

2) 编写代码,并注解配置
  • UserDao接口
public interface UserDao {
    void save();
}

  • UserDaoImpl实现类
@Component("userDao")//等价于@Repository("userDao")
public class UserDaoImpl implements UserDao {
    @Override
    public void save() {
        System.out.println("save......");
    }
}

  • UserService接口
public interface UserService {
    void save();
}

  • UserServiceImpl实现类
@Component("userService")//等价于@Service("userService")
public class UserServiceImpl implements UserService {

    //注意:使用注解配置依赖注入时,不需要再有set方法
    @Autowired
    //@Qualifier("userDao")
    private UserDao userDao;

    @Override
    public void save() {
        userDao.save();
    }
}

3) 开启组件扫描(扫描注解)
  • 创建applicationContext.xml,注意引入的context名称空间
<?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: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">

    <!--开启组件扫描-->
    <context:component-scan base-package="com.itheima"/>
</beans>

4) 功能测试
  • 创建一个测试类,调用Service
public class UserTest {

    @Test
    public void save(){
        ApplicationContext app = new ClassPathXmlApplicationContext("applicationContext.xml");
        UserService userService = (UserService) app.getBean("userService");
        userService.save();
    }
}

步骤小结
  1. 导入jar包:spring-context

  2. 编写service和dao

    • UserServiceImpl要注册bean,就在类上加注解@Component
      • 里边有一个依赖项要注入:直接在依赖项成员变量上加@Autowired
    • UserDaoImpl要注册bean,就在类上加注解@Component
  3. 在xml里开启组件扫描

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

2. 注解使用详解

开启组件扫描
  • 在Spring中,如果要使用注解开发,就需要在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"
       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">

    <!-- 注意:必须要引入context名称空间之后,才可以使用这个标签 -->
    <!-- 开启组件扫描,让Spring容器扫描com.itheima包下的注解 -->
    <context:component-scan base-package="com.itheima"/>
    
</beans>

声明bean的注解
简介
注解说明
@Component用在类上,相当于bean标签
@Controller用在web层类上,配置一个bean(是@Component的衍生注解)
@Service用在service层类上,配置一个bean(是@Component的衍生注解)
@Repository用在dao层类上,配置一个bean(是@Component的衍生注解)
  • @Component:类级别的一个注解,用于声明一个bean,使用不多
    • value属性:bean的唯一标识。如果不配置,默认以首字母小写的类名为id
  • @Controller, @Service, @Repository,作用和@Component完全一样,但更加的语义化,使用更多
    • @Controller:用于web层的bean
    • @Service:用于Service层的bean
    • @Repository:用于dao层的bean
示例
  • UserDaoImpl类上使用注解@Repository
@Repository("userDao")
public class UserDaoImpl implements UserDao{
}

  • UserServiceImpl类上使用注解@Service
@Service("userService")
public class UserServiceImpl implements UserService{
}

  • UserController类上使用注解@Controller
@Controller
public class UserController{}

配置bean的注解
注解说明
@Scope相当于bean标签的scope属性
@PostConstruct相当于bean标签的init-method属性
@PreDestroy相当于bean标签的destory-method属性
配置bean的作用范围:
  • @Scope:配置bean的作用范围,相当于bean标签的scope属性。加在bean对象上

  • @Scope的常用值有:

  • singleton:单例的,容器中只有一个该bean对象

    • 何时创建:容器初始化时
    • 何时销毁:容器关闭时
  • prototype:多例的,每次获取该bean时,都会创建一个bean对象

    • 何时创建:获取bean对象时
    • 何时销毁:长时间不使用,垃圾回收
@Scope("prototype")
@Service("userService")
public class UserServiceImpl implements UserService{
    //...
}

配置bean生命周期的方法
  • @PostConstruct方法级别的注解,用于指定bean的初始化方法
  • @PreDestroy是方法级别的注解,用于指定bean的销毁方法
@Service("userService")
public class UserServiceImpl implements UserService {

    @PostConstruct
    public void init(){
        System.out.println("UserServiceImpl对象已经创建了......");
    }
    
    @PreDestroy
    public void destroy(){
        System.out.println("UserServiceImpl对象将要销毁了......");
    }
 
    //......
}

依赖注入的注解
注解说明
@Autowired相当于property标签的ref
@Qualifier结合@Autowired使用,用于根据名称注入依赖
@Resource相当于@Autowired + @Qualifier
@Value相当于property标签的value
注入bean对象
  • @Autowired:用于byType注入bean对象,按照依赖的类型,从Spring容器中查找要注入的bean对象
    • 如果找到一个,直接注入
    • 如果找到多个,则以变量名为id,查找bean对象并注入
    • 如果找不到,抛异常
  • @Qualifier:是按id注入,但需要和@Autowired配合使用。
  • @Resource:(是jdk提供的)用于注入bean对象(byName注入),相当于@Autowired + @Qualifier

绝大多数情况下,只要使用@Autowired注解注入即可

使用注解注入时,不需要set方法了

注入普通值
  • @Value:注入简单类型值,例如:基本数据类型和String
@Service("userService")
public class UserServiceImpl implements UserService{
    
    @Value("zhangsan")//直接注入字符串值
    private String name;
    
    //从properties文件中找到key的值,注入进来
    //注意:必须在applicationContext.xml中加载了properties文件才可以使用
    @Value("${properties中的key}")
    private String abc;
    
    //...
}

二、注解方式CURD练习

需求描述

  • 使用注解开发帐号信息的CURD功能

需求分析

  • 使用注解代替某些XML配置,能够代替的有:
    • dao层bean对象,可以在类上增加注解@Repository
    • service层bean对象,可以在类上增加注解@Service
    • Service层依赖于dao层,可以使用注解注入依赖@AutoWired
  • 不能使用注解代替,仍然要使用XML配置的的有:
    • QueryRunner的bean对象,是DBUtils工具包里提供的类,我们不能给它的源码上增加注解
    • 连接池的bean对象,是c3p0工具包里提供的类,我们不能修改源码增加注解

需求实现

JavaBean
public class Account {
    private Integer id;
    private String name;
    private Double money;

    //get/set...
    //toString...
}

dao层代码
  • AccountDao接口
public interface AccountDao {
    Account findById(Integer id) throws SQLException;

    List<Account> queryAll() throws SQLException;

    void save(Account account) throws SQLException;

    void edit(Account account) throws SQLException;

    void delete(Integer id) throws SQLException;
}

  • AccountDaoImpl实现类
@Repository("accountDao")
public class AccountDaoImpl implements AccountDao {
    
    @Autowired
    private QueryRunner runner;
    
    @Override
    public Account findById(Integer id) throws SQLException {
        return runner.query("select * from account where id = ?", new BeanHandler<>(Account.class), id);
    }

    @Override
    public List<Account> queryAll() throws SQLException {
        return runner.query("select * from account", new BeanListHandler<>(Account.class));
    }

    @Override
    public void save(Account account) throws SQLException {
        runner.update("insert into account(id,name,money) values (?,?,?)", account.getId(), account.getName(), account.getMoney());
    }

    @Override
    public void edit(Account account) throws SQLException {
        runner.update("update account set name = ?, money = ? where id = ?", account.getName(), account.getMoney(), account.getId());
    }

    @Override
    public void delete(Integer id) throws SQLException {
        runner.update("delete from account where id = ?", id);
    }
}

service层代码
  • AccountService接口
public interface AccountService {
    Account findById(Integer id) throws SQLException;

    List<Account> queryAll() throws SQLException;

    void save(Account account) throws SQLException;

    void edit(Account account) throws SQLException;

    void delete(Integer id) throws SQLException;
}

  • AccountServiceImpl接口
@Service("accountService")
public class AccountServiceImpl implements AccountService {

    @Autowired
    private AccountDao accountDao;

    @Override
    public Account findById(Integer id) throws SQLException {
        return accountDao.findById(id);
    }

    @Override
    public List<Account> queryAll() throws SQLException {
        return accountDao.queryAll();
    }

    @Override
    public void save(Account account) throws SQLException {
        accountDao.save(account);
    }

    @Override
    public void edit(Account account) throws SQLException {
        accountDao.edit(account);
    }

    @Override
    public void delete(Integer id) throws SQLException {
        accountDao.delete(id);
    }
}

提供配置
  • 创建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"
       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">

    <!--开启注解扫描-->
    <context:component-scan base-package="com.itheima"/>

    <!--配置QueryRunner-->
    <bean id="runner" class="org.apache.commons.dbutils.QueryRunner">
        <constructor-arg name="ds" ref="dataSource"/>
    </bean>
    <!--配置连接池-->
    <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
        <property name="driverClass" value="com.mysql.jdbc.Driver"/>
        <property name="jdbcUrl" value="jdbc:mysql:///spring"/>
        <property name="user" value="root"/>
        <property name="password" value="root"/>
    </bean>
</beans>

功能测试
public class AccountTest {

    @Test
    public void queryAll() throws SQLException {
        ApplicationContext app = new ClassPathXmlApplicationContext("classpath:applicationContext.xml");
        AccountService accountService = app.getBean("accountService", AccountService.class);
        List<Account> accounts = accountService.queryAll();
        for (Account account : accounts) {
            System.out.println(account);
        }
    }

    @Test
    public void findById() throws SQLException {
        ApplicationContext app = new ClassPathXmlApplicationContext("classpath:applicationContext.xml");
        AccountService accountService = app.getBean("accountService", AccountService.class);
        Account account = accountService.findById(2);
        System.out.println(account);
    }

    @Test
    public void save() throws SQLException {
        ApplicationContext app = new ClassPathXmlApplicationContext("classpath:applicationContext.xml");
        AccountService accountService = app.getBean("accountService", AccountService.class);

        Account account = new Account();
        account.setName("jerry");
        account.setMoney(2000d);
        accountService.save(account);
    }

    @Test
    public void edit() throws SQLException {
        ApplicationContext app = new ClassPathXmlApplicationContext("classpath:applicationContext.xml");
        AccountService accountService = app.getBean("accountService", AccountService.class);

        Account account = new Account();
        account.setId(2);
        account.setName("tom");
        account.setMoney(10000d);

        accountService.edit(account);
    }

    @Test
    public void delete() throws SQLException {
        ApplicationContext app = new ClassPathXmlApplicationContext("classpath:applicationContext.xml");
        AccountService accountService = app.getBean("accountService", AccountService.class);

        accountService.delete(3);
    }
}

三、纯注解开发IoC

​在上边的CURD练习中,仍然有部分配置需要使用applicationContext.xml,那么能不能使用注解替换掉所有的xml呢?Spring提供了一些新注解,可以达到这个目标。

请注意:Spring提供的这部分新注解,并非为了完全代替掉XML,只是提供了另外一种配置方案

注解简介

注解说明
@Configuration被此注解标记的类,是配置类
@ComponentScan用在配置类上,开启注解扫描。使用basePackage属性指定扫描的包
@PropertySource用在配置类上,加载properties文件。使用value属性指定properties文件路径
@Import用在配置类上,引入子配置类。用value属性指定子配置类的Class
@Bean用在配置类的方法上,把返回值声明为一个bean。用name/value属性指定bean的id

注解详解

1 @Configuration配置类
简介
  • @Configuration把一个Java类声明为核心配置类
    • 加上Java类上,这个Java类就成为了Spring的核心配置类,用于代替applicationContext.xml
    • @Component的衍生注解,所以:核心配置类也是bean,里边可以注入依赖
示例
/**
 * 配置类,代替XML配置文件
 * Configuration注解:把当前类声明成为一个配置类
 */
@Configuration
public class AppConfig {
}

2 配置类上的注解
简介
  • @ComponentScan配置组件注解扫描
    • basePackages或者value属性:指定扫描的基本包
  • @PropertySource用于加载properties文件
    • value属性:指定propertis文件的路径,从类加载路径里加载
    • 相当于xml中的<context:property-placeholder location="classpath:jdbc.properteis"/>
  • @Import用于导入其它配置类
    • Spring允许提供多个配置类(模块化配置),在核心配置类里加载其它配置类
    • 相当于xml中的==<import resource="模块化xml文件路径"/>==标签
示例
  • 总配置类
@Configuration
@ComponentScan(basePackages="com.itheima")
@PropertySource("classpath:db.properties")
@Import(JdbcConfig.class)
public class AppConfig{
    
}

  • 模块配置类:JdbcConfig。

    被加载的模块配置类,可以不用再添加@Configuration注解

public class JdbcConfig{
}

3 @Bean声明bean
1) @Bean定义bean
简介
  • @Bean注解:方法级别的注解
    • 用于把方法返回值声明成为一个bean,作用相当于<bean>标签
    • 可以用在任意bean对象的方法中,但是通常用在@Configuration标记的核心配置类中
  • @Bean注解的属性:
    • value属性:bean的id。如果不设置,那么方法名就是bean的id
示例
@Configuration
public class AppConfig {
    @Bean
    public UserService myService() {
        return new UserServiceImpl();
    }
}

2) @Bean的依赖注入
简介
  • @Bean注解的方法可以有任意参数,这些参数即是bean所需要的依赖,默认采用byType方式注入
  • 可以在方法参数上增加注解@Qualifier,用于byName注入
示例
@Configuration
public class AppConfig {
    @Bean("myService")
    public UserService myService(UserDao userDao) {
        //通过set方法把myDao设置给MyService
        UserService myService = new UserServiceImpl();
        myService.setMyDao(userDao);
        return myServie;
    }
    
    /**
     * 方法参数上使用@Qualifer注解,用于byName注入
     * 		@Qualifer:可以独立用在方法参数上用于byName注入
     */
    @Bean("myService2")
    public UserService myService2(@Qualifier("myDao2") UserDao userDao){
        return new UserServiceImpl(userDao);
    }
}

小结

  • 配置类上要加注解@Configuration
  • 要开启组件扫描,在配置类上@ComponentScan("com.itheima")
  • 如果要把jar包里的类注册成bean:
    • 在配置类里加方法,方法上加@Bean,会把方法返回值注册bean对象
@Configuration
@ComponentScan("com.itheima")
public class AppConfig{
    
    @Bean(value="person", initMethod="init", destroyMethod="destroy")
    @Scope("prototype")
    public Person p(){
        Person p = new Person();
        p.setName("jack");
        p.setAge(20);
        return p;
    }
    
    //byType注入依赖
    @Bean
    public QueryRunner runner(DataSource dataSource){
        return new QueryRunner(dataSource);
    }
    
    //byName注入依赖:注入名称为ds的bean对象
    @Bean
    public QueryRunner runner1(@Qualifier("ds") DataSource dataSource){
        return new QueryRunner(dataSource);
    }    
}

  • 如果要引入外部的properties文件,在配置类上加@PropertySource("classpath:xxx.properties")
@Configuration
@PropertySource("classpath:jdbc.properties") //引入properties文件
public class AppConfig{
    
    //把properties里的数据注入给依赖项(成员变量)
    @Value("${jdbc.driver}")
    private String driver;
    
    @Bean
    public DataSource ds(){
        ComboPooledDataSource ds = new ComboPooledDataSource();
        //直接使用成员变量,它的值已经注入了
        ds.setDrvierClass(drvier);
        ....
        return ds;
    }
}

  • 引入模块配置类,在配置类上使用@Import(子配置类.class)
@Configuration
@PropertySource("classpath:jdbc.properties") //引入properties文件
@Import(ServiceConfig.class)
public class AppConfig{

}

//子配置类
public class ServiceConfig{
    //可以引入properties里的值(只要这个properties被引入过)
	@Value("${jdbc.driver}")
    private String driver;
}

四、纯注解方式CURD练习

需求描述

  • 使用Spring的新注解,代替CURD练习里,applicationContext.xml的所有配置

需求实现

  • 提供jdbc配置文件:jdbc.properties
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql:///spring
jdbc.username=root
jdbc.password=root

  • 提供核心配置类:SpringConfig
@Configuration //声明当前类是一个配置类
@ComponentScan("com.itheima")//开启注解扫描
@PropertySource("classpath:jdbc.properties")//加载properties资源文件
public class SpringConfig {

    @Value("${jdbc.driver}")//注入properties中,jdbc.driver的值
    private String driver;

    @Value("${jdbc.url}")//注入properties中,jdbc.url的值
    private String url;

    @Value("${jdbc.username}")//注入properties中,jdbc.username的值
    private String username;

    @Value("${jdbc.password}")//注入properties中,jdbc.password的值
    private String password;

    //声明一个bean对象:数据库连接池对象,id是dataSource
    @Bean("dataSource")
    public DataSource dataSource() throws PropertyVetoException {
        ComboPooledDataSource dataSource = new ComboPooledDataSource();
        dataSource.setDriverClass(driver);
        dataSource.setJdbcUrl(url);
        dataSource.setUser(username);
        dataSource.setPassword(password);
        return dataSource;
    }

    //声明一个bean对象:QueryRunner对象,id是runner。
    //方法参数DataSource,需要dataSource,使用@Qualifier注入依赖
    @Bean("runner")
    public QueryRunner queryRunner(@Qualifier("dataSource") DataSource dataSource){
        return new QueryRunner(dataSource);
    }
}

功能测试

public class AccountTest {

    @Test
    public void queryAll() throws SQLException {
        ApplicationContext app = new AnnotationConfigApplicationContext(MyConfig.class);
        AccountService accountService = app.getBean("accountService", AccountService.class);
        List<Account> accounts = accountService.queryAll();
        for (Account account : accounts) {
            System.out.println(account);
        }
    }

    @Test
    public void findById() throws SQLException {
        ApplicationContext app = new AnnotationConfigApplicationContext(MyConfig.class);
        AccountService accountService = app.getBean("accountService", AccountService.class);
        Account account = accountService.findById(2);
        System.out.println(account);
    }

    @Test
    public void save() throws SQLException {
        ApplicationContext app = new AnnotationConfigApplicationContext(MyConfig.class);
        AccountService accountService = app.getBean("accountService", AccountService.class);

        Account account = new Account();
        account.setName("jerry");
        account.setMoney(2000d);
        accountService.save(account);
    }

    @Test
    public void edit() throws SQLException {
        ApplicationContext app = new AnnotationConfigApplicationContext(MyConfig.class);
        AccountService accountService = app.getBean("accountService", AccountService.class);

        Account account = new Account();
        account.setId(2);
        account.setName("tom");
        account.setMoney(10000d);

        accountService.edit(account);
    }

    @Test
    public void delete() throws SQLException {
        ApplicationContext app = new AnnotationConfigApplicationContext(MyConfig.class);
        AccountService accountService = app.getBean("accountService", AccountService.class);

        accountService.delete(3);
    }
}

五、注解深入

准备环境

1. 创建Module,引入依赖
<dependencies>
    	<!-- Spring -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.0.2.RELEASE</version>
        </dependency>
    	<!-- Spring整合Junit -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-test</artifactId>
            <version>5.0.2.RELEASE</version>
        </dependency>
    	<!-- Junit -->
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
        </dependency>
    	<!-- snakeyaml,用于解析yaml文件的工具包 -->
        <dependency>
            <groupId>org.yaml</groupId>
            <artifactId>snakeyaml</artifactId>
            <version>1.25</version>
        </dependency>

		<!-- mysql驱动 -->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.47</version>
        </dependency>
    	<!-- c3p0连接池 -->
        <dependency>
            <groupId>c3p0</groupId>
            <artifactId>c3p0</artifactId>
            <version>0.9.1.2</version>
        </dependency>
    </dependencies>

2. 创建核心配置类
  • com.itheima包里创建核心配置类AppConfig
@Configuration
@ComponentScan("com.itheima")
public class AppConfig {
    
}

3. 创建单元测试类
  • src\main\testcom.itheima.test包里创建单元测试类
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = AppConfig.class)
public class SpringAnnotationTest {

    /**
     * 把IoC容器注入进来,在测试方法里要使用
     */
    @Autowired
    private ApplicationContext app;

}

@ComponentScan

BeanName生成策略
说明
  • 默认的BeanName生成策略:

    • 如果注册bean时指定了id/name,以配置的id/name作为bean的名称
    • 如果没有指定id/name,则以类名首字母小字作为bean的名称
  • 在模块化开发中,多个模块共同组成一个工程。

    • 可能多个模块中,有同名称的类,按照默认的BeanName生成策略,会导致名称冲突。
    • 这个时候可以自定义beanname生成策略解决问题
  • @ComponentScannameGenerator,可以配置自定义的BeanName生成策略,步骤:

  1. 创建Java类,实现BeanNameGenerator接口,定义BeanName生成策略

  2. 在注解@ComponentScan中,使用nameGenerator指定生成策略即可

示例
  1. com.itheima.beans里创建一个Java类:Course如下
//定义bean的名称为c
@Component("c")
public class Course {
    private String courseName;
    private String teacher;
	//get/set...
    //toString
}

  1. com.itheima.beanname里创建Java类,实现BeanNameGenerator接口,定义BeanName生成策略
public class MyBeanNameGenerator implements BeanNameGenerator {

    /**
     * 生成bean名称。当Spring容器扫描到类之后,会调用这个方法,获取bean的名称
     *
     * @param definition bean的定义信息
     * @param registry bean的注册器,用于管理容器里的bean,可以:
     *                 注册新的bean;查询某个bean;删除bean 等等
     * @return bean的名称
     */
    @Override
    public String generateBeanName(BeanDefinition definition, BeanDefinitionRegistry registry) {
        //获取bean的全限定类名
        String beanClassName = definition.getBeanClassName();
        //获取bean的类名(去掉包名,只要类名)
        String shortName = ClassUtils.getShortName(beanClassName);
        //把bean命名为:my+类名
        return "my" + shortName;
    }
}

  1. 在注解@ComponentScan中,使用==nameGenerator==指定生成策略
@Configuration
@ComponentScan(
        basePackages = "com.itheima",
        nameGenerator = MyBeanNameGenerator.class
)
public class AppConfig {
}

  1. 编写测试类,测试获取bean
    /**
     *  Bean命名规则
     *      如果定义了bean的名称,以定义的名称为准
     *      如果没有定义bean的名称,默认情况下:
     *          类名首字母小写作为bean的名称(id)。比如:Course,名称为course
     *          如果类名前2位都是大写,则仍然使用原类名。比如:URL,名称仍然是URL
     *
     * 自定义BeanName命名策略:
     *      1. 创建类,实现BeanNameGenerator,自定义名称生成策略
     *      2. 在核心配置类的@ComponentScan里,使用nameGenerator指定生成策略名称
     *
     * 如果使用了自定义的命名策略,则bean的原本的名称将会失效
     */
    @Test
    public void testBeanName(){
        //获取Course类型的bean的名称
        String[] names = app.getBeanNamesForType(Course.class);
        for (String name : names) {
            System.out.println(name);
        }
    }

扫描规则过滤器
说明
  • @ComponentScan默认的扫描规则:
    • 扫描指定包里的@Component及衍生注解(@Controller,@Service,@Repository)配置的bean
  • @ComponentScan注解也可以自定义扫描规则,来包含或排除指定的bean。步骤:
    1. 创建Java类,实现TypeFilter接口,重写match方法
      • 方法返回boolean。true表示匹配过滤规则;false表示不匹配过滤规则
    2. 使用@ComponentScan注解的属性,配置过滤规则:
      • includeFilter:用于包含指定TypeFilter过滤的类,符合过滤规则的类将被扫描
      • excludeFilter:用于排除指定TypeFilter过滤的类,符合过滤规则的类将被排除
示例1-根据注解过滤
@Configuration
@ComponentScan(
        basePackages = "com.itheima",
        nameGenerator = MyBeanNameGenerator.class,
        //使用Component注解标注的类不扫描
        excludeFilters = @ComponentScan.Filter(
            	//过滤类型:根据注解进行过滤
                type = FilterType.ANNOTATION,
            	//要过滤的注解是:Component
                classes = Component.class
        )
)
public class AppConfig {
}

示例2-根据指定类过滤
@Configuration
@ComponentScan(
        basePackages = "com.itheima",
        nameGenerator = MyBeanNameGenerator.class,
        //Course及其子类、实现类不扫描
        excludeFilters = @ComponentScan.Filter(
                type = FilterType.ASSIGNABLE_TYPE,
                classes = Course.class
        )
)
public class AppConfig {

}

示例3-自定义过滤
  1. 创建Java类
//类上有@Component
@Component
public class BeanTest {
	//类里有单元测试方法
    @Test
    public void test1(){
        System.out.println("---------");
    }
}

  1. 编写过滤器,实现TypeFilter接口,重写match方法
public class TestFilter implements TypeFilter {
    /**
     * 如果被扫描的类是单元测试类(类里有单元测试方法),则排除掉不要
     *
     * @param metadataReader 被扫描的类的读取器,可以读取类的字节码信息、类的注解信息
     * @param metadataReaderFactory 读取器的工厂对象
     * @return 是否匹配。如果被扫描的类是单元测试类,则匹配,
     */
    @Override
    public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException {
        //获取被扫描的类上的注解信息
        AnnotationMetadata annotationMetadata = metadataReader.getAnnotationMetadata();
        //获取被扫描的类里,使用@Test注解标记的方法
        Set<MethodMetadata> annotatedMethods = annotationMetadata.getAnnotatedMethods(Test.class.getName());
        //如果有被@Test标记的方法,返回true;否则,返回false
        return annotatedMethods!=null && annotatedMethods.size()>0;
    }
}

  1. 使用注解@ComponentScan,配置过滤规则
@Configuration
@ComponentScan(
        basePackages = "com.itheima",
        nameGenerator = MyBeanNameGenerator.class,
        //单元测试类不扫描(使用自定义的过滤器,排除掉 过滤出来的单元测试类)
        excludeFilters = @ComponentScan.Filter(
                type = FilterType.CUSTOM,
                classes = TestFilter.class
        )
)
public class AppConfig {

}

  1. 测试
    @Test
    public void testScan(){
        //获取不到BeanTest,因为这个类里有单元测试方法,被我们自定义的过滤器给排除掉了
        BeanTest beanTest = app.getBean(BeanTest.class);
        System.out.println(beanTest);
    }

@PropertySource

yml配置文件介绍
  • 大家以前学习过的常用配置文件有xmlproperties两种格式,但是这两种都有一些不足:
    • properties
      • 优点:键值对的格式,简单易读
      • 缺点:不方便表示复杂的层级
    • xml
      • 优点:层次结构清晰
      • 缺点:配置和解析语法复杂
  • springboot采用了一种新的配置文件:yaml(或yml),它综合了xmlproperties的优点
    • yaml are't markup language => yaml
    • 使用空格表示层次关系:相同空格的配置项属于同一级
    • 配置格式是key:空格value,键值对之间用==:空格==表示
  • yaml文件示例:
jdbc:
	driver: com.mysql.jdbc.Driver # 注意:英文冒号后边必须有一个空格
	url: jdbc:mysql:///spring
	username: root
	password: root
	
jedis:
	host: localhost
	port: 6379

使用@PropertySource加载yml
说明
  • @PropertySource可以使用factory属性,配置PropertySourceFactory,用于自定义配置文件的解析
  • 步骤:
    1. 创建yaml文件:application.yml
    2. 导入依赖snakeyaml,它提供了解析yml文件的功能
    3. 创建Java类,实现PropertySourceFactory接口,重写createPropertySource方法
    4. 使用@PropertySource注解,配置工厂类
示例
  1. resources目录里创建yaml文件:application.yml
jdbc:
	driver: com.mysql.jdbc.Driver # 注意:英文冒号后边必须有一个空格
	url: jdbc:mysql:///spring
	username: root
	password: root
	
jedis:
	host: localhost
	port: 6379

  1. pom.xml增加导入依赖snakeyaml,它提供了解析yml文件的功能
<dependency>
    <groupId>org.yaml</groupId>
    <artifactId>snakeyaml</artifactId>
    <version>1.25</version>
</dependency>

  1. 创建Java类,实现PropertySourceFactory接口,重写createPropertySource方法
public class YamlSourceFactory implements PropertySourceFactory {
    /**
     * 解析yaml配置文件
     * @param name 名称
     * @param resource 配置文件EncodedResource对象 
     * @return PropertySource
     */
    @Override
    public PropertySource<?> createPropertySource(String name, EncodedResource resource) throws IOException {
        //1. 创建yaml解析的工厂
        YamlPropertiesFactoryBean factoryBean = new YamlPropertiesFactoryBean();
        //2. 设置要解析的资源内容
        factoryBean.setResources(resource.getResource());
        //3. 把资源文件解析成Properties对象
        Properties properties = factoryBean.getObject();
        //4. 把properties封装成PropertySource对象并返回
        return new PropertiesPropertySource("application", properties);
    }
}

  1. 使用@PropertySource注解,配置工厂类
@Configuration("appConfig")
@PropertySource(
    value = "classpath:application.yml", 
    factory = YamlSourceFactory.class
)
public class AppConfig {

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

    @Value("${jedis.host}")
    private String jedisHost;

    public void showProps(){
        System.out.println("jdbc.driver: " + this.jdbcDriver);
        System.out.println("jedis.host: " + this.jedisHost);
    }
}

  1. 测试
@Test
public void testProps(){
    AppConfig appConfig = app.getBean(AppConfig.class);
    appConfig.showProps();
}

@Import

注册bean的方式

如果要注解方式配置一个bean,可以如下方式:

  • 在类上使用@Component,@Controller, @Service, @Repository:只能用于自己编写的类上,jar包里的类不能使用(比如ComboPooledDataSource)
  • 在方法上使用@Bean:把方法返回值配置注册成bean到IoC容器,通常用于注册第三方jar里的bean
  • 在核心配置类上使用@Import
    • @Import(类名.class),注册的bean的id是全限定类名
    • @Import(自定义ImportSelector.class):把自定义ImportSelector返回的类名数组,全部注册bean
    • @Import(自定义ImportBeanDefinitionRegister.class):在自定义ImportBeanDefinitionRegister里手动注册bean
ImportSelector导入器
示例1-直接导入注册bean
@Configuration
@Import(Catalog.class) //导入Catalog注册bean
public class AppConfig {
    @Value("${jdbc.driver}")
    private String jdbcDriver;

    @Value("${jedis.host}")
    private String jedisHost;

    public void showProps(){
        System.out.println("jdbc.driver: " + this.jdbcDriver);
        System.out.println("jedis.host: " + this.jedisHost);
    }
}

示例2-使用ImportSelector注册bean
//导入Catalog, 并把MyImportSelector选择的全限定类名也注册bean
@Import({Catalog.class, MyImportSelector.class})
@Configuration
public class AppConfig {
    @Value("${jdbc.driver}")
    private String jdbcDriver;

    @Value("${jedis.host}")
    private String jedisHost;

    public void showProps(){
        System.out.println("jdbc.driver: " + this.jdbcDriver);
        System.out.println("jedis.host: " + this.jedisHost);
    }
}


示例3-ImportSelector的高级使用
说明
  • springboot框架里有大量的@EnableXXX注解,底层就是使用了ImportSelector解决了模块化开发中,如何启动某一模块功能的问题
  • 例如:
    • 我们开发了一个工程,要引入其它的模块,并启动这个模块的功能:把这个模块的bean进行扫描装载

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ABqtMgFU-1619528725483)(img/image-20200713154934953.png)]

  • 步骤:
    1. 创建一个Module:module_a
      1. 在包com.a里创建几个Bean
      2. 创建核心配置文件AConfig,扫描com.a
      3. 定义一个ImportSelector:AImportSelector,导入AConfig
      4. 定义一个注解@EnableModuleA
    2. 在我们的Module的核心配置文件AppConfig上增加注解:@EnableModuleA
      1. 引入module_a的坐标
      2. 测试能否获取到Module a里的bean
第一步:创建新Module:module_a
<!-- module_a的坐标 -->
<groupId>com.itheima</groupId>
    <artifactId>spring_module_a</artifactId>
    <version>1.0-SNAPSHOT</version>

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rJ7lpvbH-1619528725486)(img/image-20200713160011267.png)]

  1. 在包com.a.beans里创建类 DemoBeanA

    @Component
    public class DemoBeanA {
    }
    
    
  2. 创建核心配置类AConfig

@Configuration
@ComponentScan("com.a")
public class AConfig {
}

  1. 创建导入器:创建Java类,实现ImportSelector接口,重写selectImports方法
public class AImportSelector implements ImportSelector {
    /**
     * @param importingClassMetadata。被@Import注解标注的类上所有注解的信息
     */
    @Override
    public String[] selectImports(AnnotationMetadata importingClassMetadata) {
        return new String[]{AConfig.class.getName()};
    }
}

  1. 定义注解@EnableModuleA
@Import(AImportSelector.class)
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface EnableModuleA {
}

第二步:引入module_a,启动模块a
  1. 在我们自己的工程pom.xml里增加依赖
<dependency>
    <groupId>com.itheima</groupId>
    <artifactId>spring_module_a</artifactId>
    <version>1.0-SNAPSHOT</version>
</dependency>

  1. 在我们自己的工程核心配置类上,使用注解@EnableModuleA启动模块a
@Configuration
@EnableModuleA
public class AppConfig {

}

  1. 在我们自己的工程里测试
   @Test
    public void testBean(){
        //在我们自己的工程里,可以获取到module_a里的bean
        DemoBeanA demoBeanA = app.getBean(DemoBeanA.class);
        System.out.println(demoBeanA);
    }

ImportBeanDefinitionRegister注册器
说明
  • ImportBeanDefinitionRegister提供了更灵活的注册bean的方式

  • AOP里的@EnableAspectJAutoProxy就使用了这种注册器,用于注册不同类型的代理对象

  • 步骤:

    1. 创建注册器:

      创建Java类,实现ImportBeanDefinitionRegister接口,重写registerBeanDefinitions方法

    2. 在核心配置类上,使用@Import配置注册器

示例
  1. 创建类com.other.Other
public class Other {
    public void show(){
        System.out.println("Other.show....");
    }
}

  1. 创建注册器
public class CustomImportBeanDefinitionRegister implements ImportBeanDefinitionRegistrar {
    /**
     * @param importingClassMetadata 当前类的注解信息
     * @param registry 用于注册bean的注册器
     */
    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
        //获取bean定义信息
        BeanDefinition beanDefinition = BeanDefinitionBuilder.rootBeanDefinition("com.other.Other").getBeanDefinition();
        //注册bean,方法参数:bean的id,bean的定义信息
        registry.registerBeanDefinition("other", beanDefinition);
    }
}

  1. 在核心配置类上,使用@Import配置注册器
@Configuration
@Import({CustomImportBeanDefinitionRegister.class})
public class AppConfig {

}

  1. 测试
@Test
public void testImportRegister(){
    //获取扫描范围外,使用ImportBeanDefinitionRegister注册的bean
    Other other = app.getBean("other",Other.class);
    other.showOther();
}

@Conditional

说明
  • @Conditional加在bean上,用于选择性的注册bean:
    • 符合Condition条件的,Spring会生成bean对象 存储容器中
    • 不符合Condition条件的,不会生成bean对象
  • 示例:
    • 有一个类Person(姓名name,年龄age)
    • 如果当前操作系统是Linux:就创建Person(linus, 62)对象,并注册bean
    • 如果当前操作系统是Windows:就创建Persion(BillGates, 67)对象,并注册bean
  • 步骤
    1. 创建Person类
    2. 创建两个Java类,都实现Condition接口:
      • WindowsCondition:如果当前操作系统是Windows,就返回true
      • LinuxCondition:如果当前操作系统是Linux,就返回true
    3. 在核心配置类里创建两个bean
      • 一个bean名称为bill,加上@Conditional(WindowsCondition.class)
      • 一个bean名称为linus,加上@Conditional(LinuxCondition.class)
示例
  1. 创建Person类
public class Person {
    private String name;
    private Integer age;

    public Person() {
    }

    public Person(String name, Integer age) {
        this.name = name;
        this.age = age;
    }
 	//get/set...
    //toString...
}

  1. 创建两个Java类,实现Condition接口
public class WindowsCondition implements Condition {
    @Override
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
		//从当前运行环境中获取操作系统名称
        String osName = context.getEnvironment().getProperty("os.name");
		//判断是否是Windows系统;如果是,返回true;否则返回false
        return osName.contains("Windows");
    }
}

public class LinuxCondition implements Condition {
    @Override
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
		//从当前运行环境中获取操作系统名称
        String osName = context.getEnvironment().getProperty("os.name");
		//判断是否是Linux系统;如果是,返回true;否则返回false
        return osName.contains("Linux");
    }
}

  1. 核心配置类
@Configuration
public class AppConfig {
	/**
	 * 如果操作系统是Windows,则注册bean对象:id为bill
	 */
    @Bean
    @Conditional(WindowsCondition.class)
    public Person bill(){
        return new Person("Bill Gates", 67);
    }

    /**
     * 如果操作系统是Linux,注册bean对象:id为linus
     */
    @Bean
    @Conditional(LinuxCondition.class)
    public Person linus(){
        return new Person("Linux", 60);
    }
}

  1. 测试
@Test
public void testCondition(){
    ApplicationContext app = new AnnotationConfigApplicationContext(AppConfig.class);
    Person person = app.getBean(Person.class);
    System.out.println(person);
}

执行一次单元测试方法之后,按照以下方式,可以通过JVM参数的方式,设置os.name的参数值。

设置之后,再次执行单元测试方法

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-BTDWcKP1-1619528725488)(img/image-20200713161316994.png)]

Conditional的扩展注解

@Conditional在springboot里应用非常多,以下列出了一些@Conditional的扩展注解:

  • @ConditionalOnBean:当容器中有指定Bean的条件下进行实例化。
  • @ConditionalOnMissingBean:当容器里没有指定Bean的条件下进行实例化。
  • @ConditionalOnClass:当classpath类路径下有指定类的条件下进行实例化。
  • @ConditionalOnMissingClass:当类路径下没有指定类的条件下进行实例化。
  • @ConditionalOnWebApplication:当项目是一个Web项目时进行实例化。
  • @ConditionalOnNotWebApplication:当项目不是一个Web项目时进行实例化。
  • @ConditionalOnProperty:当指定的属性有指定的值时进行实例化。
  • @ConditionalOnExpression:基于SpEL表达式的条件判断。
  • @ConditionalOnJava:当JVM版本为指定的版本范围时触发实例化。
  • @ConditionalOnResource:当类路径下有指定的资源时触发实例化。
  • @ConditionalOnJndi:在JNDI存在的条件下触发实例化。
  • @ConditionalOnSingleCandidate:当指定的Bean在容器中只有一个,或者有多个但是指定了首选的Bean时触发实例化。

@Profile

说明
  • 在开发中,我们编写的工程通常要部署不同的环境,比如:开发环境、测试环境、生产环境。不同环境的配置信息是不同的,比如:数据库配置信息;如果每次切换环境,都重新修改配置的话,会非常麻烦,且容易出错
  • 针对这种情况,Spring提供了@Profile注解:可以根据不同环境配置不同的bean,激活不同的配置
    • @Profile注解的底层就是@Conditional
  • 例如:
    • 定义三个数据源:
      • 开发环境一个DataSource,使用@Profile配置环境名称为dev
      • 测试环境一个DataSource,使用@Profile配置环境名称为test
      • 生产环境一个DataSource,使用@Profile配置环境名称pro
    • 在测试类上,使用@ActiveProfiles激活哪个环境,哪个环境的数据源会生效
      • 实际开发中有多种方式可以进行激活,这里演示一个单元测试类里是怎样激活的
示例
  1. pom.xml中增加导入依赖mysql驱动, c3p0
<dependencies>
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>5.1.47</version>
    </dependency>
    <dependency>
        <groupId>c3p0</groupId>
        <artifactId>c3p0</artifactId>
        <version>0.9.1.2</version>
    </dependency>
    
    
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context</artifactId>
        <version>5.2.5.RELEASE</version>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-test</artifactId>
        <version>5.2.5.RELEASE</version>
    </dependency>
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.12</version>
    </dependency>
</dependencies>

  1. 在配置类里创建三个数据源,并配置@Profile
@Configuration
public class AppConfig {

    @Bean
    @Profile("dev")
    public DataSource devDataSource() throws PropertyVetoException {
        System.out.println("dev  开发环境的");
        ComboPooledDataSource dataSource = new ComboPooledDataSource();
        dataSource.setDriverClass("com.mysql.jdbc.Driver");
        dataSource.setJdbcUrl("jdbc:mysql:///devdb");
        dataSource.setUser("root");
        dataSource.setPassword("root");
        dataSource.setMaxPoolSize(20);
        return dataSource;
    }

    @Bean
    @Profile("test")
    public DataSource testDataSource() throws PropertyVetoException {
        System.out.println("test 测试环境的");
        ComboPooledDataSource dataSource = new ComboPooledDataSource();
        dataSource.setDriverClass("com.mysql.jdbc.Driver");
        dataSource.setJdbcUrl("jdbc:mysql:///testdb");
        dataSource.setUser("root");
        dataSource.setPassword("root");
        dataSource.setMaxPoolSize(100);
        return dataSource;
    }

    @Bean
    @Profile("pro")
    public DataSource proDataSource() throws PropertyVetoException {
        System.out.println("pro 生产环境的");
        ComboPooledDataSource dataSource = new ComboPooledDataSource();
        dataSource.setDriverClass("com.mysql.jdbc.Driver");
        dataSource.setJdbcUrl("jdbc:mysql:///prodb");
        dataSource.setUser("root");
        dataSource.setPassword("root");
        dataSource.setMaxPoolSize(200);
        return dataSource;
    }
}

  1. 在测试类上,使用@ActiveProfiles激活哪个环境,哪个环境的数据源会生效

    或者使用JVM参数-Dspring.profiles.active=dev

@ActiveProfiles("dev") //激活dev环境
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = AppConfig.class)
public class SpringTest {
    
    @Autowired
    private ApplicationContext app;

    @Test
    public void testProfile(){
        DataSource dataSource = app.getBean(DataSource.class);
        System.out.println(dataSource);
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值