spring 框架学习
spring 框架学习安排
目录
1. 第二天(学习前最好能复习一下注解)
1. spring中IOC的常用注解
曾经的xml配置
<bean id="accountService" class="com.liuzeyu.service.impl.IAccountServiceImpl
init-method="" destory-method="" scope=""/>
<property name="" value="" | ref="">
</property>
</bean>
上述配置可以用于创建对象,用于注入数据,用于改变作用范围和生命周期
<mark>换做注解实现:</mark>
-
用于创建对象
- dao层实现类:
@Component("accountDao") public class AccountDaoImpl implements AccountDao{ public void save() { System.out.println("保存了数据库"); } }
- service实现类:
@Component("accountService") public class IAccountServiceImpl implements IAccountService{ public void save() { ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml"); AccountDao adao = (AccountDao) ac.getBean("accountDao"); adao.save(); } }
- 测试函数
public class UITest { public static void main(String[] args) { //1.获取核心容器对象 ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml"); //2.根据id获取bean对象 //2.1方式1获取 IAccountService aservice = (IAccountService) ac.getBean("accountService"); aservice.save(); } }
其中@Component
注解的作用是把当前类存入spring的IOC容器中
属性:value用于指定bean的id
最后需要再bean.xml配置<?xml version="1.0" encoding="UTF-8"?> <!--</beans>--> <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"> <context:component-scan base-package="com.liuzeyu"></context:component-scan> </beans>
<context:component-scan base-package="com.liuzeyu"></context:component-scan>
作用怎是告知spring创建容器时要扫描的包,配置所需要的标签不是再bean约束里面,而是一个名称为context名称空间和约束里面。
<mark>补充三个注解:</mark>
1)Controller:一般用于表现层
2)Service:一般用于业务逻辑层
3)Repository:一般用于持久层
以上三个注解的属性和作用和@Component相同,它们三个是spring框架为我们提供三层架构使用的注解,使我们三次架构更加清晰。 - dao层实现类:
-
用于注入数据
- 在1的基础上,修改service实现类获取AccountDao 对象的方法,使用注入数据的方式
@Service("accountService") public class IAccountServiceImpl implements IAccountService{ @Autowired private AccountDao adao = null; public void save() { adao.save(); } }
分析:@Autowired会干一件事,就是用AccountDao 去spring的IOC容器中寻找value值为AccountDao的实现类(不会去寻找key),如果找到,则反射生成实现类实现的接口对象,即AccountDao 对象。
- 如果此时遇到有多个AccountDao的实现类,那么Autowired第一次寻找就找到了两个,此时如何区分呢?该使用哪一个接口对象呢?退而求其次,Autowired会再寻找注入的变量名称和spring的IOC容器中bean对象的被Component标记的vlaue值,也就是bean对象的id值是否相等,如果不等则报错,如果相等创建其对象。
- 为什么解决上面出现的两个dao层实现类实现同一接口使用,@Qualifier指定特定的key值
缺陷就是必须配置@Autowired使用,如何解决这一个捆绑的烦恼呢? - 为了解决上述捆绑的烦恼,可以使用为Resource的name属性配置
- 在1的基础上,修改service实现类获取AccountDao 对象的方法,使用注入数据的方式
-
用于改变作用范围和生命周期(了解)
- 单例和多例的配置
- 生命周期的问题
public class UITest { public static void main(String[] args) { //1.获取核心容器对象 ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml"); //2.根据id获取bean对象 //2.1方式1获取 IAccountService aservice = (IAccountService) ac.getBean("accountService"); aservice.save(); ((ClassPathXmlApplicationContext) ac).close(); } }
- 单例和多例的配置
2. 案例使用xml方式实现单表的CRUD操作
-
准备dao层接口和实现类
/** * 账户的持久层接口 */ public interface IAccountDao { /** * 查询所有 * @return */ List<Account> findAllAccount(); /** * 查询一个 * @return */ Account findAccountById(Integer accountId); /** * 保存 * @param account */ void saveAccount(Account account); /** * 更新 * @param account */ void updateAccount(Account account); /** * 删除 * @param acccountId */ void deleteAccount(Integer acccountId); }
/** * 账户的持久层实现类 */ public class AccountDaoImpl implements IAccountDao { private QueryRunner runner; public void setRunner(QueryRunner runner) { this.runner = runner; } 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); } } 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); } } 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); } } public void deleteAccount(Integer accountId) { try{ runner.update("delete from account where id=?",accountId); }catch (Exception e) { throw new RuntimeException(e); } } }
-
准备service接口和实现类
package com.liuzeyu.service; import com.liuzeyu.domain.Account; import java.util.List; /** * 账户的业务层接口 */ public interface IAccountService { /** * 查询所有 * @return */ List<Account> findAllAccount(); /** * 查询一个 * @return */ Account findAccountById(Integer accountId); /** * 保存 * @param account */ void saveAccount(Account account); /** * 更新 * @param account */ void updateAccount(Account account); /** * 删除 * @param acccountId */ void deleteAccount(Integer acccountId); }
/** * 账户的业务层实现类 */ @Service("accountService") public class AccountServiceImpl implements IAccountService{ private IAccountDao accountDao; public void setAccountDao(IAccountDao accountDao) { this.accountDao = accountDao; } public List<Account> findAllAccount() { return accountDao.findAllAccount(); } public Account findAccountById(Integer accountId) { return accountDao.findAccountById(accountId); } public void saveAccount(Account account) { accountDao.saveAccount(account); } public void updateAccount(Account account) { accountDao.updateAccount(account); } public void deleteAccount(Integer acccountId) { accountDao.deleteAccount(acccountId); } }
-
准备实体类
package com.liuzeyu.domain; import java.io.Serializable; /** * 账户的实体类 */ public class Account implements Serializable { private Integer id; private String name; private Float money; public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public Float getMoney() { return money; } public void setMoney(Float money) { this.money = money; } @Override public String toString() { return "Account{" + "id=" + id + ", name='" + name + '\'' + ", money=" + money + '}'; } }
-
准备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" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <!-- 配置Service --> <bean id="accountService" class="com.liuzeyu.service.impl.AccountServiceImpl"> <!-- 注入dao --> <property name="accountDao" ref="accountDao"></property> </bean> <!--配置Dao对象--> <bean id="accountDao" class="com.liuzeyu.dao.impl.AccountDaoImpl"> <!-- 注入QueryRunner --> <property name="runner" ref="runner"></property> </bean> <!--配置QueryRunner--> <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/eesy"></property> <property name="user" value="root"></property> <property name="password" value="809080"></property> </bean> </beans>
-
准备测试函数
/** * Created by liuzeyu on 2020/4/20. */ public class AnnotationTest { @Test public void testFindAll(){ ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml"); IAccountService ias = ac.getBean("accountService", IAccountService.class); List<Account> accounts = ias.findAllAccount(); for (Account account : accounts) { System.out.println(account); } } @Test public void testFindById(){ ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml"); IAccountService ias = ac.getBean("accountService", IAccountService.class); Account account = ias.findAccountById(1); System.out.println(account); } @Test public void testInsert(){ ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml"); IAccountService ias = ac.getBean("accountService", IAccountService.class); Account account = new Account(); account.setName("liuzeyu"); account.setMoney(50000f); ias.saveAccount(account); testFindAll(); //测试 } @Test public void testUpdate(){ ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml"); IAccountService ias = ac.getBean("accountService", IAccountService.class); Account account = ias.findAccountById(4); account.setName("wahaha"); account.setMoney(88888f); ias.updateAccount(account); testFindAll(); //测试 } @Test public void testDelete(){ ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml"); IAccountService ias = ac.getBean("accountService", IAccountService.class); ias.deleteAccount(4); testFindAll(); //测试 } }
- 依赖pom.xml
<packaging>jar</packaging> <dependencies> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>5.0.2.RELEASE</version> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-test</artifactId> <version>5.0.2.RELEASE</version> </dependency> <dependency> <groupId>commons-dbutils</groupId> <artifactId>commons-dbutils</artifactId> <version>1.4</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> </dependencies>
3. 改造基于xml的IOC案例,使用注解和注入的方式实现
- 在2的基础上修改使用注解的方式:
-
修改bean,xml的约束和删除使用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"> <context:component-scan base-package="com.liuzeyu"></context:component-scan> <!--配置QureyRunner--> <bean id="runner" class="org.apache.commons.dbutils.QueryRunner" scope="prototype"> <constructor-arg name="ds" ref="dataSource"/> </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/eesy"></property> <property name="user" value="root"></property> <property name="password" value="809080"></property> </bean> </beans>
-
持久层
-
业务层
-
- 在上面1的基础上继续优化,bean.xml使用注解替换
- bean.xml哪些东西需要使用注解替换掉
- 如上图,需要使用注解替换的地方有1,2,3处
- 1处的替换使用一个配置类
/** * Created by liuzeyu on 2020/4/21. * spring的配置文件类。使用注解方式用于替换bean.xml的配置信息 * spring用到的注解: * 1.@Configuration用于指定当前类是一个注解类 * 2.@ComponentScan("com.liuzeyu")指定spring创建IOC容器是需要扫描的包 */ @Configuration @ComponentScan("com.liuzeyu") public class SpringConfig { }
- 2处是根据构造函数来注入数据,3处是根据setXXX来注入数据所以我们也可以自己定义构造出函数来注入数据
@Bean注解:
作用:把当前方法的返回值作为bean对象存入到spring的IOC容器中。
属性:name用于指定bean的id,如果不写默认为当前方法的名称
<mark>细节:</mark>
当我们使用注解配置方法是,如果方法有参数,spring框架会去容器中寻找有没有可用的bean对象,查找的方式和Autowired的方式一样,自动按照类型注入,如果有唯一匹配,则注入,否则报错。如果出现多个类型匹配,在进而匹配它的变量名称。 - 可以将QueryRunner改为多例
只需要在QueryRunner注入处添加注解@Scope(“prototype”)即可/** * Created by liuzeyu on 2020/4/21. * 测试QueryRunner是单例还是多例 */ public class QueryRunnerTest { @Test public void testQueryRunner(){ ApplicationContext ac = new AnnotationConfigApplicationContext(SpringConfig.class); QueryRunner runner1 = ac.getBean("runner", QueryRunner.class); QueryRunner runner2 = ac.getBean("runner", QueryRunner.class); System.out.println(runner1 == runner2); //true :单例 } }
- @Import注解的使用
-
发现一个问题,配置类缺少@Configuration程序依然可以运行
这是因为在测试类中加载类配置文件的字节码ApplicationContext ac = new AnnotationConfigApplicationContext(SpringConfig.class);
-
如果出现多个配置文件,并且将SpringConfig配置文件的信息写入到JDBCConfig中
@ComponentScan("com.liuzeyu") public class SpringConfig { }
@Configuration @ComponentScan("com.liuzeyu") public class JDBCConfig { @Bean(name = "runner") @Scope(value = "prototype") public QueryRunner createQueryRunner(DataSource dataSource){ return new QueryRunner(dataSource); } @Bean("dataSource") public DataSource createDataSource(){ try{ ComboPooledDataSource source = new ComboPooledDataSource(); source.setUser("root"); source.setPassword("809080"); source.setDriverClass("com.mysql.jdbc.Driver"); source.setJdbcUrl("jdbc:mysql://localhost:3306/eesy"); return source; }catch (Exception e){ throw new RuntimeException(e); } } }
此时如果再次缺少了JDBCConfig的@Configuration,将会报错。因为创建ApplicationContext对象时会加载配置类SpringConfig(默认已经当作配置类),开启扫描包,未发现@Configuration标识的配置类,就会报错。此时解决办法有两个:
第一种:加载JDBCConfig.class字节码ApplicationContext ac = new AnnotationConfigApplicationContext(SpringConfig.class, JDBCConfig.class);
第二种:在使用Import注解
@ComponentScan("com.liuzeyu") @Import(JDBCConfig.class) public class SpringConfig { }
Import:
作用:用于导入其它的配置类
属性:value用于指定其它的配置类的字节码,当我们使用Import注解后,有Import的类就是父配置类,而导入的就是子配置类
-
- @PropertySource的使用
- 准备配置文件
- 删除创建DataSource方法中的jdbc连接信息
- 添加JDBCConfig的属性,通过@Value属性将外部的配置文件信息加载到属性值中
DataSource方法中的jdbc连接信息@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(name = "runner") @Scope(value = "prototype")
- 但是现在配置文件中的信息就会被加载进来吗,不可以,需要在父配类上加上注解@PropertySource
因为放到resource资源文件下的配置文件部署后都会在类路径下,这样就可以从@Value获取到值@ComponentScan("com.liuzeyu") @Import(JDBCConfig.class) @PropertySource("classpath:jdbc.properties") public class SpringConfig { }
- @Qualifier注解在方法上的使用
解决方案:
- 1处的替换使用一个配置类
- bean.xml哪些东西需要使用注解替换掉
4. spring和JUnit的整合
-
发现测试方法存在的问题
public class AnnotationTest { private IAccountService ias = null; @Before public void init(){ ApplicationContext ac = new AnnotationConfigApplicationContext(SpringConfig.class); ias = ac.getBean("accountService", IAccountService.class); } @Test public void testFindAll(){ List<Account> accounts = ias.findAllAccount(); for (Account account : accounts) { System.out.println(account); } } }
上述测试案例可以运行,但是存在一个问题,就是如果测试人员来用的话万一在init函数里面出现问题,则测试人员将无法作报告分析,因为这是开发人员留下的坑,一般的测试人员只对测试方法进行分析。
-
进行分析问题
可以使用注解的方式注入IAccountService对象,如下:public class AnnotationTest { @Autowired private IAccountService ias = null; @Test public void testFindAll(){ List<Account> accounts = ias.findAllAccount(); for (Account account : accounts) { System.out.println(account); } } }
运行会出现空指针异常,为什么呢?
要从应用程序的入口main函数开始分析,其实在junit中默认集成了一个main方法,该方***判断当前测试类哪些方法有@Test注解,junit就可以让这些方法执行。
为什么出现空指针?
junit是不管我们是否采用spring框架的,它只会使用自己的main入口函数,所以它也就不会为我们读取配置然后创建IOC容器了,因此会注入失败,出现空指针。 -
解决方案
整合junit和spring框架- 导入相关的jar包
- 使用juint提供的一个注解将原来的main方法替换掉,替换层spring提供的 @Runwith
- 告知spring运行器,spring和IOC的创建是基于xml还是注解,并说明位置 @ContextConfiguration(),属性有:
- locations:指定xml的文件位置,加上classpath关键字,表示在类路径下
- classes:指定注解所在的位置,这里要注意:注解配置类无论是否使用@Import的父配置类,都要加上@Configuration表明它是一个配置类,否则无法获取容器中的bean对象
@RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(classes = SpringConfig.class) public class AnnotationTest { @Autowired private IAccountService ias; @Test public void testFindAll(){ List<Account> accounts = ias.findAllAccount(); for (Account account : accounts) { System.out.println(account); } } }