前言
从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 {@Overridepublic 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/beanshttp://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/contexthttp://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/beanshttp://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/contexthttp://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/beanshttp://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/contexthttp://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 {@Overridepublic 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 {@Overridepublic 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 {@Overridepublic 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;@Overridepublic void say() {userDao.say();System.out.println("UserServiceImpl....");}@PostConstructpublic void init(){System.out.println("执行初始化");}@PreDestroypublic 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 {@Autowiredprivate UserDao userDao;@Overridepublic void say() {userDao.say();System.out.println("UserServiceImpl....");}@PostConstructpublic void init(){System.out.println("执行初始化");}@PreDestroypublic 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;@Overridepublic void say() {userDao.say();System.out.println("UserServiceImpl....");}@PostConstructpublic void init(){System.out.println("执行初始化");}@PreDestroypublic void destroy(){System.out.println("执行销毁");}
}
- 使用@Qualifier注解必须依赖Autowired注解,因此不能删除
- 可以使用@Resource注解,也是按照名称进行依赖注入,但要区分两者之间的区别
@Component("userService")
public class UserServiceImpl implements UserService {@Resource(name = "userDao")private UserDao userDao;@Overridepublic void say() {userDao.say();System.out.println("UserServiceImpl....");}@PostConstructpublic void init(){System.out.println("执行初始化");}@PreDestroypublic 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;@Overridepublic void say() {userDao.say();System.out.println("UserServiceImpl....");System.out.println(hello);}@PostConstructpublic void init(){System.out.println("执行初始化");}@PreDestroypublic 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;@Overridepublic void say() {userDao.say();System.out.println("UserServiceImpl....");System.out.println(hello);}@PostConstructpublic void init(){System.out.println("执行初始化");}@PreDestroypublic 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 {@Beanpublic 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 {@Beanpublic 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 |