【Spring总结】注解开发
本篇讲的内容主要是基于Spring v2.5的注解来完成bean的定义
之前都是使用纯配置的方式来定义的bean
文章目录
- 前言
- 1. Spring v2.5 注解开发定义bean
- 第一步:在需要定义的类上写上注解`@Component`
- 第二步:在Spring Config中定义扫描包
- 第三步:主方法中测试
- 基于@Component的衍生注解
- 2. Spring v3.0 纯注解开发
- 第一步:创建配置类
- 第二步:导入配置类
- 3. Bean的作用范围(单例/多例)与生命周期
- Bean的作用范围
- Bean的生命周期
- 4.依赖注入
- 引用类型注入
- @Autowire属性默认按类型注入
- 按类型注入反射结果不唯一,按名注入
- 按名注入的标准写法(少用)
- 简单类型注入
- 引入外部的properties文件
- 5. 第三方Bean
- 管理
- 依赖注入
- 简单类型注入
- 引用类型注入
- 6. XML配置比对注解配置
前言
XML配置解耦,而注解是简化开发
1. Spring v2.5 注解开发定义bean
之前都是使用纯配置的方式,在Spring Config中通过<bean id="xxx" class="xxx" />
来定义的bean,从此处开始将根据注解开发定义bean
第一步:在需要定义的类上写上注解@Component
这里有两种写的形式:
@Component
或者是给其定义名称
@Component("bookDao")
通过第一种形式,就通过在getBean()
方法中写类型来获得bean
通过第二种形式,就通过在getBean()
方法中写名称来获得bean
package com.example.demo.dao.impl;
import com.example.demo.dao.BookDao;
import org.springframework.stereotype.Component;
@Component
// @Component("bookDao")
public class BookDaoImpl implements BookDao {
@Override
public void save() {
System.out.println("Book Dao Save...");
}
}
第二步:在Spring Config中定义扫描包
注意,这里也要开启命名空间
然后通过<context:component-scan base-package="...." />
进行定义扫描的包,这里就是扫描com.example.demo.dao
下边的包,修改这个包为com.example.demo
也是可以的
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
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="com.example.demo.dao" />
</beans>
第三步:主方法中测试
package com.example.demo;
import com.example.demo.dao.BookDao;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class DemoApplication {
public static void main(String[] args) {
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
// 第一种定义形式写法,定义@Component写
BookDao bookDao = ctx.getBean(BookDao.class);
System.out.println(bookDao);
// 第二种定义形式写法,定义@Component("bookDao")写
BookDao bookDao1 = (BookDao) ctx.getBean("bookDao");
System.out.println(bookDao1);
}
}
结果均为
com.example.demo.dao.impl.BookDaoImpl@3a52dba3
基于@Component的衍生注解
@Controller
:表现层定义bean
@Service
:业务层定义bean
@Repository
:数据层定义bean
比如,在BookServiceImpl
类上就使用@Service
进行定义;在BookDaoImpl
类上就使用@Repository
进行定义,加括号的规则@Component
类似,调用的方式也和原本的类似
2. Spring v3.0 纯注解开发
既然是纯注解开发,就需要删除之前冗余的这个配置文件,纯注解开发将配置的xml文件转换为了一个配置类
第一步:创建配置类
配置类写法为,实际上就是用两个注解来替代了原本配置文件中的内容:
package com.example.demo.config;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
@Configuration
@ComponentScan("com.example.demo")
public class SpringConfig {
}
配置文件中的内容分别被配置类中的内容替代,如下:
假如想扫描多个包,使用{}
来写即可,如:@ComponentScan({"com.example.demo.dao", "com.example.demo.service"})
第二步:导入配置类
将原本的加载配置文件的ClassPathXmlApplicationContext
换成了加载配置类AnnotationConfigApplicationContext
,里面的参数是注解类名称.class
,即读取这个注解类,剩下getBean
的操作和原来一样。
package com.example.demo;
import com.example.demo.config.SpringConfig;
import com.example.demo.dao.BookDao;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class DemoApplication {
public static void main(String[] args) {
ApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig.class);
BookDao bookDao = ctx.getBean(BookDao.class);
System.out.println(bookDao);
}
}
3. Bean的作用范围(单例/多例)与生命周期
Bean的作用范围
之前配置Bean的作用范围是在配置文件里通过指定scope
属性定义的:
scope="singleton/prototype"
<bean id="bookDao" name="dao" class="com.example.demo231116.dao.impl.BookDaoImpl" scope="prototype" />
因为Bean默认是单例模式,如果我们想要修改成多例模式,就在类上加@Scope
注解,括号的内容写prototype
为多例,singleton
为单例
package com.example.demo.dao.impl;
import com.example.demo.dao.BookDao;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;
@Repository
@Scope("prototype")
public class BookDaoImpl implements BookDao {
@Override
public void save() {
System.out.println("Book Dao Save...");
}
}
Bean的生命周期
之前提到,Bean的生命周期其实就是我们可以定义在Bean加载的时候初始化一些资源,在销毁前销毁一些资源,在配置文件中写初始化和销毁是通过init-method
和destroy-method
实现的:
<bean id="bookDaoCycle" class="com.example.demo231116.dao.impl.BookDaoImpl" init-method="init" destroy-method="destroy" />
在注解开发中,我们只需要在类中写好初始化和销毁方法,并分别加上PostConstruct
(注解后执行init方法)和PreDestroy
(销毁前执行destroy方法)两个注解,注意,初始化和销毁方法的方法名是任意的:
package com.example.demo.dao.impl;
import com.example.demo.dao.BookDao;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
@Repository
//@Scope("prototype")
public class BookDaoImpl implements BookDao {
@Override
public void save() {
System.out.println("Book Dao Save...");
}
@PostConstruct
public void init(){
System.out.println("init...");
}
@PreDestroy
public void destroy(){
System.out.println("destroy");
}
}
注意,如果提示没找到注解的话需要在pom.xml
中导入坐标:
<dependency>
<groupId>javax.annotation</groupId>
<artifactId>javax.annotation-api</artifactId>
<version>1.2</version>
</dependency>
然后再执行主方法:
package com.example.demo;
import com.example.demo.config.SpringConfig;
import com.example.demo.dao.BookDao;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class DemoApplication {
public static void main(String[] args) {
ApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig.class);
BookDao bookDao = ctx.getBean(BookDao.class);
System.out.println(bookDao);
}
}
执行的结果为:
init...
com.example.demo.dao.impl.BookDaoImpl@7eac9008
没有调用Destroy方法的原因是没有关闭IoC容器/注册关闭钩子,只需要像之前一样在程序结束前使用ctx.close()
或通过ctx.registerShutdownHook()
来注册关闭钩子
(该方法注意不能使用ApplicationContext
类,因为没有close()
方法和registerShutdownHook()
方法)
package com.example.demo;
import com.example.demo.config.SpringConfig;
import com.example.demo.dao.BookDao;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class DemoApplication {
public static void main(String[] args) {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig.class);
// 方法二
ctx.registerShutdownHook();
BookDao bookDao = ctx.getBean(BookDao.class);
System.out.println(bookDao);
// 方法一
ctx.close();
}
}
注意:非单例模式不会执行destroy,因为多例模式下Spring不负责销毁。Spring默认是单例的,它只管单例的销毁而不管多例的销毁
4.依赖注入
引用类型注入
依赖注入使用自动装配的方式,这里阉割掉了配置文件中的Setter注入、构造注入等等一系列冗杂的方法,使用自动装配。
@Autowire属性默认按类型注入
这里注入很简单,只需要在属性上加上@Autowire
注解,就能够实现自动装配,这里的自动装配是基于暴力反射实现的,也无需再BookDaoImpl
中实现set方法
package com.example.demo.service.impl;
import com.example.demo.dao.BookDao;
import com.example.demo.service.BookService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class BookServiceImpl implements BookService {
@Autowired
BookDao bookDao;
@Override
public void save() {
System.out.println("Book Service Save...");
bookDao.save();
}
}
假如我有两个类实现了BookDao.java
这个类:BookDaoImpl
和BookDaoImpl2
,这时候再执行自动装配会报错,因为基于类型反射得到的类不唯一
按类型注入反射结果不唯一,按名注入
针对这种不唯一的情况,给两个类的@Repository
注解后加名字,BookDaoImpl
和BookDaoImpl2
分别为@Repository("bookDao")
和@Repository("bookDao2")
:
package com.example.demo.dao.impl;
import com.example.demo.dao.BookDao;
import org.springframework.stereotype.Repository;
@Repository("bookDao")
public class BookDaoImpl implements BookDao {
@Override
public void save() {
System.out.println("Book Dao Save...");
}
}
package com.example.demo.dao.impl;
import com.example.demo.dao.BookDao;
import org.springframework.stereotype.Repository;
@Repository("bookDao2")
public class BookDaoImpl2 implements BookDao {
@Override
public void save() {
System.out.println("Book Dao Save...2");
}
}
此时,假如我们在Service中的属性定义是
@Autowired
BookDao bookDao2;
则会注入对应BookDaoImpl2
这个类,如果写的是bookDao1则会注入对应BookDaoImpl
这个类
以上其实就是说明,Spring注解开发中,如果按类型注入不成功,就会按名注入,这些都是自动的,无需人为配置按类型注入/按名注入
按名注入的标准写法(少用)
事实上,按名注入更标准的方法是使用@Qualifier
注解,这种注解还是要基于给BookDaoImpl
和BookDaoImpl2
都在@Repository
后指定了名称的情况下进行的,标准写法如下:
package com.example.demo.service.impl;
import com.example.demo.dao.BookDao;
import com.example.demo.service.BookService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Service;
@Service
public class BookServiceImpl implements BookService {
@Autowired
@Qualifier("bookDao2")
BookDao bookDao;
@Override
public void save() {
System.out.println("Book Service Save...");
bookDao.save();
}
}
@Qualifier
后指定你想让bookDao属性被哪个类注入,注意,该注解必须搭配@Autowired
注解使用
简单类型注入
使用@Value
属性完成注入,括号内写值
package com.example.demo.dao.impl;
import com.example.demo.dao.BookDao;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Repository;
@Repository
public class BookDaoImpl implements BookDao {
@Value("namenamename!!!")
String name;
@Override
public void save() {
System.out.println("Book Dao Save..." + this.name);
}
}
就这样的方法,可能会有疑问,这样写和String name = "namenamename!!!"
的区别是什么?
不一样的地方在于:你注解里面的这个字符串,可能来自于外部(比如来自于外部的properties文件)
引入外部的properties文件
假如在Resource下有jdbc.properties
文件,有内容:
name=namenamename!!!
那么我们需要在配置类中加载这个文件,加上注解@PropertySource
,括号内是文件名:
package com.example.demo.config;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
@Configuration
@ComponentScan("com.example.demo")
@PropertySource("jdbc.properties")
public class SpringConfig {
}
然后在值名称上写使用${}
引用即可:
package com.example.demo.dao.impl;
import com.example.demo.dao.BookDao;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Repository;
@Repository
public class BookDaoImpl implements BookDao {
@Value("${name}")
String name;
@Override
public void save() {
System.out.println("Book Dao Save..." + this.name);
}
}
假如想加载多个配置文件,使用{}
来写即可,类似于@ComponentScan
但是注意:之前在配置文件里加载的时候支持使用通配符*
,但这里完全不支持使用通配符!
但是加上@PropertySource("classpath:jdbc.properties")
是可以的,只是不能使用通配符
5. 第三方Bean
此处以数据库连接为例,需要在pom.xml中导入坐标:
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.16</version>
</dependency>
管理
在配置类中写一个方法,一般方法名为Bean的ID。然后new一个第三方对象,并返回。
在该方法上加上@Bean
注解,表明该方法返回的是一个Bean
package com.example.demo.config;
import com.alibaba.druid.pool.DruidDataSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import javax.sql.DataSource;
@Configuration
@ComponentScan("com.example.demo")
public class SpringConfig {
@Bean
public DataSource dataSource(){
DruidDataSource ds = new DruidDataSource();
ds.setDriverClassName("com.mysql.jdbc.Driver");
ds.setUrl("jdbc:mysql://localhost:3306/mysql");
ds.setUsername("root");
ds.setPassword("123456");
return ds;
}
}
这样写就可以在主方法里像平常一样获得Bean:
DataSource dataSource = ctx.getBean(DataSource.class);
System.out.println(dataSource);
但是将第三方的配置写在Spring的配置类中可能比较乱,所以我们给它新建一个文件JdbcConfig.java
:
package com.example.demo.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 {
@Bean
public DataSource dataSource(){
DruidDataSource ds = new DruidDataSource();
ds.setDriverClassName("com.mysql.jdbc.Driver");
ds.setUrl("jdbc:mysql://localhost:3306/mysql");
ds.setUsername("root");
ds.setPassword("123456");
return ds;
}
}
想要让这个文件被扫描到
第一种方法是在这个配置文件上加上注解@Configuration
,然后在配置类中用@ComponentScan()
扫描这个包:
@ComponentScan("com.example.config")
第二种方法(推荐使用)无需在文件上加注解@Configuration
,而是直接在配置类中加上@Import
注解,并在括号内写这个新的配置文件名.class
:@Import(JdbcConfig.class)
完整代码如下:
package com.example.demo.config;
import com.alibaba.druid.pool.DruidDataSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import javax.sql.DataSource;
@Configuration
//@ComponentScan("com.example.demo.config")
@Import(JdbcConfig.class)
public class SpringConfig {
@Bean
public DataSource dataSource(){
DruidDataSource ds = new DruidDataSource();
ds.setDriverClassName("com.mysql.jdbc.Driver");
ds.setUrl("jdbc:mysql://localhost:3306/mysql");
ds.setUsername("root");
ds.setPassword("123456");
return ds;
}
}
多个数据也使用数组模式
依赖注入
简单类型注入
只需要在类里面定义简单类型的成员变量,并在方法中使用成员变量即可,同样通过@Value()
注解赋值
package com.example.demo.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 {
@Value("com.mysql.jdbc.Driver")
String driverClassName;
@Value("jdbc:mysql://localhost:3306/mysql")
String url;
@Value("root")
String usernmae;
@Value("123456")
String password;
@Bean
public DataSource dataSource(){
DruidDataSource ds = new DruidDataSource();
ds.setDriverClassName(driverClassName);
ds.setUrl(url);
ds.setUsername(usernmae);
ds.setPassword(password);
return ds;
}
}
引用类型注入
假如要注入引用类型,需要在方法的参数里面写引用类型
package com.example.demo.config;
import com.alibaba.druid.pool.DruidDataSource;
import com.example.demo.dao.BookDao;
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 {
@Value("com.mysql.jdbc.Driver")
String driverClassName;
@Value("jdbc:mysql://localhost:3306/mysql")
String url;
@Value("root")
String usernmae;
@Value("123456")
String password;
@Bean
public DataSource dataSource(BookDao bookDao){
bookDao.save();
DruidDataSource ds = new DruidDataSource();
ds.setDriverClassName(driverClassName);
ds.setUrl(url);
ds.setUsername(usernmae);
ds.setPassword(password);
return ds;
}
}
因为我们已经注解了这个dataSource
是个Bean,所以对于方法里面的形参,Spring会自动到IoC里面查找对应的类进行注入