学习基于注解的Ioc配置,咱们脑海中须要有一个认知,就是注解配置和xml配置实现的功能都是同样的,都是要下降程序间的耦合,只是配置的形式不同java
在实际开发中究竟是使用xml仍是注解,每一个公司有不一样的使用习惯,全部这两种配置方式咱们都须要掌握mysql
咱们在讲解注解配置时,采用上一章的案例,把Spring的xml配置内容改成使用注解逐步实现spring
@Component(如下三个是Spring框架为咱们明确提供的三层使用注解,使咱们的三层对象更加清晰,如下是Component的衍生类)sql
Controller(这个对象通常用在表现层)数据库
Service(这个对象一遍用于业务层)apache
Repository(这个业务通常用于持久层)
做用:把当前对象存入Spring容器中
属性:value 用于指定bean的id,当咱们不写的时候,它的默认是当前类名,且首字母小写,也能够指定一个名称app
@Component("accountServiceImpl4") public class AccountServiceImpl4 implements IAccountService { private String name; private Integer age; private Date brithday; AccountServiceImpl4(String name, Integer age, Date brithday){ this.name=name; this.age=age; this.brithday=brithday; } public void saveAccount(){ System.out.println("service中的saveAccount方法执行了...."+name+age+brithday); } }
<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 http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/tx"> <!--新添加一个约束:告诉Spring在建立容器时要扫描的包,配置所须要的标签不是在bean的约束中,而是一个名称为 context的名称和空间中,这个时候就会扫描base-pack类上或者接口上的注解--> <context:component-scan base-package="com.ithema.jdbc"></context:component-scan> </beans>
③测试类框架
package com.ithema.jdbc.ui; import com.ithema.jdbc.service.IAccountService; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; /** * 模拟一个表现层,用于调用业务层 */ public class Client2 { public static void main(String[] args) { ApplicationContext ac=new ClassPathXmlApplicationContext("ApplicationContext2.xml"); IAccountService as= (IAccountService) ac.getBean("accountServiceImpl4"); System.out.println(as); as.saveAccount(); } }
显示结果以下ide
Autowired单元测试
做用:自动按照类型注入,主要容器中有惟一一个bean对象和要注入的变量类型匹配,就能够注入成功,若是Ioc容器中没有任何bean类型和要注入的变量类型匹配,则报错
若是有Ioc容器中有多个匹配类型:首先按照类型圈定出来匹配的对象,使用变量名称做为bean的id,在圈定出来的两个里面继续查找,若是bean id有同样也能够注入成功
出现位置:能够是变量上,也能够是方法上
细节:在使用注解注入时候,set方法就不用了
可是这种状况不是咱们想看到的,那么有新的方法吗??以下
Qualifier(不能独立使用,须要和Autowired配合使用,有解决的方案吗?以下一个属性)
做用:在按照类中注入的基础上再按照名称注入,它在给类成员注入时,不能单独使用,但在给方法参数注入时候能够
属性:value 用于指定bean的id
Resource:直接按照bean的id注入,平时用的比较多,它能够独立使用
@Resource(name="accountDao")
以上三个注解只能注入其余bean类型的数据,而基本数据类型和String类型没法使用上述注解实现,另外,集合类型的注入只能使用xml来实现,怎么解决以下
Value:
做用:涌入注入基本类型和String类型的数据
属性 value:用于指定数据的值,它可使用Spring中spel(也就是Spring中的el表达式),spel写法$(表达式)
①接口上修改代码
②启动测试类显示结果以下,成功注入数据
存在一bean对象时候
存在多个bean对象时候,要注入的变量名称和某个bean的id保持一致也能够注入成功
做用:用于指定bean的做用范围,属性:value 指定范围取值,经常使用取值singleton(默认值)/prototype
测试以下,做用范围默认是单例的
那么若是咱们把做用范围改为prototype的啦?结果固然是false
PreDestory 做用:用于指定销毁方法
PostConstruct 做用:初始化方法
项目目录
StudentDao方法接口
package com.it.dao; import com.it.entity.Student; import java.util.List; public interface StudentDao { List<Student> findAllStudent(); Student findByid(int id); void saveStudent(Student student); void updateStudent(Student student); void deleteStudent(int id); }
StdentDao接口实现类
package com.it.dao; import com.it.entity.Student; import org.apache.commons.dbutils.QueryRunner; import org.apache.commons.dbutils.handlers.BeanHandler; import org.apache.commons.dbutils.handlers.BeanListHandler; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Repository; import java.util.List; @Repository("StudentDao") public class StudentDaoImpl implements StudentDao { @Autowired private QueryRunner runner; @Override public List<Student> findAllStudent() { //BeanListHandler将结果集中的每一行数据都封装到一个对应的JavaBean实例中,存放到List里 try{ return runner.query("select * from student",new BeanListHandler<Student>(Student.class)); }catch (Exception e){ throw new RuntimeException(e); } } @Override public Student findByid(int id) { //BeanHandler:将结果集中的第一行数据封装到一个对应的JavaBean实例中 try{ return runner.query("select * from student where id=?",new BeanHandler<Student>(Student.class),id); }catch (Exception e){ throw new RuntimeException(e); } } @Override public void saveStudent(Student student) { try{ runner.update("insert into student(id,stuno,name,classid)values(?,?,?,?)",student.getId(),student.getStuno(),student.getName(),student.getClassid()); }catch (Exception e){ throw new RuntimeException(e); } } @Override public void updateStudent(Student student) { try{ runner.update("update student set stuno=?,name=?,classid=? where id=?",student.getStuno(),student.getName(),student.getClassid(),student.getId()); }catch (Exception e){ throw new RuntimeException(e); } } @Override public void deleteStudent(int id) { try{ runner.update("delete from student where id=?",id); }catch (Exception e){ throw new RuntimeException(e); } } }
Service层方法接口
package com.it.service; import com.it.entity.Student; import java.util.List; public interface StudentService { /** * 查找全部学生 * @return */ List<Student> findAllStudent(); /** * 根据id查找学生 */ Student findByid(int id); /** * 保存操做 */ void saveStudent(Student student); /** * 更新操做 */ void updateStudent(Student student); /** * 删除操做 */ void deleteStudent(int id); }
Service层接口实现类,这里就是用了注入的方式给StudentDao注入参数
package com.it.service; import com.it.dao.StudentDao; import com.it.entity.Student; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Repository; import java.util.List; @Repository("studentService") public class StudentServiceImpl implements StudentService{ @Autowired private StudentDao studentDao; @Override public List<Student> findAllStudent() { return studentDao.findAllStudent(); } @Override public Student findByid(int id) { return studentDao.findByid(id); } @Override public void saveStudent(Student student) { studentDao.saveStudent(student); } @Override public void updateStudent(Student student) { studentDao.updateStudent(student); } @Override public void deleteStudent(int id) { studentDao.deleteStudent(id); } }
学生的实体类
package com.it.entity; import java.io.Serializable; /** * 学生实体类 */ public class Student implements Serializable { private int id; private String stuno; private String name; private int classid; public int getId() { return id; } public void setId(int id) { this.id = id; } public String getStuno() { return stuno; } public void setStuno(String stuno) { this.stuno = stuno; } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getClassid() { return classid; } public void setClassid(int classid) { this.classid = classid; } @Override public String toString() { return "Student{" + "id=" + id + ", stuno='" + stuno + '\'' + ", name='" + name + '\'' + ", classid=" + classid + '}'; } }
父配置类,指定当前类为注解类的标签,Configuration
package com.it.config; import org.springframework.context.annotation.*; @Configuration @Import(JbdcConfig.class) @ComponentScan("com.it") @PropertySource("classpath:JdbcConfig.properties") public class SpringConfiguration { }
子配置类,主要是配置的链接jdbc的数据库
package com.it.config; import com.mchange.v2.c3p0.ComboPooledDataSource; import org.apache.commons.dbutils.QueryRunner; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Scope; import javax.sql.DataSource; public class JbdcConfig { /** * 建立dataSource对象 * @param dataSource * @return */ @Value("${jdbc.driver}") private String driver; @Value("${jdbc.url}") private String url; @Value("${jdbc.username}") private String username; @Value("${jdbc.password}") private String password; @Bean("runner") @Scope("prototype") public QueryRunner createQueryRunner(DataSource dataSource) { return new QueryRunner(dataSource); } /** * 建立数据源对象 */ @Bean("dataSource") public DataSource createDataSource() { try { ComboPooledDataSource ds = new ComboPooledDataSource(); ds.setDriverClass(driver); ds.setJdbcUrl(url); ds.setUser(username); ds.setPassword(password); return ds; } catch (Exception e) { throw new RuntimeException(e); } } }
下面是配置文件
<?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容器在建立的时候要扫描包,配置所须要的标签不在bean约束中,而是 在一个叫作context的空降名称和约束中--> <context:component-scan base-package="com.it"></context:component-scan> <!--配置QuerryRunner对象--> <bean id="runner" 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/fresh?characterEncoding=UTF-8"></property> <property name="user" value="root"></property> <property name="password" value="root"></property> </bean> </beans>
数据库配置文件
jdbc.driver=com.mysql.jdbc.Driver jdbc.url=jdbc:mysql://localhost:3306/fresh?characterEncoding=UTF-8&serverTimezone=UTC jdbc.username=root jdbc.password=root
日志输出文件配置,主要是配置输出到控制台
log4j.rootLogger=info,CONSOLE ############################################################# # Console Appender ############################################################# log4j.appender.CONSOLE=org.apache.log4j.ConsoleAppender log4j.appender.Threshold=info ##log4j.appender.CONSOLE.DatePattern=yyyy-MM-dd log4j.appender.CONSOLE.Target=System.out log4j.appender.CONSOLE.layout=org.apache.log4j.PatternLayout log4j.appender.CONSOLE.layout.ConversionPattern=%d{yyyy-M-d HH:mm:ss}%x[%4p]:%m%n' ##输出到文件 ##添加到数据库
最后是测试类,这里咱们采用了spring整合junit4的方式
package com.it; import com.it.config.SpringConfiguration; import com.it.entity.Student; import com.it.service.StudentService; import org.apache.log4j.Logger; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import java.util.List; /** * ** * * 使用Junit单元测试:测试咱们的配置 * * Spring整合junit的配置 * * 一、导入spring整合junit的jar:spring-test(坐标) * * 二、使用Junit提供的一个注解把原有的main方法替换了,替换成spring提供的 * * @Runwith * * 三、告知spring的运行器,spring和ioc建立是基于xml仍是注解的,而且说明位置 * * @ContextConfiguration * * locations:指定xml文件的位置,加上classpath关键字,表示在类路径下 * * classes:指定注解类所在地位置 * * * * 当咱们使用spring 5.x版本的时候,要求junit的jar必须是4.12及以上 * */ @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(classes = SpringConfiguration.class) public class TestClient { Logger logger=Logger.getLogger(TestClient.class); @Autowired private StudentService as; @Test public void findAll(){ List<Student> studentList =as.findAllStudent(); for (Student student:studentList) { //System.out.println(student); logger.info(student); } } @Test public void findbyidtest() { //ApplicationContext ac = new ClassPathXmlApplicationContext("ApplicationContext.xml"); Student student = as.findByid(4); logger.info(student); } @Test public void saveTest() { Student student=new Student(); student.setId(7); student.setStuno("10007"); student.setName("陈多糖"); student.setClassid(2); as.saveStudent(student); } @Test public void updatetest() { Student student = as.findByid(4); student.setName("陈柳柳"); as.updateStudent(student); } @Test public void deletetest() { ApplicationContext ac = new ClassPathXmlApplicationContext("ApplicationContext.xml"); StudentService as = ac.getBean("studentService", StudentService.class); as.deleteStudent(7); } }
输出结果以下
基于注解的 IoC 配置已经完成,可是你们都发现了一个问题:咱们依然离不开 spring 的 xml 配置文件,那么能不能不写这个 bean.xml,全部配置都用注解来实现呢?固然,咱们们也须要注意一下,咱们选择哪一种配置的原则是简化开发和配置方便,而非追求某种技术。
package com.it.config; import com.mchange.v2.c3p0.ComboPooledDataSource; import org.apache.commons.dbutils.QueryRunner; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Scope; import javax.sql.DataSource; @Configuration @ComponentScan("com.it") public class SpringConfiguration { /** *建立一个QueryRunner对象 */ @Bean("runner") @Scope("prototype") public QueryRunner createQueryRunner(DataSource dataSource) { return new QueryRunner(dataSource); } /** * 建立数据源对象 */ @Bean("dataSource") public DataSource createDataSource() { try { ComboPooledDataSource ds = new ComboPooledDataSource(); ds.setDriverClass("com.mysql.jdbc.Driver"); ds.setJdbcUrl("jdbc:mysql://localhost:3306/fresh?characterEncoding=UTF-8"); ds.setUser("root"); ds.setPassword("root"); return ds; } catch (Exception e) { throw new RuntimeException(e); } } }
Configuration
* 做用:指定当前类是一个配置类
* 细节:当配置类做为AnnotationConfigApplicationContext对象建立的参数时,该注解能够不写
ComponentScan
* 做用:用于经过注解指定spring在建立容器时要扫描的包
* 属性:
* value:它和basePackages的做用是同样的,都是用于指定建立容器时要扫描的包。
* 咱们使用此注解就等同于在xml中配置了:
* <context:component-scan base-package="com.itheima"></context:component-scan>
Bean
* 做用:用于把当前方法的返回值做为bean对象存入spring的ioc容器中,该注解只能写在方法上
* 属性:
* name:用于指定bean的id,当不写时,默认值是当前方法的名称
* 细节:
* 当咱们使用注解配置方法时,若是方法有参数,spring框架会去容器中查找有没有可用的bean对象。
* 查找的方式和Autowired注解的做用是同样的
package com.it; import com.it.config.SpringConfiguration; import com.it.entity.Student; import com.it.service.StudentService; import org.junit.Test; import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.AnnotationConfigApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; import java.util.List; public class TestClient { @Test public void findAll(){ //ApplicationContext ac= new ClassPathXmlApplicationContext("ApplicationContext.xml"); ApplicationContext ac= new AnnotationConfigApplicationContext(SpringConfiguration.class); StudentService as = ac.getBean("studentService",StudentService.class); List<Student> studentList =as.findAllStudent(); for (Student student:studentList) { System.out.println(student); } } }
测试结果以下:
问题来了,上面划线的部分两个的做用是同样的嘛?效果是不同的,下面划线部分,当把对象建立好了后,会把对象给你丢到Spring Ioc容器中,上面划线的部分固然不会那么作,它只是会给你返回一个QueryRunner的对象,这个时候就须要咱们手动把对象丢到容器里面去,就须要采用@Bean的方法
线面还有一个问题就是咱们的QueryRunner对象是否是一个单例对象啦?这个问题关系到咱们线程的问题
那怎么改变bean的做用范围啦?这个是时候寄须要用的scope属性啦,结果以下
对于配置类咱们想要把SpringConfiguration中的jbdc配置专门提取到一个配置类中去?这样的话也方便后期的管理和修改
单元测试findAll的测试结果以下
导入配置perproties配置文件@PropertySource
* 做用:用于指定properties文件的位置
* 属性:
* value:指定文件的名称和路径。
* 关键字:classpath,表示类路径下
*
在以前JdbcConfig配置类中dataSource的全部的属性都是写死的后来咱们进行了以下改造
package com.it.config; import com.mchange.v2.c3p0.ComboPooledDataSource; import org.apache.commons.dbutils.QueryRunner; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Scope; import javax.sql.DataSource; public class JbdcConfig { /** * 建立dataSource对象 * @param dataSource * @return */ @Value("${jdbc.driver}") private String driver; @Value("${jdbc.url}") private String url; @Value("${jdbc.username}") private String username; @Value("${jdbc.password}") private String password; @Bean("runner") @Scope("prototype") public QueryRunner createQueryRunner(DataSource dataSource) { return new QueryRunner(dataSource); } /** * 建立数据源对象 */ @Bean("dataSource") public DataSource createDataSource() { try { ComboPooledDataSource ds = new ComboPooledDataSource(); ds.setDriverClass(driver); ds.setJdbcUrl(url); ds.setUser(username); ds.setPassword(password); return ds; } catch (Exception e) { throw new RuntimeException(e); } } }
对于新建立的private String driver属性,咱们使用@Valeue("${jdbc.diver}")来注入perproties中的值,在SpringConfigruation配置父类中使用@PropertySource来注入配置文件
测试结果以下,同样能查询到结果
那么咱们通常适合用那种配置方式?用注解仍是,xml啦?
用到已经写好的jar的,通常状况下咱们用xml来配置,比较省事,要是这个类是咱们本身写的话,用注解来配置比较方便
在咱们的测试类中存在不少的重复的方法,那么有没方法解决啦!!我先采用init的方式先加载建立容器的方式
结果显示没有任何问题,那么这个问题被完全解决掉了嘛?
根据分析获得,junit运行原理
使用Junit单元测试:测试咱们的配置,Spring整合junit的配置
一、导入spring整合junit的jar:spring-test(坐标)
二、使用Junit提供的一个注解把原有的main方法替换了,替换成spring提供的@Runwith
三、告知spring的运行器,spring和ioc建立是基于xml仍是注解的,而且说明位置
@ContextConfiguration
属性:locations:指定xml文件的位置,加上classpath关键字,表示在类路径下
classes:指定注解类所在地位置
细节:当咱们使用spring 5.x版本的时候,要求junit的jar必须是4.12及以上
package com.it; import com.it.config.SpringConfiguration; import com.it.entity.Student; import com.it.service.StudentService; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import java.util.List; @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(classes = SpringConfiguration.class) public class TestClient { @Autowired private StudentService as; @Test public void findAll(){ List<Student> studentList =as.findAllStudent(); for (Student student:studentList) { System.out.println(student); } } @Test public void findbyidtest() { //ApplicationContext ac = new ClassPathXmlApplicationContext("ApplicationContext.xml"); Student student = as.findByid(4); System.out.println(student); } @Test public void saveTest() { Student student=new Student(); student.setId(7); student.setStuno("10007"); student.setName("陈多糖"); student.setClassid(2); as.saveStudent(student); } @Test public void updatetest() { Student student = as.findByid(4); student.setName("陈柳柳"); as.updateStudent(student); } @Test public void deletetest() { ApplicationContext ac = new ClassPathXmlApplicationContext("ApplicationContext.xml"); StudentService as = ac.getBean("studentService", StudentService.class); as.deleteStudent(7); } }
测试结果以下:
为何 不把测试类配到 xml
在解释这个问题以前,先解除你们的疑虑,配到 XML 中能不能用呢? 答案是确定的,没问题,可使用。 那么为何不采用配置到 xml 中的方式呢? 这个缘由是这样的: 第一:当咱们在 xml 中配置了一个 bean,spring 加载配置文件建立容器时,就会建立对象。 第二:测试类只是咱们在测试功能时使用,而在项目中它并不参与程序逻辑,也不会解决需求上的问 题,因此建立完了,并无使用。那么存在容器中就会形成资源的浪费。 因此,基于以上两点,咱们不该该把测试配置到 xml 文件中。