一、Spring框架概述
- Spring是一个开源框架
- Spring是于2003 年兴起的一个轻量级(重量和开销)的Java开发框架,由Rod Johnson在其著作Expert One-On-One J2EE Development and Design中阐述的部分理念和原型衍生而来。
- 它是为了解决企业应用开发的复杂性而创建的。框架的主要优势之一就是其分层架构,分层架构允许使用者选择使用哪一个组件,同时为 J2EE 应用程序开发提供集成的框架。
- Spring使用基本的JavaBean来完成以前只可能由EJB(j2ee提供给开发者使用的三层框架)完成的事情。然而,Spring的用途不仅限于服务器端的开发。从简单性、可测试性和松耦合的角度而言,任何Java应用都可以从Spring中受益。
- Spring的核心是控制反转(IoC)和面向切面(AOP)。简单来说,Spring是一个分层的JavaSE/EEfull-stack(一站式) 轻量级开源框架。
- EE开发分成三层结构
- WEB层 – Spring MVC
- 业务层 – Bean管理:(IOC)
- 持久层 – Spring的JDBC模板.ORM模板用于整合其他的持久层框架
二、Spring框架特点
- 方便解耦,简化开发
- Spring就是一个大工厂,可以将所有对象创建和依赖关系维护,交给Spring管理。
- AOP编程的支持
- Spring提供面向切面编程,可以方便的实现对程序进行权限拦截、运行监控等功能。
- 声明式事务的支持
- 只需要通过配置就可以完成对事务的管理,而无需手动编程。
- 方便程序的测试
- Spring对Junit4支持,可以通过注解方便的测试Spring程序。
- 方便集成各种优秀框架
- Spring不排斥各种优秀的开源框架,其内部提供了对各种优秀框架(如:Struts2、Hibernate、MyBatis、Quartz等)的直接支持。
- 降低JavaEE API的使用难度
- Spring 对JavaEE开发中非常难用的一些API(JDBC、JavaMail、远程调用等),都提供了封装,使这些API应用难度大大降低。
三、Spring七大模块
- Spring Core:
Core封装包是框架的最基础部分,提供IOC和依赖注入特性。这里的基础概念是BeanFactory,它提供对Factory模式的经典实现来消除对程序性单例模式的需要,并真正地允许你从程序逻辑中分离出依赖关系和配置。 - Spring Context:
构建于Core封装包基础上的 Context封装包,提供了一种框架式的对象访问方法,有些象JNDI注册器。Context封装包的特性得自于Beans封装包,并添加了对国际化(I18N)的支持(例如资源绑定),事件传播,资源装载的方式和Context的透明创建,比如说通过Servlet容器。 - Spring DAO:
i. DAO (Data Access Object)提供了JDBC的抽象层,它可消除冗长的JDBC编码和解析数据库厂商特有的错误代码。 并且,JDBC封装包还提供了一种比编程性更好的声明性事务管理方法,不仅仅是实现了特定接口,而且对所有的POJOs(plain old Java objects)都适用。 - Spring ORM:
ORM 封装包提供了常用的“对象/关系”映射APIs的集成层。 其中包括JPA、JDO、Hibernate 和 iBatis 。利用ORM封装包,可以混合使用所有Spring提供的特性进行“对象/关系”映射,如前边提到的简单声明性事务管理。 - Spring AOP:
Spring的 AOP 封装包提供了符合AOP Alliance规范的面向方面的编程实现,让你可以定义,例如方法拦截器(method-interceptors)和切点(pointcuts),从逻辑上讲,从而减弱代码的功能耦合,清晰的被分离开。而且,利用source-level的元数据功能,还可以将各种行为信息合并到你的代码中。 - Spring Web:
Spring中的 Web 包提供了基础的针对Web开发的集成特性,例如多方文件上传,利用Servlet listeners进行IOC容器初始化和针对Web的ApplicationContext。当与WebWork或Struts一起使用Spring时,这个包使Spring可与其他框架结合。 - Spring Web MVC:
Spring中的MVC封装包提供了Web应用的Model-View-Controller(MVC)实现。Spring的MVC框架并不是仅仅提供一种传统的实现,它提供了一种清晰的分离模型,在领域模型代码和Web Form之间。并且,还可以借助Spring框架的其他特性。
四、理解控制反转IoC(Inversion of Control)
- 理解:是面向编程中的一种设计理念,用来降低程序代码间的耦合度。
举个例子:
/**
* 用户Dao接口
* @author 14062
*
*/
public interface UserDao {
public void save(User user);
}
/**
* 用户业务实现类
* @author 14062
*
*/
public class UserServiceImpl implements UserService {
@Override
public void addNewUser(User user) {
//实例化所依赖的userDao对象
UserDao uDao=new UserDaoImpl();
//调用用户dao的方法保存用户信息
uDao.save(user);
}
}
首先考虑什么是依赖:在A类方法中,实例化了B类的对象并调用其方法以完成特定功能,我们就说A类依赖于B类;而以上代码中UserServiceImpl通过创建了UserDaoImpl的对象,并通过该对象调用了其save()新增用户信息的方法,故UserServiceImpl与UserDaoImpl存在依赖关系。
所以到这里就出现一个问题:即UserServiceImpl和UserDaoImpl高度耦合,如果根据需求变化要替换UserDaoImpl类,将导致UserServiceImpl中的代码大量修改,如此程序将不具备优良的可扩展性和可维护性。
解决方案: 利用简单工厂和工厂模式解决此问题。
/**
* 增加用户Dao工厂,负责用户Dao实例的创建工作
* @author 14062
*
*/
public class UserDaoFactory {
/**
* 负责创建UserDao的实例方法
* @return UserDao实例
*/
public static Object getInstance(){
UserDao uDao=null;
try {
//根据全限名获取对象
uDao=(UserDao)Class.forName("cn.test.dao.impl").newInstance();
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
return uDao;
}
}
public class UserServiceImpl implements UserService {
@Override
public void save(User user) {
//获取userDao实例
UserDao uDao=(UserDao)UserDaoFactory.getInstance();
//调用用户DAO的方法保存用户信息
uDao.save(user);
}
}
这里的用户Dao工厂类UserDaoFactory体现了“控制反转”的思想,UserServiceImpl不再依靠自身代码去获得所依赖的对象,而是把这一项工作交了给第三方----UserDaoFactory,从而避免了和具体Dao层实现类的耦合,在如何获取所依赖的对象的事上,“控制权”发生了“反转”,这就是所谓的“控制反转”。
五、实现控制反转
需求: 实现在控制台打印 “Hello,Spring!”
实现控制反转步骤
-
导入Spring所需jar包
-
编写Spring配置文件
-
编写代码通过Spring获取HelloSpring实例
代码如下
HelloSpring.java
package cn.test.demo;
/**
* 第一个Spring,输出Hello,Spring
* @author 14062
*
*/
public class HelloSpring {
//定义who属性,该属性的值将通过Spring框架进行设置
private String who=null;
/**
* 打印方法,输出一句完整的问候
*/
public void print(){
System.out.println("Hello,"+this.getWho());
}
/**
* 获得who
* @return
*/
public String getWho() {
return who;
}
/**
* 设置who
* @param who
*/
public void setWho(String who) {
this.who = who;
}
}
配置Spring配置文件—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" xmlns:aop="http://www.springframework.org/schema/aop"
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.2.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-3.2.xsd">
<!-- 通过bean元素声明需要Spring创建的实例。
该实例的类型通过class属性指定,并通过id属性为该实例指定一个名称,
以便于访问 -->
<bean id="helloSpring" class="cn.test.demo.HelloSpring">
<!--property元素用来为实例的属性赋值,
此处实际是调用setWho()方法实现赋值操作-->
<property name="who">
<!--此处将字符串"Spring" 赋值给who属性-->
<value>Spring</value>
</property>
</bean>
</beans>
注:
- 在Spring配置文件中,使用
<bean>
元素来定义Bean(可称为组件)的实例。该元素的两个属性:
id 表示定义的Bean实例的名称
class 表示定义的Bean实例的类型 - 使用
<bean>
元素定义一个组件时,通常需要使用id属性为其指定一个用来访问的唯一的名称,如果想为Bean指定更多的别名,可以通过name属性指定,名称之间使用逗号,分号或空格进行分隔. - Spring为Bean的属性赋值是通过调用属性的setter方法实现的,这种做法称为“设置注入”,而非直接为属性赋值。若属性名为who,但是setter方法名为setSomebody(),Spring配置文件中应写成name=“somebody”,而非name=“who”。
HelloSpringTest.java测试类
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import cn.test.demo.HelloSpring;
public class HelloSpringTest {
@Test
public void test01(){
//通过ClassPathXmlApplicationContext实例化Spring的上下文
ApplicationContext context=new ClassPathXmlApplicationContext("applicationContext.xml");
//通过ClassPathXmlApplicationContext的getBean()方法,根据id来获取Bean的实例
HelloSpring helloSpring=(HelloSpring)context.getBean("helloSpring");
//执行spring方法
helloSpring.print();
}
}
运行结果
讲解:
- ApplicationContext 是一个接口,负责读取Spring配置文件,管理对象的加载,生成,维护Bean对象与Bean对象之间的依赖关系,负责Bean的声明周期等。ClassPathXmlApplicationContext是ApplicationContext 接口的实现类,用于从classpath路径中读取Spring配置文件。
- 除了ApplicationContext 及其实现类,还可以通过BeanFactory接口及其实现类对Bean组件实施管理,事实上,ApplicationContext 就是建立在BeanFactory的基础之上,BeanFactory是Spring IoC容器的核心,负责管理组件和它们之间的依赖关系,应用程序通过BeanFactory接口与Spring IoC容器交互。ApplicationContext 是BeanFactory的子接口,可以对企业级开发提供更全面的支持。
六、Spring中的设计模式
-
第一种:简单工厂
- 又叫做静态工厂方法(StaticFactory Method)模式,但不属于23种GOF设计模式之一。 简单工厂模式的实质是由一个工厂类根据传入的参数,动态决定应该创建哪一个产品类。 spring中的BeanFactory就是简单工厂模式的体现,根据传入一个唯一的标识来获得bean对象,但是否是在传入参数后创建还是传入参数前创建这个要根据具体情况来定。
-
第二种:工厂方法(Factory Method)
- 通常由应用程序直接使用new创建新的对象,为了将对象的创建和使用相分离,采用工厂模式,即应用程序将对象的创建及初始化职责交给工厂对象。一般情况下,应用程序有自己的工厂对象来创建bean.如果将应用程序自己的工厂对象交给Spring管理,那么Spring管理的就不是普通的bean,而是工厂Bean。
-
第三种:单例模式(Singleton)
- 保证一个类仅有一个实例,并提供一个访问它的全局访问点。
spring中的单例模式完成了后半句话,即提供了全局的访问点BeanFactory。但没有从构造器级别去控制单例,这是因为spring管理的是是任意的java对象。
- 保证一个类仅有一个实例,并提供一个访问它的全局访问点。
-
第四种:适配器(Adapter)
- 在Spring的Aop中,使用的Advice(通知)来增强被代理类的功能。Spring实现这一AOP功能的原理就使用代理模式(1、JDK动态代理。2、CGLib字节码生成技术代理。)对类进行方法级别的切面增强,即,生成被代理类的代理类, 并在代理类的方法前,设置拦截器,通过执行拦截器重的内容增强了代理方法的功能,实现的面向切面编程。
-
第五种:包装器(Decorator)
- 在我们的项目中遇到这样一个问题:我们的项目需要连接多个数据库,而且不同的客户在每次访问中根据需要会去访问不同的数据库。我们以往在spring和hibernate框架中总是配置一个数据源,因而sessionFactory的dataSource属性总是指向这个数据源并且恒定不变,所有DAO在使用sessionFactory的时候都是通过这个数据源访问数据库。但是现在,由于项目的需要,我们的DAO在访问sessionFactory的时候都不得不在多个数据源中不断切换,问题就出现了:如何让sessionFactory在执行数据持久化的时候,根据客户的需求能够动态切换不同的数据源?我们能不能在spring的框架下通过少量修改得到解决?是否有什么设计模式可以利用呢?
首先想到在spring的applicationContext中配置所有的dataSource。这些dataSource可能是各种不同类型的,比如不同的数据库:Oracle、SQL Server、MySQL等,也可能是不同的数据源:比如apache 提供的org.apache.commons.dbcp.BasicDataSource、spring提供的org.springframework.jndi.JndiObjectFactoryBean等。然后sessionFactory根据客户的每次请求,将dataSource属性设置成不同的数据源,以到达切换数据源的目的。
spring中用到的包装器模式在类名上有两种表现:一种是类名中含有Wrapper,另一种是类名中含有Decorator。基本上都是动态地给一个对象添加一些额外的职责。
- 在我们的项目中遇到这样一个问题:我们的项目需要连接多个数据库,而且不同的客户在每次访问中根据需要会去访问不同的数据库。我们以往在spring和hibernate框架中总是配置一个数据源,因而sessionFactory的dataSource属性总是指向这个数据源并且恒定不变,所有DAO在使用sessionFactory的时候都是通过这个数据源访问数据库。但是现在,由于项目的需要,我们的DAO在访问sessionFactory的时候都不得不在多个数据源中不断切换,问题就出现了:如何让sessionFactory在执行数据持久化的时候,根据客户的需求能够动态切换不同的数据源?我们能不能在spring的框架下通过少量修改得到解决?是否有什么设计模式可以利用呢?
-
第六种:代理(Proxy)
- 为其他对象提供一种代理以控制对这个对象的访问。 从结构上来看和Decorator模式类似,但Proxy是控制,更像是一种对功能的限制,而Decorator是增加职责。
spring的Proxy模式在aop中有体现,比如JdkDynamicAopProxy和Cglib2AopProxy。
- 为其他对象提供一种代理以控制对这个对象的访问。 从结构上来看和Decorator模式类似,但Proxy是控制,更像是一种对功能的限制,而Decorator是增加职责。
-
第七种:观察者(Observer)
- 定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。
spring中Observer模式常用的地方是listener的实现。如ApplicationListener。
- 定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。
-
第八种:策略(Strategy)
- 定义一系列的算法,把它们一个个封装起来,并且使它们可相互替换。本模式使得算法可独立于使用它的客户而变化。
-
第九种:模板方法(Template Method)
- 定义一个操作中的算法的骨架,而将一些步骤延迟到子类中。Template Method使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。
Template Method模式一般是需要继承的。这里想要探讨另一种对Template Method的理解。spring中的JdbcTemplate,在用这个类时并不想去继承这个类,因为这个类的方法太多,但是我们还是想用到JdbcTemplate已有的稳定的、公用的数据库连接,那么我们怎么办呢?我们可以把变化的东西抽出来作为一个参数传入JdbcTemplate的方法中。但是变化的东西是一段代码,而且这段代码会用到JdbcTemplate中的变量。怎么办?那我们就用回调对象吧。在这个回调对象中定义一个操纵JdbcTemplate中变量的方法,我们去实现这个方法,就把变化的东西集中到这里了。然后我们再传入这个回调对象到JdbcTemplate,从而完成了调用。这可能是Template Method不需要继承的另一种实现方式吧。
- 定义一个操作中的算法的骨架,而将一些步骤延迟到子类中。Template Method使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。
七、依赖注入DI(Dependency Injection)
- DI—Dependency Injection,即“依赖注入”:组件之间依赖关系由容器在运行期决定,形象的说,即由容器动态的将某个依赖关系注入到组件之中。依赖注入的目的并非为软件系统带来更多功能,而是为了提升组件重用的频率,并为系统搭建一个灵活、可扩展的平台。通过依赖注入机制,我们只需要通过简单的配置,而无需任何代码就可指定目标需要的资源,完成自身的业务逻辑,而不需要关心具体的资源来自何处,由谁实现。
八、理解Spring AOP
1、AOP概述
- 什么是AOP的技术?
在软件业,AOP为Aspect Oriented Programming的缩写,意为面向切面编程。AOP是一种编程范式(思想),隶属于软工范畴,指导开发者如何组织程序结构。 AOP最早由AOP联盟的组织提出的,制定了一套规范.Spring将AOP思想引入到框架中,必须遵守AOP联盟的规范。通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率 - AOP:面向切面编程.(思想.—解决OOP遇到一些问题)
- AOP采取横向抽取机制,取代了传统纵向继承体系重复性代码(性能监视、事务管理、安全检查、缓存)
2、AOP核心思想
- 可以在不修改源代码的前提下,对程序进行增强。
3、Spring框架的AOP底层实现
-
Srping框架的AOP技术底层也是采用的代理技术,代理的方式提供了两种
-
基于JDK的动态代理
必须是面向接口的,只有实现了具体接口的类才能生成代理对象 -
基于CGLIB动态代理
* 对于没有实现了接口的类,也可以产生代理,产生这个类的子类的方式
-
-
Spring的传统AOP中根据类是否实现接口,来采用不同的代理方式
- 如果实现类接口,使用JDK动态代理完成AOP
- 如果没有实现接口,采用CGLIB动态代理完成AOP
4、AOP的相关术语
- Joinpoint(连接点) – 所谓连接点是指那些被拦截到的点。在spring中,这些点指的是方法,因为spring只支持方法类型的连接点
- Pointcut(切入点) – 所谓切入点是指我们要对哪些Joinpoint进行拦截的定义。
- Advice(通知/增强) – 所谓通知是指拦截到Joinpoint之后所要做的事情就是通知。通知分为前置通知,后置通知,异常通知,最终通知,环绕通知(切面要完成的功能)
- Introduction(引介):是一种特殊的通知在不修改类代码的前提下, Introduction可以在运行期为类动态地添加一些方法或Field。
- Target(目标对象):代理的目标对象,指的就是你要代理的类。
- Weaving(织入):是指把增强应用到目标对象来创建新的代理对象的过程。
- Proxy(代理): 一个类被AOP织入增强后,就产生一个结果代理类。
- Aspect(切面):一个模块化的横切逻辑(或称横切关注点),可能会横切多个对象。
- AOP代理(AOP proxy):由AOP框架所创建的对象,实现执行增强处理方法等功能。
注: 切面可以理解为由增强处理和切入点组成,既包含了横切逻辑的定义,也包含了连接点的定义,面向切面编程主要关心两个问题,即在什么位置,执行什么功能。Spring AOP是负责实施切面的框架,即由Spring AOP完成织入工作。
九、如何使用AOP开发
步骤
- 导入jar包
- 编写业务类
- 编写增强类
- 编写Spring配置文件
- 编写测试类进行测试
代码如下
UserServiceImpl.java
import cn.test.dao.UserDao;
import cn.test.dao.impl.UserDaoImpl;
import cn.test.factory.UserDaoFactory;
import cn.test.pojo.User;
import cn.test.service.UserService;
public class UserServiceImpl implements UserService {
private UserDao dao; //声明接口类型的引用和具体实现类解耦合
public void setDao(UserDao dao) {
this.dao = dao;
}
@Override
public void save(User user) {
//调用用户DAO的方法保存用户信息
dao.save(user);
}
}
UserServiceLogger.java 增强类
package cn.test.service;
import java.util.Arrays;
import org.apache.log4j.Logger;
import org.aspectj.lang.JoinPoint;
/**
* 定义包含增强方法的JavaBean
* @author 14062
*
*/
public class UserServiceLogger {
private static final Logger log=Logger.getLogger(UserServiceLogger.class);
//代表前置增强的方法
public void before(JoinPoint jp){
System.out.println("调用"+jp.getTarget()+"的"+jp.getSignature().getName()+"方 法。方法入参:"
+Arrays.toString(jp.getArgs()));
}
//代表后置增强的方法
public void afterReturning(JoinPoint jp,Object result){
System.out.println("调用"+jp.getTarget()+"的"+jp.getSignature().getName()
+"方法。方法返回值:"+result);
}
}
Spring配置文件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" xmlns:aop="http://www.springframework.org/schema/aop"
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.2.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-3.2.xsd">
<!--对相关组件进行声明 -->
<bean id="userDao" class="cn.test.dao.impl.UserDaoImpl"></bean>
<bean id="service" class="cn.test.service.impl.UserServiceImpl">
<!---此处name对应属性名称dao ->
<property name="dao" ref="userDao"></property>
</bean>
<bean id="theLogger" class="cn.test.service.UserServiceLogger"/>
<aop:config>
<!--定义一个切入点表达式,并命名为"pointcut"-->
<aop:pointcut id="pointcut"
expression="execution(public void save(cn.test.pojo.User))" />
<!--引用包含增强方法的Bean-->
<aop:aspect ref="theLogger">
<!--将before()方法定义为前置增强并引用pointcut切入点-->
<aop:before method="before" pointcut-ref="pointcut"/>
<!--将afterReturning()方法定义为后置增强并引用pointcut切入点-->
<!--通过returning属性指定名为result的参数注入返回值 -->
<aop:after-returning method="afterReturning" pointcut-ref="pointcut"
returning="result"/>
</aop:aspect>
</aopconfig>
</beans>
测试类
@Test
public void test(){
ApplicationContext context=new ClassPathXmlApplicationContext("applicationContext.xml");
UserService uService=(UserService)context.getBean("service");
User user=new User();
user.setId(1);
user.setUserName("test");
user.setUserPassword("123456");
user.setAddress("太原市小店区");
uService.save(user);
}
运行结果
讲解:
Spring配置文件:
-
与AOP相关的配置都放在
<aop:config>
标签中,如配置切入点的
<aop:ponitcut>
-
<aop:ponitcut>
的expression属性可以配置切入点表达式。 -
execution是切入点指示符,它的括号中是一个切入点表达式,可以配置需要切入增强处理的方法的特征,切入点表达式支持模糊匹配,常用的几种模糊匹配如下。
-
public * addNewUser(entity.User); 表示匹配所有类型的返回值
-
public void *(entity.User); 表示匹配所有方法名
-
public void addNewUser(..);
… 表示匹配所有参数个数和类型
com.service.*.*(..);
这个表达式匹配com.service包下所有类的所有方法com.service..*.*(..);
这个表达式匹配com.service包及其子包下所有类的所有方法
-
-
在
<aop:config>
中使用<aop:aspect>
引用包含增强方法的Bean。 -
<aop:before>
将方法声明为前置增强。 -
<aop:after-returning>
将方法声明为后置增强,在此元素中可以通过。returning属性指定需要注入返回值的属性名。方法的JoinPoint类型参数无需处理,Spring会自动为其注入连接点实例。
关注一下吧~【Pretty Kathy】