Spring
Spring可以说是一个框架,也可以说是一家公司,Spring为我们提供了非常庞大的开发生态,到现在它已经深入我们日常工作中,如空气,氧气,那么自然也是那么重要。其产品众多,耳熟能详的,spring framework,springmvc、spring boot 、springcloud 、spring data JPA等等。本文主要讲spring framework,其包括两个重要组成部分IOC/DI和AOP,即“控制反转/依赖注入”和“面向切面编程”。
一、Spring IOC/DI
Ioc是一个抽象的概念或者思想。通俗的讲:将对象的控制权交给spring容器。对象的新建、使用、销毁被称为对象的生命周期,对象的新建由我们通过new或者其他手段手动创建,使用不用说,销毁由jvm垃圾回收器自动为我们回收。现在有了spring,我们不用关心对象要如何被创建,销毁,这一切都只需交给spring容器,由它全权负责,这样我们就腾出时间一门心思写业务逻辑了。这有点像当初从C语言跨到java,java有垃圾回收器一样。DI,常常和IOC放在一起,因为DI是IOC的具体实现。
1.依赖注入
两种方式:构造器注入和属性注入。
//applicationContext.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 id="person" class="spring01.Person">
<!-- 构造器注入:需要有对应的构造方法 -->
<!-- <constructor-arg name="id" value="1"></constructor-arg>
<constructor-arg name="name" value="wangle"></constructor-arg>
<constructor-arg name="food" ref="food"></constructor-arg>
-->
<!-- 属性注入:需要有对用的set方法 -->
<property name="id" value="2"></property>
<property name="name" value="2"></property>
<property name="food" ref="food"></property>
<!-- int数组-->
<property name="array" >
<array>
<value>1</value>
<value>2</value>
<value>3</value>
</array>
</property>
<!-- String类型List集合 -->
<property name="list" >
<list >
<value>上海</value>
<value>北京</value>
<value>成都</value>
</list>
</property>
<!-- Properties 类型-->
<property name="property">
<props>
<prop key="asdf1">asdf</prop>
<prop key="asdf2">asdf</prop>
<prop key="asdf3">asdf</prop>
</props>
<!-- <value>
1=ads,
2=23
</value> -->
</property>
<!-- String类型map -->
<property name="map">
<map>
<entry key="1" value="house"></entry>
<entry key="2" value="room"></entry>
</map>
</property>
<!-- Food类型Set集合 -->
<property name="set">
<set>
<!-- <bean class="spring01.Food">
<property name="name" value="苹果"></property>
<property name="location" value="富士"></property>
</bean>
<bean class="spring01.Food">
<property name="name" value="西瓜"></property>
<property name="location" value="攀枝花"></property>
</bean> -->
<!-- 或者ref方式 -->
<ref bean="food1"/>
<ref bean="food2"/>
</set>
</property>
</bean>
<bean class="spring01.Food" id="food1">
<property name="name" value="香蕉"></property>
<property name="location" value="新疆"></property>
</bean>
<bean class="spring01.Food" id="food2">
<property name="name" value="荔枝"></property>
<property name="location" value="西域"></property>
</bean>
</beans>
//Person
public class Person {
private int id;
private String name;
private Food food;
private List<String> list;
private Map<String,String> map;
private int[] array;
private Set<Food> set;
private Properties property;
public Person() {
}
public Person(int id) {
this.id = id;
}
public Person(int id, String name) {
super();
this.id = id;
this.name = name;
}
public Person(int id, String name, Food food) {
super();
this.id = id;
this.name = name;
this.food = food;
}
//省略getter和setter
}
//Food
package spring01;
public class Food {
private String name;
private String location;
public Food() {
super();
// TODO Auto-generated constructor stub
}
public Food(String name) {
super();
this.name = name;
}
//省略getter和setter
}
2.applicationContext.xml
既然用到applicationContext.xml,那么就再探讨一下其它用法。在spring中这个配置文件非常重要,名字是约定俗成,约定大于配置嘛。spring框架中既可以用xml也可以用注解的方式进行配置。便于阅读,采用伪代码。
1)depends-on
依赖
<bean id="A" class="com.wangle.A"></bean>
<!-- 在加载某个类的时候,先加载和初始化其他类。在这里就是在加载类B的时候先去加载并初始化了C -->
<bean id="B" class="com.wangle.B" depends-on="C"></bean>
<bean id="C" class="com.wangle.C"></bean>
main:
public static void main(String[] args) {
ClassPathXmlApplicationContext context =
new ClassPathXmlApplicationContext("applicationContext.xml");
}
//打印顺序:
//A
//C
//B
总结:
1.depends-on决定配置在xml中类的加载顺序,不配置的话默认是按照配置的先后顺序。
2.应用场景:在初始化一个对象前,需要另一个对象的时候。
2)lazy-init
懒加载
配置:
<!-- 只有懒加载情况 -->
<bean id="food2" class="spring.Food" lazy-init="true">
</bean>
<!-- depends-on在bean属性配置可以创建bean的加载顺序,默认是按照配置的先后 -->
<bean id="pet2" class="spring.Pet" lazy-init="true" >
</bean>
<!-- 加载food类的时候,先加载Pet类 -->
<bean id="person2" class="spring.Person" lazy-init="true" >
</bean>
mian方法:
public static void main(String[] args) {
ClassPathXmlApplicationContext beans =
new ClassPathXmlApplicationContext("applicationContext.xml");
new Pet();
new Person();
new Food();
}
//打印顺序:
//Pet
//person
//food
总结:
1.加载配置文件时,对象不会初始化。
2.new对象或者getBean()时会初始化
3.初始化顺序按照代码的的执行顺序,而不是配置的bean的顺序。
和depends-on互用的情况
<bean id="food" class="spring.Food">
<property name="name" value="荔枝"></property>
<property name="location" value="西域"></property>
</bean>
<!-- depends-on在bean属性配置可以创建bean的加载顺序,默认是按照配置的先后 -->
<bean id="pet" class="spring.Pet" depends-on="person" >
</bean>
<!-- 加载food类的时候,先加载Pet类 -->
<bean id="person" class="spring.Person" lazy-init="true" >
</bean>
main:
public static void main(String[] args) {
ClassPathXmlApplicationContext beans =
new ClassPathXmlApplicationContext("applicationContext.xml");
}
//打印顺序:
//food
//person
//pet
总结:
1.加载配置文件时,对象会初始化。
2.即使bean是懒加载,但是被其他依赖的话,还是会在xml加载期初始化。
3)循环依赖
<bean id="Person" class="spring01.Person" scope="prototype">
<property name="pet" ref="Pet"></property>
</bean>
<bean id="Pet" class="spring01.Pet" scope="prototype">
<property name="food" ref="Food"></property>
</bean>
<bean id="Food" class="spring01.Food" scope="prototype">
<property name="person" ref="Person"></property>
</bean>
main
public static void main(String[] args) {
ClassPathXmlApplicationContext beans =
new ClassPathXmlApplicationContext("applicationContext.xml");
Food Food = beans.getBean("Food", Food.class);
}
//报错:
//Requested bean is currently in creation: Is there an unresolvable circular reference?
更改其中一个bean的scope为singleton
<bean id="Person" class="spring01.Person" scope="prototype">
<property name="pet" ref="Pet"></property>
</bean>
<bean id="Pet" class="spring01.Pet" scope="singleton">
<property name="food" ref="Food"></property>
</bean>
<bean id="Food" class="spring01.Food" scope="prototype">
<property name="person" ref="Person"></property>
</bean>
//main不变,不会报错。
总结:
1.在spring中原型bean的循环引用是被禁止的,目的是防止内存溢出。
2.如果其中一个bean不是原型,就不会出错。
3.prototype修饰的类在初始化前会先做检查,检查是否有引用,有先去初始化引用,发现是循环引用的话就会报异常。而singleton是不管有没引用,先初始化对象再说。
4.singleton可能带来的问题,如下图,如果多个线程同时修改同一个controller对象的num值,就会发生线程安全问题。所以一般我们不会在controller中设置属性,要设置都要使用线程安全的类,如Threadlocal、Collections.synchronizedXXX()等等,即便这样也是会牺牲一部分效率的。
5.prototype一般使用在web应用的entity类中,比如一个用户User类,它表示一个登陆用户的信息,每个用户的信息都是不同的,如果使用singleton是不合理的。
补充说明:scope对象作用域,有六个值:singleton(单例),prototype(原型)websocket(长连接),request(单次请求)、session(会话期),application(应用程序范围)。前面两个表示实例是否是单例,后面表示的是对象的生命周期,他们都是单例的。
4)静态和实例工厂
applicationContext.xml
<!-- 静态工厂注入:意思是类.方法().方法是静态的 -->
<bean id="factory" class="spring01.CarFactory" factory-method="getCar">
<!-- 表示方法的参数:type是参数类型(能没有),name是参数名(能没有),index表示参数索引(能没有),value是参数值,不能没有。只能用<constructor-arg>标签,不能用property标签 -->
<constructor-arg type="java.lang.String" name="price" value="BMW"></constructor-arg>
<constructor-arg type="java.lang.String" name="name" value="20w"></constructor-arg>
</bean>
<!-- 实例工厂注入:通过实例化方法得到bean -->
<bean id="factory2" class="spring01.CarFactory2" >
</bean>
<bean id="car" factory-bean="factory2" factory-method="getCar2">
<!-- 表示方法的参数:type是参数类型(能没有),name是参数名(能没有),index表示参数索引(能没有),value是参数值,不能没有。只能用<constructor-arg>标签,不能用property标签 -->
<constructor-arg value="BMW"></constructor-arg>
<constructor-arg value="20w"></constructor-arg>
</bean>
CarFactory
public class CarFactory {
public static Car getCar(String name,String price) {
Car car;
if(name.equals("BMW")) {
car = new BMW(name,price);
}else if(name.equals("BenZ")){
car = new BenZ(name,price);
}else {
car = new Audi(name,price);
}
return car;
}
}
CarFactory2
public class CarFactory2 {
public Car getCar2(String name,String price) {
Car car;
if(name.equals("BMW")) {
car = new BMW(name,price);
}else {
car = new Audi(name,price);
}
return car;
}
}
main
public static void main(String[] args) {
ClassPathXmlApplicationContext beans =
new ClassPathXmlApplicationContext("applicationContext.xml");
//实例
CarFactory2 f2 = beans.getBean("factory2", CarFactory2.class);
System.out.println(f2.getCar2("Aodi", "110w"));
//静态
Car car = CarFactory.getCar("BenZ","30W");
System.out.println(car);
}
静态工厂和实例工厂是对java中工厂模式的一种实现。
5)自动装配autowire
autowire自动装配:意思是给属性自动初始化值。包含的配置值有:default、no、byName、byType、constructor。
default:默认是no,不自动装配。 no:不自动装配。
byName:按类注册的bean标签上的id和name查找装配。id和name的值要和类的属性名称一致。
byType:按类注册的bean标签上class的属性值查找装配。 constructor:按构造方法装配。
applicationcontext.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"
default-autowire="byType"><!-- 全局自动装配 -->
<!-- 给已有的bean起别名 -->
<alias name="Person" alias="Person1"/>
<!-- autowire自动装配:意思是给属性自动初始化值。包含的配置值有:default、no、byName、byType、constructor。
default:默认是no,不自动装配。
no:不自动装配。
byName:按类注册的bean标签上的id和name查找装配。id和name的值要和类的属性名称一致。
byType:按类注册的bean标签上class的属性值查找装配。
constructor:按构造方法装配。 -->
<bean id="Person" class="com.wangle.Person" autowire="byName">
</bean>
<bean id="pet" class="com.wangle.Pet">
<property name="name" value="cat"></property>
</bean>
<bean id="pet2" class="com.wangle.Pet">
<property name="name" value="pig"></property>
</bean>
<!-- 自动装配对java自带的引用类型不具备装配功能,采用默认值null -->
<bean id="name" class="java.lang.String">
<constructor-arg value="hh1"></constructor-arg>
</bean>
<bean id="name2" class="java.lang.String">
<constructor-arg value="hh2"></constructor-arg>
</bean>
</beans>
Person类
public class Person {
private int age;
//bytype autowired String reference same . NO work
private String name;
private String name2;
private boolean sex;
//byType autowired references are same。throw Exception
private Pet pet;
private Pet pet2;
private int[] array;
private char cha;
...省略getter、setter
}
main
public static void main(String[] args) {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
Person person = context.getBean("Person1", Person.class);
System.out.println(ToStringBuilder.reflectionToString(person));
}
//打印结果:
//com.wangle.Person@13e39c73[age=0,name=<null>,name2=<null>,sex=false,pet=cat,pet2=pig,array=<null>,cha= ]
总结:
1.按名字自动装配是精确查找,按类型查找可能出现发现多个匹配的异常,原因在于在bean容器中配置有多个相同类型的bean。因为这个原因,如果在类中有多个非自带的引用类型相同的属性,就必须使用byName。
2.自动装配如果不赋初始值,默认值和java类加载机制相同使用的都是默认值,如整型为0,char为/u0000空格,boolean为false,String为null。其中String等自带引用类型即使是在bean中带初始值注入都会为null。
3.在beans标签中有个属性default-autowire=“byType”,是全局的自动装配。但是它的优先级比bean上配置要低。
6)空值注入
//表示空串
<value></value>
//表示null
<null></null>
3.注解注册和注入
1)注册:@component + 类,还需要一个配置才起作用,context:component-scan标签
<!-- 扫描base-package及子包下的类,管理bean的周期。@component只是标记,并不是真正的注册到bean容器,不配置扫描,是不行的。 -->
<context:component-scan base-package="com.wangle"></context:component-scan>
2)注入:@Autowired + 引用类型属性 ,默认是byType自动装配,注解不像xml,这里是不需要getter,setter的。
具体例子如下:
@Component("person")//可以设定name
@Scope("prototype")//对象的作用域
public class Person {
@Value("1")
private int id;
@Value("zhangsan2")
private String name;
@Autowired
private Food food;
}
@Component
public class Food {
}
main
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
Person p1 = context.getBean("person", Person.class);
Person p2 = context.getBean("person", Person.class);
System.out.println(p1);
System.out.println(p2);//
//打印结果:
Person@3c9754d8[id=1,name=zhangsan2,food=com.wangle.entity.Food@3fc2959f]
Person@3bf7ca37[id=1,name=zhangsan2,food=com.wangle.entity.Food@3fc2959f]
3)指定@Qualifier(“具体类的id”),当一个接口又多个实现,autowired默认是按byType查找,有多个实现就会报异常,这是需要指明具体实现。
@Component
public class MainService {
@Autowired
@Qualifier("oracle")//指定具体实现的类
private MainDao dao;
}
@Component("mysql")
public class MysqlDaoImpl implements MainDao{
}
@Component("oracle")
public class OracleDaoImpl implements MainDao{
}