目录
1. 初识Spring
1.1 什么是Spring
Spring是一个开源框架,它由Rod Johnson创建。
Spring框架是由于软件开发的复杂性而创建的。Spring使用的是基本的JavaBean来完成以前只可能由EJB完成的事情。然而,Spring的用途不仅仅限于服务器端的开发。从简单性、可测试性和松耦合性角度而言,绝大部分Java应用都可以从Spring中受益。
◆目的:解决企业应用开发的复杂性
◆功能:使用基本的JavaBean代替EJB,并提供了更多的企业应用功能
◆范围:任何Java应用
Spring是一个轻量级控制反转(IoC)和面向切面(AOP)的容器框架。
Spring给复杂的J2EE开发带来了春天。它的核心是轻量级的IoC容器,它的目标是为J2EE应用提供了全方位的整合框架,在Spring框架下实现多个子框架的组合,这些子框架之间可以彼此独立,也可以使用其它的框架方案加以代替,Spring希望为企业应用提供一站式(one-stopshop)的解决方案
1.2 Spring采取的4种关键策略
- 基于POJO的轻量级和最小入侵性编程
- 通过依赖注入和面向接口实现松耦合
- 基于切面和惯例进行声明式编程
- 通过切面和模板减少样板式代码
1.3 Spring 核心技术
控制反转(IoC:Inversion of Control ) /依赖注入(DI:Dependency Injection )
面向切面编程(AOP:Aspect Oriented Programming)
1.4 Spring的优点
- 低侵入式设计
- 独立于各种应用服务器
- 依赖注入特性将组件关系透明化,降低了耦合度
- 面向切面编程特性允许将通用任务进行集中式处理
- 与第三方框架的良好整合
补充理解:理论上spring是提高了应用的运行效率的,一般Ioc容器是启动时根据依赖关系将所有的普通对象全部创建出来并放在容器中,是常驻内存的,除非配置为启动时不创建对象,所以IoC的缺点应该是对内存的要求比较高,并且在启动时比较慢而已,但是一旦启动完成,几乎所有用到的对象都已经创建完毕,使用时直接从容器中拿就是了,效率非常高。
2.控制反转/依赖注入
当应用了IoC时,一个对象依赖的其它对象会通过被动的方式传递进来,而不是这个对象自己创建或者查找依赖对象。
- 优点:解耦
- 组件化的思想:分离关注点,使用接口,不再关注实现
- 依赖的注入:将组件的构建和使用分开
2.1 Ioc容器
Spring 容器是 Spring 框架的核心。容器将创建对象,把它们连接在一起,配置它们,并管理他们的整个生命周期从创建到销毁。
IOC 容器具有依赖注入功能的容器,它可以创建对象,IOC 容器负责实例化、定位、配置应用程序中的对象及建立这些对象间的依赖。
2.1.1 BeanFactory 容器
这是一个最简单的容器,它主要的功能是为依赖注入 (DI) 提供支持
XmlBeanFactory factory = new XmlBeanFactory(new ClassPathResource("Beans.xml"));
2.1.2 ApplicationContext 容器
Application Context 是 BeanFactory 的子接口,也被成为 Spring 上下文。
Application Context 是 spring 中较高级的容器。和 BeanFactory 类似,它可以加载配置文件中定义的 bean,将所有的 bean 集中在一起,当有请求的时候分配 bean。 另外,它增加了企业所需要的功能,比如,从属性文件中解析文本信息和将事件传递给所指定的监听器。
-
FileSystemXmlApplicationContext:该容器从 XML 文件中加载已被定义的 bean。在这里,你需要提供给构造器 XML 文件的完整路径。
-
ClassPathXmlApplicationContext:该容器从 XML 文件中加载已被定义的 bean。在这里,你不需要提供 XML 文件的完整路径,只需正确配置 CLASSPATH 环境变量即可,因为,容器会从 CLASSPATH 中搜索 bean 配置文件。
- WebXmlApplicationContext:该容器会在一个 web 应用程序的范围内加载在 XML 文件中已被定义的 bean。
//加载applicationContext.xml文件到ApplicationContext容器
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
2.2 Bean
被称作 bean 的对象是构成应用程序的支柱也是由 Spring IoC 容器管理的。bean 是一个被实例化,组装,并通过 Spring IoC 容器所管理的对象。
2.2.1 Java Bean
每一个类实现了Bean的规范才可以由Spring来接管,那么Bean的规范是什么呢?
- 必须是个公有(public)类
- 有无参构造函数
- 用公共方法暴露内部成员属性(getter,setter)
实现这样规范的类,被称为Java Bean。即是一种可重用的组件
2.2.2 Bean与Spring
2.2.3 使用XML 的配置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-3.0.xsd">
<!-- 简单bean -->
<bean id="..." class="..."></bean>
<!-- 延迟初始化 -->
<bean id="..." class="..." lazy-init="true"></bean>
<!-- 初始化方法 -->
<bean id="..." class="..." init-method="..."> </bean>
<!-- 销毁方法 -->
<bean id="..." class="..." destroy-method="..."></bean>
</beans>
2.2.4 Bean 的作用域
作用域 | 描述 |
---|---|
singleton | 在spring IoC容器仅存在一个Bean实例,Bean以单例方式存在,默认值 |
prototype | 每次从容器中调用Bean时,都返回一个新的实例,即每次调用getBean()时,相当于执行new XxxBean() |
request | 每次HTTP请求都会创建一个新的Bean,该作用域仅适用于WebApplicationContext环境 |
session | 同一个HTTP Session共享一个Bean,不同Session使用不同的Bean,仅适用于WebApplicationContext环境 |
global-session | 一般用于Portlet应用环境,该运用域仅适用于WebApplicationContext环境 |
作用域的配置
<!-- 设置作用域的属性为singleton -->
<bean id="..." class="..." scope="singleton">
<!-- collaborators and configuration for this bean go here -->
</bean>
2.3 Spring 依赖注入
Spring框架的核心功能之一就是通过依赖注入的方式来管理Bean之间的依赖关系。
每个基于应用程序的 java 都有几个对象,这些对象一起工作来呈现出终端用户所看到的工作的应用程序。当编写一个复杂的 Java 应用程序时,应用程序类应该尽可能独立于其他 Java 类来增加这些类重用的可能性,并且在做单元测试时,测试独立于其他类的独立性。依赖注入(或有时称为布线)有助于把这些类粘合在一起,同时保持他们独立。
2.3.1 属性注入方式
标准属性注入
<!-- 属性注入 -->
<bean id="spellChecker" class="com.cs.SpellChecker">
<property name="message1" value="Hello India!"/>
</bean>
<!-- bean引用属性注入 -->
<bean id="textEditor" class="com.tutorialspoint.TextEditor">
<property name="spellChecker" ref="spellChecker"/>
</bean>
p-namespace 方式注入
beans增加:xmlns:p="http://www.springframework.org/schema/p"
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">
<bean name="jane" class="com.example.Person"
p:name="John Doe"/>
</bean>
<!-- 注:**-ref 部分表明这不是一个直接的值,而是对另一个 bean 的引用。-->
<bean id="john-classic" class="com.example.Person"
p:name="John Doe"
p:spouse-ref="jane"/>
</bean>
</beans>
2.3.2 构造注入方式
<!-- 指定构造函数参数类型构造注入 -->
<bean id="exampleBean" class="com.cs.ExampleBean">
<constructor-arg type="int" value="2001"/>
<constructor-arg type="java.lang.String" value="Zara"/>
</bean>
<!-- bean引用构造注入 -->
<bean id="textEditor" class="com.cs.TextEditor">
<constructor-arg ref="exampleBean"/>
</bean>
<!-- 指定构造函数参数的索引构造注入 -->
<bean id="exampleBean" class="com.cs.ExampleBean">
<constructor-arg index="0" value="2001"/>
<constructor-arg index="1" value="Zara"/>
</bean>
2.3.3 内部bean
内部bean是在其他 bean 的范围内定义的 bean
<bean id="outerBean" class="...">
<property name="target">
<bean id="innerBean" class="..."/>
</property>
</bean>
2.3.4 集合的注入
集合类型,如数组、List、Set、Map 等皆可通过配置文件进行设置(数组既可以使用 array 配置项,也可以使用 list 配置项)
元素 | 描述 |
---|---|
<list> | 它有助于连线,如注入一列值,允许重复。 |
<set> | 它有助于连线一组值,但不能重复。 |
<map> | 它可以用来注入名称-值对的集合,其中名称和值可以是任何类型。 |
<props> | 它可以用来注入名称-值对的集合,其中名称和值都是字符串类型。 |
<bean id="coll" class="com.yy.entity.MyCollection">
<bean id="address1" class="...">
<property name="list">
<list>
<value>first</value>
<value>second</value>
<value>third</value>
<value>first</value>
<!-- list里的Bean 引用 -->
<ref bean="address1"/>
</list>
</property>
<property name="set">
<set>
<value>first</value>
<value>second</value>
<value>third</value>
<value>first</value>
<!-- set里的Bean 引用 -->
<ref bean="address1"/>
</set>
</property>
<property name="map">
<map>
<entry key="f1" value="first" />
<entry key="f2" value="second" />
<entry key="f3" value="third" />
<!-- Map里的Bean 引用 -->
<entry key="f3" value-ref="address1" />
</map>
</property>
<property name="addressProp">
<props>
<prop key="one">INDIA</prop>
<prop key="two">Pakistan</prop>
<prop key="three">USA</prop>
<prop key="four">USA</prop>
</props>
</property>
<property name="arrs">
<array>
<value>first</value><value>second</value>
<value>third</value><value>first</value>
</array>
</property>
</bean>
2.3.5 注入 null 和空字符串的值
<!-- 空字符串值的注入 -->
<bean id="..." class="exampleBean">
<property name="email" value=""/>
</bean>
<!-- null值的注入 -->
<bean id="..." class="exampleBean">
<property name="email"><null/></property>
</bean>
2.4 Beans 自动装配
Spring 容器可以在不使用<constructor-arg>
和<property>
元素的情况下自动装配相互协作的 bean 之间的关系,这有助于减少编写一个大的基于 Spring 的应用程序的 XML 配置的数量。
你可以使用<bean>
元素的 autowire 属性为一个 bean 定义指定自动装配模式。
模式 | 描述 |
---|---|
no | 这是默认的设置,它意味着没有自动装配。 |
byName | 由属性名自动装配。 |
byType | 由属性数据类型自动装配。 |
constructor | 类似于 byType,但该类型适用于构造函数参数类型。 |
autodetect | Spring首先尝试通过 constructor 使用自动装配来连接,如果它不执行,Spring 尝试通过 byType 来自动装配。 |
可以使用 byType 或者 constructor 自动装配模式来连接数组和其他类型的集合。
2.4.1 自动装配的局限性
自动装配可以显著减少需要指定的属性或构造器参数,但你应该在使用它们之前考虑到自动装配的局限性和缺点。
限制 | 描述 |
---|---|
重写的可能性 | 你可以总是需要重写自动装配的 <constructor-arg>和 <property> 设置来重新指定依赖关系。 |
原始数据类型 | 你不能自动装配简单类型,包括基本类型,字符串和类。 |
混乱的本质 | 自动装配不如显式装配精确,所以如果可能的话尽可能使用显式装配。 |
2.5 使用注解实现注入
从 Spring 2.5 开始就可以使用注解来配置依赖注入。
使用注解方式能更快速方便实现注入,但是不能完全代替。
一般实际中是两者结合使用的,配置文件方式创建对象,注解方式注入属性
你想在 Spring 应用程序中使用的注解,想要添加如下配置。
<context:component-scan base-package="cn.kgc.service"/>
2.5.1 组件类注解
@Component :标准一个普通的spring Bean类。
@Repository:标注一个DAO组件类。
@Service:标注一个业务逻辑组件类。
@Controller:标注一个控制器组件类。
这些都是注解在平时的开发过程中出镜率极高,@Component、@Repository、@Service、@Controller实质上属于同一类注解,用法相同,功能相同,区别在于标识组件的类型。
指定了某些类可作为Spring Bean类使用后,最好还需要让spring搜索指定路径,在Spring配置文件加入如下配置:
<!-- 自动扫描指定包及其子包下的所有Bean类 -->
<context:component-scan base-package="com.test.*"/>
@Component
@Component可以代替@Repository、@Service、@Controller,因为这三个注解是被@Component标注的。代码如下
@Repository
当一个组件代表数据访问层(DAO)的时候,我们使用@Repository进行注解
@Repository
public class TestDaoImpl implements TestDao{
public void test(){
//...
}
}
@Service
当一个组件代表业务层时,我们使用@Service进行注解
@Service(value="modeTestService")
//使用@Service注解不加value ,默认名称是testService
public class TestServiceImpl implements TestService {
@Autowired//下面进行讲解
private TestDao testDao;
public void test(){
//...
}
}
@Controller
当一个组件作为前端交互的控制层,使用@Controller进行注解
@Controller
public class HappyController {
@Autowired //下面进行讲解
private TestService testService;
}
注:
- 被注解的java类当做Bean实例,Bean实例的名称默认是Bean类的首字母小写,其他部分不变。如果不想使用默认名称,可以使用value属性重新命名Bean实例的名称,但是必须是唯一的!实例:参考@Service的实例。
- 尽量使用对应组件注解的类替换@Component注解,在spring未来的版本中,@Controller,@Service,@Repository会携带更多语义。并且便于开发和维护!
2.5.2 装配bean时常用的注解
@Autowired:属于Spring 的org.springframework.beans.factory.annotation包下,可用于为类的属性、构造器、方法进行注值
@Qualifier :可能会有这样一种情况,当你创建多个具有相同类型的 bean 时,并且想要用一个属性只为它们其中的一个进行装配,在这种情况下,你可以使用 @Qualifier 注释和 @Autowired 注释通过指定哪一个真正的 bean 将会被装配来消除混乱。
@Resource:不属于spring的注解,而是来自于JSR-250位于java.annotation包下,使用该annotation为目标bean指定协作者Bean。
@PostConstruct 和 @PreDestroy 方法 实现初始化和销毁bean之前进行的操作
使用以上注解时也要注意添加配置文件到Spring,如果没有配置component-scan
<context:component-scan>
<!--<context:component-scan>的使用,是默认激活<context:annotation-config>功能-->
则一定要配置 annotation-config
<context:annotation-config/>
@Autowired 注解与@Qualifier 注解
默认情况下,其依赖的对象必须存在(bean可用),如果需要改变这种默认方式,可以设置其required属性为false。
@Autowired注解默认按照类型装配,如果容器中包含多个同一类型的Bean,那么启动容器时会报找不到指定类型bean的异常,解决办法是结合@Qualified注解进行限定,指定注入的bean名称。
应用于属性
@Autowired
private SpellChecker spellChecker;
应用于属性的setter 方法
private SpellChecker spellChecker;
@Autowired
public void setSpellChecker( SpellChecker spellChecker ){
this.spellChecker = spellChecker;
}
public SpellChecker getSpellChecker( ) {
return spellChecker;
}
@Resource
推荐使用@Resource注解在字段上,这样就不用写setter方法了.并且这个注解是属于J2EE的,减少了与Spring的耦合,这样代码看起就比较优雅 。
public class AnotationExp {
@Resource(name = "HappyClient")
private HappyClient happyClient;
@Resource(type = HappyPlayAno .class)
private HappyPlayAno happyPlayAno;
}
装配顺序:
- 如果同时指定了name和type,则从Spring上下文中找到唯一匹配的bean进行装配,找不到则抛出异常
- 如果指定了name,则从上下文中查找名称(id)匹配的bean进行装配,找不到则抛出异常
- 如果指定了type,则从上下文中找到类型匹配的唯一bean进行装配,找不到或者找到多个,都会抛出异常
- 如果既没有指定name,又没有指定type,则自动按照byName方式进行装配;如果没有匹配,则回退为一个原始类型进行匹配,如果匹配则自动装配;
@Autowired 注解与@Resource注解的对比
@Autowired | @Resource | |
提供方 | @Autowired是Spring的注解 | @Resource是javax.annotation注解,而是来自于JSR-250,J2EE提供,需要JDK1.6及以上。 |
注入方式 | @Autowired只按照Type 注入; | @Resource默认按Name自动注入,也提供按照Type 注入; |
标注位置 | @Autowired可以标注在类的属性、构造器、方法进行注值。 | @Resource可以标注在字段或属性的setter方法上。 |
属性设置 | required:设置其依赖的对象是否必须存在(bean可用)默认:true | name:按名称寻找依赖对象 type:按类型寻找依赖对象 |
使用性 | @Autowired注解不适用装配的bean类型有多个的情况,需要与@Qualitied配合使用。 | 更为灵活,可指定名称,也可以指定类型 ; |
@PostConstruct 和 @PreDestroy
@PostConstruct 和 @PreDestroy 方法 实现初始化和销毁bean之前进行的操作
@PostConstruct
public void init(){
System.out.println("Bean is going through init.");
}
@PreDestroy
public void destroy(){
System.out.println("Bean will destroy now.");
}
2.6 使用Java 创建bean
public class Foo {
public void init() {
// initialization logic
}
public void cleanup() {
// destruction logic
}
}
@Configuration
@Scope("prototype") //指定作用域
public class HelloWorldConfig {
//@Bean 注解的方法名称作为 bean 的 ID(指定任意的初始化和销毁的回调方法)
@Bean(initMethod = "init", destroyMethod = "cleanup")
public HelloWorld helloWorld(){
return new HelloWorld();
}
}
Spring 中新的 Java 配置支持的核心就是@Configuration 注解的类。这些类主要包括 @Bean 注解的方法来为 Spring 的 IoC 容器管理的对象定义实例,配置和初始化逻辑。
使用@Configuration 来注解类表示类可以被 Spring 的 IoC 容器所使用,作为 bean 定义的资源。
Bean注解主要用于方法上,有点类似于工厂方法,当使用了@Bean注解,我们可以连续使用多种定义bean时用到的注解,譬如用@Qualifier注解定义工厂方法的名称,用@Scope注解定义该bean的作用域范围,譬如是singleton还是prototype等。
使用@Component替代 @Configuration注解
@Component
public static class Config {
@Autowired
SimpleBean simpleBean;
@Bean
public SimpleBean simpleBean() {
return new SimpleBean();
}
@Bean
public SimpleBeanConsumer simpleBeanConsumer() {
return new SimpleBeanConsumer(simpleBean);
}
}
3 面向切面(AOP)
3.1 初识AOP
什么是面向切面
所谓面向切面编程,是一种通过预编译和运行期动态代理的方式实现在不修改源代码的情况下给程序动态添加功能的技术
AOP的目标
让我们可以“专心做事”
AOP原理
- 将复杂的需求分解出不同方面,将散布在系统中的公共功能集中解决
- 采用代理机制组装起来运行,在不改变原程序的基础上对代码段进行增强处理,增加新的功能
AOP相关术语
项 | 描述 |
---|---|
Aspect | 切面 |
Join point | 连接点 |
Advice | 增强处理:分为前置增强、后置增强、环绕增强、异常抛出增强、最终增强等类型 |
Pointcut | 切入点 |
Introduction | 引用允许你添加新方法或属性到现有的类中。 |
Target object | 目标对象 |
Weaving | 织入 |
AOP proxy | AOP代理 |
常用增强处理类型
增强处理类型 | 特 点 |
Before | 前置增强处理,在目标方法前织入增强处理 |
AfterReturning | 后置增强处理,在目标方法正常执行(不出现异常)后织入增强处理 |
AfterThrowing | 异常增强处理,在目标方法抛出异常后织入增强处理 |
After | 最终增强处理,不论方法是否抛出异常,都会在目标方法最后织入增强处理 |
Around | 环绕增强处理,在目标方法的前后都可以织入增强处理 |
3.2 Spring AOP实现步骤
在项目中添加Spring AOP的jar文件
- spring-aop-4.2.4.RELEASE.jar
- aopalliance-1.0.jar
- aspectjweaver-1.6.9.jar
- cglib-nodep-2.1.3.jar(已内联在Spring core中)
Spring配置文件中导入aop包
xml配置
<aop:config>
<!-- 声明一个aspect -->
<!-- 引入包含增强方法的Bean 通过ref来引入-->
<aop:aspect id="myAspect" ref="aBean">
<!-- 定义切入点
id:唯一的标识
expression="execution(* com.xyz.myapp.service.*.*(..))":切入点的规则
-->
<aop:pointcut id="businessService"
expression="execution(* com.xyz.myapp.service.*.*(..))"/>
<!-- 前置增强 -->
<aop:before pointcut-ref="businessService"
method="doRequiredTask"/>
<!-- 最终增强 -->
<aop:after pointcut-ref="businessService"
method="doRequiredTask"/>
<!-- 后置增强 -->
<aop:after-returning pointcut-ref="businessService"
returning="retVal"
method="doRequiredTask"/>
<!-- 异常抛出增强 -->
<aop:after-throwing pointcut-ref="businessService"
throwing="ex"
method="doRequiredTask"/>
<!-- 环绕增强 -->
<aop:around pointcut-ref="businessService"
method="doRequiredTask"/>
...
</aop:aspect>
</aop:config>
<bean id="aBean" class="...">
...
</bean>
//增强处理类
public class TestLogger {
//前置增强
public void before(){
System.out.println("我在业务方法之前被调用");
}
//后置增强
public void afterr(){
System.out.println("我在业务方法之后被调用");
}
}
使用注解替代xml配置
如果项目采用JDK 5.0或以上版本,可以考虑使用@AspectJ注解方式,减少配置的工作量
如果不愿意使用注解或项目采用的JDK版本较低无法使用注解,则可以选择使用<aop:aspect>配合普通JavaBean的形式
- 使用注解方式定义切面可以简化配置工作量
- 常用注解有@Aspect、@Before、@AfterReturning、@Around、@AfterThrowing、@After等
- 在配置文件中添加<aop:aspectj-autoproxy />元素,启用对于@AspectJ注解的支持
AOP配置元素 | 描 述 |
<aop:config> | AOP配置的顶层元素,大多数的<aop:*>元素必须包含在<aop:config>元素内 |
<aop:pointcut> | 定义切点 |
<aop:aspect> | 定义切面 |
<aop:after> | 定义最终增强(不管被通知的方法是否执行成功) |
<aop:after-returning> | 定义after-returning增强 |
<aop:after-throwing> | 定义after-throwing增强 |
<aop:around> | 定义环绕增强 |
<aop:before> | 定义前置增强 |
<aop:aspectj-autoproxy> | 启动@AspectJ注解驱动的切面 |
//增强处理类
@Aspect //定义切面的,意义就等于在配置文件里添加<aop:aspect ref="the">
public class UserServiceLogger {
private static final Logger log=Logger.getLogger(UserServiceLogger.class);
@Pointcut("execution(* cn.kgc.service..*.*(..))")
public void pointcut(){}
//异常抛出增强
@AfterThrowing(pointcut="execution(* cn.kgc.service..*.*(..))",throwing="e")
public void afterThrowing(RuntimeException e){
System.out.println(e.getMessage()+"sss");
}
//前置增强
@Before("pointcut()")
public void before(){
System.out.println("我在业务方法之前被调用");
}
//后置增强
@AfterReturning("pointcut()")
public void afterr(){
System.out.println("我在业务方法之后被调用");
}
@After("pointcut()")
public void afterLogger(){
System.out.println("关闭资源");
}
}