一、Spring概述与环境搭建
Spring是一个轻量级控制反转(IoC)和面向切面(AOP)的容器框架。
Spring 是最受欢迎的企业级 Java 应用程序开发框架,数以百万的来自世界各地的开发人员使用 Spring 框架来创建性能好、易于测试、可重用的代码。
Spring 框架是一个开源的 Java 平台,它最初是由 Rod Johnson 编写的,并且于 2003 年 6 月首次在 Apache 2.0 许可下发布。
Spring环境搭建
Spring5至少要JDK8,Servlet3.1,Tomcat8.5+。请在实际开发时针对自己的 开发环境,选择Spring版本,Spring4支持JDK6/7/8。
创建工程
配置pom.xml引入Spring依赖包
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.1.3.RELEASE</version>
</dependency>
</dependencies>
测试是否正常运行
创建一个类
package pojo;
public class Master {
public void sayHello(){
System.out.println("hello world"); }
}
配置spring.xml
配置文件
<bean id="master" class="pojo.Master"></bean>
创建一个测试类
@Test
public void testSpring() {
ApplicationContext context= new ClassPathXmlApplicationContext("spring.xml");
Master master = (Master)context.getBean("master"); master.sayHello();
}
//运行结果
//hello world
运行结果成功表示环境配置成功
集成Junit
<!-- JUnit单元测试框架 -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
<!--集成Junit-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.1.3.RELEASE</version>
</dependency>
测试类代码
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunne r;
import pojo.TestBean;
import javax.annotation.Resource;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration({"classpath:spring.xml"})
public class TestUnit {
@Resource(name = "testBean")
private TestBean p;
@Test
public void testCreatePerson() {
System.out.println(p);
}
}
二、控制反转IoC
在传统的方式中,如果我们要用到一个类的对象,需要自己new一个出 来,如:
@Test
public void testMaster() {
Master master = new Master();
master.sayHello();
}
Master的对象master在testMaster方法中创建,这时master对象的控制权是属于testMaster的。而在Spring中,我们看不到new Master()的操作。而是通过Spring的ApplicationContext获得:
ApplicationContext context= new ClassPathXmlApplicationContext("spring.xml");
//不是自己new的对象,而是从ApplicationContext中获得 Master master = (Master) context.getBean("master");
其中:
context.getBean("master")
getBean方法的参数值master,对应的是spring.xml中的bean的id:
<bean id="master" class="pojo.Master"></bean>
可见master对象是由Spring创建的,其控制权属于Sping而不属于使用者。 这种设计叫做“控制反转(Inversion of Control,英文缩写为IoC)”。
为什么要控制反转
IoC把对象生成放在了XML里定义,所以当我们需要换一个实现子类将会变成很简单(一般这样的对象都是实现于某种接口的),只要修改XML就可以了。
IoC的缺点是,生成一个对象的步骤变复杂了,对于不习惯这种方式的人, 会觉得有些别扭和不直观。对象生成因为是使用反射编程,在效率上有些 损耗。但相对于IoC提高的维护性和灵活性来说,这点损耗是微不足道的, 除非某对象的生成对效率要求特别高。
三、多种方式实现依赖注入
1、属性注入
属性注入是通过属性的setter方法进行注入,需要注入的属性必须有setter 方法且命名必须符合规范。如属性名是abc,那么setter方法应为setAbc()
基本数据类型
<property name="属性名" value="基本类型的属性值"/>
示例:
<bean id="master" class="pojo.Master">
<property name="name" value="张三"/>
</bean>
注入对象类型
需要先创建对象bean
<property name="属性名" ref="对象的bean的id"/>
示例:
<bean id="master" class="pojo.Master">
<property name="pet" ref="pet"/>
</bean>
注入集合和数组类型
list和数组注入方式相同
<!--给数组或者List赋值-->
<property name="stringList">
<list>
<!--普通类型-->
<value>abc</value>
<value>efg</value>
<!--注入对象类型-->
<!--<ref bean="其他bean的id"/>-->
</list>
</property>
<!--注入Set类型-->
<property name="stringSet">
<set>
<!--普通类型-->
<value>abc</value>
<value>abc2</value>
<!--注入对象类型-->
<!--<ref bean="其他bean的id"/>-->
</set>
</property>
<!--注入Map-->
<property name="objectMap">
<map>
<!--注入普通类型-->
<entry key="a" value="aa"/>
<!--注入对象类型-->
<!--<entry key="b" value-ref="其它bean的id"/>-->
</map>
</property>
2、构造方法注入
Spring创建对象默认使用的是类的无参构造方法。所以在某个类添加带参构造函数的时候,记得将无参构造写出来,否则会导致某些情况下Spring无法创建对象。
按类型注入
constructor-arg
标签用于给构造方法注入参数,type
是参数类型,value
是参数值。
对于有特殊字符的属性,在value子节点中只用CDATA。如示例中,注入的 String类型参数值为“”,其中<>是xml中的节点符号,属于特殊字符。
注入对象类型,使用ref引用其他bean的id。
<constructor-arg type="属性类型" value="基本类型属性值"/>
<constructor-arg type="属性类型" ref="对象的bean的id"/>
示例:
<bean id="petBean" class="pojo.Cat"></bean>
<bean id="master" class="pojo.Master">
<!--注入基本数据类型-->
<constructor-arg type="java.lang.Integer" value="20"/> <!-- 对于包含特殊字符的属性值,可以在value子节点使用CDATA -->
<constructor-arg type="java.lang.String">
<value><![CDATA[<sansan>]]></value>
</constructor-arg>
<!--ref用于注入对象类型-->
<constructor-arg type="pojo.Pet" ref="petBean"/>
</bean>
按位置注入
如果构造方法里有多个参数的类型是相同的,可以使用按位置注入的方式
constructor-arg 的index属性用于指定构造方法中参数的索引,从0开始。
示例:
<bean id="petBean" class="pojo.Cat"></bean>
<bean id="master" class="pojo.Master">
<constructor-arg value="20" index="1"/>
<constructor-arg value="张三" index="0"/>
<constructor-arg ref="pet" index="2"/>
</bean>
按名称注入(最常用)
<bean id="pet" class="pojo.Cat"></bean>
<bean id="master" class="pojo.Master">
<!--注入基本数据类型-->
<constructor-arg name="age" value="20"/>
<!-- 对于包含特殊字符的属性值,可以在value子节点使用CDATA -->
<constructor-arg name="name">
<value><![CDATA[<sansan>]]></value>
</constructor-arg>
<!--ref用于注入对象类型-->
<constructor-arg name="pet" ref="pet"/>
</bean>
P命名空间注入
P命名空间注入需要先引入头文件:
xmlns:p="http://www.springframework.org/schema/p"
在标签上使用p:属性名=“属性值”进行值注入。使用p:属性名-ref=”bean的id” 进行对象注入。
示例:
<bean class="pojo.Master" id="master5" p:name="筑梦者" p:age="18" p:cat-ref="pet"/>
工厂方法注入
静态工厂注入
工厂bean的class都是工厂类型,但实际的bean的对象是通过factory- method获得的。getCat()和getDog()返回的都是Pet类型的对象。所以 catBean和dogBean都是Pet类型的。
PetShop类:
package pojo;
public class PetShop {
public static Pet getDog() {
return new Dog();
}
public static Pet getCat() {
return new Cat();
}
public static Pet getPet(String name) {
if (name == "cat") {
return new Cat();
} else {
return new Dog();
}
}
配置文件写法
<!--创建工厂bean,调用不带参数的方法-->
<bean id="catBean" class="pojo.PetShop" factory- method="getCat"/>
<!--创建工厂bean,调用带参数的方法-->
<bean id="dogBean" class="pojo.PetShop" factory- method="getPet">
<constructor-arg value="dog"/>
</bean>
<!--可以给master分别注入catBean和dogBean,看运行结果-->
<bean id="master" class="pojo.Master">
<property name="pet" ref="dogBean"/>
</bean>
测试
@Test
public void testFactory() {
ApplicationContext context = new ClassPathXmlApplicationContext("spring.xml");
Master master = (Master) context.getBean("master"); master.getPet().shout();
}
实例工厂注入
实例工厂和静态工厂的区别在于实例工厂必须先初始化工厂对象,工厂中的方法没有static
示例:
package pojo;
public class PetShop {
public Pet getDog() {
return new Dog();
}
public Pet getCat() {
return new Cat();
}
public Pet getPet(String name) {
if (name == "cat") {
return new Cat();
} else {
return new Dog();
}
}
}
配置文件
<bean id="shopBean" class="pojo.PetShop"></bean>
<bean id="petBean" factory-bean="shopBean" factory- method="getCat"></bean>
<bean id="master" class="pojo.Master">
<property name="pet" ref="petBean"/>
</bean>
使用注解
使用注解可以批量生成bean,扫描注解修饰的类。需要引入头文件。
xmlns:context="http://www.springframework.org/schema/context"
//不指定id,默认id是类名首字母小写
@Component("person")
public class Person {
@Qualifier("cat")//注入其它bean
private Pet pet;
//getter/setter方法略
}
@Component
注解修饰一个类,这个类会创建成bean。@Component(“bean 的id”),如果不指定id,默认的id是类名首字母小写。
@Qualifier
注解用于注入其它bean(按bean的id注入)。
@Autowired
注解是按类型注入,默认情况下它要求依赖对象必须存在,如 果允许null值,可以设置它的required属性为false。当有多个类型一样的 bean存在时,会出现异常。
@Resource
注解是java的注解,spring提供了支持。它可以实现按名称注入 和按类型注入。
//@Resource(name = "cat")//按名称注入
@Resource(type = Cat.class)//按类型注入
private Pet pet;
@Qualifier, @Autowired, @Resource
可以用在属性上,也可以放在setter方法 上。
其它注解:
@Controller
分层开发中用于修饰web层的类。
@Service
分层开发时用于修饰service层的类。
@Repository
分层开发时用于修饰dao层的类。
这三个注解都是对于@Component更具体的实现。
@PostConstruct
依赖注入成功后被调用。构造方法->@Autowire
- >@PostConstruct
@PreDestroy
销毁前执行。
Spring4之后新增一系列注解可以代替xml文件。
import org.springframework.context.annotation.*;
import org.springframework.stereotype.Repository;
import org.springframework.stereotype.Service;
import pojo.Cat;
import pojo.Dog;
import pojo.Pet;
@Configuration//相当于代替一个xml文件
@ComponentScan(basePackages = "service",
excludeFilters =
@ComponentScan.Filter(Repository.class),
includeFilters = @ComponentScan.Filter(Service.class), useDefaultFilters = false)
public class AppConfig {
@Bean//默认单例
public Pet cat() {
return new Cat();
}
@Bean
@Scope("prototype")//每次都获得一个新的实例
public Dog dog() {
return new Dog();
}
}
四、Bean的生命周期和作用域
单例Bean的生命周期
1、实例化一个Bean--也就是我们常说的new;
2、按照Spring上下文对实例化的Bean进行配置--也就是IOC注入;
3、如果这个Bean已经实现了BeanNameAware
接口,会调用它实现的 setBeanName(String)
方法,此处传递的就是Spring配置文件中Bean的 id值
4、如果这个Bean已经实现了BeanFactoryAware
接口,会调用它实现的setBeanFactory(setBeanFactory(BeanFactory)
传递的是Spring工厂自身
(可以用这个方式来获取其它Bean,只需在Spring配置文件中配置一 个普通的Bean就可以);
5、 如果这个Bean已经实现了ApplicationContextAware
接口,会调用setApplicationContext(ApplicationContext)
方法,传入Spring上下文
(同样这个方式也可以实现步骤4的内容,但比4更好,因为 ApplicationContext是BeanFactory的子接口,有更多的实现方法);
6、如果这个Bean关联了BeanPostProcessor
接口,将会调用postProcessBeforeInitialization(Object obj, String s)
方法, BeanPostProcessor
经常被用作是Bean内容的更改,并且由于这个是在 Bean初始化结束时调用那个的方法,也可以被应用于内存或缓存技术;
7、 如果Bean在Spring配置文件中配置了init-method
属性会自动调用其配置的初始化方法。
8、如果这个Bean关联了BeanPostProcessor
接口,将会调用 postProcessA
9、当Bean不再需要时,会经过清理阶段,如果Bean实现了DisposableBean
这个接口,会调用那个其实现的destroy()
方法;
10、最后,如果这个Bean的Spring配置中配置了destroy-method
属性,会自动调用其配置的销毁方法。
Bean的作用域
当通过Spring容器创建一个Bean实例时,不仅可以完成Bean实例的实例化,还可以为Bean指定特定的作用域。Spring支持如下5种作用域:
singleton
:单例模式,在整个Spring IoC容器中,使用singleton定义的Bean将只有一个实例
prototype
:原型模式,每次通过容器的getBean方法获取prototype定义的 Bean时,都将产生一个新的Bean实例。Bean完全交给客户端管理,容器不在跟踪生命周期。
request
:对于每次HTTP请求,使用request
定义的Bean都将产生一个新实例,即每次HTTP请求将会产生不同的Bean实例。只有在Web应用中使用 Spring时,该作用域才有效
session
:对于每次HTTP Session,使用session
定义的Bean都将产生一个新实例。同样只有在Web应用中使用Spring时,该作用域才有效
application
:web环境中,一个ServletContext生命周期中的单例对象。
websocket
:WebSocket环境中使用。
其中比较常用的是singleton和prototype两种作用域。对于singleton作用域的Bean,每次请求该Bean都将获得相同的实例。容器负责跟踪Bean实例的状态,负责维护Bean实例的生命周期行为;如果一个Bean被设置成 prototype作用域,程序每次请求该id的Bean,Spring都会新建一个Bean实 例,然后返回给程序。在这种情况下,Spring容器仅仅使用new关键字创建Bean实例,一旦创建成功,容器不在跟踪实例,也不会维护Bean实例的状态。
如果不指定Bean的作用域,Spring默认使用singleton作用域。Java在创建Java实例时,需要进行内存申请;销毁实例时,需要完成垃圾回收,这些工作都会导致系统开销的增加。因此,prototype作用域Bean的创建、销毁代价比较大。而singleton作用域的Bean实例一旦创建成功,可以重复使用。因此,除非必要,否则尽量避免将Bean被设置成prototype作用域。
五、面向切面AOP
核心概念
a)、Aspect
: 切面,一个模块化的关注点,它横跨多个类。事务管理是企业 Java应用程序中横切关注点的一个很好的例子。在Spring AOP中,切面通过使用常规类(基于模式的方法)或通过@Aspect注解修饰的类来实现。
b)、Join point
: 连接点。程序执行过程中的一个点,如在方法上进行统一的异 常处理。SpringAOP中,某个方法执行前、执行后、抛出异常后等一些具有 边界性质的特定点,称作连接点。
c)、Advice
: 增强。在一个特定切入点上采取的动作。
d)、Pointcut
: 切点。某个特定的连接点就是切点。
e)、Introduction
: 引介。引介是一种特殊的增强,它为类添加一些属性和方法. 这样,即使一个业务类原本没有实现某一个接口,通过AOP的引介功能,也可以 动态地为该业务类添加接口的实现逻辑.让业务类成为这个接口的实现类。
f)、Target object
: 目标对象。增强逻辑的织入目标类。如果没有AOP,那么目标 业务类需要自己实现所有逻辑,如果使用AOP可以把一些非逻辑性代码通过 AOP织入到主程序代码上。由于通过使用运行时代理来实现Spring AOP,所 以该对象始终是代理对象。
g)、AOP proxy
: AOP框架为了实现切面增强而创建的对象。在Spring框架中, AOP代理是JDK动态代理或CGLIB代理。
h)、Weaving
: 织入。织入是将增强添加到目标类具体链接点上的过程
Spring中的增强有五种:
1、前置增强(Before
): 在方法执行前调用。
2、后置增强(AfterReturning
): 在方法执行后调用(方法正常结束,没有异
常退出)。
3、异常抛出增强(AfterThrowing
): 在方法抛出异常时调用。。
4、环绕增强(Around
): 在方法执行前后都调用。。
5、最终增强(After
): 在方法执行后执行(不管是正常退出还是异常退出)