文章目录
spring概念
Spring 是分层的 Java SE/EE 应用 full-stack 轻量级开源框架,以 IoC(Inverse Of Control:反转控制)和 AOP(Aspect Oriented Programming:面向切面编程)为内核,提供了展现层 Spring MVC 和持久层 Spring JDBC 以及业务层事务管理等众多的企业级应用技术还能整合开源世界众多著名的第三方框架和类库,逐渐成为使用最多的 Java EE 企业应用开源框架。
1.Spring 的发展历程
1997 年 IBM 提出了 EJB 的思想
1998 年,SUN 制定开发标准规范 EJB1.0
1999 年,EJB1.1 发布
2001 年,EJB2.0 发布
2003 年,EJB2.1 发布
2006 年,EJB3.0 发布
Rod Johnson(spring 之父)
Expert One-to-One J2EE Design and Development(2002)
阐述了 J2EE 使用 EJB 开发设计的优点及解决方案
Expert One-to-One J2EE Development without EJB(2004)
阐述了 J2EE 开发不使用 EJB 的解决方式(Spring 雏形)
2017 年 9 月份发布了 spring 的最新版本 spring 5.0 通用版(GA)
2.spring 的优势
1.方便解耦,简化开发
通过 Spring 提供的 IoC 容器,可以将对象间的依赖关系交由 Spring 进行控制,避免硬编码所造成的过度程序耦合。用户也不必再为单例模式类、属性文件解析等这些很底层的需求编写代码,可以更专注于上层的应用。
2.AOP 编程的支持
通过 Spring 的 AOP 功能,方便进行面向切面的编程,许多不容易用传统 OOP 实现的功能可以通过 AOP 轻松应付。
3.声明式事务的支持
可以将我们从单调烦闷的事务管理代码中解脱出来,通过声明式方式灵活的进行事务的管理,提高开发效率和质量。
4.方便程序的测试
可以用非容器依赖的编程方式进行几乎所有的测试工作,测试不再是昂贵的操作,而是随手可做的事情。
5.方便集成各种优秀框架
Spring 可以降低各种框架的使用难度,提供了对各种优秀框架(Struts、Hibernate、Hessian、Quartz等)的直接支持。
6.降低 JavaEE API 的使用难度
Spring 对 JavaEE API(如 JDBC、JavaMail、远程调用等)进行了薄薄的封装层,使这些 API 的使用难度大为降低。
7.Java 源码是经典学习范例
Spring 的源代码设计精妙、结构清晰、匠心独用,处处体现着大师对 Java 设计模式灵活运用以及对 Java 技术的高深造诣。它的源代码无意是 Java 技术的最佳实践的范例。
3.spring 的体系结构
4.spring在3层架构位置图解
IoC 的概念和作用
1.什么是程序的耦合
在软件工程中,耦合指的就是就是对象之间的依赖性。对象之间的耦合越高,维护成本越高。因此对象的设计应使类和构件之间的耦合最小。软件设计中通常用耦合度和内聚度作为衡量模块独立程度的标准。划分模块的一个准则就是高内聚低耦合。( 降低耦合性,可以提高其独立性)。
1.反射解耦
通过反射来注册驱动此时的好处是,我们的类中不再依赖具体的驱动类,此时就算删除 mysql 的驱动 jar 包,依然可以编译(运
行就不要想了,没有驱动不可能运行成功的)。
同时,也产生了一个新的问题,mysql 驱动的全限定类名字符串是在 java 类中写死的,一旦要改还是要修改源码。
解决这个问题也很简单,使用配置文件配置。
DriverManager.registerDriver(new com.mysql.jdbc.Driver());
Class.forName("com.mysql.jdbc.Driver");//此处只是一个字符串
/**
* 程序的耦合
* 耦合:程序间的依赖关系
* 包括:
* 类之间的依赖
* 方法间的依赖
* 解耦:
* 降低程序间的依赖关系
* 实际开发中:
* 应该做到:编译期不依赖,运行时才依赖。
* 解耦的思路:
* 第一步:使用反射来创建对象,而避免使用new关键字。
* 第二步:通过读取配置文件来获取要创建的对象全限定类名
*
*/
public class JdbcDemo1 {
public static void main(String[] args) throws Exception{
//1.注册驱动
//DriverManager.registerDriver(new com.mysql.jdbc.Driver());
Class.forName("com.mysql.jdbc.Driver");
//2.获取连接
Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/eesy","root","1234");
//3.获取操作数据库的预处理对象
PreparedStatement pstm = conn.prepareStatement("select * from account");
//4.执行SQL,得到结果集
ResultSet rs = pstm.executeQuery();
//5.遍历结果集
while(rs.next()){
System.out.println(rs.getString("name"));
}
//6.释放资源
rs.close();
pstm.close();
conn.close();
}
}
2.工厂模式解耦
在实际开发中我们可以把三层的对象都使用配置文件配置起来,当启动服务器应用加载的时候,让一个类中的方法通过读取配置文件,把这些对象创建出来并存起来。在接下来的使用的时候,直接拿过来用就好了。
那么,这个读取配置文件,创建和获取三层对象的类就是工厂。
/**
* 工厂类用于创建对象,其实spring底层也是这样做的。
*/
public class BeanFactory {
private static Properties p = null;
private static HashMap<String, Object> hashMap = new HashMap<>();
static {
try {
p = new Properties();
InputStream is = BeanFactory.class.getClassLoader().getResourceAsStream("bean.properties");
p.load(is);
//获取配置文件中所有键
Enumeration<Object> keys = p.keys();
//遍历所有键
while (keys.hasMoreElements()) {
//获取键
String key = (String) keys.nextElement();
//通过键获取类全名称
String beanName = (String) p.get(key);
//通过类全名称使用反射创建对象
Object obj = Class.forName(beanName).newInstance();
//将键和创建的对象存储到map集合中
hashMap.put(key,obj);
}
} catch (IOException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
/**
* 根据beanName创建对象,类加载时就创建所有对象。
* @param beanName
* @return
*/
public static Object getBean(String beanName){
return hashMap.get(beanName);
}
/**
* 根据beanName创建对象,使用时创建对象。
*
* @param beanName
* @return
*/
public static Object getBean2(String beanName) {
Object obj = null;
try {
//通过键获取实体类名称
String name = (String) p.get(beanName);
obj = Class.forName(name).newInstance();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
return obj;
}
}
web
public class AccountClient {
//web层依赖service中的对象
//private static AccountService as = new AccountServiceImpl();
public static void main(String[] args) {
//使用工厂类创建对象,来解耦。
AccountServiceImpl as = (AccountServiceImpl) BeanFactory.getBean("accountService");
as.save();
}
}
service
public class AccountServiceImpl implements AccountService {
//service层依赖dao中的对象
//private AccountDao ad= new AccountDaoImpl();
@Override
public void save() {
//使用工厂类创建对象,来解耦。
AccountDaoImpl ad = (AccountDaoImpl) BeanFactory.getBean("accountDao");
ad.save();
}
}
dao
public class AccountDaoImpl implements AccountDao {
@Override
public void save() {
System.out.println("存钱");
}
}
2.控制反转-Inversion Of Control
上一小节解耦的思路有 2 个问题:
1、存哪去?
分析:由于我们是很多对象,肯定要找个集合来存。这时候有 Map 和 List 供选择。
到底选 Map 还是 List 就看我们有没有查找需求。有查找需求,选 Map。
所以我们的答案就是
在应用加载时,创建一个 Map,用于存放三层对象。
我们把这个 map 称之为容器。
2、还是没解释什么是工厂?
工厂就是负责给我们从容器中获取指定对象的类。这时候我们获取对象的方式发生了改变。
原来:
我们在获取对象时,都是采用 new 的方式。是主动的。
现在:
我们获取对象时,同时跟工厂要,有工厂为我们查找或者创建对象。是被动的。
new方式图解
采用工厂图解
这种被动接收的方式获取对象的思想就是控制反转,它是 spring 框架的核心之一。
控制反转图解
明确 ioc 的作用:
削减计算机程序的耦合(解除我们代码中的依赖关系)。
使用 spring 的 IOC 解决程序耦合
1.准备 spring 的开发包
官网:http://spring.io/
下载地址:
http://repo.springsource.org/libs-release-local/org/springframework/spring
解压:(Spring 目录结构:)
docs:API 和开发规范.
libs :jar 包和源码.
schema :约束.
特别说明:
spring5 版本是用 jdk8 编写的,所以要求我们的 jdk 版本是 8 及以上。
同时 tomcat 的版本要求 8.5 及以上。
2.导入spring核心包
3.在类的根路径resource下创建一个任意名称的 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">
<!--
bean 标签:用于配置让 spring 创建对象,并且存入 ioc 容器之中
id 属性:对象的唯一标识。
class 属性:指定要创建对象的全限定类名
-->
<!--配置service-->
<bean id="accountService" class="com.ginger.service.AccountServiceImpl"></bean>
<!--配置dao-->
<bean id="accountDao" class="com.ginger.dao.AccountDaoImpl"></bean>
</beans>
4.测试配置是否成功
获取spring的Ioc核心容器,并根据id获取对象
ApplicationContext的三个常用实现类:
ClassPathXmlApplicationContext:它可以加载类路径下的配置文件,要求配置文件必须在类路径下。不在的话,加载不了。(更常用)
FileSystemXmlApplicationContext:它可以加载磁盘任意路径下的配置文件(必须有访问权限)
AnnotationConfigApplicationContext:它是用于读取注解创建容器的。
核心容器的两个接口引发出的问题:
ApplicationContext: 单例对象适用 采用此接口
它在构建核心容器时,创建对象采取的策略是采用立即加载的方式。也就是说,只要一读取完配置文件马上就创建配置文件中配置的对象。
BeanFactory: 多例对象使用
它在构建核心容器时,创建对象采取的策略是采用延迟加载的方式。也就是说,什么时候根据id获取对象了,什么时候才真正的创建对象。
@param args
这个两个接口图解
web
public class AccountClient {
public static void main(String[] args) {
//创建spring容器对象
ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
//获取容器中对象
AccountServiceImpl asi = (AccountServiceImpl) ac.getBean("accountService");
AccountDaoImpl adi = (AccountDaoImpl) ac.getBean("accountDao");
//调用方法
asi.save();//保存service
adi.save();//保存dao
//--------BeanFactory----------
//Resource resource = new ClassPathResource("bean.xml");
//BeanFactory factory = new XmlBeanFactory(resource);
//IAccountService as = (IAccountService)factory.getBean("accountService");
//System.out.println(as);
}
}
service
public class AccountServiceImpl implements IAccountService{
public void save() {
System.out.println("保存service");
}
}
dao
public class AccountDaoImpl implements IAccountDao {
public void save() {
System.out.println("保存dao");
}
}
spring创建bean的三种方式
把对象的创建交给spring来管理
spring对bean的管理细节
1.创建bean的三种方式
第一种方式:使用默认构造函数创建。
第二种方式: 使用普通工厂中的方法创建对象(使用某个类中的方法创建对象,并存入spring容器)
第三种方式:使用工厂中的静态方法创建对象(使用某个类中的静态方法创建对象,并存入spring容器)
2.bean对象的作用范围
3.bean对象的生命周期
1.第一种方式:使用默认构造函数创建。
第一种方式:使用默认构造函数创建。
在spring的配置文件中使用bean标签,配以id和class属性之后,且没有其他属性和标签时。
采用的就是默认构造函数创建bean对象,此时如果类中没有默认构造函数,则对象无法创建。
测试代码演示
public class AccountClient {
public static void main(String[] args) {
//创建spring容器对象
ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
//获取容器中对象
AccountServiceImpl asi = (AccountServiceImpl) ac.getBean("accountService");
//调用方法
System.out.println(asi);//com.ginger.service.AccountServiceImpl@5b275dab
}
}
spring配置文件
<?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">
<!--第一种方式:使用默认构造函数创建。-->
<bean id="accountService" class="com.ginger.service.AccountServiceImpl"></bean>
</beans>
2.第二种方式:使用普通工厂中的方法创建对象。
第二种方式: 使用普通工厂中的方法创建对象(使用某个类中的方法创建对象,并存入spring容器)
spring为什么需要工厂和静态创建对象?
如果有一个类为了解耦不能使用构造直接创建对象(构造已有),而是用工厂来创建对象但是该类又在jar包里面所以修改不了源码所以所,以为了满足该需求spring就提供了工厂来创建对象。如果没有工厂创建对象呢?这个问题就是在扯淡了。如果一个类构造私有,还没有工厂方式创建该对象那这个类将毫无意义。
测试代码演示
public class AccountClient {
public static void main(String[] args) {
//创建spring容器对象
ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
//获取容器中对象
AccountServiceImpl asi = (AccountServiceImpl) ac.getBean("accountService");
//调用方法
System.out.println(asi);//com.ginger.service.AccountServiceImpl@5b275dab
}
}
工厂类
/*
*模拟一个工厂类(该类可能是存在于jar包中的,我们无法通过修改源码的方式来提供默认构造函数)
*/
public class InstanceFacory {
public AccountServiceImpl getBean(){
return new AccountServiceImpl();
}
}
spring配置文件
<?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">
<!--第二种方式:使用普通工厂中的方法创建对象。-->
<bean id="instanceFacory" class="com.ginger.factory.InstanceFacory" ></bean>
<bean id="accountService" factory-method="getBean" factory-bean="instanceFacory"></bean>
</beans>
3.第三种方式:使用工厂中的静态方法创建对象。
第三种方式:使用工厂中的静态方法创建对象(使用某个类中的静态方法创建对象,并存入spring容器)
测试代码演示
public class AccountClient {
public static void main(String[] args) {
//创建spring容器对象
ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
//获取容器中对象
AccountServiceImpl asi = (AccountServiceImpl) ac.getBean("accountService");
//调用方法
System.out.println(asi);//com.ginger.service.AccountServiceImpl@5b275dab
}
}
静态工厂类
public class StaticFactory {
public static AccountServiceImpl getBean(){
return new AccountServiceImpl();
}
}
spring配置文件
<?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">
<!--第三种方式:使用工厂中的静态方法创建对象。-->
<bean id="accountService" class="com.ginger.factory.StaticFactory" factory-method="getBean"></bean>
</beans>
4.设置bean的作用范围
spring配置文件
<?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">
<!-- bean的作用范围调整
bean标签的scope属性:
作用:用于指定bean的作用范围
取值: 常用的就是单例的和多例的
singleton:单例的(默认值)常用
prototype:多例的 常用
request:作用于web应用的请求范围
session:作用于web应用的会话范围
global-session:作用于集群环境的会话范围(全局会话范围),当不是集群环境时,它就是session。
<bean id="accountService" class="com.itheima.service.impl.AccountServiceImpl" scope="prototype"></bean>
-->
<!-- bean对象的生命周期
单例对象
出生:当容器创建时对象出生
活着:只要容器还在,对象一直活着
死亡:容器销毁,对象消亡
总结:单例对象的生命周期和容器相同
多例对象
出生:当我们使用对象时spring框架为我们创建
活着:对象只要是在使用过程中就一直活着。
死亡:当对象长时间不用,且没有别的对象引用时,由Java的垃圾回收器回收
-->
<bean id="accountService" class="com.itheima.service.impl.AccountServiceImpl"
scope="prototype" init-method="init" destroy-method="destroy"></bean>
</beans>
5.我对普通工厂和静态工厂创建对象疑问
其实这个疑问时出现在配置文件中的,通过查看配置文件普通工厂和静态工厂配置方式不一致,难道就是因为一个方法不是静态,一个方法是静态的吗?其实就是这样的。
普通工厂
查看配置文件中普通工厂配置方式分析:
因为普通工厂中的方法是普通方法,所以要创建对象来调用所以在,所以在spring文件中配置工厂类,表示创建对象了,下面要调用方法了配置了一个调用方法并返回对象的配置。
静态工厂
查看配置文件中静态工厂配置方式分析:
静态工厂中的方法是静态的,所以我不需要创建对象来来调用方法,通过类名直接调用,就少了一个步骤。
我做过测试普通工厂按照静态工厂的配置方式就会报错。
<?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">
<!--第二种方式:使用普通工厂中的方法创建对象。-->
<bean id="instanceFacory" class="com.ginger.factory.InstanceFacory"></bean>
<bean id="accountService" factory-method="getBean" factory-bean="instanceFacory"></bean>
<!--第三种方式:使用工厂中的静态方法创建对象。-->
<bean id="accountService" class="com.ginger.factory.StaticFactory" factory-method="getBean"></bean>
</beans>
IOC中的依赖注入(DI)三种方式
spring中的依赖注入
依赖注入:
Dependency Injection
IOC的作用:
降低程序间的耦合(依赖关系)
依赖关系的管理:
以后都交给spring来维护
在当前类需要用到其他类的对象,由spring为我们提供,我们只需要在配置文件中说明
依赖关系的维护:
就称之为依赖注入。
依赖注入:
能注入的数据:有三类
基本类型和String
其他bean类型(在配置文件中或者注解配置过的bean)
复杂类型/集合类型
注入的方式:有三种
第一种:使用构造函数提供
第二种:使用set方法提供
第三种:使用注解提供
1.第一种:使用构造函数提供
测试代码演示
public class AccountClient {
public static void main(String[] args) {
//创建spring容器对象
ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
//获取容器中对象
AccountServiceImpl asi = (AccountServiceImpl) ac.getBean("accountService");
//调用方法
//AccountServiceImpl{name='格雷福斯', age=35, date=Sat May 30 22:41:32 CST 2020}
System.out.println(asi);
}
}
AccountServiceImpl类
public class AccountServiceImpl implements IAccountService{
private String name;
private Integer age;
private Date date;
public AccountServiceImpl(String name, Integer age, Date date) {
this.name = name;
this.age = age;
this.date = date;
}
public void save() {
System.out.println("保存service");
}
@Override
public String toString() {
return "AccountServiceImpl{" +
"name='" + name + '\'' +
", age=" + age +
", date=" + date +
'}';
}
}
spring配置文件
<?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">
<!--构造函数注入:
使用的标签:constructor-arg
标签出现的位置:bean标签的内部
标签中的属性:
type:用于指定要注入的数据的数据类型,该数据类型也是构造函数中某个或某些参数的类型
index:用于指定要注入的数据给构造函数中指定索引位置的参数赋值。索引的位置是从0开始
name:用于指定给构造函数中指定名称的参数赋值 常用的
如果有多个构造函,可以使用这3个属性定位到具体使用哪个构造函数。
=============以上三个用于指定给构造函数中哪个参数赋值===============================
value:用于提供基本类型和String类型的数据
ref:用于指定其他的bean类型数据。它指的就是在spring的Ioc核心容器中出现过的bean对象
优势:
在获取bean对象时,注入数据是必须的操作,否则对象无法创建成功。
弊端:
改变了bean对象的实例化方式,使我们在创建对象时,如果用不到这些数据,也必须提供。
-->
<bean id="accountService" class="com.ginger.service.AccountServiceImpl">
<constructor-arg name="name" value="格雷福斯"></constructor-arg>
<constructor-arg name="age" value="35"></constructor-arg>
<constructor-arg name="date" ref="date"></constructor-arg>
</bean>
<!--把日期类交给spring管理-->
<bean id = "date" class="java.util.Date"></bean>
</beans>
2.第二种:使用set方法提供
1.set方式注入基本类型和string类型
测试代码演示
public class AccountClient {
public static void main(String[] args) {
//创建spring容器对象
ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
//获取容器中对象
AccountServiceImpl2 asi = (AccountServiceImpl2) ac.getBean("accountService");
//调用方法
//AccountServiceImpl2{name='影流之主', age=26, date=Sat May 30 22:56:31 CST 2020}
System.out.println(asi);
}
}
AccountServiceImpl2类
public class AccountServiceImpl2 implements IAccountService{
private String name;
private Integer age;
private Date date;
public void setName(String name) {
this.name = name;
}
public void setAge(Integer age) {
this.age = age;
}
public void setDate(Date date) {
this.date = date;
}
public void save() {
System.out.println("保存service");
}
@Override
public String toString() {
return "AccountServiceImpl2{" +
"name='" + name + '\'' +
", age=" + age +
", date=" + date +
'}';
}
}
spring配置文件
<?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">
<!-- set方法注入 更常用的方式
涉及的标签:property
出现的位置:bean标签的内部
标签的属性
name:用于指定注入时所调用的set方法名称
value:用于提供基本类型和String类型的数据
ref:用于指定其他的bean类型数据。它指的就是在spring的Ioc核心容器中出现过的bean对象
优势:
创建对象时没有明确的限制,可以直接使用默认构造函数
弊端:
如果有某个对象成员必须有值,如果没有set方法则注入数据不成功。
-->
<bean id="accountService" class="com.ginger.service.AccountServiceImpl2">
<property name="name" value="影流之主"></property>
<property name="age" value="26"></property>
<property name="date" ref="date"></property>
</bean>
<!--把日期类交给spring管理-->
<bean id = "date" class="java.util.Date"></bean>
</beans>
2.set方式注入复杂类型
测试代码演示
public class AccountClient {
public static void main(String[] args) {
//创建spring容器对象
ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
//获取容器中对象
AccountServiceImpl3 asi = (AccountServiceImpl3) ac.getBean("accountService");
//调用方法
//AccountServiceImpl3{myStrs=[诺克, 盖伦, 鳄鱼], myList=[亚索, 劫, 泰隆], mySet=[薇恩, 女警, 奥巴马], myMap={辅助1=娜美, 辅助2=风女, 辅助3=璐璐}, myProps={打野3=剑圣, 打野2=男枪, 打野1=盲僧}}。
System.out.println(asi);
}
}
AccountServiceImpl3类
public class AccountServiceImpl3 {
private String[] myStrs;
private List<String> myList;
private Set<String> mySet;
private Map<String,String> myMap;
private Properties myProps;
public void setMyStrs(String[] myStrs) {
this.myStrs = myStrs;
}
public void setMyList(List<String> myList) {
this.myList = myList;
}
public void setMySet(Set<String> mySet) {
this.mySet = mySet;
}
public void setMyMap(Map<String, String> myMap) {
this.myMap = myMap;
}
public void setMyProps(Properties myProps) {
this.myProps = myProps;
}
@Override
public String toString() {
return "AccountServiceImpl3{" +
"myStrs=" + Arrays.toString(myStrs) +
", myList=" + myList +
", mySet=" + mySet +
", myMap=" + myMap +
", myProps=" + myProps +
'}';
}
}
spring配置文件
<?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">
<!-- 复杂类型的注入/集合类型的注入
用于给List结构集合注入的标签:
list array set
用于个Map结构集合注入的标签:
map props
结构相同,标签可以互换
-->
<bean id="accountService" class="com.ginger.service.AccountServiceImpl3">
<!--String[]类型注入-->
<property name="myStrs">
<array>
<value>诺克</value>
<value>盖伦</value>
<value>鳄鱼</value>
</array>
</property>
<!--List<String> 类型注入-->
<property name="myList">
<list>
<value>亚索</value>
<value>劫</value>
<value>泰隆</value>
</list>
</property>
<!--Set<String>-->
<property name="mySet">
<set>
<value>薇恩</value>
<value>女警</value>
<value>奥巴马</value>
</set>
</property>
<!--Map<String,String>类型注入-->
<property name="myMap">
<map>
<entry key="辅助1" value="娜美"></entry>
<entry key="辅助2">
<value>风女</value>
</entry>
<entry key="辅助3">
<value>璐璐</value>
</entry>
</map>
</property>
<!--Properties类型注入-->
<property name="myProps">
<props>
<prop key="打野1">盲僧</prop>
<prop key="打野2">男枪</prop>
<prop key="打野3">剑圣</prop>
</props>
</property>
</bean>
</beans>
3.第三种:使用注解提供
在spring注解介绍里面有写这里就不写了。
spring注解介绍
曾经XML的配置:
<bean id="accountService" class="com.itheima.service.impl.AccountServiceImpl"
scope="" init-method="" destroy-method="">
<property name="" value="" | ref=""></property>
</bean>
1.spring创建对象注解
用于创建对象的
他们的作用就和在XML配置文件中编写一个<bean>标签实现的功能是一样的
Component:
作用:用于把当前类对象存入spring容器中
属性:
value:用于指定bean的id。当我们不写时,它的默认值是当前类名,且首字母改小写。
Controller:一般用在表现层
Service:一般用在业务层
Repository:一般用在持久层
以上三个注解他们的作用和属性与Component是一模一样。
他们三个是spring框架为我们提供明确的三层使用的注解,使我们的三层对象更加清晰
测试代码演示
//@Controller
public class AccountClient {
public static void main(String[] args) {
//创建spring容器对象
ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
//获取容器中对象
IAccountService as = (AccountServiceImpl) ac.getBean("accountService");
IAccountDao ad = (AccountDaoImpl) ac.getBean("accountDao");
//调用方法
as.save();//保存service
ad.save();//保存dao
}
}
service
这里我没有添加dao的依赖,其实就是测试创建对象的注解,没有测试注入数据的注解。
//@Component
@Service("accountService")
public class AccountServiceImpl implements IAccountService{
public void save() {
System.out.println("保存service");
}
}
dao
//@Component
@Repository("accountDao")
public class AccountDaoImpl implements IAccountDao {
public void save() {
System.out.println("保存dao");
}
}
spring配置文件
导入context新的约束
<?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.ginger"></context:component-scan>
</beans>
2.spring注入数据注解
1.Autowired细节图解
使用Autowired注入,如果Ioc容器中有多个类型匹配时怎么注入。
2.引入外部的属性文件(配置@Value使用)
定义属性文件
jdbc.driverClass=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql:///spring_day02
jdbc.username=root
jdbc.password=123
<!--bean.xml配置方式一:-->
<bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="location" value="classpath:jdbc.properties"/>
</bean>
另一种方式:
<context:property-placeholder location="classpath:jdbc.properties"/>
3.数据注解
注入数据的注解作用就和在xml配置文件中的bean标签中写一个<property>标签的作用是一样的
Autowired:
作用:自动按照类型注入。只要容器中有唯一的一个bean对象类型和要注入的变量类型匹配,就可以注入成功。
如果ioc容器中没有任何bean的类型和要注入的变量类型匹配,则报错。
如果Ioc容器中有多个类型匹配时:
出现位置:
可以是变量上,也可以是方法上
细节:
在使用注解注入时,set方法就不是必须的了。
Qualifier:
作用:在按照类中注入的基础之上再按照名称注入。它在给类成员注入时不能单独使用。但是在给方法参数注入时可以。
属性:
value:用于指定注入bean的id。
Resource
作用:直接按照bean的id注入。它可以独立使用
属性:
name:用于指定bean的id。
以上三个注入都只能注入其他bean类型的数据,而基本类型和String类型无法使用上述注解实现。
另外,集合类型的注入只能通过XML来实现。
Value
作用:用于注入基本类型和String类型的数据
属性:
value:用于指定数据的值。它可以使用spring中SpEL(也就是spring的el表达式)
SpEL的写法:${表达式}
测试代码演示
//@Controller
public class AccountClient {
public static void main(String[] args) {
//创建spring容器对象
ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
//获取容器中对象
IAccountService as = (AccountServiceImpl) ac.getBean("accountService");
//AccountServiceImpl{accountDao=com.ginger.dao.AccountDaoImpl@69a10787, name='影流之主', age=28}
System.out.println(as);
//调用方法
as.save();//存钱啦
}
}
service
//@Component
@Service("accountService")
public class AccountServiceImpl implements IAccountService {
//@Autowired//按照类型注入
//@Qualifier("accountDao")//根据dao的id注入,但是不能单独使用需要@Autowired配合。
@Resource(name = "accountDao")//根据bean的id注入
IAccountDao accountDao;
//使用@Value注解注入基本类型和String类型。
@Value("影流之主")
private String name;
@Value("28")
private Integer age;
public void save() {
accountDao.save();
}
@Override
public String toString() {
return "AccountServiceImpl{" +
"accountDao=" + accountDao +
", name='" + name + '\'' +
", age=" + age +
'}';
}
}
dao
@Repository("accountDao")
public class AccountDaoImpl implements IAccountDao {
public void save() {
System.out.println("存钱啦");
}
}
3.spring中设置bean作用范围注解
他们的作用就和在bean标签中使用scope属性实现的功能是一样的
Scope
作用:用于指定bean的作用范围
属性:
value:指定范围的取值。常用取值:singleton prototype
测试代码
//@Controller
public class AccountClient {
public static void main(String[] args) {
//创建spring容器对象
ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
//获取容器中AccountServiceImpl1单例对象
IAccountService as1 = (AccountServiceImpl1) ac.getBean("accountService1");
IAccountService as2 = (AccountServiceImpl1) ac.getBean("accountService1");
System.out.println(as1 ==as2);//true
//获取容器中AccountServiceImpl2多例对象
IAccountService as3 = (AccountServiceImpl2) ac.getBean("accountService2");
IAccountService as4 = (AccountServiceImpl2) ac.getBean("accountService2");
System.out.println(as3==as4);//false
}
}
AccountServiceImpl1类
//@Component
@Service("accountService1")
@Scope("singleton")//设置bean作用范围为单例
public class AccountServiceImpl1 implements IAccountService {
public void save() {
System.out.println("保存service");
}
}
AccountServiceImpl2类
//@Component
@Service("accountService2")
@Scope("prototype")//设置bean作用范围为多例
public class AccountServiceImpl2 implements IAccountService {
public void save() {
System.out.println("保存service");
}
}
4.spring设置生命周期方法注解
他们的作用就和在bean标签中使用init-method和destroy-methode的作用是一样的
PreDestroy
作用:用于指定销毁方法
PostConstruct
作用:用于指定初始化方法
测试代码演示
//@Controller
public class AccountClient {
public static void main(String[] args) {
//创建spring容器对象
ClassPathXmlApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
//获取容器中对象
IAccountService as1 = (AccountServiceImpl) ac.getBean("accountService");
//调用方法
as1.save();
//main线程执行完后,spring还没来得及关闭,main就执行完了,所以就看不见AccountServiceImpl销毁方法,
//手动销毁spring容器。
ac.close();
//初始化方法
//保存service
//销毁方法
}
}
AccountServiceImpl类
//@Component
@Service("accountService")
public class AccountServiceImpl implements IAccountService {
public void save() {
System.out.println("保存service");
}
@PostConstruct
public void init(){
System.out.println("初始化方法");
}
@PreDestroy
public void distory(){
System.out.println("销毁方法");
}
}
5.spring中的新注解
该类是一个配置类,它的作用和bean.xml是一样的
spring中的新注解
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注解的作用是一样的
Import
作用:用于导入其他的配置类
属性:
value:用于指定其他配置类的字节码。
当我们使用Import的注解之后,有Import注解的类就父配置类,而导入的都是子配置类。
PropertySource
作用:用于指定properties文件的位置
属性:
value:指定文件的名称和路径。
关键字:classpath,表示类路径下
测试代码
AccountServiceTest类
@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);
}
}
QueryRunnerTest类
/**
* 测试queryrunner是否单例
*/
public class QueryRunnerTest {
@Test
public void testQueryRunner(){
//1.获取容易
ApplicationContext ac = new AnnotationConfigApplicationContext(SpringConfiguration.class);
//2.获取queryRunner对象
QueryRunner runner = ac.getBean("runner",QueryRunner.class);
QueryRunner runner1 = ac.getBean("runner",QueryRunner.class);
System.out.println(runner == runner1);
}
}
使用java类配置spring的bean.xml文件
SpringConfiguration类
//@Configuration
@ComponentScan("com.itheima")
@Import(JdbcConfig.class)
@PropertySource("classpath:jdbcConfig.properties")
public class SpringConfiguration {
}
JdbcConfig类
/**
* 和spring连接数据库相关的配置类
*/
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")
@Scope("prototype")
/*
默认是按照类型注入,在按照属性名称注入。如果DataSource 需要自定义属性名称注入加上@Qualifier这个注解就可以了。
createQueryRunner(DataSource dataSource)
*/
public QueryRunner createQueryRunner(@Qualifier("ds2") DataSource dataSource){
return new QueryRunner(dataSource);
}
/**
* 创建数据源对象
* @return
*/
@Bean(name="ds2")
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);
}
}
@Bean(name="ds1")
public DataSource createDataSource1(){
try {
ComboPooledDataSource ds = new ComboPooledDataSource();
ds.setDriverClass(driver);
ds.setJdbcUrl("jdbc:mysql://localhost:3306/eesy02");
ds.setUser(username);
ds.setPassword(password);
return ds;
}catch (Exception e){
throw new RuntimeException(e);
}
}
}
jdbcConfig.properties
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql:///travel
jdbc.username=root
jdbc.password=1234
AccountServiceImpl类
/**
* 账户的业务层实现类
*/
@Service("accountService")
public class AccountServiceImpl implements IAccountService{
@Autowired
private IAccountDao 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);
}
}
AccountDaoImpl类
/**
* 账户的持久层实现类
*/
@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 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 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 +
'}';
}
}
Spring整合Junit测试
使用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及以上。
spring整合junit包
使用spring整合junit测试代码
/**
* 使用Junit单元测试:测试的配置
*/
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:bean.xml")
public class AccountServiceTest {
@Autowired
private IAccountService as = null;
/**
* 其实在开发中,测试的人员可能不懂spring,你让他它写下面的,通过容器获取对象测试方法,其实是不现实的
* 他只关注下面的测试方法,是否测试通过不关注其它代码。所以这里init是不能需要。
*
* 要想获取容器那咋办?
* 分析
*1.应用程序的入口
* main方法
*2.junit单元测试中,没有main方法也能执行。
* junit集成了一个main方法
* 该方法就会判断当前测试类中有哪些方法有 @Test注解
* junit让有Test注解的方法执行
*3.junit不知道我会使用spring框架
* 在执行测试方法时,junit根本不知道我是不是使用了spring框架。
* 所以也就不会自动读取配置文件/配置类创建spring核心容器。
*4.由上诉几点可知,当测试方法执行时,没有IOC容器,就写了Autowired注解,也无法实现注入。
*
*junit不知道当然spring知道,也解决了这个问题,可以采用spring整合junit来测试,就可以避免这个问题。
*/
/*@Before
public void init() {
AnnotationConfigApplicationContext acac = new AnnotationConfigApplicationContext(SpringJUnit4ClassRunner.class);
as = acac.getBean("accountServicec", IAccountService.class);
}*/
@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);
}
}