Spring入门
安排:
day1: spring框架的概述以及spring中基于XML的IOC(反转控制)配置
day2:Spring中基于注解的IOC和IOC案例
day3:spring中的AOP和基于XML以及注解的AOP(面向切面编程)配置
day4:spring中的JDBC Temlate以及Spring事物控制
Day1 spring框架的概述以及spring中基于XML的IOC(反转控制)配置
1、Spring的概述
1.1spring的优势:方便解耦,简化开发
AOP编程的支持
声明式事务的支持
方便程序的测试
方便集成各种优秀框架
Spring可以降低各种框架的使用难度,提供了对各种优秀框架(Struts、Hibernate、 Hessian、Quartz等)的直接支持。
降低JavaEE API的使用难度
Java源码是经典学习范例
2、 程序的耦合和解耦
划分模块的一个准则就是高内聚低耦合
耦合的原则:耦合是影响软件复杂程度和设计质量的一个重要因素,在设计上我们应采用以下原则:如果模块间必须存在耦合,就尽量使用数据耦合,少用控制耦合,限制公共耦合的范围,尽量避免使用内容耦合。
1.2 spring 是什么?
1.3 spring 的两大核心
core container是spring的核心之一,spring的任何其他部分运行都离不开它。
工厂模式创建对象的过程: 1. 用反射的方式创建对象 bean = Class.forName(beanPath).newInstance();
-
有能反射的全限定类名
accountService = com.itheima.service.impl.AccountImpl
accountDao = com.itheima.service.dao.impl.AccountDaoImpl
-
通过读取配置文件的方式来反射从而得到
-
Sverlet是一个单例对象
在此次的例子中,
bean = Class.forName(beanPath).newInstance(); //每次都会调用默认构造函数 创建对象
每次都会创建新的对象,而旧的被垃圾回收机制默认回收了。这就带来一个问题,多例对象运行效率没有单例对象高,因此我们使用容器解决这个问题,使它变成个单例的。
``
package com.itheima.service.factory;
import java.io.InputStream;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
/*
* 一个创建Bean对象工厂
*
* Bean在计算机语言中,有可重用组件的含义
* JavaBean:用java语言编写的可重用组件
* javabean > 实体类
*
* 一个创建Bean对象工厂,它是创建我们的service和dao对象。
* 第一个:需要配置文件来配置我们的service和dao
* 配置的内容:唯一标志的名字=全限定类名(key=value)
* 第二个/
*:通过读取配置文件中的内容,反射创建对象
*
* */
public class BeanFactory {
private static Properties prop;
//定义一个map,用来存放我们要创建的对象
private static Map<String,Object> beans;
//使用静态代码块为prop赋值,编译器就开始执行
static {
try {
//实例化prop
prop = new Properties();
//获取properties的流对象
InputStream in = BeanFactory.class.getClassLoader().getResourceAsStream("bean.properties");
prop.load(in);
//实例化beans
beans = new HashMap<String, Object>();
Enumeration keys = prop.keys();
//遍历枚举
while(keys.hasMoreElements()){
//取出每个key值
String key = keys.nextElement().toString();
//根据key值得到路径
String beanPath = prop.getProperty(key);
//利用的到的路径,创建反射对象
Object value = Class.forName(beanPath).newInstance();
//把得到的key,value存到容器中
beans.put(key,value);
}
}catch(Exception e){
throw new ExceptionInInitializerError("初始化对象错误");
}
}
/**
* 根据bean的名称获取对象
* @param beanName
* @return
*/
public static Object getBean(String beanName)
{
return beans.get(beanName);
}
/**
*根据Bean的名称来获取bean对象
* @param beanNamme
* @return
*
public static Object getBean(String beanNamme){
Object bean = null;
try{
String beanPath = prop.getProperty(beanNamme);
bean = Class.forName(beanPath).newInstance(); //每次都会调用默认构造函数 创建对象
}catch(Exception e){
e.printStackTrace();
}
return bean;
}*/
}
``
3、IOC概念和Spring中的IOC
spring中基于XML的环境搭建
控制反转:(Inversion of Control)把创建对象的权利交给框架,是框架的重要特征。它包括依赖注入和依赖查找。
ioc的作用,降低程序间的耦合
具体解耦过程入下面步骤所示
- 添加依赖
<?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.itheima</groupId>
<artifactId>day01_eesy_spring</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>jar</packaging>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.1.9.RELEASE</version>
</dependency>
</dependencies>
</project>
- 1)配置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
https://www.springframework.org/schema/beans/spring-beans.xsd">
<!--把对象的创建交给spring来管理-->
<bean id="accountDao" class="com.itheima.dao.impl.AccountDaoImpl"></bean>
<bean id="accountService" class="com.itheima.AccountService.impl.AccountImpl"></bean>
</beans>
2)解析spring对bean的管理细节
1.创建bean的三种方式
第一种:使用默认构造函数创建在spring的配置文件中使用bean标签,配以属性id和class后没有其 他属性时采用这种方式创建,如果此类中没有默认构造函数,则对象无法被创建。
第二种:使用普通工厂中的方法创建,使用某个类中的方法创建对象,并存入spring容器(当类存 在于jar包中的,我们无法通过修改源码的方式来提供默认构造函数)
<bean id="accountService" factory-bean="instanceFactory" factory- method="getAccountService" >
</bean>
第三种:使用某个类中的静态方法创建
<bean id="accountService" class="com.itheima.factory.StaticFactory" factory-method="getAccountService" >
</bean>
3)Spring中的依赖注入
注入数据: 可以注入三种数据:第一种: 基本类型和String类型
第二种:其他bean类型(在配置文件中或者注解配置过的bean)
第三种:复杂类型/集合类型
注入的方式: ①默认构造函数注入 :
使用的标签:constructor-arg
属性标签中的属性:
type:用于指定构造函数中的某个或者某些参数的数据类型
index:用于指定构造函数的位置,从0开始
name:用于指定构造函数中的参数的名字,更为常用
value:为基本数据类型和String类型注入具体的内容
ref:用于指定其他的bean类型,它指定的是spring容器中出现过的bean对象
优势:必须将构造函数中所有的参数进行内容注入
<bean id="accountService" class="com.itheima.AccountService.impl.AccountImpl">
<constructor-arg name="name" value="Bazahai"></constructor-arg>
<constructor-arg name="age" value="18"></constructor-arg>
<constructor-arg name="birthday" ref="tommoron"></constructor-arg>
</bean>
<bean id="tommoron" class="java.util.Date"></bean>
②用set方法
使用的标签:property
不可以提供构造函数,因为构造函数注入和set方法注入是不兼容的
标签中的属性:
name:用于注入时所调用的方法名
value:为基本数据类型和String类型注入具体的内容
ref:用于指定其他的bean类型,它指定的是spring容器中出现过的bean对象
优势:可以为我们想要注入的数据注入内容,更加灵活
<bean id="accountService2" class="com.itheima.AccountService.impl.AccountImpl2">
<property name="userName" value="Bazahai"></property>
<property name="age" value="17"></property>
<property name="birthday" ref="tommoron"></property>
</bean>
<bean id="tommoron" class="java.util.Date"></bean>
3)使用注解提供/复杂类型的注入
用于给List集合注入的标签:
list、 array、 set
用于给map集合注入的标签:
map、 props
结构相同,标签可以互换
<bean id="accountService3" class="com.itheima.AccountService.impl.AccountImpl3">
<property name="myList">
<list>
<value>AAA</value>
<value>BBB</value>
<value>CCC</value>
</list>
</property>
<property name="myStr">
<array>
<value>AAA</value>
<value>BBB</value>
<value>CCC</value>
</array>
</property>
<property name="mySet">
<set>
<value>AAA</value>
<value>BBB</value>
<value>CCC</value>
</set>
</property>
<property name="myMap">
<map>
<entry key="TestA" value="AAA"></entry>
<entry key="TestB" value="BBB"></entry>
<entry key="TestC" value="CCC"></entry>
</map>
</property>
</bean>
<bean id="tommoron" class="java.util.Date"></bean>
2.bean对象的生命周期
单例对象,和容器同生共死
多例对象,出生:当我们使用对象时
活着:对象在使用过程中一直活着
死亡:当对象长时间不用时,也没有别的对象引用时,被java垃圾回收机制回收
3.bean对象的作用范围
bean标签的scope属性,用于指定bean的作用范围。
singleton 单例的,默认值
prototype 多例的
request 作用于web应用的请求范围
session 作用于web应用的的会话范围
global-session 作用于集群环境的会话范围,区别见下图,当不是集群时,作用等同于 session
- 编写程序ui,运行代码
package com.itheima.ui;
import com.itheima.AccountService.IAccountService;
import com.itheima.dao.AccountDao;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
/*
* 模拟一个表现层,调用业务层
* */
public class Client {
/**
*获取spring的Ioc核心容器,并根据id获取对象
* ApplicationContextde三个常用实现类
* ClassPathXmlApplicationContext 他可以加载类路径下的配置文件,配置文件必须在类路径下,不在的话加载不了(更常用)
* FlieSystemXmlApplicationContext 他可以加载磁盘任意路径下的配置文件(必须有访问权限)
* AnnotationConfigApplicationContext 他是用于读取注解创建容器的
*
*核心容器的两个接口引发的问题:
* ApplicationContext:
* 它在构建核心容器时,创建的方式是立即加载的方式,当配置文件被读取后就会立即创建对象
* BeanFactory:
* 它构建核心容器时采用延迟加载的方式,也就是说,什么时候根据id获取对象了,什么时候才创建对象
*
* @param args
*/
public static void main(String[] args) {
// IAccountService as = new AccountImpl();
//获取核心容器对象
ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
//根据id获取bean对象
IAccountService as = (IAccountService)ac.getBean("accountService");
AccountDao ado = (AccountDao) ac.getBean("accountDao");
// AccountDao ado = ac.getBean("accountDao","accountDao.class");
System.out.println(as);
System.out.println(ado);
}
}
5、Aditions:
-
java的三层架构:
SSH:
Struts(表示层)+Spring(业务层)+Hibernate(持久层)
Struts是一个表示层框架,主要作用是界面展示,接收请求,分发请求
Hibernate:Hibernate是一个持久层框架,它只负责与关系数据库的操作。
Spring:Spring是一个业务层框架,是一个整合的框架,能够很好地黏合表示层与持久层。
-
编译期错误
程序开发过程的各个阶段都可能发生错误,可以将程序设计中的错误分成五类:
1)编译期错误
2)连接错误
3)运行期错误
4)逻辑性错误
5)警告性错误
排错是非常困难的,有可能花费很长的时间。程序设计的目标应该是避免出现太多的问题。对减少 排错能有所帮助的技术包括:好的设计、好的风格、边界条件测试、合理性检查、限制全局数据等等-
Ioc到底干了一件什么事?
层层封装,形成模块化,良好代码环境,便于维护。维护不用修改源码,只操作bean就可。
当我们不想new一个对象,在获得容器的控制权后,利用镜像的方式创建操作的类对象,调用类方法。
-
第二天:spring基于注解的IOC以及IoC的案例
1、spring中ioc的常用注解
2、案例使用xml方式和注解方式实现单表的CRUD操作
持久层技术选择:dbutils
3、改造基于注解的ioc案例,使用纯注解的方式实现
spring的一些新注解使用
4、spring和Junit整合
1)关键字解析
当用于创建对象时,@Component @Service @Repository @Contoller的作用就和在XML配置文件中编写一个标签实现的功能是一样的。当使用注解注入时需要在xml文件中告知要扫描的包 “<context:component-scan base-package=“com.itheima”></context:component-scan>”
①@Component
- 作用:用于把当前类对象存入spring容器中
- 属性:value:用于指定bean的id。当我们不写时,它的默认值是当前类名,且首字母改小写。
②@Service:一般用在业务层
③@Repository:一般用在持久层
④@Contoller:一般用在表现层
以上三个注解他们的作用和属性与Component是一模一样。他们三个是spring框架为我们提供明确的三层使用的注解,使我们的三层对象更加清晰。
用于注入数据时,@Autowierd @Qualifiy @Resource的作用就和在xml配置文件中的bean标签中写一个标签的作用是一样的。
⑤@Autowierd
- 作用:自动按照类型注入。只要容器中有唯一的一个bean对象类型和要注入的变量类型匹配,就可以注入成功
- 如果ioc容器中没有任何bean的类型和要注入的变量类型匹配,则报错。
- 如果Ioc容器中有多个类型匹配时:出现位置:可以是变量上,也可以是方法上
- 细节:在使用注解注入时,set方法就不是必须的了。
⑥@Qualifiy
-
作用:在按照类中注入的基础之上再按照名称注入。它在给类成员注入时不能单独使用。但是在给方法参数注入时可以(稍后我们讲)
-
属性:value:用于指定注入bean的id。
⑦@Resource
- 作用:直接按照bean的id注入。它可以独立使用
- 属性:
- name:用于指定bean的id。
以上三个注入都只能注入其他bean类型的数据,而基本类型和String类型无法使用上述注解实现。另外,集合类型的注入只能通过XML来实现。
/**
* 账户的业务层实现类
*
* 曾经XML的配置:
* <bean id="accountService" class="com.itheima.service.impl.AccountServiceImpl"
* scope="" init-method="" destroy-method="">
* <property name="" value="" || ref=""></property>
* </bean>
*
* Value
* 作用:用于注入基本类型和String类型的数据
* 属性:
* value:用于指定数据的值。它可以使用spring中SpEL(也就是spring的el表达式)
* SpEL的写法:${表达式}
*
* 用于改变作用范围的
* 他们的作用就和在bean标签中使用scope属性实现的功能是一样的
* Scope
* 作用:用于指定bean的作用范围
* 属性:
* value:指定范围的取值。常用取值:singleton prototype
*
* 和生命周期相关的(了解)
* 他们的作用就和在bean标签中使用init-method和destroy-methode的作用是一样的
* PreDestroy
* 作用:用于指定销毁方法
* PostConstruct
* 作用:用于指定初始化方法
*/
//@Service("accountService")
//@Scope("prototype")
public class AccountServiceImpl implements IAccountService {
// @Autowired
// @Qualifier("accountDao1")
// @Resource(name = "accountDao2")
@Autowired
private IAccountDao accountDao=null;
@PostConstruct
public void init(){
System.out.println("初始化方法执行了");
}
@PreDestroy
public void destroy(){
System.out.println("销毁方法执行了");
}
public void saveAccount(){
accountDao.saveAccount();
}
}
2)java的类实现
①账户的持久层实现类(接口略)
public class AccountDaoImpl implements AccountDao {
@Autowireds
private QueryRunner runner;
/* public void setRunner(QueryRunner runner) 当我们用注解注入的时候,set方法就不是必须的了
{this.runner=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 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);
}
}
@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);
}
}
}
②账户的业务层实现类(接口略)
public class AccountServiceImpl implements AccountService {
@Autowired
private AccountDao accountDao;
@Override
public List<Account> findAllAccount() {
return accountDao.findAllAccount();
}
@Override
public Account findAccountById(Integer accountId) {
return accountDao.findAccountById(accountId);
}
@Override
public void saveAccount(Account account) {
accountDao.saveAccount(account);
}
@Override
public void updateAccount(Account account) {
accountDao.updateAccount(account);
}
@Override
public void deleteAccount(Integer acccountId) {
accountDao.deleteAccount(acccountId);
}
}
③账户的实体类
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 +
'}';
}
}
3) 用xml方式实现
①数据配置: 数据最终注入AccountService中,因此从终结点出发,一步一步将剩下的配置齐
<!--配置AccountService-->
<bean id="accountService" class="AccountService的类路径">
<!--数据的注入,accountService对象的使用必须建立在accountDao对象的建立之上-->
<property name="accountDao" ref="accountDao"></property>
</bean>
<!--配置AccountDao-->
<bean id="accountDao" class="AccountDao的类路径">
<!--注入数据,AccountDao的使用必须建立在QueryRunner对象建立的基础之上-->
<property name="runner" ref="runner1"></property>
</bean>
<!--配置QueryRunner-->
<bean id="runner1" class="org.apache.commons.dbutils.QueryRunner" scope="prototype">
<!--注入数据源,需要数据源对象-->
<constructor-arg name="ds" ref="dataSource"></constructor-arg>
</bean>
<!--配置dataSource-->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<properties name="driverClass" value="com.mysql.jdbc.Driver"></properties>
<properties name="jdbcUrl" value="jdbc:mysql://localhost:3306/eesy"></properties>
<properties name="user" value="root"></properties>
<properties name="password" value="1234"></properties>
</bean>
<!-- 当使用注解注入时需要告知spring在创建容器时要扫描的包 -->
<!--<context:component-scan base-package="com.itheima"></context:component-scan>
并在需要注入的类中使用@Component @Service @Repository @Contoller 等
-->
②测试类的编写
public class AccountServiceTest{
@Test
public void testFindAll() {
//1.获取容器
ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
//2.得到业务层对象
AccountServiceImpl as = ac.getBean("accountService", AccountServiceImpl.class);
//3.执行方法
List<Account> accounts = as.findAllAccount();
for(Account account : accounts){
System.out.println(account);
}
}
}
4)Configration,Import方式实现
/**
* 添加注解Configuration后这个类就变成了配置类,它的作用和bean.xml是一样的
* spring中的新注解
* @Configuration
* 作用:指定当前类是一个注解类
* @ComponentScan
* 作用:用于通过注解指定spring在创建容器时要扫描的包
* 属性:basePackages,value的作用是一样的,用于创建容器时指定扫描的包,我们使用此注解相当于在 * bean.xml中配置了
* <context:component-scan base-package="itheima"></context:component-scan>
* 细节:当配置类作为annotationConfigApplicationContext对象创建参数时,该注解可以不写
* 例如:在下面的测试类中 ac是一个annotationConfigApplicationContext对象
* @Test
* public void testFindAll(){
* ApplicationContext ac = new * annotationConfigApplicationContext(SpringConfigration.class);
* ......
* }
* ??? 当annotationConfigApplicationContext();()中没有指定配置类路径的情况下不能省略。
* @Bean
* 作用:用于把当前方法的返回值作为bean对象传入到spring的ioc容器中
* 属性:
* name:用于指定bean的id。当不写时,默认是当前的方法名。
* 细节:
*
* @Import
* 作用:用于导入其它配置类
* 属性:value:用于指定其他配置类的字节码
* 当我们使用@import后,有Import注解类就是主或者父配置类。(推荐使用)
*
*
*
*/
@Configuration
@ConpnentScan(basePackages = {"com.itheima"}) //basePackages,和value属性都是数组,左侧为它的标准写法。
//@ConpnentScan(com.itheima) 当注解的属性有且只有一个值,那么可以写成左边的形式
@Import(JdbcConfig.class)
public class SpringConfiguration{
@Bean(name="runner")
public QueryRunner createRunner(DataSource dataSource)
{
return new QueryRunner(dataSource);
}
//上面方法的作用效果,在加@Bean后和下面注释的内容效果相同
/*
<bean id="runner" class="org.apache.commons.dbutils.QueryRunner" scope="prototype">
<!--注入数据源,需要数据源对象-->
<constructor-arg name="ds" ref="dataSource"></constructor-arg>
</bean>
/*
* 创建数据源对象
* @return
*/
@Bean(name="dataSource")
public QueryRunner createRunner(){
CombolPooledDataSource ds = CombolPooledDataSource();
ds.setDriverClass("com.mysql.jdbc.Driver");
ds.setJdbcUrl("jdbc:mysql://localhost:3306/eesy");
ds.setUser("root");
ds.setPassWord("1234");
return ds;
}
//效果和下面注释代码相同
/*
<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="1234"></property>
</bean>
*/
}
5)@PropertySource方式实现
①配置.properties文件 (jdbcConfig.properties)
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/eesy
jdbc.username=root
jdbc.password=1234
②编写配置类
/**
* @PropertySource
* 作用:用于指定 .properties 文件的位置
* 属性:
* value:指定文件的名称和路径。关键字: classpath
*/
@PropertySource(classpath:jdbcConfig.properties)
public class JdbcConfig{
@value("${jdbc.driver}")
private String driver;
@value("${jdbc.url}")
private String url;
@value("${jdbc.usename}")
private String userName;
@value("${jdbc.password}")
private String password;
@Bean(name="runner")
@Scope("prototyype")
public QueryRunner createRunner(DataSource dataSource)
{
return new QueryRunner(dataSource);
}
@Bean(name="dataSource")
public QueryRunner createRunner(){
CombolPooledDataSource ds = CombolPooledDataSource();
ds.setDriverClass("driver");
ds.setJdbcUrl("url");
ds.setUser("userName");
ds.setPassWord("password");
return ds;
}
}
6)junit整合
1、应用程序的入口
main方法
2、junit单元测试中,没有main方法也能执行
junit集成了一个main方法
该方法就会判断该类中有哪些方法使用了@Test注解
junit就会用有@Test注解的方法执行
3、junit不管我们是否使用了spring框架
在执行测试方法时junit不知道我们是否使用了spring‘框架,他也不会为我们读取配置文件/配置类,创建 spring核心容器
4、由以上三点可知,测试方法执行时,没有ioc容器的存在,所以就算使用了@Autowierd注解,也无法注入
/**
* 使用Junit单元测试:测试我们的配置
* Spring整合junit的配置
* 1、导入spring整合junit的jar(坐标)
* 2、使用Junit提供的一个注解把原有的main方法替换了,替换成spring提供的
* @Runwith
* 3、告知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 AccountServiceTest {
@Autowired
private IAccountService as = null;
@Test
public void testFindAll() {
//3.执行方法
List<Account> accounts = as.findAllAccount();
for(Account account : accounts){
System.out.println(account);
}
}
@Test
public void testFindOne() {
//3.执行方法
Account account = as.findAccountById(1);
System.out.println(account);
}
@Test
public void testSave() {
Account account = new Account();
account.setName("test anno");
account.setMoney(12345f);
//3.执行方法
as.saveAccount(account);
}
@Test
public void testUpdate() {
//3.执行方法
Account account = as.findAccountById(4);
account.setMoney(23456f);
as.updateAccount(account);
}
@Test
public void testDelete() {
//3.执行方法
as.deleteAccount(4);
}
}
7)总结
1)spring中的注解,有@Configration和@Import两种,前者支持的是并列关系的两个或两个以上 的注解类,使用后者则代表这是一个父配置类。
2)几种配置方式优缺点对比
xml:配置有一定的复杂
注解:并不一定省事
很多情况下用xml和注解混用的方式
从整个案例的过程可以看出一些端倪,业务逻辑层和持久层之间的联系。业务层调用持久层,对数据库进行操作。用maven工程创建整个案例的过程中,我们发现当创建了工程后,导入一堆坐标,即引如一堆可以使用的jar包,我们只可以使用但不可以修改这些包的内瓤。spring这个容器给了我们很多方便。容许我们导入,并且创建包内各类的对象,方便我们的使用。
第三天
1、回顾之前讲过的一个技术:动态代理(proxy)
动态代理:
- 特点:字节码随用随创建,随用随加载。它与静态代理的区别也在于此。因为静态代理是字节码一上来就 创建好,并完成加载。 装饰者模式就是静态代理的一种体现。
- 作用:在不修改源码的基础上对方法增强
- 分类:①基于jdk的动态代理 (基于接口的动态代理)
②基于子类的动态类
-
基于接口的动态代理
如何创建代理对象:使用Proxy类中的newProxyInstance方法
1)涉及的类:jdk官方提供的 Proxy;
2)要求:被代理类必须至少实现一个接口,如果没有则不能使用
3)newProxyInstance方法的参数:
classLoader类加载器:它是用于加载代理对象字节码的,和被加载对象使用相同的类加载器
Class[] 字节码数组
InvocationHandler 用于提供增强的代码,通常情况下时匿名内部类,但不必须
-
基于子类的动态代理:
如何创建代理对象:使用Enhancer类中的create方法
1)涉及的类:Enhancer;提供者:cglib库
2)要求:被代理类不能是最终类
3)create()方法的参数:
class:字节码
类加载器:它是用于加载代理对象字节码的,和被加载对象使用相同的类加载器
Callback: 用于提供增强的代码,通常情况下时匿名内部类,但不必须。我们一般写的是该接口的子 接口实现类
- 导入依赖
<dependencies>
<dependency>
<groupId>cglib<groupId>
<artifactId>cglib<artifactId>
<version>版本号<version>
<dependency>
<dependencies>
/*
* 基于接口的动态代理
*/
IProducer proxyProducer = (IProducer) Proxy.newProxyInstance(producer.getClass().getClassLoader(),
producer.getClass().getInterfaces(),
new InvocationHandler() {
/**
* 作用:执行被代理对象的任何接口方法都会经过该方法
* 方法参数的含义
* @param proxy 代理对象的引用
* @param method 当前执行的方法
* @param args 当前执行方法所需的参数
* @return 和被代理对象方法有相同的返回值
* @throws Throwable
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//提供增强的代码
Object returnValue = null;
//1.获取方法执行的参数
Float money = (Float)args[0];
//2.判断当前方法是不是销售
if("saleProduct".equals(method.getName())) {
returnValue = method.invoke(producer, money*0.8f);
}
return returnValue;
}
});
proxyProducer.saleProduct(10000f);
/*
* 基于子类的动态代理
*/
Producer cglibProducer = (Producer)Enhancer.create(producer.getClass(), new MethodInterceptor() {
/**
* 执行北地阿里对象的任何方法都会经过该方法
* @param proxy
* @param method
* @param args
* 以上三个参数和基于接口的动态代理中invoke方法的参数是一样的
* @param methodProxy :当前执行方法的代理对象
* @return
* @throws Throwable
*/
@Override
public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
//提供增强的代码
Object returnValue = null;
//1.获取方法执行的参数
Float money = (Float)args[0];
//2.判断当前方法是不是销售
if("saleProduct".equals(method.getName())) {
returnValue = method.invoke(producer, money*0.8f);
}
return returnValue;
}
});
cglibProducer.saleProduct(12000f);
2、AOP的概念
作用: 在程序运行期间,不修改源码对已有方法进行增强。
优势: 减少重复代码 提高开发效率 维护方便
AOP的实现方式:使用动态代理技术
<!--配置AOP-->
<aop:config>
<!-- 配置切入点表达式 id属性用于指定表达式的唯一标识。expression属性用于指定表达式内容
此标签写在aop:aspect标签内部只能当前切面使用。
它还可以写在aop:aspect外面,此时就变成了所有切面可用
-->
<aop:pointcut id="pt1" expression="execution(* com.itheima.service.impl.*.*(..))"></aop:pointcut>
<!--配置切面 -->
<aop:aspect id="logAdvice" ref="logger">
<!-- 配置前置通知:在切入点方法执行之前执行-->
<aop:before method="beforePrintLog" pointcut-ref="pt1" ></aop:before>
<!-- 配置后置通知:在切入点方法正常执行之后值。它和异常通知永远只能执行一个-->
<aop:after-returning method="afterReturningPrintLog" pointcut-ref="pt1"></aop:after-returning>
<!-- 配置异常通知:在切入点方法执行产生异常之后执行。它和后置通知永远只能执行一个-->
<aop:after-throwing method="afterThrowingPrintLog" pointcut-ref="pt1"></aop:after-throwing>
<!-- 配置最终通知:无论切入点方法是否正常执行它都会在其后面执行-->
<aop:after method="afterPrintLog" pointcut-ref="pt1"></aop:after>
<!-- 配置环绕通知 详细的注释请看Logger类中-->
<aop:around method="aroundPringLog" pointcut-ref="pt1"></aop:around>
</aop:aspect>
</aop:config>
3、spring中的AOP相关术语
* JoinPoint 连接点:所有的切入点都是连接点,反过来不成立
* pointcut 切入点:被增强的方法称为切入点
* Advice 通知:
- Target 目标对象
- Weaving 织入:是指把增强应用到目标对象来创建新的代理对象的过程。 spring 采用动态代理织入,而 AspectJ 采用编译期织入和类装载期织入
- Proxy 代理:一个类被 AOP 织入增强后,就产生一个结果代理类
- Aspect 切面:是切入点和通知(提供了公共代码的类)(引介)的结合。
4、spring中基于XML和注解的AOP配置
思路分析:要将数据库的四个连接进行管理,要么全部发生,要么全不发生
1. 创建好转账方法后
2 创建一个连接的工具类,用于从数据源上获取连接,实现和和线程的
3 创建和事务管理相关的类,包含了开启、提交、回滚事务和释放连接
4 为了防止因为修改某些类中的方法而造成后期维护问题,因此使用动态代理。
*在业务中使用动态代理,一般是为了给需要实现的方法添加预处理或者添加后续操作,但是不干 预实现类的正常业务,把一些基本业务和主要的业务逻辑分离。我们一般所熟知的Spring的AOP 原理就是基于动态代理实现的。
5.、总结:
- 第一步是对我们业务进行事务管理(原例中为try,catch,finally方式实现),让所有的sql语句执行时同时发生,如果出现异常则都不执行。但第一种方法产生了新的问题:代码变得十分臃肿。
- 第二步使用动态代理,解决代码臃肿问题(基于结构的动态代理和基于子类的动态代理)此种方法的增强应用于本地对象的所有方法
- 使用AOP配置的方式实现前置,后置,异常,最终和环绕通知。
6、Additions:
连接池:把消耗时间获取连接的一部分放到应用加载一开始。在web应用中当我们启动tomCat加载应用时,我们创建一些连接从而在后续项目运行时不再和数据库进行连接,保证了我们使用connection时的效率。此时服务器也有一个池(线程池:它的特点是当TomCat启动时会初始化一大堆的线程,放到一个容器中,此后每当我们需要时他就会从线程池中取出一个线程供我们使用)
第四天
1、Jdbc简介
jdbc都是单表使用,不加多表是因为,多表操作是sql语句的职责。
jdbcTemplate:用来操作关系型数据库的。支持增删改。
位于:spring-jdbc-5.0.2.RELEASE.jar
还需要导入:spring-tx-5.0.2.RELEASE.jar,用于事务管理的
使用jdbc模板时需要配置模板
<!--配置JdbcTemplate-->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 配置数据源-->
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
<property name="url" value="jdbc:mysql://localhost:3306/eesy"></property>
<property name="username" value="root"></property>
<property name="password" value="1234"></property>
</bean>
jdbcTemplate使用操作
//JdbcTemplate的CRUD操作
public class JdbcTemplateDemo2{
public static void main(String[] args) {
//1.获取容器
ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
//2.获取对象
JdbcTemplate jt = ac.getBean("jdbcTemplate",JdbcTemplate.class);
//3.执行操作
jt.execute("insert into account(name,money)values('ddd',2222)");
//保存操作
jt.update("insert into account(name,money)values(?,?)","fff",5000);
//更新操作
jt.update("update account set money = money-? where id = ?",300,6);
//删除操作
jt.update("delete from account where id = ?",6);
//查询所有
// List<Account> accounts = jt.query("select * from account where money > ?",new AccountRowMapper(),1000f);
//AccountRowMapper,BeanPropertyRowMapper从集合的角度讲他们俩的作用是一样的,都是将Account对象封装到集合中。
List<Account> accounts = jt.query("select * from account where money > ?",new BeanPropertyRowMapper<Account>(Account.class),1000f);
for(Account account : accounts){
System.out.println(account);
}
}
/**
* 定义Account的封装策略
*/
class AccountRowMapper implements RowMapper<Account>{
/**
* 把结果集中的数据封装到Account中,然后由spring把每个Account加到集合中
* @param rs
* @param rowNum
* @return
* @throws SQLException
*/
@Override
public Account mapRow(ResultSet rs, int rowNum) throws SQLException {
Account account = new Account();
account.setId(rs.getInt("id"));
account.setName(rs.getString("name"));
account.setMoney(rs.getFloat("money"));
return account;
}
}
//查询一个
//使用RowMapper的方式:常用的方式
// List<Account> as = jt.query("select * from account where id = ? ", new AccountRowMapper(), 55);
// 使用ResultSetExtractor的方式:不常用的方式
// Account account = jt.query("select * from account where id = ?", new AccountResultSetExtractor(),3);
List<Account> accounts = jt.query("select * from account where id = ?",new BeanPropertyRowMapper<Account>(Account.class),1);
System.out.println(accounts.isEmpty()?"没有内容":accounts.get(0));
//查询返回一行一列(使用聚合函数,但不加group by子句)
Long count = jt.queryForObject("select count(*) from account where money > ?",Long.class,1000f);
System.out.println(count);
当dao有很多个时,每个dao都有重复代码,能不能把他抽取出来呢?spring框架正好给我们提供了一个dbcDaoSupport,使用时我们可以让dao继承它。使用它的set方法,方便在xml中注入。
让Dao继承JdbcDaoSupport的方式,只能用于基于XML的方式,注解用不了。
public final void setDataSource(DataSource dataSource) {
if (this.jdbcTemplate == null || dataSource != this.jdbcTemplate.getDataSource()) {
this.jdbcTemplate = this.createJdbcTemplate(dataSource);
this.initTemplateConfig();
}
}
2、事务控制
1)事务控制简介
第一:JavaEE体系进行分层开发,事务处理位于业务层,Spring提供了分层设计业务层的事务处理解决方案。
第二:spring框架为我们提供了一组事务控制的接口。这组接口是在spring-tx-5.0.2.RELEASE.jar中。
1)PlatformTransactionManager 此接口是spring的事务管理器,它里面提供了我们常用的操作事务的方法
实现类:org.springframework.jdbc.datasource.DataSourceTransactionManager 使用Spring JDBC或iBatis 进行持久化数据时使用
–获取事务状态信息:TransactionStatue getTransaction(TransactionDefinition define)
–提交事务:void commit(TransactionStatus status)
–回滚事务:void rollback(TransactionStatus status)
2)TransactionDefinition 它是事务的定义信息对象
获取事务对象名称:String getName()
获取事务隔离级:int getIsolationLevel()
未提交读取(Read Uncommitted) Spring标识:ISOLATION_READ_UNCOMMITTED
已提交读取(Read Committed)Spring标识:ISOLATION_READ_COMMITTED。
可重复读取(Repeatable Read)Spring标识:ISOLATION_REPEATABLE_READ。
序列化(Serializable)Spring标识:ISOLATION_SERIALIZABLE。
读数据一致性 | 脏读 | 不可读 | 幻读 | |
---|---|---|---|---|
未提交读取 | 最低阶别,只能保证不读取物理上损坏的数据 | √ | √ | √ |
已提交读取 | 语句级 Oracle默认级 | × | √ | √ |
可重复读取 | 事务级 Mysql默认级别 | × | × | √ |
序列化 | 最高级别,事务级 | × | × | × |
获取事务传播行为:int getPropagationBehavior()
①REQUIRED:如果当前没有事务,就新建一个事务,如果已经存在一个事务中,加入到这个事务中。一般的选 择(默认值)
②SUPPORTS:支持当前事务,如果当前没有事务,就以非事务方式执行(没有事务)
③MANDATORY:使用当前的事务,如果当前没有事务,就抛出异常
④REQUERS_NEW:新建事务,如果当前在事务中,把当前事务挂起。
⑤NOT_SUPPORTED:以非事务方式执行操作,如果当前存在事务,就把当前事务挂起
⑥NEVER:以非事务方式运行,如果当前存在事务,抛出异常
⑦NESTED:如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则执行REQUIRED类似的操作。
获取事务超时时间:int getTimeOut() 默认值是-1,没有超时限制。如果有,以秒为单位进行设置。
//读写型事务,怎删改,也会开启事务
获取事务是否只读:boolean isReadOnly() //只读型事务,执行查询时也会开启事务
3)TransactionStatus 此接口提供的是事务具体的运行状态
刷新事务:void flush()
获取是否存在存储点:boolean hasSavepoint()
获取事务是否完成:boolean isComplecated()
获取事务是否为新事物:boolean isNewTransaction()
获取事务是否回滚:boolean isRollBackOnly()
设置事务回滚:void setRollBackOnly()
第三:spring的事务控制都是基于AOP的,它既可以使用编程的方式实现,也可以使用配置的方式实现。我们学习的重点是使用配置的方式实现。
@Component("txManager")
@Aspect
public class TransactionManager {
@Autowired
private ConnectionUtils connectionUtils;
@Pointcut("execution(* com.itheima.service.impl.*.*(..))")
private void pt1(){}
/**
* 开启事务
*/
public void beginTransaction(){
try {
connectionUtils.getThreadConnection().setAutoCommit(false);
}catch (Exception e){
e.printStackTrace();
}
}
/**
* 提交事务
*/
public void commit(){
try {
connectionUtils.getThreadConnection().commit();
}catch (Exception e){
e.printStackTrace();
}
}
/**
* 回滚事务
*/
public void rollback(){
try {
connectionUtils.getThreadConnection().rollback();
}catch (Exception e){
e.printStackTrace();
}
}
/**
* 释放连接
*/
public void release(){
try {
connectionUtils.getThreadConnection().close();//还回连接池中
connectionUtils.removeConnection();
}catch (Exception e){
e.printStackTrace();
}
}
@Around("pt1()")
public Object aroundAdvice(ProceedingJoinPoint pjp){
Object rtValue = null;
try {
//1.获取参数
Object[] args = pjp.getArgs();
//2.开启事务
this.beginTransaction();
//3.执行方法
rtValue = pjp.proceed(args);
//4.提交事务
this.commit();
//返回结果
return rtValue;
}catch (Throwable e){
//5.回滚事务
this.rollback();
throw new RuntimeException(e);
}finally {
//6.释放资源
this.release();
}
}
}
@Component("connectionUtils")
public class ConnectionUtils {
private ThreadLocal<Connection> tl = new ThreadLocal<Connection>();
@Autowired
private DataSource dataSource;
/**
* 获取当前线程上的连接
* @return
*/
public Connection getThreadConnection() {
try{
//1.先从ThreadLocal上获取
Connection conn = tl.get();
//2.判断当前线程上是否有连接
if (conn == null) {
//3.从数据源中获取一个连接,并且存入ThreadLocal中
conn = dataSource.getConnection();
conn.setAutoCommit(false);
tl.set(conn);
}
//4.返回当前线程上的连接
return conn;
}catch (Exception e){
throw new RuntimeException(e);
}
}
/**
* 把连接和线程解绑
*/
public void removeConnection(){
tl.remove();
}
}
2)基于XML的声明式事务控制
导入xsi、aop和tx命名空间
<?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:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">
</beans>
在配置文件中配置业务层和持久层对
<!-- 配置service -->
<bean id="accountService" class="com.itheima.service.impl.AccountServiceImpl">
<property name="accountDao" ref="accountDao"></property>
</bean>
<!-- 配置dao -->
<bean id="accountDao" class="com.itheima.dao.impl.AccountDaoImpl">
<!-- 注入dataSource -->
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 配置数据源 -->
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver">
</property> <property name="url" value="jdbc:mysql:///spring_day04">
</property> <property name="username" value="root">
</property> <property name="password" value="1234"></property>
</bean>
<!-- 配置一个事务管理器 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!-- 注入DataSource -->
<property name="dataSource" ref="dataSource"></property>
</bean>
配置事务的通知引用事务管理器
<!-- 事务的配置 -->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
</tx:advice>
配置事务的属性:
<!--在tx:advice标签内部 配置事务的属性 -->
<tx:attributes>
<!-- 指定方法名称:是业务核心方法
read-only:是否是只读事务。默认false,不只读。
isolation:指定事务的隔离级别。默认值是使用数据库的默认隔离级别。
propagation:指定事务的传播行为。
timeout:指定超时时间。默认值为:-1。永不超时。
rollback-for:用于指定一个异常,当执行产生该异常时,事务回滚。产生其他异常,事务不回滚。没有默认值, 任何异常都回滚。
no-rollback-for:用于指定一个异常,当产生该异常时,事务不回滚,产生其他异常时,事务回滚。没有默认值,任何异常都回滚。 -->
<tx:method name="*" read-only="false" propagation="REQUIRED"/>
<tx:method name="find*" read-only="true" propagation="SUPPORTS"/>
</tx:attributes>
配置切入点表达式
<!-- 配置aop -->
<aop:config>
<!-- 配置切入点表达式 -->
<aop:pointcut expression="execution(* com.itheima.service.impl.*.*(..))" id="pt1"/> </aop:config>
<!-- 在aop:config标签内部:建立事务的通知和切入点表达式的关系 -->
<aop:advisor advice-ref="txAdvice" pointcut-ref="pt1"/>
3)基于注解的配置方式
创建aop、xsi、context和tx命名空间
<!-- 配置spring创建容器时要扫描的包 -->
<context:component-scan base-package="com.itheima"></context:component-scan>
<!-- 配置JdbcTemplate-->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 配置spring提供的内置数据源 -->
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
<property name="url" value="jdbc:mysql://localhost:3306/spring_day02"></property>
<property name="username" value="root"></property>
<property name="password" value="1234"></property>
</bean>
<!-- 配置事务管理器 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 开启spring对注解事务的支持 -->
<tx:annotation-driven transaction-manager="transactionManager"/>
在业务层使用@Transactional注解
@Service("accountService")
@Transactional(readOnly=true,propagation=Propagation.SUPPORTS)
public class AccountServiceImpl implements IAccountService {
@Autowired
private IAccountDao accountDao;
@Override
public Account findAccountById(Integer id)
{ return accountDao.findAccountById(id); }
@Override
@Transactional(readOnly=false,propagation=Propagation.REQUIRED)
public void transfer(String sourceName, String targeName, Float money){
}
}
该注解的属性和xml中的属性含义一致。该注解可以出现在接口上,类上和方法上。
出现接口上,表示该接口的所有实现类都有事务支持。
出现在类上,表示类中所有方法有事务支持
出现在方法上,表示方法有事务支持。
以上三个位置的优先级:方法>类>接口
3、spring5新特性
核心容器的更新:Spring Framework 5.0 现在支持候选组件索引作为类路径扫描的替代方案。索引读取 &扫描类路径JetBrains Kotlin语言支持:响应式编程风格:Junit5支持:依赖类库的更新:终止支持一些类库
Spring中都用到了那些设计模式?
- 工厂设计模式: Spring使用工厂模式通过 BeanFactory、ApplicationContext 创建 bean 对象。
- 代理设计模式 : Spring AOP 功能的实现。
- 单例设计模式 : Spring 中的 Bean 默认都是单例的。
- 模板方法模式 : Spring 中 jdbcTemplate、hibernateTemplate 等以 Template 结尾的对数据库操作的类,它们就使用到了模板模式。
- 包装器设计模式 : 我们的项目需要连接多个数据库,而且不同的客户在每次访问中根据需要会去访问不同的数据库。这种模式让我们可以根据客户的需求能够动态切换不同的数据源。
- 观察者模式: Spring 事件驱动模型就是观察者模式很经典的一个应用。
- 适配器模式 :Spring AOP 的增强或通知(Advice)使用到了适配器模式、spring MVC 中也是用到了适配器模式适配Controller。