- 注解配置与xml配置都实现了减低程序间耦合的功能。
- 不同公司由不同习惯,有可能是纯xml、纯注解(很少)或者xml与注解混合使用(基于注解的IOC配置)。
1. 基于注解的IOC配置
1.1 创建一个简单案例
1.1.1 创建项目,添加依赖(pom.xml)
<!--导入spring组件包-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
<!--导入junit-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
1.1.2 使用@Component注解创建对象
- 使用注解创建对象的条件:
- 使用@Component对类进行注解;
- 开启注解扫描
@Component
public class User {
}
1.1.3 bean.xml配置
1.1.3.1 开启注解扫描(重要)
- 语法:
<context:component-scan base-package="指定扫描的包名"/>
- 开启后会扫描当前包下所有的类,以及当前包下所有子包下所有的类的注解,会扫描@Component注解及器衍生注解@Controller @Service @Repository
<?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/util http://www.springframework.org/schema/util/spring-util.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
<!--开启注解扫描-->
<!--
base-package
1. 指定扫描的包名称
2. 会扫描当前包下所有的类,以及当前包下所有子包下所有的类
3. 如果要扫描多个包,用逗号隔开
-->
<context:component-scan base-package="com.azure.entity"/>
</beans>
1.1.4 测试类
public class annoTest {
@Test
public void test(){
ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
User user = (User) ac.getBean("user");
System.out.println(user);
}
}
2. 常用注解
2.1 创建对象注解
2.1.1 @Component
- 作用:把对象交给 spring的ioc容器来管理。相当于在 xml 中配置一个 bean标签
- 属性:value:指定对象的名称,相当于bean标签的id。**如果不指定 value 属性,默认对象名称为当前类的类名,且首字母小写。**如@Component(value=“user”),属性值只有一个时,可以省略成@Component(“user”)
2.1.2 @Controller @Service @Repository
-
@Component的衍生注解,其作用及属性与@Component一致
-
作用范围不同:
@Controller: 一般用于表现层的注解。
@Service: 一般用于业务层的注解。
@Repository: 一般用于持久层的注解。
@Component: 除了上述三层架构的其余注解,如工具类等。 -
如果注解中有且只有一个属性要赋值时,且名称是 value, value 在赋值是可以不写。
2.2 依赖注入注解
2.2.1 @Autowired(重点)
- 作用:自动按照类型注入依赖,相当于使用set方法注入。
- 属性:required(一般很少会设置)
- true:默认值,标识必须从容器中找到对应的对象注入,否则报错;
- false:如果去容器中没有找到对应的对象注入不会报错。
- 注意事项:只能注入bean类型
2.2.1.1 定义到字段
查找顺序:
- 先根据字段的类型,寻找容器中该类型对应的对象注入;
- 如果该类型的对象有多个
- 再根据字段的名称查找容器中的对象,成功找到就注入;
- 如果根据名称找不到对应对象,则报错。
2.2.1.2 定义到方法
查找顺序:
- 先根据参数的类型,寻找容器中该类型对应的对象注入;
- 如果该类型的对象有多个
- 再根据参数的名称查找容器中的对象,成功找到就注入;
- 如果根据名称找不到对应对象,则报错。
2.2.1.3 演示示例
-
bean.xml配置
<!--创建String类型对象并放入ioc容器中--> <bean id="str" class="java.lang.String"> <constructor-arg value="JoJo"></constructor-arg> </bean> <bean id="name" class="java.lang.String"> <constructor-arg value="Dio哒"></constructor-arg> </bean>
-
定义到字段
@Component("user") public class User { /** * @Autowired * 1.先根据修饰的字段的类型,去容器中找该类型对应的对象注入; * 2.如果该类型的对象有多个, * a.会根据字段的名称去容器中找对象注入,找到就成功注入; * b.根据字段的名称去容器中没有找到对应的对象,就报错。 * 3.细节 * @Autowired注解的属性required * true 默认值,标识必须从容器中找到对应的对象注入否则报错。 * false 如果去容器中没有找到对应的对象注入不会报错 */ @Autowired private String name; @Override public String toString() { return "User{" + "name='" + name + '\'' + '}'; } }
由于ioc容器中有多个String类型的对象,所以会根据字段名“name”查找,如果只有一个String类型的对象,则会把该对象注入
-
定义到方法
/** * @Autowired 定义在方法上 * 1.先根据参数类型去容器找对象注入 * 2.如果类型有多个,会根据参数名称去容器中找对象注入;根据名称也没有找到就报错。 */ @Autowired public void setName(String name) { System.out.println(name); }
同样,由于ioc容器中有多个String类型的对象,所以会根据字段名“name”查找,如果只有一个String类型的对象,则会把该对象注入
2.2.2 @Qualifier
- 作用:在自动按照类型注入依赖的基础上,在按照Bean的id注入。
- 属性:value:用于指定bean的id
- 注意事项:给字段注入时不能独立使用,必须和@Autowire 一起使用;只有给方法参数注入时,才可以独立使用(纯注解时使用)
/**
* @Qualifier("str")
* 1.表示根据str这个名称,去容器中找对象注入
* 2.@Qualifier通常都要与@Autowired一起使用
* 只要@Qualifier修饰方法参数时候才可以单独使用(纯注解时候使用)
* 3.根据@Autowired的特性,注入的应该是名为name的String类型对象,而使用@Qualifier
* 则会将str对象注入
*/
@Autowired
@Qualifier("str")
private String nickname;
2.2.3 @Resource
-
作用:按照 bean 的 id 注入或者类型注入。
-
属性:name:指定 bean 的 id(名称),name不能省略
type:指定bean的类型
-
查找顺序:
- 根据字段的名称,在容器中找对应名称对象注入;
- 如果该类型的对象有多个
- 再根据参数的名称查找容器中的对象,成功找到就注入;
- 如果根据名称找不到对应对象,则报错。
-
注意事项:只能注入bean类型;jdk9及之后版本不再支持此注解
/**
* @Resource
* 1.根据字段名称去容器中找对象注入
* 2.如果根据名称会根据类型找,如果类型有多个就报错
* 3.也可以通过注解的name属性指定只根据名称找
* 如果指定type只会根据类型找。
* 不推荐使用。因为jdk8以后的版本不支持此注解。
*/
@Resource(name = "str")
private String oldName;
2.2.4 @Value
- 作用:给基本数据类型和String类型的字段注入值;获取加载的 properties配置文件的值(纯注解中使用)
- 属性:value:被注入的值
/**
* @Value
* 1.给简单类型的字段赋值
* 2.获取加载的properties配置文件的值(纯注解中讲解)
*/
@Value("100")
private int id;
@Value("女")
private String sex;
2.3 改变作用范围注解
2.3.1 @Scope
-
作用:指定 bean 的作用范围。类似于bean标签的scope属性
-
属性:value:指定范围的值。
取值:
- singleton:单例,默认值
- prototype:多例
- request:创建的对象的范围与request域一样。web项目
- session:创建的对象的范围与session域一样。web项目
- global session:全局session。分布式系统中使用。web项目
/*@Scope注解*/
@Component("user")
//@Scope("singleton")// 单例,默认值
@Scope("prototype")// 多例
public class User {
//...
}
2.4 与生命周期相关的注解
2.4.1 @PostConstruct
- 作用:指定初始化方法。相当于bean标签中的init-method属性
2.4.2 @PreDestroy
- 作用:指定容器销毁方法。相当于bean标签中的destroy-method属性
2.4.3 @Lazy
- 作用:延迟初始化。在第一次从容器中获取对象时才会创建对象。相当于bean标签中的lazy-init属性
- 注意事项:只对单例有效
2.4.4 生命周期注解示例
/*生命周期注解*/
@Component("user2")
@Scope("singleton")
@Lazy // 延迟初始化,再第一次从容器中获取对象时候才创建对象。只对单例有效
public class User2 {
// 创建对象之后执行
@PostConstruct
public void init() {
System.out.println("创建对象之后执行");
}
// 销毁容器之前执行
@PreDestroy
public void preDestroy() {
System.out.println("销毁容器之前执行");
}
}
2.5 Spring中注解与xml方式的选择
-
注解的优势:
配置简单,维护方便(找到类相当于找到对应的配置)。
-
XML 的优势:
修改时,不用改源码。不涉及重新编译和部署。
Spring 管理 Bean,XML与注解方式的比较:
小结
- 基于注解的 spring IoC 配置中, bean 对象的特点和基于 XML 配置是一致的。
- xml 配置 可以与注解配置一起使用。
目前开发比较常用的是:
- XML + 注解 混合使用
- XML: 配置一些全局的对象(举例:DataSource/JdbcTemplate…)
- 注解: 每一个模块的dao/service等对象的创建可以用注解,简化配置
3. 使用注解 IOC 改造三层架构案例
- 需求:使用xml+注解的形式改造纯xml配置的三层架构案例
3.1 环境搭建和实体类
- 不用修改
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.azure</groupId>
<artifactId>day52projects_spring_xml</artifactId>
<version>1.0-SNAPSHOT</version>
<dependencies>
<!--导入spring组件包-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
<!--导入druid-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.10</version>
</dependency>
<!--导入mysql驱动包-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.32</version>
</dependency>
<!--导入junit-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
</dependencies>
</project>
实体类
public class account {
private int accountId;
private int uid;
private double money;
/*省略无参、有参、toString、getter&setter*/
}
3.3 dao层
3.3.1 dao接口
- 不用修改
public interface IAccountDao {
//添加
void save(Account account);
//更新
void update(Account account);
//删除
void delete(int id);
//查询
List<Account> findAll();
}
3.3.2 dao实现类
/*标识持久层,加入ioc容器中*/
@Repository
public class AccountDaoImpl implements IAccountDao {
//从容器中获取JdbcTemplate对象并自动注入
@Autowired
private JdbcTemplate jdbcTemplate;
@Override
public void save(Account account) {
jdbcTemplate.update("insert into account values (null,?,?)",account.getUid(),account.getMoney());
}
@Override
public void update(Account account) {
jdbcTemplate.update("update account set uid=?,money=? where accountId=?",account.getUid(),account.getMoney(),account.getAccountId());
}
@Override
public void delete(int id) {
jdbcTemplate.update("delete from account where accountId=?",id);
}
@Override
public List<Account> findAll() {
return jdbcTemplate.query("select * from account",new BeanPropertyRowMapper<>(Account.class));
}
}
3.4 service层
3.4.1 service接口
- 不用修改
public interface IAccountService {
//添加
void save(Account account);
//更新
void update(Account account);
//删除
void delete(int id);
//查询
List<Account> findAll();
}
3.4.2 service实现类
/*标识业务层,加入ioc容器中*/
@Service
public class AccountServiceImpl implements IAccountService {
//从容器中获取dao对象并自动注入
@Autowired
private IAccountDao accountDao;
@Override
public void save(Account account) {
accountDao.save(account);
}
@Override
public void update(Account account) {
accountDao.update(account);
}
@Override
public void delete(int id) {
accountDao.delete(id);
}
@Override
public List<Account> findAll() {
return accountDao.findAll();
}
}
3.5 bean_IOC.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/util http://www.springframework.org/schema/util/spring-util.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
<!--开启注解扫描-->
<context:component-scan base-package="com.azure"/>
<!--加载jdbc.properties配置文件-->
<!--注意整个语句的写法-->
<context:property-placeholder location="classpath:jdbc.properties"/>
<!--1.创建连接池对象-->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<!--使用${key}根据指定key在properties文件中获取对应的值}-->
<property name="driverClassName" value="${jdbc.driver}"></property>
<property name="url" value="${jdbc.url}"></property>
<property name="username" value="${jdbc.username}"></property>
<property name="password" value="${jdbc.password}"></property>
</bean>
<!--2.创建jdbcTemplate对象,注入连接池对象-->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"></property>
</bean>
</beans>
3.6 测试类
- 重点:改造后,service对象由ioc容器中获取。根据@Service标签的特性,由于设置标签时没有指定对象名称(即value属性值),所以按照默认对象名称(即当前类的类名,且首字母小写)!!所以getBean方法传入的对象名称是accountServiceImpl而不是accountService
public class WebApp {
/*测试类中创建容器获取service对象*/
private ApplicationContext ac = new ClassPathXmlApplicationContext("bean_IOC.xml");
private IAccountService accountService = (IAccountService) ac.getBean("accountServiceImpl");
@Test
public void save() {
Account account = new Account();
account.setUid(46);
account.setMoney(99D);
accountService.save(account);
}
@Test
public void update() {
Account account = new Account();
account.setAccountId(1);
account.setUid(46);
account.setMoney(99D);
accountService.update(account);
}
@Test
public void delete() {
accountService.delete(4);
}
@Test
public void findAll() {
System.out.println(accountService.findAll());
}
}
4. spring 的纯注解配置
- 将bean.xml的所有配置都用注解实现
- 使用纯注解比较少见,因为如果要修改部分功能,可能会修改源码,反而会增加耦合性。
- 根据企业要求或者简化开发的原则来选择配置原则,不一定非要纯注解
4.1 纯注解配置所需的新注解
4.1.1 @Configuration
- 作用:用于指定当前类是一个 spring 配置类, 当创建容器时会从该类上加载注解,用于取代bean.xml。
- 属性:value:用于指定配置类的字节码
- 注意事项:获取容器时需要使用AnnotationApplicationContext类,才可以加载@Configuration注解的类的字节码对象。
/**
* @Configuration
* 1.注解修饰的类就是配置管理类,相当于bean.xml配置
* 2.现在创建容器时候,只需要加载此注解修饰的类即可。
*/
@Configuration
public class SpringConfiguration {
}
4.1.2 @ComponentScan
- 作用:用于指定 spring 在初始化容器时要扫描的包。 作用和在 spring 的 xml 配置文件中的:
<context:component-scan base-package="指定扫描的包名"/>
一致。 - 属性:basePackages:用于指定要扫描的包。与该注解中的 value 属性作用一致。
/**
* @Configuration
* 1.注解修饰的类就是配置管理类,相当于bean.xml配置
* 2.现在创建容器时候,只需要加载此注解修饰的类即可。
*
* @ComponentScan
* 开启注解扫描,指定要扫描的包
* 可以写成@ComponentScan("com.azure")/@ComponentScan(value = "com.azure")/@ComponentScan(basePackages = "com.azure")
*/
@Configuration
@ComponentScan(value = "com.azure")
public class SpringConfiguration {
}
4.1.3 @Bean和@PropertySource
4.1.3.1 @Bean
-
作用:该注解只能写在方法上,@Bean 会自动把方法返回的结果加入ioc容器。同时将容器中的对象注入到方法参数中。
-
属性:name:给当前@Bean 注解方法创建的对象指定一个名称(即相当于bean 标签的 id)。
-
@Bean 修饰的方法的参数:
- 自动根据参数类型去容器中对象注入;
- 如果类型有多个,就根据形参名称取容器找该名称对应的对象注入
- 如果要根据指定名称在容器中找对象注入参数,需要用@Qualifier注解
4.1.3.2 @PropertySource
- 作用:用于加载.properties 文件中的配置。
- 属性:value[]:用于指定 properties 文件位置。
- 注意事项:属性value[]中的文件位置是在类路径下,需要写上classpath
4.1.3.3 使用新的类加载数据库连接池、jdbcTemplate
@PropertySource("jdbc.properties")
public class JdbcConfig {
// 通过@Value获取配置文件值
@Value("${jdbc.driver}")
private String driver;
@Value("${jdbc.url}")
private String url;
@Value("${jdbc.username}")
private String username;
@Value("${jdbc.password}")
private String password;
/**
* 1.创建连接池,加入ioc容器
* @Bean 会自动把方法返回的结果加入ioc容器
* name属性指定加入ioc容器的对象的名称
*/
@Bean(name = "dataSource")
public DataSource createDataSource(){
DruidDataSource ds = new DruidDataSource();
ds.setDriverClassName(driver);
ds.setUrl(url);
ds.setUsername(username);
ds.setPassword(password);
return ds;
}
/**
* 2.创建JdbcTemplate,加入ioc容器
* @Bean 修饰的方法的参数:
* 1.会自动根据参数类型去容器中对象注入;
* 2.如果类型有多个,就根据形参名称取容器找该名称对应的对象注入。
*/
@Bean(name = "jdbcTemplate")
public JdbcTemplate createJdbcTemplate(DataSource dataSource){
return new JdbcTemplate(dataSource);
}
}
4.1.4 @Import
-
作用:用于导入其他配置类,也就是建立各配置类与主配置类的关系。
-
属性:value[]:用于指定其他配置类的字节码对象。
-
注意事项:在引入其他配置类时,可以不用再写@Configuration 注解,也可以写
/**
* @Configuration
* 1.注解修饰的类就是配置管理类,相当于bean.xml配置
* 2.现在创建容器时候,只需要加载此注解修饰的类即可。
*
* @ComponentScan
* 开启注解扫描,指定要扫描的包
* 可以写成@ComponentScan("com.azure")/@ComponentScan(value = "com.azure")/@ComponentScan(basePackages = "com.azure")
*
* @Import
* 用来导入其他的配置类
*/
@Configuration
@ComponentScan(value = "com.azure")
@Import(JdbcConfig.class)
public class SpringConfiguration {
}
4.2 测试类
public class annoTest {
@Test
public void test(){
//使用注解方式创建容器
ApplicationContext ac = new AnnotationConfigApplicationContext(SpringConfiguration.class);
// 从容器中获取对象
IAccountService accountService = (IAccountService) ac.getBean("accountServiceImpl");
System.out.println(accountService.findAll());
}
}
4.3 使用@Qualifier注解指定@Bean参数名称
/**
* 1.创建连接池,加入ioc容器
* @Bean 会自动把方法返回的结果加入ioc容器
* name属性指定加入ioc容器的对象的名称
*/
@Bean(name = "dataSource1")
public DataSource createDataSource1(){
DruidDataSource ds = new DruidDataSource();
ds.setDriverClassName(driver);
ds.setUrl(url);
ds.setUsername(username);
ds.setPassword(password);
return ds;
}
@Bean(name = "dataSource2")
public DataSource createDataSource2(){
DruidDataSource ds = new DruidDataSource();
ds.setDriverClassName(driver);
ds.setUrl(url);
ds.setUsername(username);
ds.setPassword(password);
return ds;
}
/**
* 2.创建JdbcTemplate,加入ioc容器
* @Bean 修饰的方法的参数:
* 1.会自动根据参数类型去容器中对象注入;
* 2.如果类型有多个,就根据形参名称取容器找该名称对应的对象注入。
* 3.如果想根据指定的名称去容器中找对象,注入到方法的形参上,使用@Qualifier注解
* @Qualifier("dataSource2") 去容器找名称是“dataSource2”的对象注入方法形参。
*/
@Bean(name = "jdbcTemplate")
public JdbcTemplate createJdbcTemplate(@Qualifier("dataSource2") DataSource dataSource){
return new JdbcTemplate(dataSource);
}
}
5. Spring整合Junit
- 利用Spring框架提供的运行器,根据配置文件或注解来在Junit代码中的获取ioc容器
5.1添加spring-test依赖
5.2 测试类
5.2.1 纯注解方式
/*spring对Junit的支持*/
@RunWith(SpringJUnit4ClassRunner.class)
//指定容器类
@ContextConfiguration(classes = SpringConfiguration.class)
public class Test01 {
// 注入service(因为运行junit容器已经初始化,标识运行的测试类在容器范围内,所有可以直接注入。)
@Autowired
private IAccountService accountService;
@Test
public void findAll() {
System.out.println(accountService.findAll());
}
}
5.2.2 加载bean.xml配置文件
/*spring对Junit的支持*/
@RunWith(SpringJUnit4ClassRunner.class)
//指定容器类
@ContextConfiguration(locations = "classpath:bean_test.xml")
public class Test02 {
// 注入service(因为运行junit容器已经初始化,标识运行的测试类在容器范围内,所有可以直接注入。)
@Autowired
private IAccountService accountService;
@Test
public void findAll() {
System.out.println(accountService.findAll());
}
}
6. 总结
-
JdbcTemplate实现crud,SpringIOC容器创建对象,依赖注入, XML方式
-
常用的注解
-
@Component 创建对象加入容器, 例如:工具类、其他组件
-
@Repository 创建对象加入容器, 例如:标识数据库访问层的组件
-
@Service 创建对象加入容器, 例如:标识乐屋逻辑层的组件
-
@Controller 创建对象加入容器 例如:标识控制层的组件
-
@Autowired 从容器中找对象,给属性赋值。根据类型、名称去容器中找。
-
@Qualifier 结合Autowired使用,可以指定按照名称去容器找对象注入。
-
@Value 给简单类型属性直接赋值/获取配置文件值@Value(“${key}”)
-
@Resource 从容器中找对象,给属性赋值。 根据名称、类型去容器中查找
-
@Scope 单例/多例
-
@PostConstruct 初始化方法,创建对象后执行
-
@PreDestroy 销毁先执行
-
@Lazy 延迟初始化
-
-
优化案例
-
零配置使用的注解、案例优化
- @Configuration 取代bean.xml ;
- @ComponentScan 注解扫描
- @Import 导入其他配置类
- @Bean 修饰方法,自动把方法返回值放入容器
- @PropertySource 加载外部配置文件
- @Value 获取@ PropertySource加载的配置文件的值