Spring 简介

什么是 Spring

  1. Spring 是一个轻量级的 DI / IOC 和 AOP 容器的开源框架,来源于 Rod Johnson 在其著作**《Expert one on one J2EE design and development》**中阐述的部分理念和原型衍生而来。

  2. Spring 提倡以**“最少侵入”**的方式来管理应用中的代码,这意味着我们可以随时安装或者卸载 Spring。

  • 适用范围:任何 Java 应用
  • Spring 的根本使命:简化 Java 开发

框架中常用术语

  • 框架:

    是能完成一定功能的半成品
    框架能够帮助我们完成的是:项目的整体结构布局、一些基础功能、规定了类和对象如何创建,如何协作等,当我们开发一个项目时,框架帮助我们完成了一部分功能,我们自己再完成一部分,那这个项目就完成了。

  • 非侵入式设计:
    从框架的角度可以理解为:无需继承框架提供的任何类
    这样我们在更换框架时,之前写过的代码几乎可以继续使用。

  • 轻量级和重量级:
    轻量级是相对于重量级而言的,轻量级一般就是非入侵性的、所依赖的东西非常少、资源占用非常少、部署简单等等,其实就是比较容易使用,而重量级正好相反。

  • JavaBean:
    符合 JavaBean 规范的 Java 类

  • POJO:

    Plain Old Java Objects,简单老式 Java 对象
    它可以包含业务逻辑或持久化逻辑,但不担当任何特殊角色不继承或不实现任何其它Java框架的类或接口。

  • 容器:
    在日常生活中容器就是一种盛放东西的器具,从程序设计角度看就是装对象的的对象,因为存在放入、拿出等操作,所以容器还要管理对象的生命周期

Spring 的优势

  • 低侵入 / 低耦合 (降低组件之间的耦合度,实现软件各层之间的解耦)
  • 声明式事务管理(基于切面和惯例)
  • 方便集成其他框架(如MyBatis、Hibernate)
  • 降低 Java 开发难度
  • Spring 框架中包括了 J2EE 三层的每一层的解决方案(一站式)

Spring 能帮我们做什么

  • Spring 能帮我们根据配置文件创建及组件对象之间的依赖关系
  • Spring 面向切面编程能帮助我们无耦合的实现日志记录,性能统计,安全控制
  • Spring 能非常简单的帮我们管理数据库事务
  • Spring 还提供了与第三方数据访问框架(如 Hibernate、JPA)无缝集成,而且自己也提供了一套 JDBC访问模板 来方便数据库访问。
  • Spring 还提供与第三方Web(如 Struts1/2、JSF)框架无缝集成,而且自己也提供了一套 SpringMVC 框架,来方便 web 层搭建。
  • Spring 还能方便的与JavaEE(如Java Mail、任务调度)整合,与更多技术整合(如缓存框架)

Spring 的框架结构

  • Data Access/Integration层包含有JDBC、ORM、OXM、JMS(java邮件服务)和Transaction模块。
  • **Web层:**包含了Web、Web-Servlet、WebSocket、Web-Porlet模块。
  • **AOP模块:**提供了一个符合AOP联盟标准的面向切面编程的实现。
  • **Core Container(核心容器):**包含有Beans(Bean工厂,创建对象)、Core(一切的基础)、Context(上下文)和SpEL(Spring的表达式语言)模块。
  • **Test模块:**支持使用JUnit和TestNG对Spring组件进行测试。

Spring IOC 和 DI 简介

IOC:Inverse of Control(控制反转)

  • 不是什么技术,而是一种设计思想,就是将原本在程序中动手创建对象的控制权,交由 Spring 框架来管理。
  • 正控:若要使用某个对象,需要自己去负责对象的创建
  • 反控:若要使用某个对象,只需要从 Spring 容器中获取需要使用的对象,不关心对象的创建过程,也就是把创建对象的控制权反转给了 Spring 框架
  • **好莱坞法则:**Don’t call me,I’ll call you.

举个例子

在现实生活中,人们要用到一样东西的时候,第一反应就是去找到这件东西,比如想喝新鲜橙汁,在没有饮品店的日子里,最直观的做法就是:买果汁机、买橙子,然后准备开水。值得注意的是:这些都是你自己**“主动”创造**的过程,也就是说一杯橙汁需要你自己创造。

然而到了今时今日,由于饮品店的盛行,当我们想喝橙汁时,第一想法就转换成了找到饮品店的联系方式,通过电话等渠道描述你的需要、地址、联系方式等,下订单等待,过一会儿就会有人送来橙汁了。

请注意你并没有“主动”去创造橙汁,橙汁是由饮品店创造的,而不是你,然而也完全达到了你的要求,甚至比你创造的要好上那么一些。

以上这个简单的例子就可以抽象出控制反转的概念,就是把创建对象就相当于做橙汁,饮品店就相当于框架,我们把做橙汁这个控制权交给饮品店,我们直接享用成果就可以了。

编写第一个 Spring 程序

  1. 新建一个普通的 maven 项目,命名为【spring】

  2. 在【po】包下新建一个实体类 Source

  3. 新建【resource】资源文件夹,并创建配置文件 applicationContext.xml,通过 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.xsd">

    <bean name="source" class="pojo.Source">
    <property name="fruit" value="橙子"/>
    <property name="sugar" value="多糖"/>
    <property name="size" value="超大杯"/>
    </bean>
    </beans>
  4. 在 Packge【test】下新建一个【TestSpring】类:

    public class TestSpring {
    @Test
    public void test(){
    /**
    * 1、加载配置文件
    * 2、获取bean对象
    * 3、执行bean方法
    */
    ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
    Source source = (Source) context.getBean("source");
    System.out.println(source.getFruit());
    System.out.println(source.getSugar());
    System.out.println(source.getSize());
    }
    }
  5. 运行测试代码,可以正常拿到 xml 配置的 bean

总结

  • 传统方式:通过new关键字主动创建一个对象
  • **IOC 方式:**对象的生命周期由 Spring 来管理,直接从 Spring 那里去获取一个对象。IOC 是反转控制(Inversion Of Control)的缩写,就像控制权从本来在自己手里,交给了 Spring。

Spring Ioc 实例化 bean 对象的三种方式

默认构造方法

最常用的初始化bean方式,必须提供默认构造方法

public class Person {
private String name;
private Integer age;
public Person() {
System.out.println("这是一个无参构造函数");
}
public Person(String name) {
this.name = name;
System.out.println("带有名字="+name+"参数的构造函数");
}
public Person(String name, Integer age) {
this.name = name;
this.age = age;
System.out.println("带有名字="+name+"和年龄="+age+"参数的构造函数");
}
}

bean.xml

<bean id="person" class="com.maple.Person"></bean>
<bean id="personWithParam" class="com.maple.Person">
<constructor-arg name="name" value="枫叶"/>
</bean>
<bean id="personWirhParams" class="com.maple.Person">
<constructor-arg name="name" value="枫叶"/>
<constructor-arg name="age" value="23"/>
</bean>

静态工厂方法

当采用静态工厂方法创建bean时,除了需要指定class属性外,还需要通过factory-method属性来指定创建bean实例的工厂方法。Spring将调用此方法返回实例对象,就此而言,跟通过普通构造器创建类实例没什么两样。

public class MyBeanFactory {  
/**
* 创建实例
* @return
*/
public static UserService createService(){
return new UserServiceImpl();
}

public static UserService createService(String name){
return new UserServiceImpl(name);
}

public static UserService createService(String name,int age){
return new UserServiceImpl(name,age);
}
}

bean.xml

<bean id="userServiceId" class="com.maple.MyBeanFactory" factory-method="createService"></bean>
<bean id="userServiceId" class="com.maple.MyBeanFactory" factory-method="createService">
<constructor-arg name="name" value="枫叶"/>
</bean>
<bean id="userServiceId" class="com.maple.MyBeanFactory" factory-method="createService">
<constructor-arg name="name" value="枫叶"/>
<constructor-arg name="age" value="23"/>
</bean>

实例工厂方法

需要配置工厂 bean,并在业务 bean 中配置 factory-bean,factory-method 属性实例化工厂定义。
必须先有工厂实例对象,通过实例对象创建对象。提供所有的方法都是“非静态”的。

/**
* 实例工厂,所有方法非静态
*
*/
public class MyBeanFactory {
/**
* 实例化工厂
*/
public UserService createService(){
return new UserServiceImpl();
}

/**
* 实例化工厂1
*/
public static UserService createService(String name){
return new UserServiceImpl(name);
}

/**
* 实例化工厂2
*/
public static UserService createService(String name,int age){
return new UserServiceImpl(name,age);
}
}

bean.xml

<!-- 创建工厂实例 -->
<bean id="myBeanFactoryId" class="com.maple.MyBeanFactory"></bean>
<!-- 获得userservice
* factory-bean 确定工厂实例
* factory-method 确定普通方法
-->
<bean id="userServiceId" factory-bean="myBeanFactoryId" factory-method="createService"></bean>

<bean id="userServiceId" factory-bean="myBeanFactoryId" factory-method="createService">
<constructor-arg name="name" value="枫叶"/>
</bean>

<bean id="userServiceId" factory-bean="myBeanFactoryId" factory-method="createService">
<constructor-arg name="name" value="枫叶"/>
<constructor-arg name="age" value="23"/>
</bean>

DI:Dependency Injection(依赖注入)

  • 指 Spring 创建对象的过程中,将对象依赖属性(简单值,集合,对象)通过配置设值给该对象

继续上个例子

  1. 在【po】包下新建一个【JuiceMaker】类:

    public class JuiceMaker {

    // 唯一关联了一个 Source 对象
    // @Resource
    @Autowired
    private Source source;

    public String makeJuice(){
    String juice = "XXX用户点了一杯" + source.getFruit() + source.getSugar()
    + source.getSize();
    return juice;
    }
    }
  2. 注入Source对象

    • 方式一:采用注解方式注入bean,spring 对于注解有专门的解释器,对定义的注解
      进行解析,实现对应 bean 对象的注入,反射技术实现

      • 加入 spring-aop jar 包 spring-aop-4.3.2.RELEASE.jar

      • Xml 配置: 加入 context 命名空间 和 xsd 地址

      • 添加**context:annotation-config/**配置

      • 使用 @Autowired 或 @Resource 在属性字段或 set 方法上(如上)

        @Autowired 默认按 bean 的类型匹配,可以修改按名称匹配,与 @Qualifier 配合使用

        @Resource 默认按名称进行装配,名称可以通过 name 属性进行指定,如果没
        有指定 name 属性,当注解写在字段上时,默认取字段名进行匹配注入 ,如果
        注解写在 setter 方法上默认取属性名进行装配。当找不到与名称匹配的 bean 时才按照类型进行装配。

        推荐使用 @Resource 注解,它是属于 J2EE 的,减少了与 spring 的耦合。

      <?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:context="http://www.springframework.org/schema/context"
      xsi:schemaLocation="http://www.springframework.org/schema/beans
      http://www.springframework.org/schema/beans/spring-beans.xsd
      http://www.springframework.org/schema/context
      http://www.springframework.org/schema/context/spring-context.xsd">

      <!-- 开启注解 -->
      <context:annotation-config/>

      <bean name="source" class="cn.spring.po.Source">
      <property name="fruit" value="橙子"/>
      <property name="sugar" value="多糖"/>
      <property name="size" value="超大杯"/>
      </bean>
      <!--<bean name="juiceMaker" class="cn.spring.service.JuiceMaker">-->
      <!--<property name="source" ref="source"/>-->
      <!--</bean>-->
      </beans>
    • 方式二:在 xml 文件中配置 JuiceMaker 对象

      <?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 name="source" class="pojo.Source">
      <property name="fruit" value="橙子"/>
      <property name="sugar" value="多糖"/>
      <property name="size" value="超大杯"/>
      </bean>
      <bean name="juickMaker" class="pojo.JuiceMaker">
      <property name="source" ref="source" />
      </bean>
      </beans>
  3. 在 【TestSpring】 中添加如下代码:

    public class TestSpring {
    @Test
    public void test(){
    /**
    * 1、加载配置文件
    * 2、获取bean对象
    * 3、执行bean方法
    */
    ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
    Source source = (Source) context.getBean("source");
    System.out.println(source.getFruit());
    System.out.println(source.getSugar());
    System.out.println(source.getSize());

    JuiceMaker juickMaker = (JuiceMaker) context.getBean("juickMaker");
    System.out.println(juickMaker.makeJuice());
    }
    }
  4. 运行测试代码:

总结

IoC 和 DI 其实是同一个概念的不同角度描述,DI 相对 IOC 而言,明确描述了 “被注入对象依赖 IOC 容器配置依赖对象”

Spring 支持的四种注入方式

Set 注入

xml 配置(同时 spring 也提供了对于基本数据类型的 set 注入方式)

<?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="userServiceImpl" class="com.shsxt.service.impl.UserServiceImpl">
<property name="userDao" ref="userDao"/>
</bean>
<bean id="userDao" class="com.shsxt.dao.UserDao"/>

</beans>

Java 类

public class UserServiceImpl {
private UserDao userDao;
public void setUserDao(UserDao userDao) {
this.userDao = userDao;
}
public UserDao getUserDao() {
return userDao;
}
public void saveUser(User user){
System.out.println("userName:"+userName+"price:"+price);
userDao.add(user);
}
}

基本数据类型 set 注入

<bean id="userServiceImpl" class="com.shsxt.service.impl.UserServiceImpl">
<property name="userDao" ref="userDao"/>
<property name="userName" value="sxt"/>
<property name="price" value="123"/>
</bean>
同时对应 Service 提供对应属性字段 以及 get 、set 方法即可

构造器注入

xml 配置(也提供对于基本数据类型、字符串等值的注入)

<bean id="userDao" class="com.shsxt.dao.UserDao"></bean>
<bean id="userServiceImpl2" class="com.shsxt.service.impl.UserServiceImpl2">
<constructor-arg ref="userDao"/>
</bean>

Java 类 提供构造函数

public class UserServiceImpl2 {
private UserDao userDao;
public UserServiceImpl2(UserDao userDao) {
this.userDao = userDao;
}
public void saveUser(User user){
userDao.add(user);
}
}

构造器注入字符串值,Index 属性为参数顺序 如果只有一个参数 index 可以不设置。

<bean id="userServiceImpl" class="com.shsxt.service.impl.UserServiceImpl">
<constructor-arg name="userName" index="0" value="123"/>
<constructor-arg name="userPwd" index="1" value="321"/>
</bean>

静态工厂注入

xml 配置

<bean id="userDao" class="com.shsxt.factory.StaticFactory" factory-
method="createUserDao"></bean>
<bean id="userService" class="com.shsxt.service.UserService">
<property name="userDao" ref="userDao"/>
</bean>

静态工厂类

package com.shsxt.factory;
import com.shsxt.dao.UserDao;
public class StaticFactory {
public static UserDao createUserDao(){
return new UserDao();
}
}

实例化工厂

xml 配置

<bean id="sxtBeanFactory" class="com.shsxt.factory.SxtBeanFactory"/>
<bean id="instanceFactory" class="com.shsxt.factory.InstanceFactory"/>
<bean id="userDao" factory-bean="instanceFactory" factory-method="createUserDao"/>
<bean id="userService" class="com.shsxt.service.UserService">
<property name="userDao" ref="userDao"></property>
</bean>

实际开发中基本使用 set 方式注入 bean

Spring AOP 简介

如果说 IoC 是 Spring 的核心,那么面向切面编程就是 Spring 最为重要的功能之一了,在数据库事务中切面编程被广泛使用。

AOP:Aspect Oriented Program 面向切面编程

功能划分

  • 核心业务:比如登录、增加数据、删除数据
  • 周边功能:比如性能统计、日志、事务管理等等

周边功能在 Spring 的面向切面编程AOP思想里,即被定义为切面。

在面向切面编程AOP的思想里面,核心业务功能和切面功能分别独立进行开发,然后把切面功能和核心业务功能 “编织” 在一起,这就叫AOP。

AOP 的作用

AOP 能够将那些与业务无关,却为业务模块所共同调用的逻辑或责任(比如事务处理、日志管理、权限控制)封装起来,便于减少系统的重复代码,降低模块间的耦合度,并有利于未来的可拓展性和可维护性。

AOP 当中的概念

  • 连接点(Joinpoint)

    被拦截到的每个点,spring 中指被拦截到的每一个方法,spring aop 一个连接点即代表一个方法的执行

  • 切入点(Pointcut)

    在哪些类,哪些方法上切入(where),spring 这块有专门的表达式语言定义。

  • 通知(Advice)

    在方法执行的什么时间(when:方法前/方法后/方法前后)做什么(what:增强的功能)

    • 前置通知 (前置增强) – before() 执行方法前通知
    • 返回通知(返回增强)-- afterReturn 方法正常结束返回后的通知
    • 异常抛出通知(异常抛出增强)–afetrThrow()
    • 最终通知—after 无论方法是否发生异常,均会执行该通知。
    • 环绕通知—around 包围一个连接点(join point)的通知,如方法调用。这是最强大的一种通知类型。 环绕通知可以在方法调用前后完成自定义的行为。它也会选择是否继续执行连接点或直接返回它们自己的返回值或抛出异常来结束执行。
  • 切面(Aspect)

    切面 = 切入点 + 通知,通俗点就是:在什么时间,什么地方,做什么增强

  • 目标对象(Target)

    被代理的目标对象

  • 引入(Introduction)

    引入允许我们向现有的类添加新方法或属性

  • 织入(Weaving)

    把切面加入到对象,并创建出代理对象的过程。(由 Spring 来完成)

AOP 编程解决日志处理问题

Aop 配置有两种方式 注解方式xml 方式

注解方式

  1. jar 包坐标引入

    l<!-- aop 注解核心包-->
    <dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjweaver</artifactId>
    <version>1.8.9</version>
    </dependency>
  2. beans.xml 配置

  • 添加命名空间
xmlns:aop="http://www.springframework.org/schema/aop"
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
  • 配置 aop 代理
<aop:aspectj-autoproxy/>
  1. 编写业务方法

    新建 package【task】,创建 UserService 类

@Service
public class UserService {
public void addUser(){
System.out.println("UserService addUser...");
}
}
  1. 编写 aop 实现类
/**
* 声明切面组件
* 1、连接点
* 2、切入点
* 3、通知
*/
@Component
@Aspect
public class LogCut {

/**
* 定义切入点 匹配方法规则定义
* 匹配规则表达式含义,拦截 com.shsxt.service 包下以及子包下所有类的所有方法
*/
@Pointcut("execution(* com.shsxt.service..*.*(..))")
public void cut(){}

/**
* 声明前置通知 并将通知应用到定义的切入点上
* 目标泪方法执行前执行该通知
*/
@Before(value = "cut()")
public void before(){
System.out.println("前置通知。。。");
}

/**
* 声明返回通知 并将通知应用到切入点上
* 目标类方法执行完毕执行该通知
*/
@AfterReturning(value = "cut()")
public void returnValue(){
System.out.println("返回通知。。。");
}

/**
* 声明最终通知 并将通知应用到切入点上
* 目标类方法执行过程中是否发生异常 均会执行该通知相当于异常中的finally
*/
@After(value = "cut()")
public void after(){
System.out.println("最终通知。。。");
}

/**
* 声明异常通知 并将通知应用到切入点上
* 目标类方法执行时发生异常 执行该通知
* @param e
*/
@AfterThrowing(value = "cut()", throwing = "e")
public void exception(Exception e){
System.out.println("异常通知...方法执行异常时执行:"+e");
}

/**
* 声明环绕通知,并将通知应用到切入点上
* 方法执行前后,通过环绕通知定义相应处理
* @param pjp
* @return
* @throws Throwable
*/
@Around(value = "cut()")
public Object around(ProceedingJoinPoint pjp) throws Throwable {

System.out.println("环绕前置。。。");

System.out.println("环绕通知");

System.out.println(pjp.getTarget() + " - " + pjp.getSignature());

Object proceed = pjp.proceed(); // 程序继续执行

System.out.println("环绕后置。。。");

return proceed;
}
}

xml 方式

  1. 声明 aop 代理(略)
  2. 配置切面、切入点、通知
<!--xml 配置 aop-->
<aop:config>
<!-- aop 切面配置 -->
<aop:aspect ref="logCut2">
<!-- 定义 aop 切入点 -->
<aop:pointcut id="cut" expression="execution(* com.shsxt.service..*.*(..))"/>
<!-- 配置前置通知 指定前置通知方法名 并引用切入点定义 -->
<aop:before method="before" pointcut-ref="cut"/>
<!-- 配置最终通知 指定最终通知方法名 并引用切入点定义 -->
<aop:after method="after" pointcut-ref="cut"/>
<!-- 配置返回通知 指定返回通知方法名 并引用切入点定义 -->
<aop:after-returning method="returnValue" pointcut-ref="cut"/>
<!-- 配置异常通知 指定异常通知方法名 并引用切入点定义 -->
<aop:after-throwing method="exception" throwing="e" pointcut-ref="cut"/>
<!-- 配置环绕通知 指定环绕通知方法名 并引用切入点定义 -->
<aop:around method="around" pointcut-ref="cut"/>
</aop:aspect>
</aop:config>
  1. 编写业务方法(略)
  2. 定义 bean
@Component
public class LogCut2 {

public void before(){
System.out.println("前置通知。。。222");
}

public void returnValue(){
System.out.println("返回通知。。。222");
}

public void after(){
System.out.println("最终通知。。。222");
}

public void exception(Exception e){
System.out.println("异常通知。。。222 方法执行异常时执行:"+e);
}

/**
* 声明环绕通知,并将通知应用到切入点上
* 方法执行前后,通过环绕通知定义相应处理
* @param pjp
* @return
* @throws Throwable
*/
public Object around(ProceedingJoinPoint pjp) throws Throwable {

System.out.println("环绕前置。。。222");

System.out.println("环绕通知。。。222");

System.out.println(pjp.getTarget() + " - " + pjp.getSignature());

Object proceed = pjp.proceed(); // 程序继续执行

System.out.println("环绕后置。。。222");

return proceed;
}
}
  1. 编写测试类 AopTest:
public class AopTest {

@Test
public void test01() {
ApplicationContext context = new ClassPathXmlApplicationContext("spring.xml");
UserService userService = (UserService) context.getBean("userService");
userService.addUser();
}
}
  1. 运行测试结果

总结

通过AOP我们可以对程序运行中的日志进行监控,可以通过底层反射机制拿到对象所有的方法属性,包括注解。。