Skip to content

Spring

logo

概述

https://docs.spring.io/spring-framework/docs/current/reference/html/core.html

  • Spring是一个项目管理框架,同时也是一套Java EE解决方案。

  • Spring是众多优秀设计模式的组合(工厂、单例、代理、适配器、包装器、观察者、模板、策略)。

  • Spring并未替代现有框架产品,而是将众多框架进行有机整合,简化企业级开发,俗称"胶水框架"。

原生web开发中存在哪些问题?

  • 传统Web开发存在硬编码所造成的过度程序耦合(例如:Service中作为属性Dao对象)。

  • 部分Java EE API较为复杂,使用效率低(例如:JDBC开发步骤)。

  • 侵入性强,移植性差(例如:DAO实现的更换,从Connection到SqlSession)。

Spring的发展历程

Spring是分层的、JavaSE/EE一站式(full-stack)、轻量级开源框架。以IoC(Inverse Of Control:反转控制)和AOP(Aspect Oriented Programming:面向切面编程)为内核,提供了表现层Spring MVC和持久层Spring JDBC以及业务层事务管理等众多的企业级应用技术,还能整合开源世界众多著名的第三方框架和类库,逐渐成为使用最多的Java EE企业应用开源框架。

  • 1997 年

    IBM提出了EJB的思想

  • 1998 年

    SUN制定开发标准规范EJB1.0

  • 1999 年

    EJB1.1发布

  • 2001 年

    EJB2.0发布

  • 2003 年

    EJB2.1发布

  • 2006 年

    EJB3.0发布

  • 2004 年

    Spring 1.0版本发布

  • 2006 年 10 月

    Spring 2.0发布,Spring下载量超过100万。Spring 2.0具有可扩展的XML配置功能,用于简化XML配置,支持Java 5,额外的IoC容器扩展,支持动态语言(如groovy,aop增强功能和新的bean范围)

  • 2007 年 11 月

    在Rod领导下,管理Spring项目的Interface21公司更名为SpringSource。同时发布了Spring 2.5。Spring 2.5中的主要新功能包括支持Java 6/Java EE 5,支持注解配置,classpath中的组件自动检测和兼容OSGi的bundle

  • 2008 年

    SpringSource在通过来自加速合作伙伴和B轮融资筹集了额外资本。SpringSource在此期间收购了多家公司(Covalent,Hyperic,G2One等)。

  • 2009 年 8 月

    SpringSource以4.2亿美元被VMWare收购。SpringSource在几周内收购了cloud foundry,这是一家云PaaS提供商。2015年,cloud foundry转移到了非营利cloud foundry基金会。

  • 2009 年 12 月

    Spring 3.0发布,Spring 3.0具有许多重要特性,如重组模块系统,支持Spring表达式语言,基于Java的bean配置(JavaConfig),支持嵌入式数据库(如HSQL,H2和Derby),模型验证/ REST支持和对Java EE 6的支持。

  • 2012 年 7 月

    Rod Johnson离开了团队。

  • 2013 年 4 月

    VMware和EMC通过GE投资创建了一家名为Pivotal的合资企业。所有的6应用项目都转移到了Pivotal。

  • 2013 年 12 月

    Pivotal宣布发布Spring框架4.0。Spring 4.0是Spring框架的一大进步,它包含了对Java 8的全面支持,更高的第三方库依赖性(groovy 1.8+,ehcache 2.1+,hibernate 3.6+等),Java EE 7支持,groovy DSL for bean定义,对websockets的支持以及对泛型类型的支持作为注入bean的限定符。

  • 2017 年 9 月

    Spring框架5.0发布,对JDK 9运行时兼容性,在Spring Framework代码中使用JDK 8特性,添加了响应式编程支持、函数式Web框架、Jigsaw的Java模块化、对Kotlin支持......

Spring 的优势

Spring 出现是为了解决 JavaEE 实际问题

  1. 方便解耦,简化开发

    通过Spring提供的IoC容器,可以将对象间的依赖关系交由Spring进行控制,避免硬编码所造成的过度程序耦合。用户也不必再为单例模式类、属性文件解析等这些很底层的需求编写代码,可以更专注于上层的应用。

  2. AOP 编程的支持

    通过Spring的AOP功能,方便进行面向切面的编程,许多不容易用传统OOP(面向对象)实现的功能可以通过AOP轻松应付。

  3. 声明式事务的支持

    可以将我们从单调烦闷的事务管理代码中解脱出来,通过声明式方式灵活的进行事务的管理,提高开发效率和质量。

  4. 方便程序的测试

    可以用非容器依赖的编程方式进行几乎所有的测试工作,测试不再是昂贵的操作,而是随手可做的事情。

  5. 方便集成各种优秀框架

    Spring可以降低各种框架的使用难度,提供了对各种优秀框架Struts、Hibernate、Hessian(远程通讯,类似于webService)、Quartz(定时任务)等的直接支持。

  6. 降低 JavaEE API 的使用难度

    Spring对JavaEE API(如JDBC、JavaMail、远程调用等)进行了薄薄的封装层,使这些API的使用难度大为降低。

Spring生态架构

spring-overview

内聚和耦合

内聚(cohesion)

  • 偶然内聚
  • 逻辑内聚
  • 时间内聚
  • 过程内聚
  • 通信内聚
  • 顺序内聚
  • 功能内聚

耦合(coupling)

  • 内容耦合
  • 公共耦合
  • 外部耦合
  • 控制耦合
  • 标记耦合
  • 数据耦合
  • 非直接耦合

1.6 应用程序参数化

回顾 - 设计模式

原型模式

用原型实例指定创建对象的各类,并且通过拷贝这些原型创建新的对象。

单例模式

保证一个类仅有一个实例,并提供一个访问它的全局访问点。

工厂模式

抽象工厂模式

提供一个创建一系列想着或相互依赖对象的接口,而无需指定它们具体的类。

工厂方法模式

定义一个用于创建对象的接口,让子类决定实例化哪一个类。Factory Method使一个类的实例化延迟到其子类。

简单工厂模式

代理模式

为其他对象提供一种代理以控制对这个对象的访问。

模板方法模式

定义一个操作中的算法的骨架,而将一些步骤延迟到子类中。TemplateMethod使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。

核心概念

IoC

Inverse of Controll: 控制反转

反转了依赖关系的满足方式,由之前的自己创建依赖对象,变为由工厂推送。(变主动为被动,即反转)

解决了具有依赖关系的组件之间的强耦合,使得项目形态更加稳健

DI

Dependency Injection: 依赖注入

所谓依赖注入,是指程序运行过程中,如果需要调用另一个对象协助时,无须在代码中创建被调用者,而是依赖于外部的注入。Spring的依赖注入对调用者和被调用者几乎没有任何要求,完全支持对POJO之间依赖关系的管理

AOP

Aspect Orentied Programming: 面向切面编程

通过预编译方式和运行期间动态代理实现程序功能的统一维护的一种技术。AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。

IoC&DI

基于xml

快速入门

导入依赖
  • pom.xml
xml
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
    <version>5.3.7</version>
</dependency>

<dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>4.13.2</version>
    <scope>test</scope>
</dependency>
代码编写
  • UserService.java
java
public interface UserService {
}
  • UserServiceImpl.java
java
public class UserServiceImpl implements UserService {
}
配置beans
  • applicationContext.xml
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:p="http://www.springframework.org/schema/p"
       xmlns:c="http://www.springframework.org/schema/c"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="userService" class="com.future_weaver.service.impl.UserServiceImpl"/>

</beans>
编写测试代码
  • UserControllerTest.java
java
public class UserControllerTest {
    @Test
    public void testInit() {
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");

        UserService userService = context.getBean("userService", UserService.class);
        System.out.println(userService);
    }
}
小结
  1. 导入依赖spring-context
  2. 创建 xml 文件,配置 bean
  3. 编写代码,从 spring 容器中获取 java 对象

bean标签详解

练习
  • UserServiceImpl.java
java
public class UserServiceImpl implements UserService {
    public void init() {
        System.out.println("UserServiceImpl初始化");
    }

    public void destroy() {
        System.out.println("UserServiceImpl销毁");
    }
}
  • applicationContext.xml
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:p="http://www.springframework.org/schema/p"
       xmlns:c="http://www.springframework.org/schema/c"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="userService"
          class="com.future_weaver.service.impl.UserServiceImpl"
          init-method="init"
          destroy-method="destroy"
          scope="prototype"/>

</beans>
  • UserControllerTest.java
java
public class UserControllerTest {
    @Test
    public void testInit() {
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");

        UserService userService = context.getBean("userService", UserService.class);
        System.out.println(userService);

        context.close();
    }
}
id

Spring-Bean唯一标识

class

Spring-Bean全限定类名

init-method

指定Spring-Bean初始化函数

destroy-method

指定Spring-Bean销毁函数

scope

指定Spring-Bean生命周期 Spring-Bean的生命周期有以下6种:

  • singleton

单例

  • prototype

多例

  • request

请求域

  • session

会话域

  • application

上下文域

  • websocket

会话建立连接时创建,断开连接时销毁

DI

构造方法注入
  • UserDAO.java
java
public interface UserDAO {
}
  • UserDAOImpl.java
java
public class UserDAOImpl implements UserDAO {
    public void init() {
        System.out.println("UserDAOImpl初始化");
    }

    public void destroy() {
        System.out.println("UserDAOImpl销毁");
    }
}
  • UserServiceImpl.java
java
public class UserServiceImpl implements UserService {
    private UserDAO dao;

    public UserServiceImpl() {
    }

    public UserServiceImpl(UserDAO dao) {
        this.dao = dao;
    }

    public UserDAO getDao() {
        return dao;
    }

    public void setDao(UserDAO dao) {
        this.dao = dao;
    }

    public void init() throws IOException {
        System.out.println("UserServiceImpl初始化");
    }

    public void destroy() {
        System.out.println("UserServiceImpl销毁");
    }
}
标签体形式

constructor-arg

xml
    <bean id="userDAO"
          class="com.future_weaver.dao.impl.UserDAOImpl"
          init-method="init"
          destroy-method="destroy"/>

    <bean id="userService"
          class="com.future_weaver.service.impl.UserServiceImpl"
          init-method="init"
          destroy-method="destroy">
        <constructor-arg name="dao" ref="userDAO"/>
    </bean>
【扩展】标签参数形式

c-namespace

xml
    <bean id="userDAO"
          class="com.future_weaver.dao.impl.UserDAOImpl"
          init-method="init"
          destroy-method="destroy"/>

    <bean id="userService"
          class="com.future_weaver.service.impl.UserServiceImpl"
          init-method="init"
          destroy-method="destroy"
          c:dao-ref="userDAO"/>
set方法注入
标签体形式

property

xml
    <bean id="userDAO"
          class="com.future_weaver.dao.impl.UserDAOImpl"
          init-method="init"
          destroy-method="destroy"/>

    <bean id="userService"
          class="com.future_weaver.service.impl.UserServiceImpl"
          init-method="init"
          destroy-method="destroy">
        <property name="dao" ref="userDAO"/>
    </bean>
【扩展】标签属性方式

p-namespace

xml
    <bean id="userDAO"
          class="com.future_weaver.dao.impl.UserDAOImpl"
          init-method="init"
          destroy-method="destroy"/>

    <bean id="userService"
          class="com.future_weaver.service.impl.UserServiceImpl"
          init-method="init"
          destroy-method="destroy"
          p:dao-ref="userDAO"/>

IoC

构造方法创建

默认方式即构造方法创建

xml
    <context:property-placeholder location="classpath:jdbc.properties"/>
    <bean id="dataSourceBasedConstructor"
          class="com.alibaba.druid.pool.DruidDataSource"
          p:driverClassName="${jdbc.driver.className}"
          p:url="${jdbc.url}"
          p:username="${jdbc.username}"
          p:password="${jdbc.password}"/>
静态工厂创建
xml
    <util:properties id="druidConfiguration" location="classpath:druid.properties"/>
    <bean id="dataSourceBasedFactoryMethod"
          class="com.alibaba.druid.pool.DruidDataSourceFactory"
          factory-method="createDataSource"
          c:properties-ref="druidConfiguration">
    </bean>
实例工厂创建
xml
    <bean id="mybatis-config"
          class="org.apache.ibatis.io.Resources"
          factory-method="getResourceAsStream"
          c:resource="mybatis-config.xml"/>

    <bean id="sqlSessionFactoryBuilder"
          class="org.apache.ibatis.session.SqlSessionFactoryBuilder"/>

    <bean id="sqlSessionFactory"
          factory-bean="sqlSessionFactoryBuilder"
          factory-method="build"
          c:inputStream-ref="mybatis-config"/>

    <bean id="sqlSession"
          factory-bean="sqlSessionFactory"
          factory-method="openSession"
          c:autoCommit="false"
          destroy-method="close"/>
【了解】集合注入
xml
    <!--List-->
    <property name="name">
        <list>
            <value>Leonardo</value>
            <value>DiCaprio</value>
        </list>
    </property>

    <!--Array-->
    <property name="films">
        <array>
            <value>Titanic</value>
            <value>Catch me if you can</value>
            <value>Inception the dream is real</value>
        </array>
    </property>

    <!--Set-->
    <property name="mobile">
        <set>
            <value>13777777777</value>
            <value>13888888888</value>
            <value>13999999999</value>
        </set>
    </property>

    <!--Map-->
    <property name="birthplace">
        <map>
            <entry key="nationality" value="America" />
            <entry key="state" value="California" />
            <entry key="county" value="Los Angeles" />
        </map>
    </property>

    <!--Properties-->
    <property name="act">
        <props>
            <prop key="Titanic">Jack</prop>
            <prop key="Catch">Frank Abagnale</prop>
            <prop key="Inception">dom cobb</prop>
        </props>
    </property>
xml
    <bean id="dataSourceBasedProperties"
          class="com.alibaba.druid.pool.DruidDataSourceFactory"
          factory-method="createDataSource">
        <constructor-arg name="properties">
            <props>
                <prop key="driverClassName">com.mysql.jdbc.Driver</prop>
                <prop key="url">jdbc:mysql://localhost:3306/app?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=UTC</prop>
                <prop key="username">root</prop>
                <prop key="password">root</prop>
            </props>
        </constructor-arg>
    </bean>

基于注解

IoC

构造方法创建
快速入门
  • AddressService.java
java
public interface AddressService {
}
  • AddressServiceImpl.java
java
// @Component
// @Controller
// @Service
// @Repository
@Service("addressService")
public class AddressServiceImpl implements AddressService {
}
  • AddressControllerTest.java
java
public class AddressControllerTest {
    @Test
    public void testInit() {
        AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext("com.future_weaver");

        AddressService bean = applicationContext.getBean("addressService", AddressService.class);
        System.out.println(bean);

        applicationContext.close();
    }
}
控制生命周期
  • AddressServiceImpl.java
java
@Service("addressService")
public class AddressServiceImpl implements AddressService {
    @PostConstruct
    public void init() {
        System.out.println("AddressServiceImpl初始化");
    }

    @PreDestroy
    public void destroy() {
        System.out.println("AddressServiceImpl销毁");
    }
}
控制作用范围
  • AddressServiceImpl.java
java
@Service("addressService")
@Scope("prototype")
public class AddressServiceImpl implements AddressService {
    @PostConstruct
    public void init() {
        System.out.println("AddressServiceImpl初始化");
    }

    @PreDestroy
    public void destroy() {
        System.out.println("AddressServiceImpl销毁");
    }
}
工厂创建
  • RepositoryConfig.java
java
@Configuration
public class RepositoryConfig {
    @Bean(name="sqlSession", destroyMethod = "close")
    @Scope("prototype")
    public SqlSession sqlSession() throws IOException {
        System.out.println("SqlSession初始化");
        InputStream is = Resources.getResourceAsStream("mybatis-config.xml");
        SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
        SqlSessionFactory factory = builder.build(is);
        return factory.openSession(true);
    }
}
  • RepositoryTest.java
java
public class RepositoryTest {
    @Test
    public void testRepository() {
        AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext("com.future_weaver");

        SqlSession sqlSession = applicationContext.getBean(SqlSession.class);
        System.out.println(sqlSession);

        applicationContext.close();
    }
}

DI

自定义类注入
  • AddressDAOImpl.java
java
@Repository("addressDAO")
public class AddressDAOImpl implements AddressDAO {
    public final String description = "addressDAOImpl";
}
  • AddressDAOImpl2.java
java
@Repository("addressDAO2")
public class AddressDAOImpl2 implements AddressDAO {
    public final String description = "addressDAOImpl2";
}
  • AddressServiceImpl.java
java
@Service("addressService")
@PropertySource("classpath:jdbc.properties")
public class AddressServiceImpl implements AddressService {

    @Autowired
    private SqlSession sqlSession;

    @Value("abc")
    private String description;

    @Autowired
    @Qualifier("addressDAO2")
    private AddressDAO addressDAO;

    @Autowired
    @Qualifier("addressDAO2")
    private AddressDAO addressDAO22;

    @Value("${jdbc.url}")
    private String jdbcURL;

//    public AddressServiceImpl() {
//    }

//    @Autowired
//    public AddressServiceImpl(@Autowired SqlSession sqlSession,
//                              @Value("abc") String description,
//                              @Autowired @Qualifier("addressDAO2") AddressDAO addressDAO,
//                              @Autowired @Qualifier("addressDAO2") AddressDAO addressDAO22
//                              @Value("${jdbc.url}") String jdbcURL) {
//        this.sqlSession = sqlSession;
//        this.description = description;
//        this.addressDAO = addressDAO;
//        this.addressDAO22 = addressDAO22;
//        this.jdbcURL = jdbcURL;
//    }

    public SqlSession getSqlSession() {
        return sqlSession;
    }

//    @Autowired
    public void setSqlSession(SqlSession sqlSession) {
        this.sqlSession = sqlSession;
    }

    public String getDescription() {
        return description;
    }

//    @Value("abc")
    public void setDescription(String description) {
        this.description = description;
    }

    public AddressDAO getAddressDAO() {
        return addressDAO;
    }

//    @Autowired
//    @Qualifier("addressDAO2")
    public void setAddressDAO(AddressDAO addressDAO) {
        this.addressDAO = addressDAO;
    }

    public AddressDAO getAddressDAO22() {
        return addressDAO22;
    }

//    @Autowired
//    @Qualifier("addressDAO2")
    public void setAddressDAO22(AddressDAO addressDAO22) {
        this.addressDAO22 = addressDAO22;
    }

    public String getJdbcURL() {
        return jdbcURL;
    }

//    @Value("${jdbc.url}")
    public void setJdbcURL(String jdbcURL) {
        this.jdbcURL = jdbcURL;
    }

    @Override
    public String toString() {
        return "AddressServiceImpl{" +
                "sqlSession=" + sqlSession +
                ", description='" + description + '\'' +
                ", addressDAO=" + addressDAO +
                ", addressDAO22=" + addressDAO22 +
                ", jdbcURL=" + jdbcURL +
                '}';
    }

    @PostConstruct
    public void init() {
        System.out.println("AddressServiceImpl初始化");
    }

    @PreDestroy
    public void destroy() {
        System.out.println("AddressServiceImpl销毁");
    }
}
外部类注入
  • RepositoryConfig.java
java
@Configuration
@PropertySource("jdbc.properties")
public class RepositoryConfig {
    @Value("${jdbc.driver.className}")
    private String driverClassName;

    @Value("${jdbc.url}")
    private String url;

    @Value("${jdbc.username}")
    private String username;

    @Value("${jdbc.password}")
    private String password;

    @Bean
    public SqlSessionFactoryBuilder sqlSessionFactoryBuilder() {
        return new SqlSessionFactoryBuilder();
    }

    @Bean
    public SqlSessionFactory sqlSessionFactory(SqlSessionFactoryBuilder sqlSessionFactoryBuilder) throws IOException {
        InputStream is = Resources.getResourceAsStream("mybatis-config.xml");
        return sqlSessionFactoryBuilder.build(is);
    }

    @Bean(name="sqlSession", destroyMethod = "close")
    @Scope("singleton")
    public SqlSession sqlSession(SqlSessionFactory sqlSessionFactory) throws IOException {
        return sqlSessionFactory.openSession(true);
    }

    @Bean
    public DataSource dataSourceBasedFactoryMethod() throws Exception {
        ClassPathResource resource = new ClassPathResource("druid.properties");
        InputStream is = resource.getInputStream();
        Properties properties = new Properties();
        properties.load(is);
        return DruidDataSourceFactory.createDataSource(properties);
    }

    @Bean
    public DataSource dataSourceBasedConstructor() {
        DruidDataSource result = new DruidDataSource();
        result.setDriverClassName(driverClassName);
        result.setUrl(url);
        result.setUsername(username);
        result.setPassword(password);
        return result;
    }
}

xml与注解整合

注解整合注解
  • @Import

  • @ComponentScan

xml整合xml
  • <import/>
注解整合xml
  • @ImportResource
xml整合注解
  • <context:component-scan/>

Spring整合junit

导入依赖

  • pom.xml
xml
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-test</artifactId>
    <version>5.3.3</version>
</dependency>

编写测试

  • SpringTest.java
java
@RunWith(SpringJUnit4ClassRunner.class)
@ContextHierarchy({
        @ContextConfiguration("classpath:applicationContext.xml"),
        @ContextConfiguration(classes = RepositoryConfig.class)
})
public class SpringTest {
    @Autowired
    private UserService userService;

    @Autowired
    private AddressService addressService;

    @Test
    public void testWired() {
        System.out.println(userService);

        System.out.println(addressService);
    }
}

总结

  • ClassPathXmlApplicationContext

    基于xml的Spring容器,注意使用结束之后释放(容器也是一种资源)

  • AnnotationConfigApplicationContext

    基于注解的Spring容器,注意使用结束之后释放

  • ClassPathResource

    获取相对路径的文件

  • <context:component-scan>

    用于xml整合注解

  • @Component

    IoC注解,用于描述一个类,当Spring容器扫描到此类之后,将此类的实例放到Spring容器当中

    默认情况下,容器bean的id为类名的大驼峰转成小驼峰

    如果不想默认,那么显式指定@Component("userService")

  • @Controller

    同上

  • @Service

    同上

  • @Repository

    同上

  • @PostConstruct

    生命周期注解,在此SpringBean实例化之后,执行此注解所描述的方法

  • @PreDestroy

    生命周期注解,在此SpringBean销毁之前,执行此注解所描述的方法

  • @Scope

    描述作用范围,常用的作用范围singleton(单例)、prototype(多例)

    singleton: 生命周期全部交由Spring容器控制,在容器启动时创建,在容器释放时销毁

    prototype: 生命周期的创建过程交由Spring,销毁过程交由JDK

  • @Configuration

    以上7种注解,前提是能够改写的Java类,但有些Java类,我们没有它的编写权。所以通过代码创建Spring Bean

  • @Bean

    当Spring容器扫描到此注解之后,此注解所描述的方法的返回值,会放到Spring容器中

    前提,此方法所在的类,必须由Configuration描述,而且必须Spring容器能扫描到此类

  • @Autowired

    依赖注入注解

    能够注入引用类型,自动注入,能够根据类名和变量和自动注入

    弊端: 如果在Spring容器中,有多个相同类型的SpringBean,根据变量名,在Spring容器中,又找不到

  • @Qualifier

    需要和Autowired搭配使用,用于描述自动注入时,bean的id

  • @Resource

    与Autowired和Qualifier搭配使用的效果一样,但只能用于描述成员变量、方法、类型(接口、抽象类、类、枚举...),无法用于描述参数

  • @Value

    注入常量或配置文件中的选项

  • @PropertySource

    和Value搭配使用的

  • @ComponentScan

    整合相关注解,用于告诉Spring容器,扫哪些包

  • @Import【非重点】

    用于整合类另一个配置类

  • @ImportResource【非重点】

    注解整合xml

AOP

概述

什么是AOP

在软件业,AOP 为 Aspect Oriented Programming 的缩写,意为:面向切面编程,通过预编译方式和运行期间动态代理实现程序功能的统一维护的一种技术。AOP 是 OOP 的延续,是软件开发中的一个热点,也是 Spring 框架中的一个重要内容,是函数式编程的一种衍生范型。利用 AOP 可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。

AOP:全称是 Aspect Oriented Programming 即:面向切面编程。

简单的说它就是把我们程序重复的代码抽取出来,在需要执行的时候,使用动态代理的技术,在不修改源码的基础上,对我们的已有方法进行增强。

作用

在程序运行期间,不修改源码对已有方法进行增强。

优势
  • 减少重复代码
  • 提高开发效率
  • 维护方便
实现原理

使用动态代理技术

  1. 基于接口的 JDK 官方的动态代理(优先使用)
  2. 基于子类的第三方的 cglib 的动态代理
  3. 基于 Spring 的动态代理 ProxyFactory

代理模式

概述

为其他对象提供一种代理,以控制对这个对象的访问。

Provide a surrogate or placeholder for another object to control access to it.

与装饰器模式的区别
  • 随用随建,随用随加载
  • 不修改源码的基础上增强
静态代理

先确定实体(RealSubject),后确定代理(Proxy)

  • 结构模型

    Proxy-Pattern-Class-Diagram

  • 行为模型

    Proxy-Pattern-Sequence-Diagram

动态代理

先确定代理(Proxy),后确定实体(RealSubject)

Dynamic-Proxy-MapperProxy

Java动态代理

Dynamic-Proxy-jdk

cglib动态代理

Dynamic-Proxy-cglib

Spring动态代理

ProxyMultipleInheritance

java
ProxyFactory proxyFactory = new ProxyFactory(new Object());

proxyFactory.addAdvice(
        new DelegatePerTargetObjectIntroductionInterceptor(ConcurrentMother.class, Mother.class));

proxyFactory.addAdvice(
        new DelegatePerTargetObjectIntroductionInterceptor(ConcurrentFather.class, Father.class));

T proxy = (T)proxyFactory.getProxy(proxyFactory.getClass().getClassLoader());

proxy.找私房钱();
proxy.掏鸟窝();

术语

通知【重点】

定义了切面的"什么"和"何时"

什么时候,做什么

  • 前置通知
  • 后置通知
  • 返回通知
  • 异常通知
  • 环绕通知

连接点

joinpoint

虚拟概念。满足切点扫描条件的所有的时机。

切点【重点】

pointcut

定义了切面的"何处"

在哪里

切面【重点】

aspect

通知和切点的结合

引入

Introduction

向现有的类添加方法或属性。

织入

Weaving

把切面应用到目录对象,并创建新的代理对象的过程。

定义切面

基于xml的AOP配置

动态方式(切点+通知)
定义切点
xml
<bean id="imgDownloader"
      class="com.futureweaver.service.impl.ImgDownloaderImpl"/>

<bean id="imgDownloaderLogger"
      class="com.futureweaver.advisor.ImgDownloaderLogger"/>

<!-- Spring容器中的任意bean -->
<!-- 在执行download方法前,通知imgDownloaderLogger对象的beforeLog方法 -->
<!-- 在执行download方法后,通知imgDownloaderLogger对象的afterLog方法 -->
<aop:config>
    <!-- aspect: 切面,通知和切点的结合 -->
    <aop:aspect ref="imgDownloaderLogger">
        <!-- pointcut: 切点,定义了"何处" -->
        <!-- Spring容器中的任意bean的download方法 -->
        <!-- 为此切点定义一个id: "download-point-cut" -->
        <aop:pointcut id="download-point-cut" expression="execution(* download(..))"/>

        <!-- 当download-point-cut切点,执行之前(before),执行imgDownloadLogger的beforeLog方法 -->
        <aop:before pointcut-ref="download-point-cut" method="beforeLog"/>

        <!-- 当download-point-cut切点,执行之后(after),执行imgDownloadLogger的afterLog方法 -->
        <aop:after pointcut-ref="download-point-cut" method="afterLog"/>
    </aop:aspect>
</aop:config>
定义通知
java
public class MyAspect {
    public void beforeMethod(JoinPoint joinPoint) {}

    public void afterMethod(JoinPoint joinPoint) {}

    public void afterReturning(JoinPoint joinPoint, Object result) {}

    public void afterThrowing(JoinPoint joinPoint, Exception ex) {}

    public Object aroundMethod(ProceedingJoinPoint pjd) {}
}
静态方式(切点+通知类)
定义切点
xml
<bean id="imgDownloader"
      class="com.futureweaver.service.impl.ImgDownloaderImpl"/>

<bean id="imgDownloaderLogger"
      class="com.futureweaver.advisor.ImgDownloaderLogger"/>

<aop:config>
    <aop:pointcut id="download-point-cut" expression="execution(* download(..))" />
    <aop:advisor advice-ref="Advisor" pointcut-ref="download-point-cut"/>
</aop:config>
定义通知
java
// Before Advice
MethodBeforeAdvice.class;

// After Advice
AfterAdvice.class;

// After Returning Advice
AfterReturningAdvice.class;

// Throws Advice
ThrowsAdvice.class;

// Around Advice
MethodInterceptor.class;

基于注解的aop配置

  • ImgDownloaderLogger.java
java
@Order(1)
@Component
@Aspect
@EnableAspectJAutoProxy
// xml嵌套注解时,应使用<aop:aspectj-autoproxy/>配置
public class ImgDownloaderLogger {

    @Pointcut("execution(* download(..))")
    public void declareJoinPointExpression() {}

    @Before("declareJoinPointExpression()")
    public void beforeLog(JoinPoint joinPoint) {
        System.out.println("before log...");
    }

    @After("declareJoinPointExpression()")
    public void afterLog(JoinPoint joinPoint) {
        System.out.println("after log...");
    }

    @AfterReturning(value="declareJoinPointExpression()", returning="result")
    public void afterReturning(JoinPoint joinPoint, Object result) {}

    @AfterThrowing(value="declareJoinPointExpression()", throwing="ex")
    public void afterThrowing(JoinPoint joinPoint, Exception ex) {}

    @Around("declareJoinPointExpression()")
    public Object aroundMethod(ProceedingJoinPoint pjd) {}
}
  • ImgDownloaderImpl.java
java
@Controller("imgDownloader")
public class ImgDownloaderImpl implements ImgDownloader {
    @Override
    public InputStream download(String url) {
        System.out.println("下载......");
        return null;
    }
}

切点表达式

xml
<!--
execution(
    [ModifiersPattern] 
    TypePattern 
    [TypePattern . ] 
    IdPattern 
    (TypePattern | ".." , ... ) 
    [ throws ThrowsPattern ]
)

execution(
    [访问修饰符]
    [返回类型]
    [全限定类名 . ] 
    访问名
    (参数类型 | ".." , ... ) 
    [ throws 异常类型 ]
)
-->

<aop:pointcut id="my-point-cut" expression="execution([访问修饰符] 包名.类名(..))"/>

<!--匹配参数-->
<aop:pointcut id="my-point-cut" expression="execution(* *(com.futureweaver.User))" />

<!--匹配方法名(无参)-->
<aop:pointcut id="my-point-cut" expression="execution(* save())" />

<!--匹配方法名(任意参数)-->
<aop:pointcut id="my-point-cut" expression="execution(* save(..))" />

<!--匹配返回值类型-->
<aop:pointcut id="my-point-cut" expression="execution(com.futureweaver.User *(..))" />

<!--匹配类名-->
<aop:pointcut id="myPointCut" expression="execution(* com.futureweaver.service.impl.UserServiceImpl.*(..))" />

<!--匹配包名-->
<aop:pointcut id="myPointCut" expression="execution(* com.futureweaver.service.impl.*.*(..))" />

<!--匹配包名、以及子包名-->
<aop:pointcut id="myPointCut" expression="execution(* com.futureweaver.service..*.*(..))" />

整合Spring+MyBatis

http://mybatis.org/spring/zh/getting-started.html

导入依赖

xml
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-jdbc</artifactId>
    <version>5.2.9.RELEASE</version>
</dependency>

<dependency>
    <groupId>org.mybatis</groupId>
    <artifactId>mybatis-spring</artifactId>
    <version>1.3.3</version>
</dependency>

配置SqlSessionFactory

xml
<util:properties id="druidConfiguration" location="classpath:druid.properties"/>
<bean id="dataSource"
      class="com.alibaba.druid.pool.DruidDataSourceFactory"
      factory-method="createDataSource"
      c:properties-ref="druidConfiguration"/>

<bean id="mybatis-config"
      class="org.springframework.core.io.ClassPathResource"
      c:path="mybatis-config.xml"/>

<bean id="sqlSessionFactory"
      class="org.mybatis.spring.SqlSessionFactoryBean"
      p:configLocation-ref="mybatis-config"
      p:dataSource-ref="dataSource"/>

配置MapperScannerConfigure

xml
<bean id="mapperScannerConfigurer"
      class="org.mybatis.spring.mapper.MapperScannerConfigurer"
      p:basePackage="com.futureweaver.mapper"/>

事务

配置事务管理器

xml
<bean id="transactionManager" 
      class="org.springframework.jdbc.datasource.DataSourceTransactionManager"
      p:dataSource-ref="dataSource"/>

配置事务通知【基于xml】

xml
<aop:config>
      <aop:pointcut expression="execution(* com.futureweaver.service.impl.UserServiceImpl.*(..))" id="pc"/>
    <aop:advisor advice-ref="txManager" pointcut-ref="pc"/>
</aop:config>

<tx:advice id="txManager" transaction-manager="transactionManager">
    <tx:attributes>
        <!--<tx:method name="insertUser" rollback-for="Exception" isolation="DEFAULT"    
                  propagation="REQUIRED" read-only="false"/>-->
        <!-- 以User结尾的方法,切入此方法时,采用对应事务实行-->
        <tx:method name="*User" rollback-for="Exception"/>
        <!-- 以query开头的方法,切入此方法时,采用对应事务实行 -->
        <tx:method name="query*" propagation="SUPPORTS"/>
        <!-- 剩余所有方法 -->
        <tx:method name="*"/>
    </tx:attributes>
</tx:advice>

事务配置【基于注解】

  • <tx:annotation-driven/>
  • @EnableTransactionManagement
  • @Transactional(rollbackFor = Throwable.class)

事务属性

隔离级别

概念

isolation 隔离级别

名称描述
default(默认值)(采用数据库的默认的设置) (建议)
read-uncommited读未提交
read-commited读提交 (Oracle数据库默认的隔离级别)
repeatable-read可重复读 (MySQL数据库默认的隔离级别)
serialized-read序列化读

隔离级别由低到高为:read-uncommited < read-commited < repeatable-read < serialized-read

特性
  • 安全性:级别越高,多事务并发时,越安全。因为共享的数据越来越少,事务间彼此干扰减少。

  • 并发性:级别越高,多事务并发时,并发越差。因为共享的数据越来越少,事务间阻塞情况增多。

并发问题

事务并发时的安全问题

问题描述
脏读一个事务读取到另一个事务还未提交的数据。大于等于 read-commited 可防止
不可重复读一个事务内多次读取一行数据的相同内容,其结果不一致。大于等于 repeatable-read 可防止
幻影读一个事务内多次读取一张表中的相同内容,其结果不一致。serialized-read 可防止

传播行为

propagation传播行为

当涉及到事务嵌套(Service调用Service)时,可以设置:

  • SUPPORTS = 不存在外部事务,则不开启新事务;存在外部事务,则合并到外部事务中。(适合查询)

  • REQUIRED = 不存在外部事务,则开启新事务;存在外部事务,则合并到外部事务中。 (默认值)(适合增删改)

读写性

readonly 读写性

  • true:只读,可提高查询效率。(适合查询)

  • false:可读可写。 (默认值)(适合增删改)

事务超时

timeout事务超时时间

当前事务所需操作的数据被其他事务占用,则等待。

  • 100:自定义等待时间100(秒)。
  • -1:由数据库指定等待时间,默认值。(建议)

事务回滚

rollback-for 回滚属性

  • 如果事务中抛出 RuntimeException,则自动回滚

  • 如果事务中抛出 CheckException(非运行时异常 Exception),不会自动回滚,而是默认提交事务

  • 处理方案 : 将CheckException转换成RuntimException上抛,或 设置 rollback-for="Exception"

模板配置

pom.xml

xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

#if (${HAS_PARENT})
    <parent>
        <groupId>${PARENT_GROUP_ID}</groupId>
        <artifactId>${PARENT_ARTIFACT_ID}</artifactId>
        <version>${PARENT_VERSION}</version>
#if (${HAS_RELATIVE_PATH})
        <relativePath>${PARENT_RELATIVE_PATH}</relativePath>
#end
    </parent>

#end
    <groupId>${GROUP_ID}</groupId>
    <artifactId>${ARTIFACT_ID}</artifactId>
    <version>${VERSION}</version>

    <dependencies>
        ${END}
        <!-- mysql-connector-j -->
        <!-- spring-test -->
        <!-- spring-aspects -->
        <!-- spring-context -->
        <!-- spring-jdbc -->
        <!-- druid -->
        <!-- mybatis -->
        <!-- mybatis-spring -->
        <!-- pagehelper -->
        <!-- junit -->
        <!-- log4j -->
        <!-- commons-dbutils -->
        <!-- commons-beanutils -->
        <!-- commons-io -->
        <!-- fastjson -->
        <!-- jackson-databind -->
        <!-- jakarta.servlet-api -->
        <!-- jakarta.servlet.jsp.jstl-api -->
        <!-- jakarta.servlet.jsp.jstl -->
    </dependencies>

    <build>
        <resources>
            <resource>
                <directory>src/main/java</directory>
                <includes>
                    <include>*.xml</include>
                    <include>**/*.xml</include>
                </includes>
                <filtering>true</filtering>
            </resource>

            <resource>
                <directory>src/main/resources</directory>
                <includes>
                    <include>*</include>
                    <include>**/*</include>
                </includes>
                <filtering>true</filtering>
            </resource>
        </resources>
    </build>
</project>

application-context.xml

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:p="http://www.springframework.org/schema/p"
       xmlns:c="http://www.springframework.org/schema/c"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xmlns:util="http://www.springframework.org/schema/util"
       xmlns:tx="http://www.springframework.org/schema/tx"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        https://www.springframework.org/schema/context/spring-context.xsd
        http://www.springframework.org/schema/aop
        https://www.springframework.org/schema/aop/spring-aop.xsd
        http://www.springframework.org/schema/util
        https://www.springframework.org/schema/util/spring-util.xsd
        http://www.springframework.org/schema/tx 
        http://www.springframework.org/schema/tx/spring-tx.xsd">

    <context:component-scan base-package="${base_package}"/>

    <util:properties id="druidConfiguration" location="classpath:druid.properties"/>
    <bean id="dataSource"
          class="com.alibaba.druid.pool.DruidDataSourceFactory"
          factory-method="createDataSource"
          c:properties-ref="druidConfiguration"/>

    <bean id="mybatis-config"
          class="org.springframework.core.io.ClassPathResource"
          c:path="mybatis-config.xml"/>

    <bean id="sqlSessionFactory"
          class="org.mybatis.spring.SqlSessionFactoryBean"
          p:configLocation-ref="mybatis-config"
          p:dataSource-ref="dataSource"/>

    <bean id="mapperScannerConfigurer"
          class="org.mybatis.spring.mapper.MapperScannerConfigurer"
          p:basePackage="${mapper_package}"/>

    <bean id="transactionManager"
          class="org.springframework.jdbc.datasource.DataSourceTransactionManager"
          p:dataSource-ref="dataSource"/>

    <aop:aspectj-autoproxy/>
    <tx:annotation-driven/>
</beans>

mybatis-config.xml

注意

因为mybatis不再使用默认连接池,而是利用spring,将mybatis与druid整合。所以需要将mybatis-config.xml模板中与连接池相关的配置删除。

又因为mybatis的mapper扫描,已经委托给了spring,所以需要将mybatis-config.xml模板中与mapper扫描相关的配置删除

xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
    <settings>
        <setting name="lazyLoadingEnabled" value="true"/>
        <setting name="autoMappingBehavior" value="FULL"/>
        <setting name="mapUnderscoreToCamelCase" value="true"/>
        <setting name="cacheEnabled" value="false"/>
        <setting name="localCacheScope" value="STATEMENT"/>
    </settings>

    <typeAliases>
        <package name="${type_aliases_package}"/>
    </typeAliases>

    <!--
    <plugins>
        <plugin interceptor="com.github.pagehelper.PageInterceptor"/>
    </plugins>
    -->
</configuration>

课程目标总结

  • 能够理解 IoC 的容器
  • 能够编写 IoC 的入门案例
  • 能够编写 IoC 的注解代码
  • 能够描述 IoC 注解的含义
  • 能够描述 Bean 标签配置
  • 能够理解 Bean 的实例化方法
  • 能够理解 Bean 的属性注入方法
  • 能够理解复杂类型的属性注入
  • 能够编写使用注解实现组件扫描
  • 能够实现框架整合 Junit
  • 能够理解 AOP 相关概念
  • 能够说出 AOP 相关术语的含义
  • 能够实现基于 AOP 配置的事务控制案例
  • 能够编写的 AOP 中不同通知的代码
  • 能够理解的 AOP 的注解
  • 能够应用 AspectJ 表达式语言
  • 能够应用声明式事务