一、Spring中基于注解的IOC配置
1.概述
基于注解的IOC配置即注解配置和xml配置要实现的功能都是一样,都是降低程序间的耦合。只是配置形式不一样。
2.环境搭建
在IDEA中创建maven项目,写入依赖。
<packaging>jar</packaging>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
</dependencies>
3.学习内容
* 曾经xml的配置:
* <bean id="accountService" class="com.wuhao.service.implement.AccountServiceImpl" scope="" init-method="" destroy-method="" >
* <property name="" value="" | ref="" ></property>
* </bean>
*
* 用于创建对象的
* 他们的作用就和在xml配置文件中编写一个<bean>标签实现的功能是一样的
* 用于注入数据的
* 他们的作用就和在xml配置文件中的bean标签中写一个<property>标签的作用是一样的
* 用于改变作用范围的
* 他们的作用就和在bean标签中使用scope属性实现的功能是一样的
* 和生命周期相关
* 他们的作用就和在<bean>标签中使用init-method和destroy-method的作用是一样的
二、具体内容
1.用注解创建对象
* @Component:
* 作用:用于把当前类对象存入spring容器中 (spring容器是map类型,存在key和value)
* 属性:
* value:用于指定bean的id。当我们不写时,它的默认值是当前类名,且首字母改小写。
* @Controller :一般用在表现层
* @Service :一般用在业务层
* @Repository :一般用在持久层
* 以上三个注解他们的作用和属性与Component是一模一样。
* 他们三个是spring框架为我们提供明确的三层使用的注解,使我们的三层对象更加清晰
配置文件bean.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">
<!--告知spring在创建容器时要扫描的包,配置所需要的标签不是在beans的约束中,而是一个名称为context名称空间和约束中-->
<context:component-scan base-package="com.wuhao"></context:component-scan>
</beans>
实现类AccountServiceImpl:
@Component(value = "accountService") //当只有一个属性时可以不写value : @Component("accountService")
public class AccountServiceImpl implements IAccountService {
private IAccountDao accountDao;
public AccountServiceImpl(){
System.out.println("对象已创建");
}
@Override
public void saveAccount() {
accountDao.saveAccount();
}
}
表现层实现:
public static void main(String[] args) {
//1.获取核心容器对象
ApplicationContext ac=new ClassPathXmlApplicationContext("bean.xml");
//2.根据id获取Bean对象
IAccountService as=(IAccountService)ac.getBean("accountService");
IAccountDao adao=ac.getBean("accountDao",IAccountDao.class);
System.out.println(as);
System.out.println(adao);
//as.saveAccount();
}
2.用注解注入数据
1)Autowired
* @Autowired:
* *作用:自动按照类型注入。只要容器中有唯一的一个bean对象类型和要注入的变量类型匹配,就注入成功。
* 如果IOC容器中没有任何bean的类型和要注入的变量类型匹配,则报错。
* 如果IOC容器中有多个类型匹配时,首先按照数据类型找出匹配对象,然后使用变量名称作为bean的id与IOC容器中的key进行匹配,找出是否有对应的bean。
* 出现的位置:
* 可以是变量上,也可以是方法上
* 细节:
* 在使用注解注入时,set方法就不是必须的
自动按照类型注入:
解释: 如果IOC容器中有多个类型匹配时,首先按照数据类型找出匹配对象,然后使用变量名称作为bean的id与IOC容器中的key进行匹配,找出是否有对应的bean。
存在两个IAccountDao接口的实现类:IAccountDaoImpl,IAccountDaoImpl2
@Repository("accountDao1")
public class AccountDaoImpl implements IAccountDao {
@Override
public void saveAccount() {
System.out.println("保存了账户1111");
}
}
@Repository("accountDao2")
public class AccountDaoImpl2 implements IAccountDao {
@Override
public void saveAccount() {
System.out.println("保存了账户2222");
}
}
现在IAccountService接口的实现类IAccountServiceImpl调用IAccountDao接口的实现类:
@Component(value = "accountService") //当只有一个属性时可以不写value : @Component("accountService")
public class AccountServiceImpl implements IAccountService {
@Autowired
private IAccountDao accountDao=null;
public AccountServiceImpl(){
System.out.println("对象已创建");
}
@Override
public void saveAccount() {
accountDao.saveAccount();
}
}
此时注:数据类型IAccountDao的变量名称为accountDao,而在数据类型为IAccountDao的两个实现类中声明对象为accountDao1和accountDao2。
执行表现层:
public static void main(String[] args) {
//1.获取核心容器对象
ApplicationContext ac=new ClassPathXmlApplicationContext("bean.xml");
//2.根据id获取Bean对象
IAccountService as=(IAccountService)ac.getBean("accountService");
// IAccountDao adao=ac.getBean("accountDao",IAccountDao.class);
// System.out.println(as);
// System.out.println(adao);
as.saveAccount();
}
报错:
报错原因就是找见了2个匹配的bean对象。
所以只要修改IAccountService接口的实现类就行:
将 @Autowired
private IAccountDao accountDao=null;
改为 @Autowired
private IAccountDao accountDao1=null; 或
@Autowired
private IAccountDao accountDao2=null;
2)Qualifier
* @Qualifier:
* 作用:在按照类中注入的基础之上再按照名称注入。它在给类成员注入时不能单独使用。但是再给方法参数注入时可以。
* 属性:
* value:用于指定注入bean的id。
* 必须与@Autowired一起使用
@Component(value = "accountService") //当只有一个属性时可以不写value : @Component("accountService")
public class AccountServiceImpl implements IAccountService {
@Autowired
@Qualifier("accountDao1")
private IAccountDao accountDao=null;
public AccountServiceImpl(){
System.out.println("对象已创建");
}
@Override
public void saveAccount() {
accountDao.saveAccount();
}
}
* 以上三个注入都只能注入其他bean类型的数据,而基本类型和String类型无法使用上述注解实现。
* 另外,集合类型的注入只能通过xml来实现
3)value
* @Value:
* 作用:用于注入基本类型和String类型的数据
* 属性:
* value:用于指定数据的值,它可以使用spring中SpEL(也就是spring的el表达式)
* SpEL的写法:${表达式}
3.用注解改变作用范围
* Scope:
* 作用:用于指定bean的作用范围
* 属性:
* value:指定范围的取值。 常用取值:singleton prototype
IAccountService接口实现类:
public class AccountServiceImpl implements IAccountService {
// @Autowired
// @Qualifier("accountDao1")
@Resource(name="accountDao1")
private IAccountDao accountDao=null;
public AccountServiceImpl(){
System.out.println("对象已创建");
}
@Override
public void saveAccount() {
accountDao.saveAccount();
}
}
表现层:
public static void main(String[] args) {
//1.获取核心容器对象
ApplicationContext ac=new ClassPathXmlApplicationContext("bean.xml");
//2.根据id获取Bean对象
IAccountService as1=(IAccountService)ac.getBean("accountService");
IAccountService as2=(IAccountService)ac.getBean("accountService");
System.out.println(as1==as2);
}
结果:
修改IAccountService接口实现类,加入scope(prototype):
@Component(value = "accountService") //当只有一个属性时可以不写value : @Component("accountService")
@Scope("prototype")
public class AccountServiceImpl implements IAccountService {
// @Autowired
// @Qualifier("accountDao1")
@Resource(name="accountDao1")
private IAccountDao accountDao=null;
public AccountServiceImpl(){
System.out.println("对象已创建");
}
@Override
public void saveAccount() {
accountDao.saveAccount();
}
}
结果:
4.用注解修改生命周期
* @PreDestroy
* 作用:用于指定销毁方法
* @PostConstruct
* 作用:用于指定初始化方法
IAccountService接口实现类:
@Component(value = "accountService") //当只有一个属性时可以不写value : @Component("accountService")
//@Scope("prototype")
public class AccountServiceImpl implements IAccountService {
// @Autowired
// @Qualifier("accountDao1")
@Resource(name="accountDao1")
private IAccountDao accountDao=null;
public AccountServiceImpl(){
System.out.println("对象已创建");
}
@Override
public void saveAccount() {
accountDao.saveAccount();
}
@PostConstruct
public void init(){
System.out.println("初始化方法执行了");
}
@PreDestroy
public void destroy() {
System.out.println("销毁方法执行了");
}
}
表现层:
public static void main(String[] args) {
//1.获取核心容器对象
ClassPathXmlApplicationContext ac=new ClassPathXmlApplicationContext("bean.xml");
//2.根据id获取Bean对象
IAccountService as1=(IAccountService)ac.getBean("accountService");
as1.saveAccount();
ac.close();
}
结果:
三、Spring的新注解
Spring引入新注解的目的就是在基于注解的IOC配置中脱离XML文件。
未引入新注解时,用Spring连接数据库需创建XML文件,例如本实例中bean.xml:
<!--告知Spring在创建容器时要扫描的包-->
<context:component-scan base-package="com.wuhao"></context:component-scan>
<!--配置QueryRunner-->
<!--scope="prototype" 用来保证每次操作数据库都是一个新的对象,避免线程冲突-->
<bean id="queryRunner" class="org.apache.commons.dbutils.QueryRunner" scope="prototype">
<!--注入数据源-->
<constructor-arg name="ds" ref="dataSource"></constructor-arg>
</bean>
<!--配置数据源-->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<!--连接数据库的必备信息-->
<property name="driverClass" value="com.mysql.jdbc.Driver"></property>
<property name="jdbcUrl" value="jdbc:mysql://localhost:3306/spring_study?serverTimezone=GMT"></property>
<property name="user" value="root"></property>
<property name="password" value="wh456159"></property>
</bean>
数据库中表信息:
IAccountService接口的实现类AccountService:
@Scope("prototype")
@Service("accountService")
public class AccountServiceImpl implements IAccountService {
@Autowired
@Resource(name = "accountDao")
private IAccountDao accountDao;
@Override
public List<Account> findAllAccount() {
return accountDao.findAllAccount();
}
@Override
public Account findAcoountById(Integer accountId) {
return accountDao.findAcoountById(accountId);
}
@Override
public void saveAccount(Account account) {
accountDao.saveAccount(account);
}
@Override
public void updateAccount(Account account) {
accountDao.updateAccount(account);
}
@Override
public void deleteAccount(Integer accountId) {
accountDao.deleteAccount(accountId);
}
}
IAccountDao接口的实现类AccountDao:
@Repository("accountDao")
public class AccountDaoImpl implements IAccountDao {
@Autowired
private QueryRunner runner;
@Override
public List<Account> findAllAccount() {
try {
return runner.query("select * from account",new BeanListHandler<Account>(Account.class));
} catch (Exception e) {
throw new RuntimeException(e);
}
}
@Override
public Account findAcoountById(Integer accountId) {
try {
return runner.query("select * from account where id=?",new BeanHandler<Account>(Account.class),accountId);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
@Override
public void saveAccount(Account account) {
try {
runner.update("insert into account(name,money)values(?,?)",account.getName(),account.getMoney());
} catch (Exception e) {
throw new RuntimeException(e);
}
}
@Override
public void updateAccount(Account account) {
try {
runner.update("update account set name=?,money=? where id=?",account.getName(),account.getMoney(),account.getId());
} catch (Exception e) {
throw new RuntimeException(e);
}
}
@Override
public void deleteAccount(Integer accountId) {
try {
runner.update("delete from account where id=?",accountId);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
1.configuration
* @Configuration
* 作用:指定当前类是一个配置类
* 细节:当配置类作为AnnotationConfig的ApplicationContext对象创建的参数时,该注解可以不写。
2.ConponentScan
* @ConponentScan
* 作用:用于通过注解指定spring在创建容器时要扫描的包
* 属性:
* value:它和basePackages的作用是一样的,都是用于指定创建容器时要扫描的包。
* 我们使用此注解就等同于在xml中配置了:
* <context:component-scan base-package="com.wuhao"></context:component-scan>
有以上两个注解就可以去掉bean.xml文件中: <context:component-scan base-package="com.wuhao"></context:component-scan>
我们创建一个配置类SpringConfiguration:
@Configuration
@ComponentScan("com.wuhao")
public class SpringConfiguration { //主配置类
}
3.Bean
* @Bean
* 作用:用于把当前方法的返回值作为bean对象存入Spring的ioc容器中
* 属性:
* name:用于指定bean的id。 当不写时,默认值是当前方法的名称
* 细节:当我们使用注解配置方法时,如果方法有参数,spring框架会去容器中查找有没有可用的bean对象。
* 查找的方法和Autowired注解的作用是一样的
然后我们在配置类中创建连接数据库所需的QueryRunner对象和数据源对象替换掉bean.xml文件中创建QueryRunner的操作:
/**
* 用于创建一个QueryRunner对象
* @param dataSource
* @return
*/
@Bean(name = "queryRunner")
@Scope("prototype")
public QueryRunner createQueryRunner(DataSource dataSource){
return new QueryRunner(dataSource);
}
/**
* 创建数据源对象
* @return
*/
@Bean(name="dataSource")
public DataSource createDataSource(){
ComboPooledDataSource ds=new ComboPooledDataSource();
try {
ds.setDriverClass("com.mysql.jdbc.Driver");
ds.setJdbcUrl("jdbc:mysql://localhost:3306/spring_study?serverTimezone=GMT");
ds.setUser("root");
ds.setPassword("wh456159");
return ds;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
此时需要修改测试类中获取bean对象的配置文件:
ApplicationContext ac=new AnnotationConfigApplicationContext(SpringConfiguration.class);
@Test
public void testFindAll(){
//1.获取核心容器对象
// ApplicationContext ac=new ClassPathXmlApplicationContext("bean.xml");
ApplicationContext ac=new AnnotationConfigApplicationContext(SpringConfiguration.class);
//2.根据id获取bean对象 (获取业务层对象)
IAccountService as=ac.getBean("accountService",IAccountService.class);
//3.执行方法
List<Account> accounts=as.findAllAccount();
for(Account account:accounts){
System.out.println(account);
}
}
4.Import
* @Import
* 作用:用于导入其他的配置类
* 属性:
* value:用于指定其他配置类的字节码。
* 当我们使用Import的注解之后,有Import注解的类就是父配置类,而导入的都是子配置类
当我们创建一个专门用来连接数据库的配置类JdbcConfig,我们就要在主配置类上加上注解Import,表示所引入的子类。(前提是引入的子类不出现在获取核心容器对象的方法参数中)
@Configuration
@ComponentScan("com.wuhao")
@Import(JdbcConfig.class)
public class SpringConfiguration { //主配置类
}
5.PropertySource
* @PropertySource
* 作用:用于指定properties文件的位置
* 属性:
* value:指定文件的名称和路径。
* 关键字:classpath,表示类路径
现在我们优化jdbcConfig配置类中:
ds.setDriverClass("com.mysql.jdbc.Driver");
ds.setJdbcUrl("jdbc:mysql://localhost:3306/spring_study?serverTimezone=GMT");
ds.setUser("root");
ds.setPassword("wh456159");
创建一个jdbcConfig.properties文件:
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/spring_study?serverTimezone=GMT
jdbc.username=root
jdbc.password=wh456159
然后在主配置类上注明jdbcConfig.properties的路径:
@Configuration
@ComponentScan("com.wuhao")
@Import(JdbcConfig.class)
@PropertySource("classpath:jdbcConfig.properties")
public class SpringConfiguration { //主配置类
}
JdbcConfig配置类修改:
public class JdbcConfig { //数据库配置类
@Value("${jdbc.driver}")
private String driver;
@Value("${jdbc.url}")
private String url;
@Value("${jdbc.username}")
private String username;
@Value("${jdbc.password}")
private String password;
/**
* 用于创建一个QueryRunner对象
* @param dataSource
* @return
*/
@Bean(name = "queryRunner")
@Scope("prototype")
public QueryRunner createQueryRunner(DataSource dataSource){
return new QueryRunner(dataSource);
}
/**
* 创建数据源对象
* @return
*/
@Bean(name="dataSource")
public DataSource createDataSource(){
ComboPooledDataSource ds=new ComboPooledDataSource();
try {
// ds.setDriverClass("com.mysql.jdbc.Driver");
// ds.setJdbcUrl("jdbc:mysql://localhost:3306/spring_study?serverTimezone=GMT");
// ds.setUser("root");
// ds.setPassword("wh456159");
ds.setDriverClass(driver);
ds.setJdbcUrl(url);
ds.setUser(username);
ds.setPassword(password);
return ds;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}