什么是Spring(面试):
Spring是一个分层的一站式的开源框架,可以将各个层集成在一起,提高开发效率,
Spring可以给我们解耦,比如平常我们要一个对象是要new一个对象,如果这个对象被成百上千次使用,那么突然有一天要换一个对象怎么办?成百上千个文件都要换?这个时候就体现出Spring的IOC的思想的好处,如果用了ioc思想就可以将想要的对象的全域名添加到<bean>中,用的时候通过id获得,然后如果想要换的时候就可以去通过修改xmld的bean的全域名去更改,这就是方便了解耦,使得我们不再频繁更改代码就可以实现更改
IOC思想: 也叫控制反转,由主动初始化到被动由工厂创建的这个过程就是控制反转 控制:对象的创建和销毁 反转:对象的控制权从开发者给了工厂,工厂+反色+配置文件
IOC代码思想的转变:
//需求:创建service层对象
/* 1. 主动创建对象 */
UserServiceImpl userService = new UserServiceImpl();
// 存在的问题:耦合严重。 当service层类需要变动,左右两遍都要修改。
/* 2. 面向接口编程 (多态) */
UserSerivce userService = new UserServiceImpl();
// 好处:解耦。 当dao层实现类需要变动,等号左边代码不需要修改。
// 缺点:等号右边代码还要变动。
/* 3. 反射 + 配置文件 */
ResourceBundle bundle = ResourceBundle.getBundle("service");//读取配置文件
String userServiceClassName = bundle.getString("userService");//获取配置文件中信息
Class userServiceCalss = Class.forName(userServiceClassName);//反射获取类的Class对象
UserService userService = (UserService) userServiceCalss.newInstance();//实例化
// 好处: 进一步解耦。 当service层实现类需要变动,不需要修改代码(直接修改配置文件即可)
// 缺点: 代码多。 (对程序员来讲)
/* 4. 工厂模式 (工厂+反射+配置文件) */
UserService userService = (UserService) BeanFactory.getBean("userService");
// 好处: 封装了创建对象的方法,更好的管理对象。
那么他的配置文件*.XML文件是怎么加载的呢?
是使用Application接口的实现类ClassPathXmlApplication去解析xml
接下来是一个简单的通过工厂创建对象的案例:
//测试
@Test
public void test01() {
//通过ClassPathXmlApplicationContext来解析xml配置文件
ClassPathXmlApplicationContext cpxac = new ClassPathXmlApplicationContext("ApplicationContext.xml");
//通过给出配置文件的唯一标识符id的名称来获取对象
UserService userService = (UserService) cpxac.getBean("UserService");
userService.test();
}
//接口
public interface UserService {
void test();
}
//接口的实现类
public class UserServiceImpl implements UserService {
public void test() {
System.out.println("这是test,开始打印这句话.....");
}
}
他的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">
<!--这里的id是唯一标识符,可以通过id的名称去获取工厂创建的的对象,也可以写name也是唯一标识,
但是name可以有多个值,如下name中有两个值,和id用法一样但是这两个参数可以获得工厂的对象
工厂对象创建的原理:class为全域名,用来反射
1):Class clazz = Class.forName("com.itheima.service.impl.UserServiceImpl");
2):UserService userService = clazz.newInstance();创建对象
3): map.put("UserService",userService);
创建完对象以后在将id为key对象为value田间道map中,所以下次再创建这个对象的时候会先去看map中有没有id为UserService
如果有那么就直接去除如果没有就说明第一次创建就创建一个对象,所以他是一个单例模式
class中的全域名必须有一个无参的构造方法,如果没有那么就会报
No default constructor found; nested exception is java.lang.NoSuchMethodException: com.itheima.service.impl.UserServiceImpl.<init>()
这个错
-->
<bean id="UserService" name="userService1,userService2" class="com.itheima.service.impl.UserServiceImpl"></bean>
</beans>
Spring的bean标签:
在Spring中,默认是单例模式(饿汉模式):在加载配置文件的时候就会创建这个对象,并且是同一个对象。而懒汉模式是创建的时候会创建这个对象,并且不是同一个对象。
在SPring的<bean>标签可以设置他是饿汉模式还是懒汉模式,通过scope来设置
@Test
public void test01() {
//通过ClassPathXmlApplicationContext来解析xml配置文件
ClassPathXmlApplicationContext cpxac = new ClassPathXmlApplicationContext("ApplicationContext.xml");
//通过给出配置文件的唯一标识符id的名称来获取对象
UserService userService = (UserService) cpxac.getBean("UserService");
System.out.println("userService = " + userService);
UserService userService1 = (UserService) cpxac.getBean("UserService");
System.out.println("userService1 = " + userService1);
}
通过输出可以发现
userService = com.itheima.service.impl.UserServiceImpl@36b4cef0
userService1 = com.itheima.service.impl.UserServiceImpl@36b4cef0
他们的地址值是一个,说明他们是同一个对象,这是在xml默认的情况下,而且他是在加载的时候就创建了,也就是没有调用就已经随着xml文件加载被创建
将bean中用scope去改一下他的模式
<bean id="UserService" scope="prototype" name="userService1,userService2" class="com.itheima.service.impl.UserServiceImpl"></bean>
scope有两个值:prototype懒汉 singleton:饿汉,或者不写默认就是singleton
输入结果是:
userService = com.itheima.service.impl.UserServiceImpl@7a187f14
userService1 = com.itheima.service.impl.UserServiceImpl@6f195bc3
地址值不一样的,而且用了他以后就是调用的时候才回去加载他的构造方法
Spring-IOC的生命周期:就是对象的创建到销毁的过程
单例对象:scope="singleton"
生命周期:
创建:当应用加载,创建容器的时候就被创建了
活着:容器在就一直活着
死亡:容器销毁了对象也就销毁了
多例对象:scope="prototype"
生命周期:
创建:当使用对象时创建
活着:对象使用就会活着
销毁: 对象长时间不用就会被java的gc销毁,所以他的销毁不归spring容器管
可以在bean标签中,使用init-method
,destroy-method
两个属性,分别定义bean对象在初始化或销毁时要完成的工作
<bean id="UserService1" class="com.itheima.service.impl.UserServiceImpl1" init-method="init" destroy-method="des"></bean>
是表示创建UserServiceImpl1对象的时候就会调用他里面的init(_方法,如果时单例的话,当容器销毁或者关闭了就会调用des()方法,他这个方法是自己定义调用的,前提是UserServiceImpl1里有这个方法。如果是多例的话创建的时候会调用init()方法,销毁的时候不会调用,因为多例销毁不归容器管
Spring的IOC配置-bean对象创建方式:
1):方式一:直接配置
2):方式二:静态工厂
3):方式三:实例工厂
直接配置就是之前通常的配置:
<!-- 将UserService实现类装配到容器中 -->
<bean id="userService" class="com.itheima.spring.bean.UserServiceImpl"/>
通过bean标签的配置,spring会利用反射来创建这个对象并装配到容器中,通过反射创建这个对象时是默认使用类的无参构造方法来实例化bean的
应用场景:在配置的时候,知道实现类的全限定名 (一般自己写bean)
底层原理:无参构造 (要求类中必须有无参构造方法)
缺点:开发者需要知道类名
方式二:静态工厂
步骤:先写一个静态工厂类,然后里面有一个静态方法,通过静态方法去得到这个对象
public class UserServiceFactory {
public static UserServiceImpl getBean(){
return new UserServiceImpl();
}
}
<?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">
<!--他是在UserServiceFactory有一个方法getBean得到UserServiceImpl对象
底层:
1)Class clazz = Class.forName("com.itheima.Factory.UserServiceFactory");
2) Method method = clazz.getMethod("getBean")
3) UserService userService = method.invoke(null);
4)map.put("UserService",userService)
他是通过反射拿到factory-method="getBean"的参数也就是静态工厂的方法名称去调用他的方法,但是invoke为什么时null?
这是因为他是静态方法,他在创建的时候就已经创建了所以时null
-->
<bean id="UserService" class="com.itheima.Factory.UserServiceFactory" factory-method="getBean"></bean>
</beans>
方式三:实例工厂:
使用实例工厂的形式创建bean
public class InstanceFactory {
//非静态方法
public UserService getBean() {
UserService userService = new UserServiceImpl2();
return userService;
}
}
<?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">
<!--
# 实例工厂的原理
clazz = Class.forName("com.itheima.factory.InstanceFactory");
if = clazz.newInstance();
getBean = clazz.getMethod("getBean")
UserService service = getBean.invoke(if);
map.put("userService3",service);
-->
<!-- 实例工厂对象 -->
<bean id="if" class="com.itheima.spring.bean.factory.InstanceFactory"/>
<!-- UserService对象 -->
<bean id="userService3" factory-bean="if" factory-method="getBean"/>
</beans>
依赖注入:使用Spring的容器可以降低耦合,但是不能完全消除依赖,比如一个类中有一个参数是一个自定义类型,然后想要给他赋值还要通过工厂创建对象然后去接收。注入就可以实现不去接收也可以在创建这个对象的时候去给他赋值
使用前的代码:
//业务层
public class UserServiceImpl3 implements UserService {
//dao层对象
private UserDao userDao;//成员变量
//无参构造方法
public UserServiceImpl3(){
//1.加载配置文件
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
//2.获取Spring容器中的资源
userDao = (UserDao) ctx.getBean("userDao");//仅仅只是让Spring创建bean对象,降低了依赖关系。 但还是需要自己书写代码接收Spring创建的bean对象。
}
public void save(User user) {
//调用dao层对象中的添加用户方法
userDao.save(user);
}
}
常用的注入有两种方式给成员变量赋值:
1:setter方法、 2:构造方法
1:setter方法:
<?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">
<!--
set注入
1. 原理 : 空参构造 + set方法
clazz = Class.forName("com.itheima.service.impl.UserServiceImpl3");
service = class.newInstance(); //
setName = clazz.getMethod("setName")
setName.invoke(service,"zs");
// service.setName("zs");
2. 配置 : bean标签内子标签property
1). name : bean中的属性名
2). 值
value : 写基本类型、字符串、包装类
ref: 引用类型
-->
<!-- UserDao对象 -->
<bean id="userDao" class="com.itheima.spring.bean.UserDao"/>
<!-- 日期类型 -->
<bean id="birthday" class="java.util.Date"/>
<!-- UserService对象 -->
<bean id="userService4" class="com.itheima.spring.bean.UserServiceImpl3">
<!-- 注入属性值 -->
<property name="num" value="100"/>
<property name="name" value="hmh"/>
<property name="birthday" ref="birthday"/>
<property name="userDao" ref="userDao"/>
</bean>
</beans>
public class UserServiceImpl3 implements UserService {
private int num; //基本类型
private String name; //String类型
private UserDao userDao; //dao层对象
private Date birthday; //Date类型
public int getNum() {
return num;
}
public void setNum(int num) {
this.num = num;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public UserDao getUserDao() {
return userDao;
}
public void setUserDao(UserDao userDao) {
this.userDao = userDao;
}
public Date getBirthday() {
return birthday;
}
public void setBirthday(Date birthday) {
this.birthday = birthday;
}
}
}
这是上边修改的实现类,必须要写构造方法
2:构造器注入:
public class UserServiceImpl3 implements UserService {
private int num; //基本类型
private String name; //String类型
private UserDao userDao; //dao层对象
private Date birthday; //Date类型
//有参构造方法
public UserServiceImpl3(int num , String name, UserDao userDao, Date date){
this.num = num;
this.name = name;
this.userDao= userDao;
this.birthday = date;
}
//无参构造方法
public UserServiceImpl3(){
}
public void save(User user) {
System.out.println("成员变量 num:"+num);
System.out.println("成员变量 name:"+name);
System.out.println("成员变量 birthday:"+birthday);
//调用dao层对象中的添加用户方法
userDao.save(user);
}
}
<?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">
<!--
构造器注入 (了解)
1. 原理
clazz = Class.forName("com.itheima.service.impl.UserServiceImpl3");
contructor = clazz.getConstructor(String.class,int.class,UserDao.class,Date.class);
service = contructor.newInstance("zs",18,userDao,myDate);
map.put("userService44",service);
2. 配置:bean标签的子标签constructor-arg
name属性 : 构造方法中参数的名字
value属性: 非引用类型值
ref属性: 引用类型值
-->
<bean id="userDao" class="com.itheima.spring.bean.UserDao"/>
<bean id="birthday" class="java.util.Date"/>
<bean id="userService4" class="com.itheima.spring.bean.UserServiceImpl3">
<constructor-arg name="num" value="100"/>
<constructor-arg name="name" value="hmh"/>
<constructor-arg name="userDao" ref="userDao"/>
<!-- 构造方法中参数名:date -->
<constructor-arg name="date" ref="birthday"/>
</bean>
</beans>
集合类型的注入:
原理:
clazz = Class.forName("com.itheima.service.impl.UserServiceImpl5")
service = clazz.newInstance();
setList = service.getMethod("setList",List.class);
List list = new ArrayList();
list.add("zs");
list.add("ls");
list.add("ww");
setList.invoke(service,list);// service.setList(list)
-->
<!-- List类型数据注入 -->
<bean id="beanId" class="className">
<property name="成员变量名">
<list>
<value>元素</value>
</list>
</property>
</bean>
<!-- Set类型数据注入 -->
<bean id="beanId" class="className">
<property name="成员变量名">
<set>
<value>元素</value>
</set>
</property>
</bean>
<!-- Map类型数据注入 -->
<bean id="beanId" class="className">
<property name="成员变量名">
<map>
<entry key="key元素" value="value元素"/>
</map>
</property>
</bean>
<!-- Properties类型数据注入 -->
<bean id="beanId" class="className">
<property name="成员变量名">
<props>
<prop key="key元素">value元素</prop>
</props>
</property>
</bean>
<!-- 数组类型数据注入 -->
<bean id="beanId" class="className">
<property name="成员变量名">
<array>
<value>元素值</value>
</array>
</property>
</bean>
Spring的IOC的EL表达式:
就是数据引用,在配置mybatis的时候就有#{} ${} 这两个符号,而${}这个是可以读取properties文件的值,比如 username=root 就可以通过在xml中写${username}得到他的值,他在bean中的用法就是:<bean id="beanId" class="className">
<property name="成员变量名" value="EL表达式"/>
</bean>
<?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 EL
1. ${表达式}
引入配置文件中的数据
2. #{表达式}
强调的是把内容赋值给属性
#{'字符串'}
#{数字}
#{beanId}
如下的userDao和birthday就是bean的id也是可以引用的
-->
<bean id="userDao" class="com.itheima.spring.bean.UserDao"/>
<bean id="birthday" class="java.util.Date"/>
<bean id="userService6" class="com.itheima.spring.bean.UserServiceImpl3">
<!-- 使用EL表达式注入属性值 -->
<property name="num" value="#{100}"/>
<property name="name" value="#{'hmh'}"/>
<property name="birthday" value="#{birthday}"/>
<property name="userDao" value="#{userDao}"/>
</bean>
</beans>
使用properties文件的值,这里用${}
先准备context命名空间支持:
xmlns:context="http://www.springframework.org/schema/context"
加载指定的properties 文件
<context:property-placeholder location="classpath:filename.properties"/>
接下来就能使用里面的值了:
<property name="成员变量名" value="${propertiesName}"/>