一、@Controller,@Service,@Repository,@Component注解
创建一个新的工程spring-bean-anno,并导入依赖
代码语言:javascript复制<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
代码语言:javascript复制public class UserController {
}
代码语言:javascript复制public class UserService {
}
代码语言:javascript复制public class UserDao {
}
代码语言:javascript复制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配置文件配置扫描范围
代码语言:javascript复制<?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>
增加测试类
代码语言:javascript复制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为类名首字母小写
自定义bean的id只需要在注解后添加bean的id即可,如@Controller("controller"),再次执行测试
使用注解和xml配置默认都是单例模式,注解模式使用多例需要在类上添加@Scope注解,在UserController类上增加@Scope(value = "prototype"),增加测试方法
代码语言:javascript复制@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
对于自定义的类要加入到容器中可以使用注解的方式,而对于一些工具类源码如数据库连接池就没有办法加注解,只能通过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
代码语言:javascript复制<context:component-scan base-package="com.citi">
<context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
</context:component-scan>
执行testGetBeanByAnnotation测试方法,控制台输出不包含userController
assignable方式排除
注销原来的配置,增加xml配置,排出userService
代码语言:javascript复制<context:component-scan base-package="com.citi">
<context:exclude-filter type="assignable" expression="com.citi.service.UserService"/>
</context:component-scan>
同样执行testGetBeanByAnnotation测试方法,控制台输出不包含userService
指定只扫描哪些可以使用include-filter标签,位置同样放在component-scan标签内,属性及Value都与exclude-filter一致,使用include-filter首先要禁用默认全部扫描
注销xml中原来的配置,新增只扫描UserService组件的配置
代码语言:javascript复制<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
三、依赖注入@Autowire注解
在UserDao中增加方法
代码语言:javascript复制public void insert(User user){
System.out.println(this.getClass().getName() "的insert方法被调用");
}
@Autowire是按照类型注入,如果找不到会报错,如果找到多个相同类型的Bean会怎么样?存在多个同类型的Bean按照属性名为id继续装配 新增一个UserDaoExt类,继承UserDao,并加入容器中
代码语言:javascript复制@Repository
public class UserDaoExt extends UserDao {
@Override
public void insert(User user) {
System.out.println(this.getClass().getName() "的insert方法被调用");
super.insert(user);
}
}
新增测试类
代码语言:javascript复制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方法,说明是按照属性名来装配的
将UserService中属性名改为useDaoExt,再次执行测试,输出UserDaoExt的insert方法被调用,可以说明当存在多个相同类型的Bean时,@Autowire注解会根据属性名作为Bean的ID进行自动装配
@Qualifier()指定装配的Bean的ID
UserService中属性增加@Qualifier()注解
代码语言:javascript复制@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被调用
@Autowire 的 required 属性
将UserDao和UserDaoExt类上的@Repository注解注释,也就是说UserDao和UserDaoExt不会被注册到容器中,再次执行测试
当要装配的类型不存在时会报错,通过@Autowire(required=false),可以设置如果找不到Bean就装配为null,在UserService的@Autowire增加required=false,再次执行测试,此时不会在报Bean创建错误
@Autowire也可以放在方法上,此时@Autowire会把方法中的参数注入到容器中,而且这个方法也会在Bean创建的时候运行
@Qaulifier()也可以放在参数上,注入指定 ID的Bean
四、Spring单元测试
如何在单元测试中也可以使用@Autowire获取IoC容器中的元素?这就需要用到Spring Test
增加Spring Test的依赖
代码语言:javascript复制<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>${spring-version}</version>
</dependency>
新建一个SpringTest测试类
代码语言:javascript复制@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());
}
}
执行测试类
注解释义
- @ContextConfiguration, 用来指定Spring的配置文件的位置
- @RunWith(SpringJUnit4ClassRunner.class), 指定单元测试的驱动
五、泛型依赖注入
entity增加Product,Category dao层增加一个BaseDao,并定义好通用的save()方法,新增ProductDao和CategoryDao
代码语言:javascript复制public abstract class BaseDao<T> {
public abstract void save();
}
代码语言:javascript复制@Repository
public class ProductDao extends BaseDao<Product> {
@Override
public void save() {
System.out.println(this.getClass().getName() "的save()方法被调用");
}
}
代码语言:javascript复制@Repository
public class CategoryDao extends BaseDao<Category> {
@Override
public void save() {
System.out.println(this.getClass().getName() "的save()方法被调用");
}
}
service层增加ProductService,CategoryService
代码语言:javascript复制@Service
public class ProductService {
@Autowired
private ProductDao productDao;
public void save(){
productDao.save();
}
}
代码语言:javascript复制@Service
public class CategoryService {
@Autowired
private CategoryDao categoryDao;
public void save(){
categoryDao.save();
}
}
创建一个xml配置文件generic.xml
代码语言:javascript复制<?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>
新建测试类
代码语言:javascript复制@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();
}
}
执行测试类
由于调用的ProducDao和CategoryDao其实都是调用的BaseDao,新建一个BaseService方法,调用BaseDao
代码语言:javascript复制public class BaseService<T> {
@Autowired
private BaseDao<T> baseDao;
public void save(){
System.out.println(baseDao);
baseDao.save();
}
}
修改ProductService和CategoryService
代码语言:javascript复制@Service
public class CategoryService extends BaseService<Category> {
}
代码语言:javascript复制@Service
public class ProductService extends BaseService<Product>{
}
再次执行测试,同样可以成功执行save方法,那么Spring是如何确定执行的类?
ProductService继承了BaseService<Product>, BaseService中调用了BaseDao,因此通过BaseDao<Product>就可以找到ProductDao,因为ProductDao继承了BaseDao<>
Spring可以使用带泛型的父类类型来确定这个子类的类型。