前言
从spring2.0开始,spring逐步提供了各种各样的注解,到了spring2.5注解就比较完善了,到了spring3.0就是纯注解开发
使用注解进行开发可以简化开发步骤,提升开发效率,但是我们需要了解底层原理,接下来我将介绍如何使用Spring的注解来简化开发。
注解开发定义bean
使用注解定义bean和在xml配置文件中定义bean的效果是相同的,只不过是一种方式的不同表现,因此它们应当具有相同或相似的结构。
准备工作
第一步:创建maven项目,添加依赖
pom.xml
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.10.RELEASE</version>
</dependency>
第二步:接口和实体类
UserDao
public interface UserDao {
public void say();
}
UserDaoImpl
@Component("userDao")
public class UserDaoImpl implements UserDao {
@Override
public void say() {
System.out.println("UserDaoImpl start...");
}
}
UserService
public interface UserService {
public void say();
}
UserServiceImpl
第三步:创建spring配置文件
<?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">
</beans>
我这个spring配置文件,包含了基本的spring配置头信息,包括了context命名空间,但是不包括aop的命名空间。
第四步:创建测试类
APP
/**
* Hello world!
*
*/
public class App
{
public static void main( String[] args )
{
ApplicationContext context =new ClassPathXmlApplicationContext("spring.xml");
UserDao userDao = (UserDao) context.getBean("userDao");
userDa.say();
}
}
使用xml定义bean
使用xml配置文件,需要定义<bean>标签
spring.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">
<bean id="userDaoImpl" class="org.example.dao.impl.UserDaoImpl"/>
</beans>
使用注解定义bean
使用注解定义bean,本质上就是使用注解代替<bean>标签,完成bean的定义的方式
第一步:在spring.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="org.example"/>
</beans>
- 无论是xml定义bean还是注解定义bean,本质上都是spring容器启动后扫描spring的配置文件来进行bean的定义和初始化的,因此spring配置文件需要保留,但是由于定义一堆配置既麻烦又不简洁,因此出现了注解代替<bean>标签的配置方式。
- 开启注解扫描之后,我们可以扫描spring中的注解。
- base-package后面跟的是包名的话可以扫描当前包及其子包中是否存在注解
第二步:在要管理的实体类上加上@Component注解
UserDaoImpl
@Component("userDao")
public class UserDaoImpl implements UserDao {
@Override
public void say() {
System.out.println("UserDaoImpl start...");
}
}
- 此时就不需要定义<bean>标签了
- 如果使用注解不提供名字(与配置文件中bean标签的id相似),则需要使用类型进行获取对应的bean对象。
纯注解开发
- Spring3.0升级了纯注解开发模式,使用Java类代替配置文件,开启Spring快速开发赛道
- 为了变成纯注解的开发模式,xml配置文件就需要里面没有其他多余代码,因此需要换一种形式表现xml配置文件中的扫描注解的标签,在java代码中主要书写的是类,因此可以用类来替代配置文件(简单理解,就是使用配置类代替配置文件,完成bean的定义)
第一步:将spring.xml删除
其实也不用删除,不使用即可,将后缀改为bak
第二步:声明Java配置类
SpringConfig
@Configuration
@ComponentScan("org.example")
public class SpringConfig {
}
- @Configuration注解声明一个类,这个类的作用就和spring.xml文件的作用是一样的了
- @Configuration注解用于设定当前类为配置类
- @ComponentScan注解,实际上就是代替spring.xml配置文件中的开启扫描配置
- @ComponentScan注解用于设定扫描路径,此注解只能添加一次,多个数据请用数组格式
可以理解为使用一个Java类代替了xml配置文件,它俩起的作用是一样的。
基于注解的bean管理
- 在实体类上方添加@Scope注解,主要的参数有
- singleton:单例
- prototype:多例
单例
@Scope("singleton")
@Component("userDao")
public class UserDaoImpl implements UserDao {
@Override
public void say() {
System.out.println("UserDaoImpl start...");
}
}
使用的bean是同一个
public class App
{
public static void main( String[] args )
{
ApplicationContext context =new AnnotationConfigApplicationContext(SpringConfig.class);
UserDao userDao = (UserDao) context.getBean("userDao");
UserDao userDao1 = (UserDao) context.getBean("userDao");
System.out.println(userDao);
System.out.println(userDao1);
}
}
测试效果:
地址相同,为同一个bean对象
多例
@Scope("prototype")
@Component("userDao")
public class UserDaoImpl implements UserDao {
@Override
public void say() {
System.out.println("UserDaoImpl start...");
}
}
使用的bean不是同一个
测试效果:
地址不同,不是同一个bean对象
bean的生命周期
自定义方法,在方法上添加@PostConstruct注解和@PreDestory注解
@Component("userService")
public class UserServiceImpl implements UserService {
@Resource(name = "userDao")
private UserDao userDao;
@Override
public void say() {
userDao.say();
System.out.println("UserServiceImpl....");
}
@PostConstruct
public void init(){
System.out.println("执行初始化");
}
@PreDestroy
public void destroy(){
System.out.println("执行销毁");
}
}
测试效果:
只有初始化方法执行了,为什么销毁方法没有执行?
因为:JVM虚拟机执行完成后直接关闭,没有给Spring的IOC容器关闭的时机,导致销毁方法没有执行(销毁方法的时机在容器关闭时),因此需要手动设置关闭钩子或在代码执行前手动关闭Spring容器。
/**
* Hello world!
*
*/
public class App
{
public static void main( String[] args )
{
AnnotationConfigApplicationContext context =new AnnotationConfigApplicationContext(SpringConfig.class);
UserDao userDao = (UserDao) context.getBean("userDao");
UserDao userDao1 = (UserDao) context.getBean("userDao");
System.out.println(userDao);
System.out.println(userDao1);
context.close();
}
}
注意:
- close():方法属于AnnotationConfigApplicationContext和ClassPathXmlApplicationContext实现类,ApplicationContext接口中是不存在这个方法的,因此不是使用接口接收实现类的方式调用该方法。
- close():是强制关闭IOC容器,因此必须放在要执行的代码的最后位置,因为该方法的下方不能执行任何代码
- 除了使用close()方法关闭容器的方式外,还可以设置关闭钩子方法,它会监听JVM虚拟机的关闭的时机,自动在JVM关闭前关闭IOC容器,它的位置可以是任意的
public class App
{
public static void main( String[] args )
{
AnnotationConfigApplicationContext context =new AnnotationConfigApplicationContext(SpringConfig.class);
UserDao userDao = (UserDao) context.getBean("userDao");
UserDao userDao1 = (UserDao) context.getBean("userDao");
context.registerShutdownHook();
System.out.println(userDao);
System.out.println(userDao1);
}
}
测试效果:close()和registerShutdownHook() 的效果是一样的
额外注意
@PostConstruct和@PreDestroy注解位于java.xml.ws.annotation包,该包是Java EE的模块一部分。J2EE已经在Java 9中被弃用,并且计划在Java 11中删除它。我使用的JDK1.8,如果大家使用的是JDK11或17以及更高版本,可能无法使用这两个注解。
解决方案:为pom.xml添加额外的依赖
<dependency>
<groupId>javax.annotation</groupId>
<artifactId>javax.annotation-api</artifactId>
<version>1.3.2</version>
</dependency>
依赖注入
自动装配
spring注解开发,是为了加速开发,所以对原始的功能进行了阉割,如setter注入。现在使用自动装配的形式完成依赖注入
可以理解为:原先我们需要现在spring的配置文件中声明bean和bean之间的依赖关系;并且需要在要注入对象的实体类中声明set方法。现在使用注解,只需要使用@Component注解声明bean被IOC容器管理,并且使用@Autowired来注入实体类需要的对象,就可以完全替代在spring.xml的配置文件中进行一系列操作。
创建一个Service类,需要注入Dao类
UserService
public interface UserService {
public void say();
}
UserServiceImpl
@Component("userService")
public class UserServiceImpl implements UserService {
@Autowired
private UserDao userDao;
@Override
public void say() {
userDao.say();
System.out.println("UserServiceImpl....");
}
@PostConstruct
public void init(){
System.out.println("执行初始化");
}
@PreDestroy
public void destroy(){
System.out.println("执行销毁");
}
}
APP
/**
* Hello world!
*
*/
public class App
{
public static void main( String[] args )
{
AnnotationConfigApplicationContext context =new AnnotationConfigApplicationContext(SpringConfig.class);
UserService userService =(UserService) context.getBean("userService");
userService.say();
context.close();
}
}
测试效果:
- 本质上:取代了set方法,底层使用了暴力反射机制,强制给属性注入值。
- 如果类型不唯一,可以按照名称进行注入。
@Component("userService")
public class UserServiceImpl implements UserService {
@Autowired
@Qualifier(value = "userDao")
private UserDao userDao;
@Override
public void say() {
userDao.say();
System.out.println("UserServiceImpl....");
}
@PostConstruct
public void init(){
System.out.println("执行初始化");
}
@PreDestroy
public void destroy(){
System.out.println("执行销毁");
}
}
- 使用@Qualifier注解必须依赖Autowired注解,因此不能删除
- 可以使用@Resource注解,也是按照名称进行依赖注入,但要区分两者之间的区别
@Component("userService")
public class UserServiceImpl implements UserService {
@Resource(name = "userDao")
private UserDao userDao;
@Override
public void say() {
userDao.say();
System.out.println("UserServiceImpl....");
}
@PostConstruct
public void init(){
System.out.println("执行初始化");
}
@PreDestroy
public void destroy(){
System.out.println("执行销毁");
}
}
- @Qualifier(value = "userDao"):是Spring提供的注解
- @Resource(name = "userDao"):是JDK提供的注解
简单类型注入
使用@Value注解进行依赖注入
@Component("userService")
public class UserServiceImpl implements UserService {
@Resource(name = "userDao")
private UserDao userDao;
@Value("Hello World")
private String hello;
@Override
public void say() {
userDao.say();
System.out.println("UserServiceImpl....");
System.out.println(hello);
}
@PostConstruct
public void init(){
System.out.println("执行初始化");
}
@PreDestroy
public void destroy(){
System.out.println("执行销毁");
}
}
- 简单类型的注入可以通过动态加载properties文件进行
- 通过配置的形式加载properties文件
配置文件中要注意空格,因为空格也算作一个数据
@Component("userService")
@PropertySource("jdbc.properties")
public class UserServiceImpl implements UserService {
@Resource(name = "userDao")
private UserDao userDao;
@Value("${ceshi}")
private String hello;
@Override
public void say() {
userDao.say();
System.out.println("UserServiceImpl....");
System.out.println(hello);
}
@PostConstruct
public void init(){
System.out.println("执行初始化");
}
@PreDestroy
public void destroy(){
System.out.println("执行销毁");
}
}
- @PropertySource("jdbc.properties")
- 指定配置文件的位置,默认查找classpath路径
- @Value注解中使用${}引入配置文件重点键值对数据
- ${键名}
第三方bean的管理
- 因为引入第三方的bean,因为不能将配置写在其源代码中,因此只能编程的方式去获取。
- 定义的方法名建议取成你想管理的bean的id名称
接下来已管理第三方的数据源为例,演示如何使用注解的方式管理一个第三方的bean
第一步:导入数据源的依赖
我使用的是阿里巴巴的德鲁伊(druid)数据源,也可以换成其他的数据源,只是配置方式略有不同
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.16</version>
</dependency>
第二步:在配置类中声明第三方bean
@Configuration
@ComponentScan("org.example")
public class SpringConfig {
@Bean
public DataSource dataSource(){
DruidDataSource ds = new DruidDataSource();
ds.setDriverClassName("com.mysql.cj.jdbc.Driver");
ds.setUrl("jdbc:mysql://localhost:3306/ssm_vue_demo");
ds.setUsername("root");
ds.setPassword("root");
return ds;
}
}
- 定义一个第三方bean一般按照类型进行使用,使用名称的机会不大。
- 获取第三方bean :就是在配置类中使用Bean注解定义一个方法,方法的返回值就是要获取的bean对象。
测试效果:
public class App
{
public static void main( String[] args )
{
AnnotationConfigApplicationContext context =new AnnotationConfigApplicationContext(SpringConfig.class);
DataSource dataSource = context.getBean(DataSource.class);
System.out.println(dataSource);
}
}
数据源和连接信息分离
需要进一步简化spring配置,因为数据库连接池信息属于jdbc配置,直接写在spring的配置中不规范,可以拆分出去
使用独立的配置类管理第三方bean
@Configuration
public class JdbcConfig {
@Bean
public DataSource dataSource(){
DruidDataSource ds = new DruidDataSource();
ds.setDriverClassName("com.mysql.cj.jdbc.Driver");
ds.setUrl("jdbc:mysql://localhost:3306/ssm_vue_demo");
ds.setUsername("root");
ds.setPassword("root");
return ds;
}
}
两种方式
方式1:
- 将独立的配置类加入核心配置
- 扫描式
@Configuration
//使用Component注解扫描配置类所在的包,加载对应的配置类信息,我这里扫描了全包,所有可以扫描到@Configuration注解
@ComponentScan("org.example")
public class SpringConfig {
}
这种方式不推荐,因为配置类过多时,容器产生错误观察
方式2:
- 将独立配置类加入核心配置
- 导入式
@Configuration
//使用@Import注解手动加入配置类到核心配置,此注解只能添加一次,多个数据请用数组格式
@Import(JdbcConfig.class)
public class SpringConfig {
}
为第三方bean注入资源
- 简单类型注入:使用@Value注解
- 引用类型注入:只需要为bean定义方法设置形参即可,容器会根据类型自动装配对象
注解开发总结
功能 | XML配置 | 注解 |
定义bean | bean标签
| @Component
@ComponentScan |
设置依赖注入 | setter注入(set方法)
构造器注入(构造方法)
自动装配 | @Autowired
@Value |
配置第三方bean | bean标签 静态工厂、实例工厂、FactoryBean | @Bean |
作用范围 | scope属性 | @Scope |
生命周期 | 标准接口
| @PostConstrutor @PreDestroy |