Spring

Spring框架是一个分层的Java应用full-stack轻量级开源框架,以IOC和AOP为内核,提供展现层SpringMVC和持久层SpringJDBC以及业务层事务管理等技术。本文详细介绍了Spring的两大核心概念,IOC和AOP,以及Spring的优势、体系结构和在程序解耦中的应用。

概述

spring概述
Spring是分层的Java SE (Standard Edition 常用于软件制作) /EE (Enterprise Edition 常用于网站开发) 应用 full-stack 轻量级开源框架,以IOC(Inverse Of Control 反转控制)和AOP(Aspect Oriented Programming 面向切面编程)为内核。提供了展现层Spring MVC和持久层 Spring JDBC以及业务层事务管理等众多的企业级应用技术。
spring两大核心
IOC(Inverse Of Control 反转控制)和AOP(Aspect Oriented Programming 面向切面编程)
spring的优势
方便解耦,简化开发
AOP编程的支持
声明式事务的支持
方便程序的测试
方便集成各种优秀框架
降低Java EE API 的使用难度
Java源码是经典学习范例
spring的体系结构
core container: 实现IOC,存储对象的容器,依赖于beans, contexts, core, SpEL。
表现层 界面表现,调用业务层
业务层 Service 逻辑计算,调用持久层
持久层 Dao 调用数据库,实现数据的读写。

程序的耦合和解耦

耦合:程序间的依赖关系,包括类之间的依赖和方法间的依赖
解耦:降低程序间的依赖关系。实际开发中应该做到编译器不依赖,运行时才依赖。
解耦的思路:
第一步,在创建对象时使用反射来创建对象,避免使用new关键字。
利用Class.forName(String className)来进行类的装载和实例化可降低耦合性。
第二部,通过读取配置文件来获取要创建的对象全限定类名。
通过工厂模式降低耦合性:
bean在计算机英语中,有可重用组件的意思。
Javabean的范围远大于实体类。
第一步:需要一个配置文件来配置service和dao。
配置的内容:唯一标识=全限定类名(包名+类名)
第二步:通过读取配置文件中配置的内容,反射创建对象。
配置文件的选取:xml或properties
工厂模式存在的问题及改进:
问题:如果用普通工厂模型,每次调用都会创建一个新的对象,形成多例,浪费资源。为了保证对象的单例,防止被垃圾回收,我们可以在工厂中保存一个Map容器,容器中存储着所有的对象。
伪代码:

public class BeanFactory{
	private static Map<String, Object> beans;  // 对象容器
  // 饿汉式实例,在静态代码块中创建容器
  static{
  	while{
  		从配置文件中逐个读取key
    	Object value = Class.forName(key).newInstance(); // 通过key创建相应的value对象
    	beans.put(key, value);
    }
  }
  
  public static Object getBean(String beanName){
  	return beans.get(beanName);
  }
}

IOC概念和Spring中的IOC

IOC(控制反转)的定义:
当我们使用new方法来创建对象时,我们的程序和资源有着直接的依赖关系,程序可以主动查找到所需要的资源。
当我们使用工厂创建对象时,我们的程序只依赖于工厂,工厂再依赖于资源,只能通过工厂获取所需要的资源。
总结起来就是把创建对象的权利交给框架(工厂)。
在不应用IOC的代码中,各个对象创建的时机是由程序中具体的代码决定的,控制反转的意思就是将创建对象的权限从具体的代码手中交到IOC容器手中,由IOC容器统一完成对象的创建和依赖关系。
作用:这样优势是可以减小代码间的耦合,并且可以减少重复代码。
注入的定义:
与IOC密不可分的就是注入的概念。因为IOC的引用,在各个模块代码中不再直接创建对象,而是只声明一个对象,在程序运行时会从IOC容器中根据对象id为各个模块分配对象,这一分配对象的过程就叫注入。
Spring中的IOC:
准备工作:

  1. 在pom.xml 配置文件中添加依赖
<dependency>
	<groupId>org.springframework</groupId>
	<artifactId>spring-context</artifactId>
	<version>${org.springframework.version}</version>
</dependency>
  1. 在resource文件夹下创建IOC所需的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">
  <!--以上即是固定约束-->
  
  <!--将对象的创建交给spring来管理-->
  <bean id"唯一Id" class="全限定类名">
</beans>
  1. 获取spring的IOC核心容器,并根据id获取对象:
    ApplicationContext的三个常用实现类:
    ClassPathXmlApplicationContext 加载类路径下的配置文件,属性为xml文件名
    FileSystemApplicationContext 加载磁盘任意路径下的配置文件,必须有访问权限
    AnnotationConfigApplicationContext 读取注解,创建容器。当我们通过配置类配置容器时使用,属性:配置类.class
// 获取IOC核心容器
ApplicationContext ac = new ClassPathXmlApplicationContext("relative path of xml");
ApplicationContext ac = new FileSystemApplicationContext("absolute path of xml");  // 不常用

// 根据ID获取对象
MyClass mc = (MyClass)ac.getBean("唯一Id");
MyClass mc = ac.getBean("唯一Id", MyClass.class);

ApplicationContext和BeanFactory的区别:
Application采用立即加载的方式,饿汉方式,也就是说在读取完配置文件后马上创建对象。对象在容器构造好之后就全部创建完毕了。
BeanFactory采用延迟加载方式,懒汉方式。在通过Id获取容器中的对象时才会创建对象。
Spring对Bean的管理细节:
1. 创建bean的三种方式:
1. 使用默认构造函数创建:当配置文件使用bean标签,配以id和class属性,且没有其他属性时,使用默认构造函数。
2. 使用其他类中的方法创建bean:在配置文件先创建工厂类=对象,再调用工厂对象创建bean
3. 使用工厂中的静态方法创建对象:在配置文件中创建对象,并在同一行直接调用工厂中的静态方法创建bean

<bean id="classId" class="path.className"></bean>  //第一种方法
<bean id="factoryClassId" class="path.factoryclassname"></bean>
<bean id="classId" fatory-bean="factoryClassId" factory-method="getBeanMethod"></bean>  // 第二种方法
<bean id="staticFactoryID" class="path.staticFactoryName" factory-method="getBeanMethod"></bean>  // 第三种方法

2.bean对象的作用范围
bean标签中的属性:scope——bean的作用范围:
singleton: 单例
prototype: 多例
request: 作用web应用的请求范围
session:作用web应用的会话范围
global-session:全局会话范围
3.bean对象的生命周期
单例对象的生命周期与容器相同
多例对象:使用时创建,当对象长时间不用且没有被别的对象引用时,会被Java垃圾回收。
init-method: 对象创建时调用的方法
destroy-method:对象销毁时调用的方法

依赖注入(Dependency Injection):

依赖即是程序间的耦合,IOC的作用就是降低耦合,也就是降低依赖关系。
在Spring框架中,依赖关系的维护都由Spring框架完成,我们只需要在配置文件中就行说明。
依赖关系的维护就称为依赖注入。可以暂时理解成创建对象时给对象属性赋值。
可以注入的数据类型:
基本类型和String
其他基本bean类型(在配置文件中或者注解中配置过的bean),在配置文件中先定义一个bean对象,再通过ref引用注入。
复杂类型/集合类型 在property标签内部添加标签array,list,set,并添加value标签。在props,map标签内加entry标签。对于list结构,标签可以互换。对于map结构,标签也可以互换。
注入方式:

  1. 使用构造函数提供
    在bean标签内部添加一个标签constructor-arg
    标签中的属性:
    type:注入的数据类型
    index:根据参数位置指定
    name:根据名称指定注入
    value:注入值
    ref:引用关联的bean对象作为注入值
<bean id="user" class="com.sankuai.User">
	<constructor-arg name="name" value="wangbin"></constructor-arg>
	<constructor-arg name="date" ref="now"></constructor-arg>
</bean>

<bean id="now" class="java.util.Date"></bean>
  1. 使用set方法提供
    通过property标签,在bean标签内部。
    标签中的属性:
    name:注入时调用的set方法名称
    value:注入值
    ref:注入的其他对象
<bean id="user" class="com.sankuai.User">
	<property name="setName" value="wangbin"></property>
</bean>
  1. 使用注解提供

IOC中的常用注解

注解配置和xml配置所实现的功能都是一样的。区别只在于具体的实现方式不同。
使用前的准备
在配置文件beans.xml中,加入context命名空间,并告知Spring在启动时需要扫描的注解和扫描范围。

<context:component-scan base-package="需要扫描注解的包名"></context:component-scan>

注解的分类:
用于创建对象,与xml配置文件中bean标签等价
@Component:把当前类创建一个对象并存入容器中。
属性:
value:用于指定当前bean的id,默认为首字母小写的当前类名
@Controller 用于表现层View
@Service 用于业务层Service
@Repository 用于持久层Dao,以上三个注解都是@Component的具体实现,是Spring为三层结构分别提供的。以上注解写在类的定义之上。
用于注入数据,与property标签等价
@Autowired 用于注入的注解
自动按照类的类型注入,只要容器中有唯一的一个bean对象类型和要注入的变量类型匹配,就可以自动注入。
底层原理:Spring IOC容器是一个Map结构,当使用@Autowired注解时,系统会跳过Map的key,自动将想要注入的变量类型与Map中value的类型逐个比对,直到找到相同的类型实现注入。
所以当没有相匹配的类型时,注入会失败,系统报错。当有多个相同类型的value时,系统首先会按类型进行匹配,找出对应类型的几个对象,如果有对象ID和变量名相同的仍然可以注入成功,否则会注入失败。
该注解可以加在变量和方法的定义之上。
@Qualifer 在按照类注入的基础上再按照名称注入。此注解在给类成员注入时不能单独使用,需要写在@Autowired之下,并写明属性value:用于注入的bean的id。
@Resource 直接按照bean的id注入,相当于@Autowired和@Qualifer的组合。
属性:name
@Resource(name=“classId”)
以上三个注解只能实现其他bean类型的注入,无法实现基本类型和集合类型的注入。集合类型的注入只能通过xml实现。
@Value 用于基本数据类型的注入
属性:value,指定注入的值,可以使用SpEL。
SpEL的写法:${表达式}
用于改变作用范围,与scope标签等价
@Scope 指定对象的作用范围
属性:value singleton 默认
prototype
此注解写在类的定义之上。用于改变生命周期,与init-method和destroy-method等价
@PostConstruct 用于指定初始化方法
@PreDestroy 用于指定销毁方法
这两个注解写在对应的方法上。

纯注解的方式改造IOC案例

注解和配置文件方法的选择:

自己开发的类选择注解通常更方便,导入的类使用配置文件通常更方便。
使用纯注解方式涉及到的两个问题:
因为不在使用配置文件编写IOC,配置文件中声明扫描范围的一行是否可以省略
在工程中添加一个配置类,配置类起到的作用与配置文件相同。
利用注解@Configuration,可以将一个类声明为配置类
利用注解@ComponentScan,可以声明注解的扫描范围,属性:value或basePackage=”扫描的包名“
利用注解@Import,可以在一个配置类中导入其他配置类,属性:配置类.class,使用@Import的配置类是主配置类,导入的是子配置类。
当我们利用注解配置类进行容器配置时,容器的获取方法变为AnnotationConfigApplicationContext(配置类.class)
利用注解将jar包中的类产生的对象添加到容器中
当我们调用jar包中的类时,由于无法利用注解需改源代码将对象添加到容器中,所以需要使用配置文件在容器中添加对象。此时我们也可以创建一个工厂方法,利用@Bean注解,将工厂方法的返回值添加到容器中。
利用注解@Bean,可以将当前方法的返回值作为Bean对象存入IOC容器中,属性:name-用于指定bean的id,默认值是当前方法的名称。
利用注解@Scope,可以改变Bean的作用范围,属性可以是singleton,prototype。

测试代码会产生许多冗余,是否可以简化
见下一节Spring和Junit整合

Spring和JUnit整合

测试方法中出现的问题:
利用JUnit进行测试时,系统会自动创建一个main函数,并在其中调用@Test注释的方法。如果不加其他的修饰,在测试方法中Spring框架还没启动,所以无法调用相关的注释。
解决方法:

  1. 首先导入Spring和JUnit整合的jar包依赖
<dependency>
	<groupId>org.springframework</groupId>
	<artifactId>spring-test</artifactId>
	<version>${spring-version}</version>
</dependency>
  1. 使用@RunWith注解,将JUnit提供的默认main函数,替换成Spring提供的main函数。
@RunWith(SpringJUnit4ClassRunner.class)  //这一注解加在测试类之上
  1. 指明Spring IOC容器创建的方法(xml / 注解)以及文件位置
    使用注解@ContextConfiguration,属性:locations:使用xml文件创建容器,后面接xml文件地址。classes:使用配置类创建容器,后面接 类名.class
    冗余代码的解决方法:
    利用@Before注解编写一个init方法,在init方法中完成统一的初始化操作。

AOP的概念

概念:Aspect Of Programming面向切面编程,AOP是对OOP(面向对象编程)的补充和完善。OOP利用分装,继承,多态等概念来建立一种对象结构,用以模拟公共行为的一个集合。而当我们要为分散的对象引入公共行为时,OOP就显得无力。也就是说,OOP允许从上到下的纵向定义,但不支持从左到右的横向定义。这种散布在各处的无关代码成为横切代码,典型的横切代码是日志代码。如果是利用OOP,就会造成大量的重复代码。AOP恰恰相反,它把散布在各个类之间的公共代码打包成一个模块,并将这一模块命名为Aspect,即切面。这样做的好处是可以减少重复代码,降低代码的耦合度,便于后期维护。

原理:通过预编译和动态代理实现。动态代理是重点,Spring中AOP的本质就是通过配置切面类,在程序运行过程中对切入点方法进行动态代理。代理的方式有两个:通过JKD代理接口对象,通过CGLIB代理类对象。

作用:将一些可以在程序中公用的代码提取成一个模块,这个模块就叫做切面。可以在任何业务流程中方便的插入这些切面。以实现在程序运行过程中,不修改源码对已有方法进行增强。也就是对业务流程的原方法进行动态代理。

优点:减少重复代码,提高开发效率,维护方便

Spring中AOP相关的术语

相关术语:
Joinpoint 连接点:
连接点指的是被拦截的点,在Spring中,这些点指的是方法。连接点指的是类中的所有方法。
Pointcut 切入点:
切入点是指我们要对哪些Joinpoint进行拦截的定义。切入点指的是被代理的方法。
Aspect 切面:
切入业务流程的一个独立模块
Advice 通知:
动态代理时在切入点方法上执行的增强方法。
按照执行时间可以分为前置通知(@Before), 后置通知(@AfterReturning), 异常通知(@AfterThrowing), 最终通知(@After)和环绕通知(@Around)5类。

基于xml的AOP配置

步骤:
1. 首先引入AOP的依赖
在beans.xml文件中添加以下代码块

<dependency>
	<groupId>org.aspectj</groupId>
	<artifactId>aspectjweaver</artifactId>
	<version>1.8.7</version>
</dependency>

2. 在xml文件中,添加aop相关约束

xmls:aop="http://www.springframework.org/schema/aop"
xsi:schameLocation="http://www.springframework.org/schema/aop
										http://www.springframework.org/schema/aop/spring-aop.xsd"

3. 在Spring中配置xml文件

  1. 在IOC容器中添加被代理对象
  2. 在IOC容器中添加代理对象
  3. 通过aop:config标签配置AOP切面
  4. 通过aop:aspect标签配置切面
  5. 在aop:aspect标签内部,通过四种通知的对应标签,配置具体代理方法
    常用四种通知:
    before 前置通知
    after-returning 后置通知
    after-throwing 异常通知
    after 最终通知
    环绕通知 代替原代理方法
  6. 在aop:aspect标签内部通过pointcut属性配置切入点信息,或在aop:config标签内部,通过 aop:pointcut标签配置切入点
    需要注意的是,切入点的名称需要的是全限定类名。
    切入点通配符:全通配符:* .(…)
    说明:方法的修饰符public,private等可以省略。全限定类名通配符:
    表示的是当前工程下的任意目录,还可以用.来表示一级通配符。方法名通配:。参数通配:—有任意参数,…—有无参数均可。
<bean id="target" class="com.wbtest.QueryServe"></bean>
<bean id="proxy" class="com.wbtest.Logger"></bean>

<aop:config>
	<aop:pointcut expression="excution(public void com.wbtest.QueryServe.updateAccount(java.lang.String, int))">
	<aop:aspect id="aspect1" ref="proxy">
		<aop:before method="methodName"></aop:before>  //前置通知
		<aop:after-returning method="methodName"></aop:after-returning>  //后置通知
		<aop:after-throwing method="methodName"></aop:after-throwing>  //异常通知
		<aop:after method="methodName" pointcut="execution(public void com.wbtest.QueryServe.saveAccount(java.lang.String))"></aop:after>  //最终通知
	</aop:aspect>
</aop:config>

基于注解的AOP配置

首先需要在xml文件中添加相关约束:

xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schame/aop
										http://www.springframework.org/schame/aop/spring-aop.xsd
                    http://www.springframework.org/schame/context
                    http://www.springframework.org/schame/context/spring-context.xsd"
                    
<!-- 在配置文件中开启spring对AOP的支持 -->
<sop:aspectj-autoproxy></sop:aspectj-autoproxy>

相关注释:
@Aspect:表示当前类是一个切面
@Before(“pointcut1()”):表示切面中的当前方法是一个前置通知
@AfterReturning(“pointcut2()”):表示当前方法是一个后置通知
@AfterThrowing(“pointcut3()”):表示当前方法是一个异常通知
@After(“pointcut4()”):表示当前方法是一个最终通知
@PointCut:在切面类内部,可以通过@PointCut注释声明当前切面内的切入点,@PointCut(“execution(* .*(…))”)。
此注释需要写在切入点之上,所以需要在切面类中先声明一个形式上的切入点

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值