什么是Spring
这里的Spring指的是SpringForMark
官网介绍
下面是官网的介绍
Spring优势
Spring的优势是什么呢,b站老师给我们做出以下解释:
方便解耦,简化开发 通过 Spring 提供的 IoC 容器,可以将对象间的依赖关系交由 Spring 进行控制,避免硬编码所造 成的过度程序耦合。用户也不必再为单例模式类、属性文件解析等这些很底层的需求编写代码,可 以更专注于上层的应用。 AOP 编程的支持 通过 Spring 的 AOP 功能,方便进行面向切面的编程,许多不容易用传统 OOP 实现的功能可以 传智播客——专注于 Java、.Net 和 Php、网页平面设计工程师的培训 北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 通过 AOP 轻松应付。 声明式事务的支持 可以将我们从单调烦闷的事务管理代码中解脱出来,通过声明式方式灵活的进行事务的管理, 提高开发效率和质量。 方便程序的测试 可以用非容器依赖的编程方式进行几乎所有的测试工作,测试不再是昂贵的操作,而是随手可 做的事情。 方便集成各种优秀框架 Spring 可以降低各种框架的使用难度,提供了对各种优秀框架(Struts、Hibernate、Hessian、Quartz 等)的直接支持。 降低 JavaEE API 的使用难度 Spring 对 JavaEE API(如 JDBC、JavaMail、远程调用等)进行了薄薄的封装层,使这些 API 的 使用难度大为降低。 Java 源码是经典学习范例 Spring 的源代码设计精妙、结构清晰、匠心独用,处处体现着大师对 Java 设计模式灵活运用以 及对 Java 技术的高深造诣。它的源代码无意是 Java 技术的最佳实践的范例。
我们可以看到第一句是:“方便解耦,简化开发”
那什么是耦呢,耦在这里是指耦合
什么是耦合
耦合是指类与类之间的关联,在java中通常体现为“导入包”等操作
耦合是不可能完全消除的,我们能做的只能是降低耦合
这里为了更好的理解耦合的概念 写一个程序,这是一个使用jdbc访问数据库的程序
public static void main(String[] args) throws SQLException { //注册驱动 DriverManager.registerDriver(new com.mysql.cj.jdbc.Driver()); //获取链接 Connection connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/spring_test","root","adminadmin"); //获取数据库处理对象 PreparedStatement ps = connection.prepareCall("select * from account"); //执行sql得到结果集 ResultSet rs = ps.executeQuery(); //遍历结果集 while (rs.next()){ System.out.println(rs.getString("name")+" "+rs.getString("money")); } rs.close(); ps.close(); connection.close(); }
可以看到在注册驱动时我们采用了DriverManager.registerDriver(new com.mysql.cj.jdbc.Driver());
但如果这时,我们删除了msql的jar包会发生什么呢
这时我们会发现这是一个编译错误
但是如果我们通常注册驱动时是不会这样写的
我们通常采用的是通过反射来注册驱动
比如Class.forName("com.mysql.cj.jdbc.Driver");
此时运行会发现
我们的错误从编译错误变成了运行时的错误
小结一下:
对于平常创建对象的一些问题
我们对象创建通常是采用new的方式进行的,即
IAccountserver accountserver = new accountserverimpl();
但是我们采用这种方式,必然需要导入accountserverimpl这个类
如果删除类就会导致编译时报错
但是如果我们使用工厂模式的思想就可以解决这个问题
我们试着写一个工厂类
public class BeanFactory { private static Properties propertie = new Properties(); static { //读取配置文件 InputStream in = BeanFactory.class.getClassLoader().getResourceAsStream("bean.properties"); try { propertie.load(in); } catch (IOException e) { e.printStackTrace(); } } public static Object getbean(String beanname) throws Exception { Object a= null; //获取全限定类名 String classname=propertie.getProperty(beanname); System.out.println(classname); //通过反射来创建对象 a=Class.forName(classname).newInstance(); return a; } }
这就是读取配置文件再通过反射的方式来创建对象
此时就可以用以下代码来创建对象IAccountserver accountserver=(IAccountserver) BeanFactory.getbean("accountserver");
现在我们观察代码,发现已经成功的解耦了,但是还有个问题
我们修改主函数来观察一下
public static void main(String[] args) throws Exception { for (int i = 0; i < 5; i++) { IAccountserver accountserver=(IAccountserver) BeanFactory.getbean("accountserver"); System.out.println(accountserver); } }
最后输出的结果是这样的
这表示这创建的是多例对象
那如果要创建单例对象呢
我们更改工厂类的代码,主要是在获取之前先将对象创建好并存储到容器中,以遍调用
public class BeanFactory { //读取配置文件 private static Properties propertie = new Properties(); //创建容器用于存储单例对象 private static Map<String,Object> objectMap; static { objectMap=new HashMap<String,Object>(); //读取文件 InputStream in = BeanFactory.class.getClassLoader().getResourceAsStream("bean.properties"); try { //加载文件 propertie.load(in); } catch (IOException e) { e.printStackTrace(); } //获取所有关键字 Enumeration keys = propertie.keys(); //遍历关键字 while (keys.hasMoreElements()){ //枚举关键字 String key = keys.nextElement().toString(); try { //创建对象,并存入容器中 objectMap.put(key,Class.forName(propertie.getProperty(key)).newInstance()); } catch (InstantiationException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } catch (ClassNotFoundException e) { e.printStackTrace(); } } } /** * 单例工厂模式创建javabean * @param beanname * @return */ public static Object getbean(String beanname){ return objectMap.get(beanname); } }
此时运行结果便成了:
spring基于配置文件的开发
1.导入jar包
<dependencies> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>5.2.1.RELEASE</version> </dependency> </dependencies>
2.编写配置文件:
<?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="accountServer" class="com.spring.server.impl.accountserverimpl"></bean> <bean id="accountDao" class="com.spring.dao.impl.AccountDaoImpl"></bean> </beans>
关于代码开头的约束可以在下面的网站中查找到
https://www.docs4dev.com/docs/zh/spring-framework/5.1.3.RELEASE/reference/core.html#beans
3.编写测试类:
public class Test { public static void main(String[] args) throws Exception { //获取核心容器对象 //ApplicationContext的三个实现类: // ClassPathXmlApplicationContext -----类路径下的配置文件创建 // FileSystemApplicationContext -----磁盘上的配置文件 // AnnotationConfigApplicationContext -----基于注解 ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml"); //根据id获取对象 IAccountserver accountserver=(IAccountserver)ac.getBean("accountServer"); accountserver.saveAccount(); } }
注意这里 IAccountserver 是一个自己创建的接口 定义了saveAccount方法
他的实现类 com.spring.server.impl.accountserverimpl实现了saveAccount方法,public void saveUser() { System.out.println("保存成功"); }
所以此时运行结果为
那么此时创建的对象是单例对象还是多例对象呢
我们更改一下测试程序
public class Test { public static void main(String[] args) throws Exception { //获取核心容器对象 //ApplicationContext的三个实现类: // ClassPathXmlApplicationContext -----类路径下的配置文件创建 // FileSystemApplicationContext -----磁盘上的配置文件 // AnnotationConfigApplicationContext -----基于注解 ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml"); //根据id获取对象 IAccountserver accountserver=(IAccountserver)ac.getBean("accountServer"); accountserver.saveAccount(); IAccountserver accountserver2=ac.getBean("accountServer",IAccountserver.class); accountserver2.saveAccount(); System.out.println(accountserver==accountserver2); } }
此时的运行结果为:
由于两个对象地址相同,所以证明此时创建的是单例对象
另外需要注意的是
ApplicationContext 在读取配置文件时,就采用立即加载的方式将配置的对象创建完成了
ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
即这句执行完将立即创建配置文件里所有的对象
而我们采用BeanFactory是采用延迟加载的方式实现的
这里我们来验证一下
我们首先重写一下com.spring.server.impl.accountserverimpl的构造函数,让其执行构造函数时输出
public class accountserverimpl implements IAccountserver { private IaccountDao dao = new AccountDaoImpl(); public accountserverimpl() { System.out.println("创建了accountserverimpl"); } @Override public void saveAccount() { dao.saveUser(); } }
在试着在读取配置文件时打一个断点,然后在debug模式一步一步运行
由此可以证明ApplicationContext 是采用立即加载的方式
同理可以证明
所以BeanFactory采用的时延迟加载的方式
总结一下
ApplicationContext 由于在程序运行一开始就创建好了对象(对象只创建一次)所以适用于单例对象
BeanFactory由于程序运行到创建对象时才进行对象创建(对象可以创建0次或多次),所以适用于多例对象 **注意:**这里不是说BeanFactory创建的对象是多例的,而是延迟加载的方式更适合于多例对象的创建
Spring细节
spring管理的细节
1.创建bean对象的三种方式
2.bean对象的作用范围
3.ben对象的生命周期
一,创建bean的三种方式
<!-- 一,创建bean的三种方式--> <!-- 1.使用默认构造函数创建 注意:没有无参构造函数时将无法创建 --> <bean id="accountServer" class="com.spring.server.impl.accountserverimpl"></bean> <!-- 2.使用工厂中的方法来创建对象 (使用某个类中的方法(非static方法)创建对象,并存入spring容器中) --> <bean id="instanFactory" class="com.spring.factory.InstanceFactory"></bean> <bean id="accountService" factory-bean="instanFactory" factory-method="getAccountService"></bean> <!-- 3.使用工厂中的静态方法创建对象 --> <bean id="accountService" class="com.spring.factory.InstanceFactory" factory-method="getAccountService"></bean>
二,bean的作用范围调整
<!-- 二,bean的作用范围调整 bean标签的scope属性: 作用:用于指定bean的作用位置 value: singleton:单例(默认值) prototype:多例 request: web应用的请求范围 session: web应用的会话范围 global—session :集群环境的会话范围 --> <bean id="accountServer" class="com.spring.server.impl.accountserverimpl" scope="singleton"></bean>
前两个可以简单的修改测试函数来查看区别
public static void main(String[] args) throws Exception { ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml"); //根据id获取对象 IAccountserver accountserver=(IAccountserver)ac.getBean("accountServer"); IAccountserver accountserver2=(IAccountserver)ac.getBean("accountServer"); System.out.println(accountserver2==accountserver); }
先将创建bean的方式设置为单例创建即<bean id="accountServer" class="com.spring.server.impl.accountserverimpl" scope="singleton"></bean>
此时运行执行结果为
我们再试一下多例,即<bean id="accountServer" class="com.spring.server.impl.accountserverimpl" scope="prototype"></bean>
此时运行的结果是:
globa—session的作用如下所示,在服务器众多的情况下,需要一个多个服务器公用的session即globa—session
三,bean的生命周期
1.单例对象的生命周期:
我们在对象里添加两个方法
public void init() { System.out.println("初始化了"); } public void close() { System.out.println("销毁了"); }
并更改配置文件,指定初始化方法和销毁方法
<bean id="accountServer" class="com.spring.server.impl.accountserverimpl" scope="singleton" init-method="init" destroy-method="close"></bean>
执行方法运行结果为
执行主函数:
public static void main(String[] args) throws Exception { //获取核心容器对象 //ApplicationContext的三个实现类: // ClassPathXmlApplicationContext -----类路径下的配置文件创建 // FileSystemApplicationContext -----磁盘上的配置文件 // AnnotationConfigApplicationContext -----基于注解 ClassPathXmlApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml"); //根据id获取对象 IAccountserver accountserver=(IAccountserver)ac.getBean("accountServer"); accountserver.saveAccount(); ac.close(); }
分析一下 ,单例对象的生命周期的为
出生:创建容器时bean就被创建了
活着:只要容器在就一直活着
销毁:容器销毁,bean就销毁
总结:单例对象的的生命周期和容器相同
2.多例对象的生命周期
改变配置文件使用多例对象的方式
<bean id="accountServer" class="com.spring.server.impl.accountserverimpl" scope="prototype" init-method="init" destroy-method="close"></bean>
此时运行结果为:
发现对象的销毁并没有受容器的影响。
再加上多例对象采用懒加载不难做出以下总结
分析一下 ,单例对象的生命周期的为
出生:使用对象时被创建
活着:只要对象在使用过程中就一直活着
销毁:被jvm垃圾回收
DI依赖注入
编写类:
public class accountserverimpl implements IAccountserver { //如果经常改变的数据不适用于DI private String name; private Integer age; private Date birthday; public accountserverimpl() { } public accountserverimpl(String name, Integer age, Date birthday) { this.name = name; this.age = age; this.birthday = birthday; } @Override public void saveAccount() { System.out.println("accountserverimpl{" + "name='" + name + '\'' + ", age=" + age + ", birthday=" + birthday + '}'); }
构造函数注入
编写配置文件
<!-- 在程序中依赖关系的管理将交给spring管理 依赖关系的维护:称之为依赖注入 依赖注入: 能注入的三种类型: 基本数据类型和String 其他bean(配置文件或注解配置过的) 复杂类型,集合类 注入的方式:三种 构造函数 set方法 注解提供 --> <!-- 使用constructor-arg标签,采用构造函数注入,必须有有参的构造函数 具体有以下属性: 1.index 指定要插入的位置 很少单独使用 2.type 赋值全限定类名来指定数据类型导入 很少单独使用 3.name 通过形参的名称注入 最常用 ==========上面三种都是来找插入的位置的,通常单独使用第三种方式进行=================== 4.value 注入基本数据类型和String使用 5.ref 注入其他bean对象 --> <bean id="accountServer" class="com.spring.server.impl.accountserverimpl" scope="prototype"> <constructor-arg name="name" value="姓名"/> <constructor-arg name="age" value="12"/> <constructor-arg name="birthday" ref="day1"/> </bean> <bean id="day1" class="java.util.Date"> <constructor-arg name="year" value="128"/> <constructor-arg name="month" value="10"/> <constructor-arg name="date" value="20"/> </bean>
此时运行main函数可以得到结果:
public class Test { public static void main(String[] args) throws Exception { //获取核心容器对象 //ApplicationContext的三个实现类: // ClassPathXmlApplicationContext -----类路径下的配置文件创建 // FileSystemApplicationContext -----磁盘上的配置文件 // AnnotationConfigApplicationContext -----基于注解 ClassPathXmlApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml"); //根据id获取对象 IAccountserver accountserver=(IAccountserver)ac.getBean("accountServer"); accountserver.saveAccount(); ac.close(); } }
这种构造函数方式注入的方式有一个弊端:即在不需要某些参数时也必须传入参数
set方法注入
1.为类创捷get和set方法
2.更改配置文件
<!-- set注入:使用set方法注入 使用标签property 属性值 name 通过形参的名称注入 value 注入基本数据类型和String使用 ref 注入其他bean对象 --> <bean id="accountServer" class="com.spring.server.impl.accountserverimpl" scope="prototype"> <property name="name" value="hhhhh"/> <property name="age" value="15"/> <property name="birthday" ref="day1"/> </bean> <bean id="day1" class="java.util.Date"> <constructor-arg name="year" value="128"/> <constructor-arg name="month" value="10"/> <constructor-arg name="date" value="20"/> </bean>
这种方式较为灵活,但无法保证某些必须有值的成员有值
以上两种方式对比 set方式更加常用
复杂数据类型的注入
更改要创建的类
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; } public void saveAccount(){ System.out.println(Arrays.toString(myStrs)); System.out.println(myList); System.out.println(mySet); System.out.println(myMap); System.out.println(myProps); }
更改配置文件
<!-- 复杂类型的注入/集合类型的注入 用于给List结构集合注入的标签: list array set 用于个Map结构集合注入的标签: map props 结构相同,标签可以互换 --> <bean id="accountService3" class="com.itheima.service.impl.AccountServiceImpl3"> <property name="myStrs"> <set> <value>AAA</value> <value>BBB</value> <value>CCC</value> </set> </property> <property name="myList"> <array> <value>AAA</value> <value>BBB</value> <value>CCC</value> </array> </property> <property name="mySet"> <list> <value>AAA</value> <value>BBB</value> <value>CCC</value> </list> </property> <property name="myMap"> <props> <prop key="testC">ccc</prop> <prop key="testD">ddd</prop> </props> </property> <property name="myProps"> <map> <entry key="testA" value="aaa"></entry> <entry key="testB"> <value>BBB</value> </entry> </map> </property> </bean>
此时运行便可以得到想要的结果
注意
用于给List结构集合注入的标签:
list array set
用于个Map结构集合注入的标签:
map props
结构相同,标签可以互换