Spring5 全家桶 | 10 - Spring Framework 5.3

251 阅读5分钟

一、@Controller,@Service,@Repository,@Component注解

创建一个新的工程spring-bean-anno,并导入依赖

<properties>
    <spring-version>5.3.13</spring-version>
</properties>

<dependencies>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-beans</artifactId>
        <version>${spring-version}</version>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context</artifactId>
        <version>${spring-version}</version>
    </dependency>
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.12</version>
        <scope>test</scope>
    </dependency>
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>8.0.16</version>
    </dependency>
    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>druid</artifactId>
        <version>1.1.14</version>
    </dependency>
</dependencies>

新建四个包controller,service,dao,entity,分别增加四个类UserController,UserService,UserDao,User

public class UserController {
}
public class UserService {
}
public class UserDao {
}
public class User {

    private String username;
    private String password;
    private String email;
    private String signature;
    // 此处省略getter/setter/toString方法
}

在前三个类上加上注解@Controller,@Service,@Repository

  • @Controller:给controller包中的xxxController加上这个注解
  • @Service:给service包中的XxxService实现类添加这个注解
  • @Repository:给持久层增加这个注解
  • @Component:给任何注册到Spring容器中的组件或类添加这个注解

具体操作为:先在类上加相应注解,再增加xml配置自动扫描范围

resources目录下新建一个annotation.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:util="http://www.springframework.org/schema/util"
       xmlns:p="http://www.springframework.org/schema/p" xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/util
        http://www.springframework.org/schema/util/spring-util-4.3.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">

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

</beans>

增加测试类

public class AnnotationTest {

    @Test
    public void testGetBeanByAnnotation(){
        ApplicationContext context = new ClassPathXmlApplicationContext("classpath:annotation.xml");
        String[] beanDefinitionNames = context.getBeanDefinitionNames();
        for (String beanDefinitionName : beanDefinitionNames) {
            System.out.println(beanDefinitionName);
        }
    }
}

执行测试方法,默认Bean的ID为类名首字母小写

image.png

自定义bean的id只需要在注解后添加bean的id即可,如@Controller("controller"),再次执行测试 image.png

使用注解和xml配置默认都是单例模式,注解模式使用多例需要在类上添加@Scope注解,在UserController类上增加@Scope(value = "prototype"),增加测试方法

@Test
public void testGetBeanByAnnotationWithPrototype(){
    ApplicationContext context = new ClassPathXmlApplicationContext("classpath:annotation.xml");

    UserController userController = context.getBean(UserController.class);
    UserController userController1 = context.getBean(UserController.class);
    System.out.println(userController == userController1);
}

执行测试,控制台打印出false

image.png 对于自定义的类要加入到容器中可以使用注解的方式,而对于一些工具类源码如数据库连接池就没有办法加注解,只能通过bean xml配置的方式注册到容器中去,通过注解+xml配置结合可以将任意组件加入到容器中去

二、component-scan,exclude-filter,include-filter标签

<context:component-scan> 标签默认全部配置的包中的全部加了注解的组件,如果想要排除某些组件需要在标签内使用exclude-filter标签,exclude-filter有type和expression两个属性

  • type=“annotation”:指定按照注解进行排除,expression则为注解的全类名
  • type=“assignable":指定排除具体的类,expression则为具体类的全类名
  • type="aspectj":aspectj表达式,expression则为具体表达式内容
  • type="customer":自定义实现TypeFilter接口
  • type="regex":正则表达式排除

annotation方式排除

xml中component-scan标签下增加配置,排除@Controller注解标注的Bean

<context:component-scan base-package="com.citi">
    <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
</context:component-scan>

执行testGetBeanByAnnotation测试方法,控制台输出不包含userController

image.png

assignable方式排除

注销原来的配置,增加xml配置,排出userService

<context:component-scan base-package="com.citi">
    <context:exclude-filter type="assignable" expression="com.citi.service.UserService"/>
</context:component-scan>

同样执行testGetBeanByAnnotation测试方法,控制台输出不包含userService

image.png

指定只扫描哪些可以使用include-filter标签,位置同样放在component-scan标签内,属性及Value都与exclude-filter一致,使用include-filter首先要禁用默认全部扫描

注销xml中原来的配置,新增只扫描UserService组件的配置

<context:component-scan base-package="com.citi" use-default-filters="false">
    <context:include-filter type="assignable" expression="com.citi.service.UserService"/>
</context:component-scan>

执行testGetBeanByAnnotation测试方法,控制台输出只包含userService

image.png

三、依赖注入@Autowire注解

在UserDao中增加方法

public void insert(User user){
    System.out.println(this.getClass().getName() + "的insert方法被调用");
}

@Autowire是按照类型注入,如果找不到会报错,如果找到多个相同类型的Bean会怎么样?存在多个同类型的Bean按照属性名为id继续装配 新增一个UserDaoExt类,继承UserDao,并加入容器中

@Repository
public class UserDaoExt extends UserDao {

    @Override
    public void insert(User user) {
        System.out.println(this.getClass().getName() + "的insert方法被调用");
        super.insert(user);
    }
}

新增测试类

public class AutowireAnnotationTest {

    @Test
    public void testGetBeanByAnnotation(){
        ApplicationContext context = new ClassPathXmlApplicationContext("classpath:annotation.xml");

        UserService userService = context.getBean("userService", UserService.class);
        userService.save(new User());
        String[] beanDefinitionNames = context.getBeanDefinitionNames();
        for (String beanDefinitionName : beanDefinitionNames) {
            System.out.println(beanDefinitionName);
        }
    }
}

userService调用的是userDao的insert方法,说明是按照属性名来装配的 image.png 将UserService中属性名改为useDaoExt,再次执行测试,输出UserDaoExt的insert方法被调用,可以说明当存在多个相同类型的Bean时,@Autowire注解会根据属性名作为Bean的ID进行自动装配

image.png

@Qualifier()指定装配的Bean的ID

UserService中属性增加@Qualifier()注解

@Service
public class UserService {

    @Qualifier("userDao")
    @Autowired
    private UserDao userDaoExt;

    public void save(User user){
        System.out.println(this.getClass().getName() + "的save方法被调用");
        userDaoExt.insert(user);
    }
}

再次执行测试,@Qualifier注解指定的userDao被调用

image.png

@Autowire 的 required 属性

将UserDao和UserDaoExt类上的@Repository注解注释,也就是说UserDao和UserDaoExt不会被注册到容器中,再次执行测试

image.png 当要装配的类型不存在时会报错,通过@Autowire(required=false),可以设置如果找不到Bean就装配为null,在UserService的@Autowire增加required=false,再次执行测试,此时不会在报Bean创建错误

image.png

@Autowire也可以放在方法上,此时@Autowire会把方法中的参数注入到容器中,而且这个方法也会在Bean创建的时候运行

@Qaulifier()也可以放在参数上,注入指定 ID的Bean

四、Spring单元测试

如何在单元测试中也可以使用@Autowire获取IoC容器中的元素?这就需要用到Spring Test

增加Spring Test的依赖

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-test</artifactId>
    <version>${spring-version}</version>
</dependency>

新建一个SpringTest测试类

@ContextConfiguration(locations = "classpath:annotation.xml")
@RunWith(SpringJUnit4ClassRunner.class)
public class SpringTest {

    @Autowired
    private UserService userService;

    @Test
    public void testSave(){
        System.out.println(userService);
        userService.save(new User());
    }
}

执行测试类

image.png

注解释义

  • @ContextConfiguration, 用来指定Spring的配置文件的位置
  • @RunWith(SpringJUnit4ClassRunner.class), 指定单元测试的驱动

五、泛型依赖注入

entity增加Product,Category dao层增加一个BaseDao,并定义好通用的save()方法,新增ProductDao和CategoryDao

public abstract class BaseDao<T> {

    public abstract void save();
}
@Repository
public class ProductDao extends BaseDao<Product> {

    @Override
    public void save() {
        System.out.println(this.getClass().getName() + "的save()方法被调用");
    }
}
@Repository
public class CategoryDao extends BaseDao<Category> {
    @Override
    public void save() {
        System.out.println(this.getClass().getName() + "的save()方法被调用");
    }
}

service层增加ProductService,CategoryService

@Service
public class ProductService {

    @Autowired
    private ProductDao productDao;

    public void save(){
        productDao.save();
    }
}
@Service
public class CategoryService {

    @Autowired
    private CategoryDao categoryDao;

    public void save(){
        categoryDao.save();
    }
}

创建一个xml配置文件generic.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:util="http://www.springframework.org/schema/util"
       xmlns:p="http://www.springframework.org/schema/p" xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/util
        http://www.springframework.org/schema/util/spring-util-4.3.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">

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

</beans>

新建测试类

@ContextConfiguration(locations = "classpath:generic.xml")
@RunWith(SpringJUnit4ClassRunner.class)
public class GenericTest {

    @Autowired
    private ProductService productService;

    @Autowired
    private CategoryService categoryService;


    @Test
    public void testSave(){
        productService.save();
        categoryService.save();
    }
}

执行测试类

image.png

由于调用的ProducDao和CategoryDao其实都是调用的BaseDao,新建一个BaseService方法,调用BaseDao

public class BaseService<T> {

    @Autowired
    private BaseDao<T> baseDao;

    public void save(){
        System.out.println(baseDao);
        baseDao.save();
    }
}

修改ProductService和CategoryService

@Service
public class CategoryService extends BaseService<Category> {
    
}
@Service
public class ProductService extends BaseService<Product>{

}

再次执行测试,同样可以成功执行save方法,那么Spring是如何确定执行的类?

ProductService继承了BaseService<Product>, BaseService中调用了BaseDao,因此通过BaseDao<Product>就可以找到ProductDao,因为ProductDao继承了BaseDao<>

image.png

Spring可以使用带泛型的父类类型来确定这个子类的类型。