第一个纯注解开发项目

创建类

创建一个数据层、业务层的类

数据层:

1
2
3
4
5
6
7
8
9
10
11
12
package dao.impl;

import dao.BookDao;
import org.springframework.stereotype.Repository;

@Repository("bookDao")
public class BookDaoImpl implements BookDao {
@Override
public void save() {
System.out.println("BookDaoImpl...");
}
}

业务层:

1
2
3
4
5
6
7
8
9
10
11
12
package service.impl;

import org.springframework.stereotype.Service;
import service.BookService;

@Service("bookService")
public class BookServiceImpl implements BookService {
@Override
public void save() {
System.out.println("BookServiceImpl...");
}
}

细心的同学可能就发现了,这个类名上面有一个注解,对的,这个注解是用于替代bean配置文件的,下面会细说。

创建配置类

既然是纯注解开发,之前的applicationContext.xml的配置还是得有,我们将配置的方式转换成注解来实现,我们需要一个配置类,来装载我们的配置。

1
2
3
4
5
6
7
8
9
10
package config;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;

@Configuration
@ComponentScan({"dao","service"})
public class SpringConfig {

}

这个类和普通的类的区别,只是类上面多了两个注解,其余都一样。

我们以往在applicationContext.xml配置的方式是这样的:

1
<bean id="bookDao" class="dao.impl.BookDaoImpl"/>

这个配置可以分成三个部分来理解:

  1. 标签<bean />
  2. id的值:定位该bean的位置
  3. class的值:表示该bean对应的类是哪一个

刚刚在上面创建类所说到的类名上面有一个注解,他们分别是@Service("bookService")@Repository("bookDao"),这个注解就是替代bean的配置方式的。

  • @Service对<bean />标签

  • bookService对应id的值,用于定于该bean的位置

  • 注解放的位置对应该类的bean

Q:那我们要怎么找到这个bean呢?难道给全部的类都添加索引,然后在整个项目搜索吗?

A:不完全是,但也说对了一半,之前我们是用xml文件配置的,整个项目的bean都在这个文件里,找起来也方便,但是注解的话,分散在各个类里面,想找也不太好找,这时候我们就需要配置类了,配置类可以帮我们定义bean的检索范围,缩小范围,让spring找起来更快。

spring的配置类注解

spring配置类有两个注解,一个表示当前的类是配置类,另一个表示扫描bean的范围。

  1. @Configuration:表示是一个bean配置类
  2. @ComponentScan({"dao","service"}):表示扫描bean的位置范围,包的位置

程序入口

以往我们使用applicationContext.xml文件时,我们都是使用new ClassPathXmlApplicationContext("applicationContext.xml")创建Ioc容器的,但现在不使用applicationContext.xml来配置bean了,我们应该换一个方式了,既然spring3.0说纯注解开发,那肯定就有对应的方法实现,spring3.0给我们新增了一个叫AnnotationConfigApplicationContext()方法,这个方法的作用就是找到配置类,就是我们刚刚新建的SpringConfig配置类,使用方法如下:

1
ApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig.class);

在创建好Ioc容器后,使用方法和之前的一模一样啦

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import config.SpringConfig;
import dao.BookDao;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import service.BookService;

public class App {

public static void main(String[] args) {

// ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
//Spring 3.0开启了纯注解开发
ApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig.class);
// 方式一:使用id、name方式获取bean
BookService bookService = (BookService) ctx.getBean("bookService");
bookService.save();
// 方式二:使用类获取bean
BookDao bean = ctx.getBean(BookDao.class);
bean.save();
}
}

配置当前类是bean对象,有三个注解都可以实现,三个注解表示的意思是一样的,我们常常将他们区分开始为了更好的理解代码,理解当前的bean是作用,提高开发效率。

  • @Controller:控制器(注入服务)。
  • @Repository:dao持久层(实现dao访问),标注数据访问组件。
  • @Service:service服务层(注入dao),处理业务逻辑。
  • @component: 标注一个类为Spring容器的Bean,(把普通pojo实例化到spring容器中,相当于配置文件中的<bean id="" class=""/>

Bean的管理

作用范围

注解中也是有单例模式和非单例模式设置的,在需要的bean上对应的类设置@Scope即可。

  • @Scope(“prototype”) :非单例模式
  • @Scope(“singleton”): 单例模式

案例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package service.impl;

import dao.BookDao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Service;
import service.BookService;

@Service("bookService")
@Scope("prototype")
public class BookServiceImpl implements BookService {
@Autowired
@Qualifier("bookDao")
BookDao bookDao;

@Override
public void save() {
System.out.println("BookServiceImpl...");
bookDao.save();
}
}

生命周期

  • @PostConstruct:Ioc容器创建执行的事件
  • @PreDestroy:Ioc容器销毁执行的事件
1
2
3
4
5
6
7
8
9
10
11
12
13
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;

// 初始化
@PostConstruct
public void init(){
System.out.println("init....");
}
// 销毁
@PreDestroy
public void destroy(){
System.out.println("destroy...");
}
可能会遇到的错误

请注意,@PostConstruct@PreDestroy注释都是Java EE的一部分。而且由于Java EE在Java
9中已被弃用,而在Java 11中已被删除,因此我们必须添加一个附加依赖项才能使用这些注解:

1
2
3
4
5
<dependency>
<groupId>javax.annotation</groupId>
<artifactId>javax.annotation-api</artifactId>
<version>1.3.2</version>
</dependency>

依赖注入

初始化项目:

创建一个简单的项目结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
E:.
App.java # 程序入口

├─config
SpringConfig.java # Spring注解配置类

├─dao # 数据层
BookDao.java

└─impl
BookDaoImpl.java

└─service # 服务层
BookService.java

└─impl
BookServiceImpl.java

自动装配

我们先不给bookDao进行注入,运行一下看看结果

1
2
3
4
5
6
7
8
9
10
11
12
13
@Service("bookService")
public class BookServiceImpl implements BookService {
BookDao bookDao;

public void setBookDao(BookDao bookDao) {
this.bookDao = bookDao;
}
@Override
public void save() {
System.out.println("BookServiceImpl...");
bookDao.save();
}
}

在没有给bookDao配置注入时,运行项目会出现以下错误,指针为空,没有找到bookDao实例。

1
2
3
4
BookServiceImpl...
Exception in thread "main" java.lang.NullPointerException
at service.impl.BookServiceImpl.save(BookServiceImpl.java:16)
at App.main(App.java:11)

添加注解

  • @Autowired:配置自动装配模式(按类型)
  • @Qualifier(""):指定名称装配bean,该注解必须配合@Autowired注解使用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package service.impl;

import dao.BookDao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Service;
import service.BookService;

@Service("bookService")
public class BookServiceImpl implements BookService {
@Autowired
@Qualifier("bookDao")
BookDao bookDao;

@Override
public void save() {
System.out.println("BookServiceImpl...");
bookDao.save();
}
}
  • 注意:自动装配基于反射设计创建对象并暴力反射对应属性为私有属性初始化数据,因此无需提供setter方法
  • 注意:自动装配建议使用无参构造方法创建对象(默认),如果不提供对应构造方法,请提供唯一的构造方法

简单类型注入

简单类型注入也不难,在变量上方添加一下 @Value("")即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package dao.impl;

import dao.BookDao;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Repository;

@Repository("bookDao")
public class BookDaoImpl implements BookDao {
@Value("123")
String name;

@Override
public void save() {
System.out.println("BookDaoImpl..."+name);
}
}

加载properties文件

我们开发项目时,我们的需要初始化的数据可能有点多,这时我们一般都会将初始化的数据存放到properties文件中,然后在进行数据初始化时,使用注入方式就会方便许多。

  1. 新建proprietary文件
  2. 在Spring配置类中加载properties文件
  3. 在变量上添加注解
  4. 变量调用

新建properties文件

在resource文件夹下新建一个data.properties资源文件,文件内存放一个变量name

1
name=666

加载properties

  • @PropertySource("classpath:data.properties"):使用@PropertySource注解加载properties文件

注意:路径仅支持单一文件配置,多文件请使用数组格式配置,不允许使用通配符*

1
2
3
4
5
6
7
8
9
10
11
package config;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;

@Configuration
@PropertySource("classpath:data.properties")
@ComponentScan({"dao","service"})
public class SpringConfig {
}

注入

在需要引入到properties的变量,使用${param}即可。案例如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package dao.impl;

import dao.BookDao;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Repository;

@Repository("bookDao")
public class BookDaoImpl implements BookDao {
@Value("${name}")
String name;
@Override
public void save() {
System.out.println("BookDaoImpl..."+name);
}
}

// 输出结果:
// BookServiceImpl...
// BookDaoImpl...123

第三方bean管理

管理第三方bean有两种方法,第一种是导入式,使用@Import将其他类的配置导入到当前的配置类中。

第二种是扫描式,使用@Configuration再定义一个独立的配置类,然后在该类下都配置第三方的bean。

但在平常的开发中,推荐使用第一种导入式,下面讲解第一种方式。

项目结构

1
2
3
4
5
  App.java # 程序入口

└─config # 配置包
JdbcConfig.java # 第三方bean
SpringConfig.java # 主要的配置类
Maven导包

导入spring需要的依赖包。

Druid是阿里巴巴开源的一个数据库连接池管理器。

1
2
3
4
5
6
7
8
9
10
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.2.15</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.10.RELEASE</version>
</dependency>

新建一个JdbcConfig类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package config;

import com.alibaba.druid.pool.DruidDataSource;
import org.springframework.context.annotation.Bean;
import javax.sql.DataSource;

public class JdbcConfig {

// 1. 定义一个方法获取要管理的对象
// 2.添加@bean, 表示当前的方法的返回值是一个bean
@Bean
public DataSource dataSource(){
DruidDataSource ds = new DruidDataSource();
ds.setDriverClassName("com.mysql.jdbc.Driver");
ds.setUrl("jdbc:mysql://localhost:3306/spring_db");
ds.setUsername("root");
ds.setPassword("root");
return ds;
}
}

主要配置类

该类是主要的配置类,使用@Import({JdbcConfig.class})导入其他类的配置项

1
2
3
4
5
6
7
package config;
import org.springframework.context.annotation.*;

@Configuration
@Import({JdbcConfig.class})
public class SpringConfig {
}
  • @Import():使用时,如果有多个配置类,使用数组包括起来,比如@Import({JdbcConfig.class,JdbcConfig2.class})
第二种扫描式简单描述

JdbcConfig配置类信息:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package config;

import com.alibaba.druid.pool.DruidDataSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import javax.sql.DataSource;
@Configuration
public class JdbcConfig {

// 1. 定义一个方法获取要管理的对象
// 2.添加@bean, 表示当前的方法的返回值是一个bean
@Bean
public DataSource dataSource(){
DruidDataSource ds = new DruidDataSource();
ds.setDriverClassName("com.mysql.jdbc.Driver");
ds.setUrl("jdbc:mysql://localhost:3306/spring_db");
ds.setUsername("root");
ds.setPassword("root");
return ds;
}
}

JdbcConfig类中止需要添加@Configuration注解即可,表明该类是配置类。

SpringConfig主要配置类:

1
2
3
4
5
6
7
package config;
import org.springframework.context.annotation.*;

@Configuration
@ComponentScan("config")
public class SpringConfig {
}

在SpringConfig中添加扫描范围,扫描配置类所在的包,加载对应的配置类信息

程序入口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
import config.SpringConfig;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

import javax.sql.DataSource;

public class App {

public static void main(String[] args) {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig.class);
DataSource bean = ctx.getBean(DataSource.class);
System.out.println(bean);
}
}

// 输出结果:
//{
// CreateTime:"2023-01-09 20:44:33",
// ActiveCount:0,
// PoolingCount:0,
// CreateCount:0,
// DestroyCount:0,
// CloseCount:0,
// ConnectCount:0,
// Connections:[
// ]
// }

第三方bean依赖注入

我们在管理第三方bean时,经常需要初始化数据库等等,在初始化时,我们需要一些数据库的链接信息之类的,我们需要统一管理这些信息,就需要依赖注入了。

简单类型注入

在bean的配置类中,添加属性变量,使用@Value()注解配置简单类型的数据即可,可以配置上文使用的properties文件加载一起使用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
package config;

import com.alibaba.druid.pool.DruidDataSource;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import javax.sql.DataSource;
@Configuration
public class JdbcConfig {

// 1. 定义一个方法获取要管理的对象
// 2.添加@bean, 表示当前的方法的返回值是一个bean
@Value("com.mysql.jdbc.Driver")
private String driver;
@Value("jdbc:mysql://localhost:3306/spring_db")
private String url;
@Value("root")
private String userName;
@Value("root")
private String password;
@Bean
public DataSource dataSource(){
DruidDataSource ds = new DruidDataSource();
ds.setDriverClassName(driver);
ds.setUrl(url);
ds.setUsername(userName);
ds.setPassword(password);
return ds;
}
}

引用类型注入

项目结构
1
2
3
4
5
6
7
8
9
10
11
12
E:.
App.java # 程序入口

├─config
SpringConfig.java # Spring注解配置类
JdbcConfig.java # 第三方bean

├─dao # 数据层
BookDao.java

└─impl
BookDaoImpl.java

添加BookDao为bean对象

在添加引用类型的注入前,你首先需要将注入的类型添加为bean对象,不然是无法进行注入的。

在BookDaoImpl添加@Repository注解。

回顾一下。@Controller@Repository@Service都是表示配置当前类为bean对象。

1
2
3
4
5
6
7
8
9
10
11
12
package dao.impl;

import dao.BookDao;
import org.springframework.stereotype.Repository;

@Repository
public class BookDaoImpl implements BookDao {
@Override
public void save() {
System.out.println("BookDaoImpl...");
}
}

扫描BookDao所在的包

使用@ComponentScan("dao")注解扫描,目的是为了查找有添加@Repository注解的,

1
2
3
4
5
6
7
8
package config;
import org.springframework.context.annotation.*;

@Configuration
@ComponentScan("dao")
@Import(JdbcConfig.class)
public class SpringConfig {
}

添加引用类型

只需要在dataSource()方法中添加需要注入的类型参数即可,Spring会帮我们自动装配

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package config;

import com.alibaba.druid.pool.DruidDataSource;
import dao.BookDao;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import javax.sql.DataSource;
@Configuration
public class JdbcConfig {

// 1. 定义一个方法获取要管理的对象
// 2.添加@bean, 表示当前的方法的返回值是一个bean

@Bean
public DataSource dataSource(BookDao bookDao){
bookDao.save();
DruidDataSource ds = new DruidDataSource();
// 相关配置
return ds;
}
}

注解开发总结

功能 XML配置 注解
定义bean bean标签
- id属性
- class属性
@Component
- @Controller
- @Service
- @Repository
@ComponentScan
设置依赖注入 setter注入(set方法)
  -引用/简单
构造器注入(构造方法)
- 引用/简单
自动装配
@Autowired
- @Qualifier
@Value
配置第三方bean bean标签
静态工厂、实例工厂、FactoryBean
@Bean
作用范围 scope属性 @Scope
生命周期 标准接口
- init-method
- destroy-method
@PostConstructor
@PreDestroy

@Component有三种写法,功能都是一样的,但为了提高开发者的编写的逻辑代码,使用以下三个来区分不同bean。

  • @Controller():一般用于普通的bean
  • @Repository():一般用于数据层的bean
  • @Service():一般用于服务层的bean

@Autowired:配置自动装配模式(按类型),在引用类型上添加注解,会根据该类型自动查找bean对象匹配上。

@Qualifier(""):指定名称装配bean,该注解必须配合@Autowired注解使用。在引用类型有多个时,可以使用指定的名称。

@Scope的作用范围分为单例模式和非单例模式

  • @Scope(“prototype”) :非单例模式
  • @Scope(“singleton”): 单例模式
  • @PostConstruct:Ioc容器创建执行的事件
  • @PreDestroy:Ioc容器销毁执行的事件

使用@PostConstruct 和@PreDestroy时需要注意一个问题,@PostConstruct@PreDestroy注解都是Java EE的一部分。而且由于Java EE在Java 9中已被弃用,而在Java 11中已被删除,因此我们必须添加一个附加依赖项才能使用这些注解:

1
2
3
4
5
<dependency>
<groupId>javax.annotation</groupId>
<artifactId>javax.annotation-api</artifactId>
<version>1.3.2</version>
</dependency>