- <strong>
- <strong>
- <strong>
- <strong>
开篇絮叨
大家应该清楚,XML与注解常常是形影不离的,他们就像一对双胞胎,但兄弟两个的想法都是一致的,那就是帮助开发者实现想要的功能,我们所说的IOC 技术,无疑是为了降低程序间的耦合,那么,今天就来聊一聊,基于注解的IOC配置,当然为了大家有对比学习,两种配置同时讲解,同时我把例举得尽量完整一些,就来完成一个对单表进行 CURD 的案例
(一) 准备代码与环境
(1) 添加必要的依赖
- spring-context
- mysql-connector-java
- c3p0(数据库连接池)
- commons-dbutils(简化JDBC的工具)—后面会简单介绍一下
- junit (单元自测)
说明:由于我这里创建的是一个Maven项目,所以在这里修改 pom.xml 添加一些必要的依赖坐标就可以
如果创建时没有使用依赖的朋友,去下载我们所需要的 jar 包导入就可以了
<code><dependencies> <dependency> <groupid>org.springframework/<groupid> <artifactid>spring-context/<artifactid> <version>5.0.2.RELEASE/<version> /<dependency> <dependency> <groupid>mysql/<groupid> <artifactid>mysql-connector-java/<artifactid> <version>5.1.6/<version> /<dependency> <dependency> <groupid>c3p0/<groupid> <artifactid>c3p0/<artifactid> <version>0.9.1.2/<version> /<dependency> <dependency> <groupid>commons-dbutils/<groupid> <artifactid>commons-dbutils/<artifactid> <version>1.4/<version> /<dependency> <dependency> <groupid>junit/<groupid> <artifactid>junit/<artifactid> <version>4.12/<version> <scope>test/<scope> /<dependency> /<dependencies>/<code>
简单看一下,spring核心的一些依赖,以及数据库相关的依赖等就都导入进来了
(2) 创建账户表以及实体
A:创建 Account 表
<code>-- ------------------------------ Table structure for account-- ----------------------------CREATE TABLE `account` ( `id` int(11) NOT NULL AUTO_INCREMENT, `name` varchar(32), `balance` float, PRIMARY KEY (`id`))/<code>
B:创建 Account 类
没什么好说的,对应着我们的表创出实体
<code>public class Account implements Serializable { private Integer id; private String name; private Float balance; ......补充 get set toString 方法/<code>
(3) 创建Service以及Dao
A:AccountService 接口
<code>public interface AccountService { void add(Account account); void delete(Integer accpuntId); void update(Account account); List<account> findAll(); Account findById(Integer accountId);}/<account>/<code>
B:AccountServiceImpl 实现类
<code>public class AccountServiceImpl implements AccountService { private AccountDao accountDao; public void setAccountDao(AccountDao accountDao) { this.accountDao = accountDao; } public void add(Account account) { accountDao.addAccount(account); } public void delete(Integer accpuntId) { accountDao.deleteAccount(accpuntId); } public void update(Account account) { accountDao.updateAccount(account); } public List<account> findAll() { return accountDao.findAllAccount(); } public Account findById(Integer accountId) { return accountDao.findAccountById(accountId); }}/<account>/<code>
C:AccountDao 接口
<code>public interface AccountDao { void addAccount(Account account); void deleteAccount(Integer accountId); void updateAccount(Account account); List<account> findAllAccount(); Account findAccountById(Integer accountId);}/<account>/<code>
D:AccountDaoImpl 实现类
由于今天要完成的是一个增删改查的操作,所以我们引入了 DBUtils 这样一个操作数据库的工具,它的作用就是封装代码,达到简化 JDBC 操作的目的,由于以后整合 SSM 框架的时候,持久层的事情就可以交给 MyBatis 来做,而今天我们重点还是讲解 Spring 中的知识,所以这部分会用就可以了,重点看 XML 与 注解 两种配置方式
用到的内容基本讲解:
QueryRunner 提供对 sql 语句进行操作的 API (insert delete update)
ResultSetHander 接口,定义了查询后,如何封装结果集(仅提供了我们用到的)
- BeanHander:将结果集中第第一条记录封装到指定的 JavaBean 中
- BeanListHandler:将结果集中的所有记录封装到指定的 JavaBean 中,并且将每一个 JavaBean封装到 List 中去
<code>public class AccountDaoImpl implements AccountDao { private QueryRunner runner; public void setRunner(QueryRunner runner) { this.runner = runner; } public void addAccount(Account account) { try { runner.update("insert into account(name,balance)values(?,?)", account.getName(), account.getBalance()); } catch (Exception e) { throw new RuntimeException(e); } } public void updateAccount(Account account) { try { runner.update("update account set name=?,balance=? where id=?", account.getName(), account.getBalance(), account.getId()); } catch (Exception e) { throw new RuntimeException(e); } } public void deleteAccount(Integer accountId) { try { runner.update("delete from account where id=?", accountId); } catch (Exception e) { throw new RuntimeException(e); } } public List<account> findAllAccount() { try { return runner.query("select * from account", new BeanListHandler<account>(Account.class)); } catch (Exception e) { throw new RuntimeException(e); } } public Account findAccountById(Integer accountId) { try { return runner.query("select * from account where id = ? ", new BeanHandler<account>(Account.class), accountId); } catch (Exception e) { throw new RuntimeException(e); } }}/<account>/<account>/<account>/<code>
(二) XML 配置方式
在这里有两基本的方式,一是通过构造函数注入,另一种就是通过Set注入,实际上所做的就是,使用类的构造函数或者Set给成员变量进行赋值,但特别的是,这里是通过配置,使用 Spring 框架进行注入首先就是头部的依赖信息,顺便提一句,当然我们可以去官网查找贴过来
先把针对上面功能的具体配置代码贴出来
<code><beans> <bean> <property> /<bean> <bean> <property> /<bean> <bean> <constructor-arg> /<bean> <bean> <property> <property> <property> <property> /<bean>/<beans>/<code>
分析一下:
配置 Bean 标签的时候,我们见到了两种形式 property、constructor-arg 也就是对应着 set 方式 与构造函形式,先说一下比较常见的 set 方式,用上面的代码中距离:
(1) Set 方式
顾名思义,就是通过去找你给出对应的 Set 方法,然后对成员变量进行赋值,先看下类中的代码
<code>public class AccountServiceImpl implements AccountService {//成员 private AccountDao accountDao;//Set方法 public void setAccountDao(AccountDao accountDao) { this.accountDao = accountDao; } ...... 下面是增删改查的方法}/<code>
这是 bean.xml 中的配置
<code><bean> <property>/<bean>/<code>
然后 property 配置的过程中,有一些属性需要说一下
- name:与成员变量名无关,与set方法后的名称有关,例如 setAccountDao() 获取到的就是accountDao,并且已经小写了开头
- value:这里可以写基本数据类型和 String
- ref:这里可以引入另一个bean,帮助我们给其他类型赋值 (例如这里就通过 ref 引入了下面 id 值为accountDao的 bean)
当然,以后可能会见到一种方式就是 使用 p名称空间注入数据 (本质还是set)
头部中需要修改引入这一句
xmlns:p="http://www.springframework.org/schema/p"
我直接拿以前文章中的一个例子:
<code><beans> <bean><bean>/<beans>/<code>
(2) 构造函数方式
下面就是使用构造函数的一种方式,这一种的前提就是:类中必须提供一个和参数列表相对应的构造函数
由于我们选择的是 DBUtils 这样一个工具,而它为我们提供了两种构造函数,即带参和无参,所以我们可以在其中注入数据源,也可以使得每一条语句都独立事务
还有一点需要说明的就是:我们下面的数据源使用了 c3p0 这只是一种选择方式,并不是一定的,是因为使用 DBUtils 的时候需要手动传递一个 Connection 对象
<code><bean> <constructor-arg>/<bean>/<code>
来说一下所涉及到的标签:
- constructor-arg(放在 bean 标签内) 再说一说其中的一些属性值 给谁赋值: index:指定参数在构造函数参数列表的索引位置 type:指定参数在构造函数中的数据类型 name:指定参数在构造函数中的名称(更常用) 赋什么值: value:这里可以写基本数据类型和 String ref:这里可以引入另一个bean,帮助我们给其他类型赋值
(3) 注入集合属性
为了演示这些方式,我们在一个实体成员中将常见的一些集合都写出来,然后补充其 set 方法
<code>private String[] strs;private List<string> list;private Set<string> set;private Map<string> map;private Properties props;/<string>/<string>/<string>/<code>
在配置中也是很简单的,只需要按照下列格式写标签就可以了,由于增删改查中并没有涉及到集合的相关信息,这里就是简单提一下,可以自己测试一下
<code><bean><property><array> <value>张三/<value> <value>李四/<value> <value>王五/<value> /<array> /<property> <property> <list> <value>张三/<value> <value>李四/<value> <value>王五/<value> /<list> /<property> <property><value>张三/<value> <value>李四/<value> <value>王五/<value> /<property> <property> /<property> <property> <props> <prop>张三/<prop> <prop>21/<prop> /<props> /<property> /<bean>/<code>
(4) 测试代码
<code>public class AccountServiceTest { private ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml"); private AccountService as = ac.getBean("accountService", AccountService.class); @Test public void testAdd(){ Account account = new Account(); account.setName("jack"); account.setBalance(1000f); as.add(account); } @Test public void testUpdate(){ Account account = as.findById(4); account.setName("杰克"); account.setBalance(1500f); as.update(account); } @Test public void testFindAll(){ List<account> list = as.findAll(); for(Account account : list) { System.out.println(account); } } @Test public void testDelete(){ as.delete(4); }}/<account>/<code>
(5) 执行效果
添加,修改(包含了查询指定id),删除
查询所有
(三) 注解配置方式
首先,我们先将上面的例子使用注解来实现一下,再来具体的讲解:
(1) 改造原程序为注解配置
首先需要为 Dao 和 Service 的实现类中 添加注解
<code>@Service("accountService")public class AccountServiceImpl implements AccountService { @Autowired private AccountDao accountDao; 下面的原封不动}/<code>
<code>@Repository("accountDao")public class AccountDaoImpl implements AccountDao { @Autowired private QueryRunner runner; 下面的原封不动}/<code>
<code><beans> <component-scan> <bean> <constructor-arg> /<bean> <bean> <property> <property> <property> <property> /<bean>/<beans>/<code>
到这里,一个最基本的注解改造就完成了,大家可以用前面的测试类进行一下测试
下面我们正式说一下注解配置相关的知识
(2) 常用注解
A:创建对象
@Component
- 让Spring 来管理资源,相当于XML 中配置一个 bean <bean>
- 可以在括号内指定 value 值,即指定 bean 的 id ,如果不指定就会默认的使用当前类的类名
- 如果注解中只有一个value属性要赋值,value可以不写,直接写名称,如上面例子中
@Controller @Service @Repository
对于创建对象的注解,Spring 还提供了三种更加明确的说法,作用是完全相同的,但是针对不同的场景起了不同的叫法罢了
- @Controller:一般用于表现层
- @Service:一般用于业务层
- @Repository:一般用于持久层
B:注入数据
@Autowired
- 自动按类型注入,相当于XML 中配置一个 bean <property> 或者 <property>/<property>
- 容器中有一个唯一的 bean 对象类型和注入的变量类型一致,则注入成功
<code>@Autowiredprivate AccountDao accountDao;@Repository("accountDao")public class AccountDaoImpl implements AccountDao {......}/<code>
- 比如上面的例子,Spring的IOC中容器是一个Map的结构,字符串“accountDao” 以及这个可以认为是 AccountDao 类型的 AccountDaoImpl 类就被以键值对的形式存起来,被注解 @Autowired的地方,会直接去容器的 value 部分去找 AccountDao 这个类型的类 当 IoC 中匹配到了多个符合的,就会根据变量名去找,找不到则报错:例如下面,根据 AccountDao类型匹配到了两个类,所以根据变量名去找找到了 AccountDaoImplA 这个类
<code>@Autowiredprivate AccountDao accountDaoA;@Repository("accountDaoA")public class AccountDaoImplA implements AccountDao {......}@Repository("accountDaoB")public class AccountDaoImplB implements AccountDao {......}/<code>
- 可以对类的成员变量、方法以及构造函数进行标注,完成自动装配
- 使用此注解可以省略 set 方法
@Qualifier
- 在自动按类型注入的基础之上,按照 Bean 的 id 注入,给字段注入的时候不能够单独使用,需要配合上面的 @Autiwire 使用,但是给方法参数注入的时候,可以独立使用
- 使用时:value 值指定 bean 的 id
它有时候必须配合别的注解使用,有没有一个标签可以解决这个问题呢?答案就是 @Resource
@Resource
- 直接按照 bean 的 id 注入,不过只能注入其他 bean 类型
- 使用时:name 值指定 bean 的 id
前面三个都是用来注入其他的 bean 类型的数据,下面来说一说,基本类型以及String的实现
(特别说明:集合类型的注入只能通过 XML 来实现)
@Value
- 这个注解就是用来注入基本数据类型和 String 类型数据的
- 使用时:value 属性用于指定值
C:改变作用范围
@Scope
- 指定 bean 的作用范围 相当于XML 中配置一个 <bean>
- 使用时:value 属性用于指定范围的值(singleton prototype request session globalsession) /<bean>
D:生命周期相关
相当于:<bean>
@PostConstruct
- 指定初始化方法
@PreDestroy
- 指定销毁方法
(四) XML和注解 的对比与选择
(1) 优缺点
一般来说,我们两种配置方式都是有人使用的,不过我个人更习惯使用注解的方式
- XML 类之间的松耦合关系,扩展性强,利于更换修改 对象之间的关系清晰明了
- 注解: 简化配置,并且使用起来也容易,效率会高一些 在类中就能找对配置,清晰明了 类型安全
(2) 两者对比
(五) 补充新注解
为什么要补充新注解呢? 在我们使用注解时,在书写代码时,简化了很多,但是我们在 bean.xml 文件中 仍然需要 开启扫描、 进行配置QueryRunner 以及 数据源,如何彻底摆脱 xml 配置全面使用注解呢?
这也就是我们将要补充的几个新注解,作用就是让我们全面使用注解进行开发
(1) 注解讲解
A: 配置类注解
@Configuration
- 指定当前类是 spring 的一个配置类,相当于 XML中的 bean.xml 文件
- 获取容器时需要使用下列形式
<code>
依旧使用上方的 CURD 的案例代码进行修改,首先与cn同级创建了一个名为 config 的包,然后编写一个名为 SpringConfiguration 的类,当然实际上这两个名字无所谓的,添加注解
<code>@Configurationpublic class SpringConfiguration {}/<code>
B: 指定扫描包注解
@ComponentScan
@Configuration 相当于已经帮我们把 bean.xml 文件创立好了,按照我们往常的步骤,应该指定扫描的包了,这也就是我们这个注解的作用
- 指定 spring 在初始化容器时要扫描的包,在 XML 中相当于:
<code> <component-scan> /<code>
- 其中 basePackages 用于指定扫描的包,和这个注解中value属性的作用是一致的
具体使用:
<code>@Configuration@ComponentScan("cn.ideal")public class SpringConfiguration {}/<code>
C: 创建对象
@Bean
写好了配置类,以及指定了扫描的包,下面该做的就是配置 QueryRunner 以及数据源了,在 XML 中我们会通过书写 bean 标签来配置,而 Spring 为我们提供了 @Bean 这个注解来替代原来的标签
- 将注解写在方法上(只能是方法),也就是代表用这个方法创建一个对象,然后放到 Spring 的容器中去
- 通过 name 属性 给这个方法指定名称,也就是我们 XML 中 bean 的 id
具体使用:
<code>package 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 javax.sql.DataSource;public class JdbcConfig {/** * 创建一个 QueryRunner对象 * @param dataSource * @return */ @Bean(name = "runner") public QueryRunner creatQueryRunner(DataSource dataSource){ return new QueryRunner(dataSource); } /** * 创建数据源,并且存入spring * @return */ @Bean(name = "dataSource") public DataSource createDataSource() { try { ComboPooledDataSource ds = new ComboPooledDataSource(); ds.setUser("root"); ds.setPassword("1234"); ds.setDriverClass("com.mysql.jdbc.Driver"); ds.setJdbcUrl("jdbc:mysql:///spring_day02"); return ds; } catch (Exception e) { throw new RuntimeException(e); } }}/<code>
D: 配置 properties 文件
@PropertySource
上面在创建数据源的时候,都是直接把配置信息写死了,如果想要使用 properties 进行内容的配置,在这时候就需要,使用 @PropertySource 这个注解
- 用于加载 .properties 文件中的配置
- value [] 指定 properties 文件位置,在类路径下,就需要加上 classpath
<code>@Configuration@ComponentScan("cn.ideal")@PropertySource("classpath:jdbcConfig.properties")public class SpringConfiguration {}/<code>
<code>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 = "runner") public QueryRunner creatQueryRunner(DataSource dataSource){ return new QueryRunner(dataSource); } /** * 创建数据源,并且存入spring * @return */ @Bean(name = "dataSource") public DataSource createDataSource() { try { ComboPooledDataSource ds = new ComboPooledDataSource(); ds.setUser(username); ds.setPassword(password); ds.setDriverClass(driver); ds.setJdbcUrl(url); return ds; } catch (Exception e) { throw new RuntimeException(e); } }}/<code>
E:导入其他配置类
@Import
这样看来一个 JdbcConfig 就基本写好了,我们在其中配置了 QueryRunner 对象,以及数据源,这个时候,实际上我们原先的 bean.xml 就可以删掉了,但是我们虽然写好了 JdbcConfig 但是如何将两个配置文件联系起来呢?这也就是这个注解的作用
<code>@Configuration@ComponentScan("cn.ideal")@Import(JdbcConfig.class)@PropertySource("classpath:jdbcConfig.properties")public class SpringConfiguration {}/<code>
(2) 注解获取容器
修改获取容器的方式后,就可以进行测试了
<code>private ApplicationContext ac = new AnnotationConfigApplicationContext(SpringConfiguration.class);private AccountService as = ac.getBean("accountService", AccountService.class);/<code>
(五) Spring 单元测试改进
由于我们需要通过上面测试中两行代获取到容器,为了不每一次都写这两行代码,所以我们在前面将其定义在了成员位置,但是有没有办法可以省掉这个步骤呢?
也就是说,我们想要程序自动创建容器,但是原来的 junit 很显然是实现不了的,因为它并不会知道我们是否使用了 spring ,不过 junit 提供了一个注解让我们替换它的运行器,转而由 spring 提供
首先需要导入 jar 包 或者说导入依赖坐标
<code><dependency><groupid>org.springframework/<groupid><artifactid>spring-test/<artifactid><version>5.0.2.RELEASE/<version>/<dependency>/<code>
使用 @RunWith 注解替换原有运行器 然后使用 @ContextConfiguration 指定 spring 配置文件的位置,然后使用 @Autowired 给变量注入数据
<code>@RunWith(SpringJUnit4ClassRunner.class)@ContextConfiguration(classes = SpringConfiguration.class)public class AccountServiceTest { @Autowired private AccountService as; }/<code>
原文链接:https://juejin.im/post/5e537b2851882549431febe2
閱讀更多 追逐仰望星空 的文章