Spring的核心技术
- IOC(DI)
- 控制反转(依赖注入)
- AOP
- 面向切面编程
一、Spring的IOC(就是将创建对象的权利交给Spring容器)
所谓的IOC称之为控制反转,简单来说就是将对象的创建和对象的生命周期管理的过程交给Spring框架处理,从此在开发过程中不再需要关注这些细节。需要使用到对象时,直接从Spring框架中获取。
1.1 IOC的使用
- 创建项目
- 导包
- 导入核心4个包(core context expression beans)和一个日志包(jcl)核心四加一
- 配置
1.建立java类
public class Person {
public void eat(){
System.out.println("eat....");
}
}
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">
<!--以上为约束文件-->
<!--Spring 的核心配置文件以下-->
bean 就代表实体类,
id 唯一标识,一般为类名的小写,不能重复
class 那个类需要被spring容器进行管理的类,写那个类的全限定名
<bean id ="person" class="com.shengqi.entity.Person"></bean>
别名标签,duiID起别名可以将person的别名改为aaa,并且上面的 id可以改为name,与别名相对应
<bean name ="person" class="com.shengqi.entity.Person"></bean>
<alias name="person" alias="aaa"></alias>
</beans>
3.测试
- ClassPathXmlApplicationContext 从类文件下寻找对应的xml文件
- FileSystemXmlApplicationContext 从系统路径(磁盘开始)下找
public class DemoTest {
@Test
public void test01(){
//从spring容器中获取对象
//1.加载spring的核心配置文件(加载Spring容器)
ApplicationContext applicationContext=new ClassPathXmlApplicationContext("applicationContext.xml");
Person person = (Person) applicationContext.getBean("person");
person.eat();
// 把对象创建的权利和对象生命周期的权利,都交给了Spring进行管理
}
}
4.解释
ClassPathXmlApplicationContext从类路径下找到applicationContext.xml文件,找到之后,
applicationContext.getBean(“person”)找到对应的Bean 当读到<Bean标签时,根据class中的全路径名创建对象(利用反射)
- xml 也就是说bean标签底层(利用反射)创建了对象
- 并且 Spring容器,实际上就是一个map集合,也就是说在Spring容器中还执行了map的添加
- id作为key,创建好的对象作为value
- test applicationContext.getBean(“person”);其实就是执行了map.get(id)
//获取字节码文件对象
Class<?> aClass = Class.forName("com.shengqi.entity.Person");
//通过无参构造获取对象
Person person = (Person) aClass.newInstance();
map.put(id,person)
1.2 IOC的使用原理
当初始化一个Sring容器时,会先去指定路径下寻找到xml,然后解析,回去到bean标签的class属性对应的值。根据全路径名通过反射技术获取对象,并且以id的值为key,对象的值为value,存入到map中。
当我们使用getBean获取对象时,从map中通过传入的key获取值,如果获取到,那么就返回对象,如果获取不到,返回null
Person person = (Person) applicationContext.getBean("person");
Person person1 = (Person) applicationContext.getBean("person");
System.out.println(person==person1);
以上代码的结果为True,很明显,因为从同一个map中获取的对象,地址值必然相同,
现在模拟bean(反射创建对象被执行两次)
<bean id ="person" class="com.shangma.entity.Person"></bean>
<bean id ="person1" class="com.shangma.entity.Person"></bean>
Person person = (Person) applicationContext.getBean("person");
Person person1 = (Person) applicationContext.getBean("person1");
System.out.println(person==person1);
上面结果为false,bean所代表的的反射执行两次,map集合中,存储了两个对象,再调用map.get(id),时两次取的就不是一个对象了。
- 默认情况下,多次获取同一个id的bean,得到的是同一个对象
- 即使是同一个类,如果配置过多个不同id标签,每个id都会在内存中创建一个新的对象,不同的id就是对应的类型相同,但是对象也不同。
- 同一个beans标签下,不允许出现相同的id
1.3 IOC的获取对象的方式
- 根据id获取(重载)需要强转,就会有风险
Person person = (Person) classPathXmlApplicationContext.getBean("person");
- 根据class(重载的方法)并且相同class的bean标签只能有一个
Person bean = classPathXmlApplicationContext.getBean(Person.class);
- 根据id和class确定(重载) 唯一确定,不需要强转,相同class的bean标签可以有多个
Person person = applicationContext.getBean("person", Person.class);
1.4 Spring创建对象的方式(默认都是单例)(设计模式:工厂)
因为
1.4.1 通过无参构造创建
默认调用的是无参构造的方式创建对象,使用这种方式,一定要保证类中有无参构造,否则抛出异常
1.4.2 通过静态工厂创建对象(不能new的情况下抽象类:Calender)
普通方式获取日历类
Calendar calender = Calendar.getInstance();
System.out.println(calender);
- 静态工厂模式
public class CalendarStaticFactory {
/**
* 静态方法获取日历的实例
* @return 日历的实例
*/
public static Calendar getCalendar(){
return Calendar.getInstance();
}
}
bean 中添加 factory-method=“getCalendar” 获取这个方法的返回值,如果不填则返回的是类的对象,而不是这个类的方法所返回的对象
将class这个类的 factory-method 找到的方法的返回值 作为value 给id对应 在map 中
只有一行,因为不需要创建对象,通过类名.静态方法名
<bean id="calender" class="com.shangma.factory.CalendarStaticFactory" factory-method="getCalendar"></bean>
1.4.3 通过实例工厂创建对象
xml
<!- 实例工厂模式
<bean id="calender01" class="com.shangma.factory.CalendarObjectFactory"></bean>
<bean id="calender" factory-bean="calender01" factory-method="getCalendar"></bean>
<!- 第一行创建对象CalendarObjectFactory, 第二行,actory-bean="calender01"得到第一行创建的对象,
得到factory-method="getCalendar"方法的返回值,也就是得到CalendarObjectFactory对象的方法getCalendar的返回值,存在map中
entity
public class CalendarObjectFactory {
/**
* 通过类的实例对象调用方法得到Calendar的返回值
* @return clendar
*/
public Calendar getCalendar(){
return Calendar.getInstance();
}
}
1.4.3 内置工厂创建对象
需要实现Spring内置的工厂接口(FactoryBean)。
entity
public class CalendarSpringFactory implements FactoryBean {
/**
* 使用工厂接口产生的对象
*/
@Override
public Object getObject() throws Exception {
return Calendar.getInstance();
}
/**
* 获取对象的类型
*/
@Override
public Class<?> getObjectType() {
return Calendar.class;
}
/**
* 是否是单例
* 在JDK8之后,接口中已经默认实现了该方法。返回的结果是true,说明默认就是单例
* false:多例
* true:单例 内存中只有一个,无论new多少次
* @return
*/
// @Override
// public boolean isSingleton() {
// return false;
// }
}
xml
<!--内置工厂-->
<bean id="calendarspring" class="com.shangma.factory.CalendarSpringFactory"></bean>
1.5 单例和多例
- pring容器管理的bean在默认情况下是单例的。就是说,一个bean只会创建一个对象,放入到对应的map中。无论根据id获取多少次该bean,都返回的是同一个对象
- 多例 ,每次getbean就会拿一个,会造成内存资源的浪费
1.5.1验证
Object calender = classPathXmlApplicationContext.getBean("calendarspring");
Object calender2 = classPathXmlApplicationContext.getBean("calendarspring");
System.out.println(calender==calender2);
- 结果为true
如果在实际开发中有多例的需求,Spring也可以设置。scope=“prototype”
<!--通过spring的内置工厂创建对象-->
<bean id="person" class="com.shangma.cn.entity.Person" scope="prototype"></bean>
1.5.2单例模式下bean对象的生命周期
bean在单例模式下,Spring容器启动时解析xml发现该bean标签,直接将对象创建成功并且存入内置的map中保存。此后无论调用多少次getBean()获取该bean属性,都是从map中获取该对象,所以一直是同一个对象。此对象一直被Spring容器持有,直到容器退出时对象才被销毁。
ClassPathXmlApplicationContext classPathXmlApplicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
//bean在单例模式下,上面这行代码执行完,对象已经被创建了
Object calender = classPathXmlApplicationContext.getBean("calendarspring");
Object calender2 = classPathXmlApplicationContext.getBean("calendarspring");
System.out.println(calender==calender2);
//无论多少次getBean()获取的都是一个对象,此对象一直被spring容器持有
1.5.3 多例模式下bean对象的生命周期
bean在多例模式下,Spring容器启动时解析xml发现该bean标签后,只是将该bean进行管理,并不会创建 对象。此后每次调用getBean()获取该bean时,都会创建一个新的对象,每次都是新的对象。这个对象Spring并不会持有,什么时候销毁取决于使用该对象的用户什么时候销毁对象。
1.6 懒加载机制
为什么引入懒加载?: Spring默认是所有的Bean都在加载容器时创建,所以有很多Bean加载,创建对象也会造成资源的浪费,时间上效率低,并且我只需要一个,却全部都加载了,不需要那么多,所以需要懒加载机制(用到的时候才需要,不用不创建)
所谓的懒加载机制就是可以规定指定的bean不在启动时马上加载,而是在用户真正第一次使用到时才去加载对象。
懒加载机制只对单例bean有作用。对于多例bean设置懒加载没有意义。
局部配置
<bean id="person" class="com.shangma.cn.entity.Person" lazy-init="true"></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 http://www.springframework.org/schema/beans/spring-beans.xsd"
全局设置:
default-lazy-init="true"
>
java就近原则,全局设置不懒加载,局部设置,局部会生效,局部优先级高
1.7 Spring的初始化和销毁方法
xml
<!--
配置初始化和销毁的方法
-->
init-method和 destroy-method想让那个方法先执行,后执行都可以放在这两个方法里
<bean id="productDao" class="com.shangma.cn.dao.ProductDao" init-method="init" destroy-method="destory"></bean>
test
public void test06(){
ProductDao productDao = (ProductDao) classPathXmlApplicationContext.getBean("productDao");
productDao.add();
//没有手动销毁时,未执行 销毁方法,因为这个类,仍然被Spring容器把持
// 需要手动销毁才会被执行
classPathXmlApplicationContext.close();
}
}
entity
public class ProductDao {
public ProductDao() {
System.out.println("无参构造");
}
public void init(){
System.out.println("init");
}
public void destory(){
System.out.println("关闭容器");
}
public void add(){
System.out.println("close");
}
}
结果
二. Spring的DI(属性注入值)
在创建对象的过程中Spring可以根据配置对对象中的属性进行赋值,这个过程就称之为依赖注入。
依赖注入分为两大方式:
- set方法注入
- 构造方法注入
2.1 set方法注入
通常的javabean属性都会私有化,所以前提条件是提供了set方法才可以赋值
set方法可以注入的内置类型:基本数据类型,内置引用数据类型(String、int[]、List、Set、Map、Properties)
set方法可以注入自定义类型
xml
entity
public class Dog {
private int id;
private String name;
public void setId(int id) {
this.id = id;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "Dog{" +
"id=" + id +
", name='" + name + '\'' +
'}';
}
}
public class Hero {
// 英雄id
private int id;
// 英雄名
private String name;
// 数组
private int[] array;
// list
private List<String> list;
// Set
private Set<String> set;
// Map
private Map<String,String> map;
// properties
private Properties properties;
// dog
private Dog dog;
public void setId(int id) {
this.id = id;
}
public void setName(String name) {
this.name = name;
}
public void setArray(int[] array) {
this.array = array;
}
public void setList(List<String> list) {
this.list = list;
}
public void setSet(Set<String> set) {
this.set = set;
}
public void setMap(Map<String, String> map) {
this.map = map;
}
public void setProperties(Properties properties) {
this.properties = properties;
}
public void setDog(Dog dog) {
this.dog = dog;
}
@Override
public String toString() {
return "Hero{" +
"id=" + id +
", name='" + name + '\'' +
", array=" + Arrays.toString(array) +
", list=" + list +
", set=" + set +
", map=" + map +
", properties=" + properties +
", dog=" + dog +
'}';
}
}
《创建对象:bean》
<bean id="hero" class="com.shangma.entity.Hero" >
《给属性赋值: property》
<property name="id" value="10"></property>
<property name="name" value="压缩"></property>
<property name="array">
<array>
<value>6</value>
<value>11</value>
<value>15</value>
</array>
</property>
<property name="list">
<list>
<value>上</value>
<value>中</value>
<value>下</value>
</list>
</property>
<property name="set">
<set>
<value>肉</value>
<value>adc</value>
<value>法师</value>
</set>
</property>
<property name="map">
<map>
<entry key="王" value="8000"></entry>
<entry key="陈" value="12000"></entry>
</map>
</property>
<property name="properties">
<props>
<prop key="陈">money</prop>
<prop key="李">ok</prop>
</props>
</property>
<!--自定义类型
如果是对象里嵌套对象使用
ref导入(引用)-->
<property name="dog" ref="dog"></property>
</bean>
<bean id="dog" class="com.shangma.entity.Dog">
<property name="id" value="5"></property>
<property name="name" value="5"></property>
等同与 Dog gog=new dog
dog.setName(“5”)
dog.setid(5)
</bean>
public void test07(){
Hero bean = classPathXmlApplicationContext.getBean(Hero.class);
System.out.println(bean);
}
结果
Hero{id=10, name='压缩', array=[6, 11, 15], list=[上, 中, 下], set=[肉, adc, 法师], map={王=8000, 陈=12000}, properties={陈=money, 李=ok}, dog=Dog{id=5, name='5'}}
2.2 自动装配
- 自动装配:
可以根据id名或者类型自动进行赋值 - 参数解析
1.autowire设定自动装配:
2.byName:根据类中需要注入的属性的名字,在Spring容器中找对应id的bean标签,如果找到,自动匹配
<bean id="dog1" class="com.shangma.entity.Dog" autowire="byName">
<property name="id" value="15"></property>
<property name="name" value="456"></property>
<property name="cat" ref="cat111"></property>
</bean>
<bean id="cat111" class="com.shangma.entity.Cat">
<property name="name" value="123"></property>
</bean>
3.byType:根据类中需要注入的属性的类型,在Spring容器中找对应的class类型的bean标签。将该bean标签的对象赋值给当前的属性
*有两个相同的bean时,会报错,使用范围低最好使用byName
<bean id="dog1" class="com.shangma.entity.Dog" autowire="byType">
<property name="id" value="15"></property>
<property name="name" value="456"></property>
不需要引入cat属性
</bean>
<bean id="cat111" class="com.shangma.entity.Cat">
<property name="name" value="123"></property>
</bean>
4.byType的方式 根据类型进行匹配,如果找不到对应的类型,匹配null。如果Spring容器中有多个相同的类型,会抛出异常
5.全局自动装配
<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"
全局懒加载
default-lazy-init="true"
全局自动装配
default-autowire="byName"
>
2.3 构造方法注入
<bean id="catt" class="com.shangma.entity.Cat" >
<constructor-arg index="0" name="name" value="11"></constructor-arg>
<constructor-arg index="1" ref="dogg"></constructor-arg>
</bean>
<bean id="dogg" class="com.shangma.entity.Dog">
<property name="id" value="1"></property>
<property name="name" value="12"></property>
</bean>
public Cat(String name, Dog dog) {
this.name = name;
this.dog = dog;
}
构造器自动装配 autowire=“constructor” 可以不用写属性
<bean id="catt" class="com.shangma.entity.Cat" autowire="constructor" >
<constructor-arg index="0" name="name" value="11"></constructor-arg>
</bean>
<bean id="dogg" class="com.shangma.entity.Dog">
<property name="id" value="1"></property>
<property name="name" value="12"></property>
</bean>