Spring系列(二)——Spring的注解开发
前言
在上一篇的文章中,我们介绍了spring使用配置文件的方式进行IOC和DI的特性的使用,本篇文章将介绍另一种相对来说更加简便的方式:注解,来讲解我们如何利用注解进行我们的开发,文章会对Spring的原始注解和新注解(spring后面版本推出的注解)分开进行讲解,希望对各位读者有所参考。
说在前面
Spring是轻代码而重配置的框架,配置比较繁重,影响开发效率,所以注解开发是一种趋势,注解代替xml配置文件可以简化配置,提高开发效率,熟练掌握常用的注解进行开发也是后端开发人员所需要具备的能力。
一、Spring的原始注解
Spring的原始注解主要是替代<Bean>的配置
注解 | 作用 |
---|---|
@Component | 使用在类上用于实例化Bean |
@Controller | 使用在web层类上用于实例化Bean |
@Service | 使用在service层类上用于实例化Bean |
@Repository | 使用在dao层类上用于实例化Bean |
@Autowired | 使用在字段上用于根据类型依赖注入 |
@Qualifier | 结合@Autowired一起使用用于根据名称进行依赖注入 |
@Resource | 相当于@Autowired+@Qualifier,按照名称进行注入 |
@Value | 注入普通属性 |
@Scope | 标注Bean的作用范围 |
@PostConstruct | 使用在方法上标注该方法是Bean的初始化方法 |
@PreDestroy | 使用在方法上标注该方法是Bean的初销毁法 |
我们可以看到,其实上面的注解对应的都是我们在上一篇文章中讲到的配置文件的操作,下面我们就用这些注解进行开发吧。
注解的使用扫描
在使用注解前,我们需要先在spring的核心配置文件中配置组件扫描,作用是指定哪个包及其子包下的Bean需要进行扫描以便识别使用注解配置的类、字段和方法。
<!--配置spring的组件扫描,也就是告知spring我们在哪些地方使用了注解进行开发-->
<context:component-scan base-package="com.qiqv"></context:component-scan>
@Component注解
我们可以使用@Component
注解来用于实例化Bean
下面的代码等价于<bean id="userService" class="com.qiqv.service.impl.UserServiceImpl"></bean>
配置
@Component("userSerivce")
public class UserServiceImpl implements UserService {
public void save() {
System.out.println("userServiceImpl 调用了save方法 。。。");
}
}
@Controller、@Service、@Repository注解
这三者其实和@Component
注解其实是一样的,但是为了更好地区分注解的使用范围,我们会对web层、service层和dao层的bean实例配置分别使用@Controller
、@Service
、@Repository
注解来代替@Component
注解的使用
@Repository("userDao")
public class UserDaoImpl implements UserDao {
public void save() {
System.out.println("save方法被调用了...");
}
}
@Service("userService")
public class UserServiceImpl implements UserService {
public void save() {
ApplicationContext app = new ClassPathXmlApplicationContext("applicationContext.xml");
UserDao userDao = (UserDao) app.getBean("userDao");
System.out.println("userServiceImpl 调用了save方法 。。。");
userDao.save();
}
}
@Controller
public class UserController {
public static void main(String[] args) {
ApplicationContext app = new ClassPathXmlApplicationContext("applicationContext.xml");
UserService userService = (UserService) app.getBean("userService");
}
}
@Scope注解
我们可以使用@Scope
注解来标识Bean的范围
@Service("userService")
@Scope("prototype")
public class UserServiceImpl implements UserService {
public void save() {
...
}
}
@Autowired和@Qulifier注解
@Autowired
注解和@Qulifier
注解是用于依赖注入的处理的,前者会根据接口的类型找到对应的实现类,但如果接口存在有多个实现子类的话,那么我们就需要结合@Qulifler
注解来实现类型+名称的绑定,找到我们真正需要使用的实现类。
@Service("userService")
public class UserServiceImpl implements UserService {
@Autowired
@Qualifier("userDao")
private UserDao userDao;
public void save() {
System.out.println("userServiceImpl 调用了save方法 。。。");
userDao.save();
}
}
@Repository("userDao")
public class UserDaoImpl implements UserDao {
public void save() {
System.out.println("save方法被调用了...");
}
}
@Resource注解
@Resource
注解其实很好理解,它就是@Autowired
和@Qulifiler
两个注解的结合。
@Service("userService")
public class UserServiceImpl implements UserService {
@Resource(name = "userDao")
private UserDao userDao;
public void save() {
...
}
}
@Repository("userDao")
public class UserDaoImpl implements UserDao {
public void save() {
...
}
}
@Value注解
我们可以使用@Value
注解来注入普通属性
@Service("userService")
public class UserServiceImpl implements UserService {
@Value("${jdbc.user}")
private String jdbcUsername;
@Value("10")
private Integer age;
@Resource(name = "userDao")
private UserDao userDao;
public void save() {
System.out.println("userServiceImpl 调用了save方法 。。。");
System.out.println("jdbcUsername : "+jdbcUsername +" age : "+age);
userDao.save();
}
}
这里需要注意,如果想要在@value注解中使用${ }
表达式的话,需要现在核心配置文件中引入外部配置文件
<!--引入外部properties配置-->
<context:property-placeholder location="classpath:jdbc.properties"></context:property-placeholder>
@PostConstruct和@PreDestroy注解
这两个注解分别用于标识类实例化的初始和销毁方法
@Service("userService")
public class UserServiceImpl implements UserService {
...
@PostConstruct
public void init(){
System.out.println("userService初始化了...");
}
@PreDestroy
public void destroy(){
System.out.println("userService实例销毁了...");
}
}
二、Spring新注解
使用上面的原始注解还不能全部替代xml配置文件,比如下面的两个配置:
- 加载properties文件的配置:context:property-placeholder
- 组件扫描的配置:context:component-scan
所以,这就引出了新注解的出现:
注解 | 说明 |
---|---|
@Configuration | 用于指定当前类是一个 Spring 配置类,当创建容器时会从该类上加载注解 |
@ComponentScan | 用于指定 Spring 在初始化容器时要扫描的包。 作用和在 Spring 的 xml 配置文件中的 <context:component-scan base-package="com.itheima"/>一样 |
@Bean | 使用在方法上,标注将该方法的返回值存储到 Spring 容器中 |
@PropertySource | @用于加载.properties 文件中的配置 |
@Import | 用于导入其他配置类 |
@PropertySource注解
我们可以使用该注解来加载我们的properties
文件
其作用相当于是使用<context:property-placeholder>
标签导入外部的配置文件
@PropertySource("classpath:jdbc.properties")
public class DatasourceConfiguration {
@Value("${jdbc.driverClass}")
private String driver;
@Value("${jdbc.jdbcUrl}")
private String url;
@Value("${jdbc.user}")
private String username;
@Value("${jdbc.password}")
private String password;
@Bean("dataSource")
public DataSource getDataSource() throws PropertyVetoException {
ComboPooledDataSource dataSource = new ComboPooledDataSource();
dataSource.setDriverClass(driver);
dataSource.setJdbcUrl(url);
dataSource.setUser(username);
dataSource.setPassword(password);
return dataSource;
}
}
在上面的代码中,我们使用@PropertySource
导入外部文件,使用@Value
注入配置文件中的value,最后封装到DataSource后,使用@Bean
注解注入到spring容器中。当然,此时我们还不能直接使用这个数据源配置Bean,因为Spring并不知道我们有这个文件。我们需要借助@Configuration
注解才行
@Configuration 和 @ComponentScan注解
@Configuration
@ComponentScan("com.qiqv")
public class SpringConfiguration {
}
@Configuration
注解表示当前类是配置类,其实就相当于是这个类替代了我们的applicationContext.xml
文件,当我们在创建容器时会去加载这个配置类。而@ComponentScan
注解相当于是配置文件中的<context:component-scan>
注解。我们可以在上面配置包扫描
@Import注解
这个注解相当于是配置文件中的<import>
标签,我们可以用它来导入其他模块的配置文件(非properties文件)
@Configuration
@ComponentScan("com.qiqv")
@Import({DatasourceConfiguration.class})
public class SpringConfiguration {
}
通过@import
注解,我们就把上面的DatasourceConfiguration
类给加载到主配置文件中,进而被容器加载。
spring注解核心配置文件启动类
在原先配置文件方式中,我们是通过new ClassPathXMLApplicationContext()
的方式来指定配置文件路径的,如果我们换成注解后,应该怎么获取上下文对象呢?
答案是使用AnnotationConfigApplicationContext
类来实现
ApplicationContext app = new AnnotationConfigApplicationContext(SpringConfiguration.class);
app.getBean("xxx");
三、Spring整合Junit
我们在spring中进行测试时,每次调用方法时都需要手动获取上下文来加载spring容器,就像下面的代码一样:
ApplicationContext ac = new ClassPathXmlApplicationContext("xxx.xml");
XXXService as = ac.getBean("xxx");
我们能不能让测试类在启动的时候帮我们做好Spring容器的初始化呢?
这自然是可以的,spring为我们提供了整合Junit的依赖,来帮助我们解决这个问题
步骤一:导入spring集成Junit的依赖
<!-- spring整合Junit的依赖 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.0.5.RELEASE</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
步骤二:使用@RunWith
注解和@ContextConfiguration
注解加载和启动spring容器
@RunWith(SpringJUnit4ClassRunner.class)
// 也可以写成这种@ContextConfiguration(value = {"classpath:applicationContext.xml"})
@ContextConfiguration(classes = {SpringConfiguration.class})
public class SpringJunitTest {
}
这里的话大家可能会好奇,@RunWith
和@ContextConfiguration
两个注解到底起到了什么作用呢?
其实,@RunWith
可以看成是一个运行期,我们需要指定下面的测试是在什么环境下进行,使用了SpringJUnit4ClassRunner.class
之后,就相当于告诉了运行器,我们接下来的测试是要在spring的环境下运行,这就势必要在运行时帮我们提前加载spring容器。
但spring容器的加载是需要我们指定配置文件的路径(或者是指定配置类)的,你不指定的话运行器也没那么智能知道你的配置文件在哪。所以我们需要结合@ContextConfiguration
注解来标识我们的配置文件位置,方便运行器在启动过程中顺利加载我们的spring容器。
步骤三:使用代码进行测试
@RunWith(SpringJUnit4ClassRunner.class)
// 也可以写成这种@ContextConfiguration(value = {"classpath:applicationContext.xml"})
@ContextConfiguration(classes = {SpringConfiguration.class})
public class SpringJunitTest {
@Autowired
private DataSource dataSource;
@Test
public void springtest(){
System.out.println(dataSource);
}
}
至此,有关Spring的注解开发就讲解到这里了。想要了解更多内容,可以关注Spring系列的其他文章。
参考资料
@runWith注解起什么作用:
https://www.imooc.com/qadetail/79560