1、Spring 入门

1.1、Spring 介绍

Spring 通常是指 Spring Framework,属于 Spring 生态体系中的一个模块。

Spring 由 Rod Johnson 创建,目的是为了解决企业软件开发的复杂性。

Spring 整合了现有的框架技术,使这些技术更加实用 。

官方网址:https://spring.io/。

GitHub 下载:https://github.com/spring-projects。

主要特点

  • 开源:开放源码且免费。
  • 轻量级:相关 Jar 包仅有几 M。
  • 非侵入式:不会对现有代码造成影响。
  • 控制反转(IoC):低耦合。
  • 面向切面编程(AOP):处理事务与日志等。
  • 容器:Spring 管理对象的创建与生命周期。

七大模块

Spring 分为七大模块,各个模块可以单独或一起使用。功能如下:

模块 说明 详情
Spring Core 核心容器 Spring 的基本功能。使用 Bean 管理各个组件,使用 BeanFactory 生产与管理 Bean(工厂模式),BeanFactory 使用控制反转(IoC)思想。
Spring Context 应用上下文 其实现了 ApplicationContext 接口,拓展了核心容器。支持事件处理、国际化、其他企业级服务(例如 JNDI、EJB、电子邮件等)。
Spring AOP 面向切面编程 支持面向切面编程(AOP),提供声明式事务。
Spring DAO JDBC 与 DAO 模块 支持异常统一处理(例如打开与关闭连接),极大降低代码数量。
Spring ORM 对象关系映射 支持多种 ORM 框架,包括 JDO、Hibernate、IBatis SQL Map 等。
Spring Web Web 模块 基于 Spring Context,提供 Web 应用上下文。支持 Web 开发,包括处理请求、表单、异常等。
Spring Web MVC MVC 模块 全功能的创建 Web 应用的 MVC 实现。配置方便,并且包含大量视图技术,包括 JSP、Velocity、Tiles、IText、POI 等。

1.2、准备内容

  • Maven 依赖。
  • Dao 接口与实现类。
  • Service 接口与实现类。

Maven 依赖

<!--Spring WebMVC-->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-webmvc</artifactId>
    <version>5.3.13</version>
</dependency>
<!--Junit-->
<dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>4.12</version>
</dependency>
<!--Lombok-->
<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>1.18.12</version>
</dependency>

Dao 接口与实现类

public interface UserDao {
    void save();
}
public class UserDaoImpl implements UserDao {
    @Override
    public void save() {
        System.out.println("Save User ...");
    }
}

Service 接口与实现类

public interface UserService {
    void save();
}
public class UserServiceImpl implements UserService {
    private UserDao userDao;

    public void setUserDao(UserDao userDao) {
        this.userDao = userDao;
    }

    @Override
    public void save() {
        userDao.save();
    }
}

1.3、入门示例(Set 方法注入)

创建 Spring 的 XML 配置文件(applicationContext.xml),然后使用 Spring API 进行测试。

<?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="userDao" class="com.feng.dao.UserDaoImpl"/>
    <bean id="userService" class="com.feng.service.UserServiceImpl">
        <property name="userDao" ref="userDao"/>
    </bean>
</beans>
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
UserService userService = applicationContext.getBean(UserService.class);
userService.save();

1.4、核心 API

BeanFactory 接口

Spring 容器的基本接口,提供配置、创建、管理 Bean 的基本方法。使用工厂模式,用于获取 Bean。

public interface BeanFactory {
	String FACTORY_BEAN_PREFIX = "&";
	Object getBean(String name) throws BeansException;// 使用 Bean 的唯一标识符获取 Bean 实例
	<T> T getBean(String name, Class<T> requiredType) throws BeansException;
	Object getBean(String name, Object... args) throws BeansException;
	<T> T getBean(Class<T> requiredType) throws BeansException;// 使用 Bean 的类型获取 Bean 实例,该类型可以是父类、父接口
	<T> T getBean(Class<T> requiredType, Object... args) throws BeansException;
	<T> ObjectProvider<T> getBeanProvider(Class<T> requiredType);
	<T> ObjectProvider<T> getBeanProvider(ResolvableType requiredType);
	boolean containsBean(String name);
	boolean isSingleton(String name) throws NoSuchBeanDefinitionException;// Bean 是否为单例
	boolean isPrototype(String name) throws NoSuchBeanDefinitionException;// Bean 是否为多例
	boolean isTypeMatch(String name, ResolvableType typeToMatch) throws NoSuchBeanDefinitionException;
	boolean isTypeMatch(String name, Class<?> typeToMatch) throws NoSuchBeanDefinitionException;
	Class<?> getType(String name) throws NoSuchBeanDefinitionException;
	Class<?> getType(String name, boolean allowFactoryBeanInit) throws NoSuchBeanDefinitionException;
	String[] getAliases(String name);
}

ApplicationContext 接口

ApplicationContext 接口称为 Spring 应用上下文,就是 Spring 容器。该接口最终继承 BeanFactory,并提供多个实现类:

  • ClassPathXmlApplicationContext:使用类路径中的 XML 获取 Spring 容器。
  • FileSystemXmlApplicationContext:使用文件系统中的 XML 配置文件获取 Spring 容器,绝对路径。
  • AnnotationConfigApplicationContext:使用 Spring 配置类获取 Spring 容器。
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
// 使用 Bean 的唯一标识符获取 Bean 实例
UserService userService = (UserService) applicationContext.getBean("userService");
UserServiceImpl userServiceImpl = (UserServiceImpl) applicationContext.getBean("userService");

// 使用 Bean 的类型获取 Bean 实例,该类型可以是父类、父接口
UserService userService02 = applicationContext.getBean(UserService.class);
UserServiceImpl userServiceImpl02 = applicationContext.getBean(UserServiceImpl.class);

2、DI - Spring 配置文件

2.1、Spring 配置文件介绍

Spring 配置文件(applicationContext.xml)用于配置 Spring 容器。

beans 标签(基本不用)

用于配置所有 Bean。

  • default-autowire:所有 Bean 使用自动装配。需要在 bean 标签中配置 autowaire=“default”。
  • default-autowire-candidates:所有 Bean 是否接受被自动装配。提供两种 default-autowire-candidate=“[true|false]”。
    • true(默认):接受被自动装配。
    • false:不接受被自动装配。
  • default-init-method:所有 Bean 的初始化方法。
  • default-destroy-method:所有 Bean 的销毁方法。
  • default-lazy-init:所有 Bean 使用懒加载。提供两种 default-lazy-init=“[true|false]”,仅在 scope=“singleton” 时有效。
    • true:容器初始化时不会创建 Bean 的实例,首次调用 getBean() 方法时才创建。
    • false(默认):容器初始化时立即创建 Bean 的实例。???

bean 标签

用于配置 Bean。

  • id:Bean 的唯一标识符。通常使用类名小写。
  • name(基本不用):Bean 的别名。可定义多个并使用逗号、分号、空格隔开。
  • class:Bean 的全限定类名。只能使用类名不能使用接口、抽象类。
  • autowaire:自动装配。提供五种 autowire=“[default|byName|byType|constructor|no]”。
    • default:跟随 beans 标签中的 default-autowire 属性。(例如:如果在 beans 标签中配置 default-autowire=“byName”,则在 bean 标签中的 autowire=“default” 表示 byName)
    • byName:按照 Bean 的名称使用 Set 方法自动装配。
    • byType:按照 Bean 的类型使用 Set 方法自动装配。
    • constructor:使用有参构造自动装配。
    • no(默认):不使用自动装配。
  • autowire-candidate: Bean 是否接受被自动装配。提供两种 autowire-candidate=“[true|false]”。
    • true(默认):接受被自动装配。
    • false:不接受被自动装配。
  • scope:Bean 的作用域。提供五种 scope=“[singleton|prototype|request|session|application]”。
    • singleton(默认):单例。容器初始化时立即创建 Bean 的实例,之后每次调用 getBean() 方法时返回同一个实例。
    • prototype:原型。容器初始化时不会创建 Bean 的实例,之后每次调用 getBean() 方法时返回一个新的实例。
    • request:请求。在 Web 环境中,每次 request 产生一个新的实例,该实例在当前 request 中共享。
    • session:会话。在 Web 环境中,每次 session 产生一个新的实例,该实例在当前 session 中共享。
    • application:应用。在 Web 环境中,每次 application 产生一个新的 Bean 实例,该实例在当前 application 中共享。
  • lazy-init:Bean 的懒加载。提供两种 lazy-init=“[true|false]”,仅在 scope=“singleton” 时有效。
    • true:容器初始化时不会创建 Bean 的实例,首次调用 getBean() 方法时才创建。
    • false(默认):容器初始化时立即创建 Bean 的实例。
  • init-method:Bean 的初始化方法,Bean 初始化后调用该方法。
  • destroy-method:Bean 的销毁方法,Bean 销毁前调用该方法。
  • abstract:Bean 是否可以被实例化。提供两种 abstract=“[true|false]”。
    • true:Bean 不能被实例化,必须搭配 parent 一起使用使用。
    • false(默认):Bean 可以被实例化。
  • parent:引用父 Bean 中的属性注入当前Bean。parent 与 Java 中的继承无关。
  • factory-bean:实例化工厂 Bean。
  • factory-method:调用工厂方法。
  • depends-on:依赖关系。实例化 Bean 时优先实例化 depends-on 的 Bean,销毁 Bean 时优先销毁 depends-on 的 Bean。如果存在多个依赖的 Bean 可以使用“,”隔开。
  • primary:是否优先注入。提供两种 primary=“[true|false]”。
    • true:优先注入。如果存在多个类型相同的 Bean 且只有一个 primary=“true”,则使用 @Autowired 会优先注入该 Bean,其他情况使用 @Autowired 直接报错。
    • false(默认):不优先注入。
  • property 子标签:Set 方法注入。
    • name:Set 方法名称。
    • ref/value:ref 为注入引用类型,value 为注入基本类型或 String。
  • constructor-arg 子标签:有参构造注入。
    • name/index/type:name 为构造方法的参数名称,index 为构造方法的参数索引,type 为构造方法的参数类型。
    • ref/value:ref 为注入引用类型,value 为注入基本类型或 String。

Alias 标签(基本不用)

用于配置 Bean 的别名,等价于 bean 标签的 name 属性。

  • name:Bean 的名称。
  • alias:Bean 的别名。

import 标签

用于导入其他 Spring 配置文件。

  • resource:资源路径。

示例

<!--bean 标签-->
<bean id="userDao" name="userDao01 userDao02, userDao03; userDao04" class="com.feng.dao.UserDaoImpl"/>

<!--alias 标签-->
<alias name="userDao" alias="userDao05"/>

<!--import 标签-->
<import resource="applicationContext.xml"/>
<import resource="classpath:applicationContext.xml"/>
<import resource="classpath*:applicationContext.xml"/>

2.2、依赖注入分类

当 Spring 容器创建某个 Bean 的实例时,可以同时创建该 Bean 依赖的 Bean 的实例(注入属性),称为依赖注入(DI)。

依赖注入是实现控制反转的一种方式。

依赖注入分类

  • 方式一、纯 XML:使用 XML 配置 Bean,同时实现依赖注入。
    • Set 方法注入:前提需要无参构造方法与 Set 方法。原理:使用反射,Spring 先调用无参构造方法创建 Bean 的实例,再调用 Set 方法为属性赋值,该属性就是依赖对象。
    • 有参构造注入:前提需要有参构造方法。原理:使用反射,Spring 调用有参构造方法创建 Bean 的实例,同时为属性赋值,该属性就是依赖对象。
    • P 命名空间注入:Property,本质还是 Set 方法注入。
    • C 命名空间注入:Constructor,本质还是有参构造注入。
    • 自动装配。
  • 方式二、XML 与注解:使用 XML 配置 Bean,注解实现依赖注入。
  • 方式三、纯注解:使用注解配置 Bean,同时实现依赖注入。

2.3、方式一、纯 XML

Set 方法注入

<bean id="userDao" class="com.feng.dao.UserDaoImpl"/>
<bean id="userService" class="com.feng.service.UserServiceImpl">
    <property name="userDao" ref="userDao"/>
</bean>

有参构造注入

<bean id="userDao" class="com.feng.dao.UserDaoImpl"/>
<bean id="userService" class="com.feng.service.UserServiceImpl">
    <constructor-arg name="userDao" ref="userDao"/>
    <!--<constructor-arg index="0" ref="userDao"/>-->
    <!--<constructor-arg type="com.feng.dao.UserDao" ref="userDao"/>-->
</bean>

P 命名空间注入

<bean id="userDao" class="com.feng.dao.UserDaoImpl"/>
<bean id="userService" class="com.feng.service.UserServiceImpl" p:userDao-ref="userDao"/>

C 命名空间注入

<bean id="userDao" class="com.feng.dao.UserDaoImpl"/>
<bean id="userService" class="com.feng.service.UserServiceImpl" c:userDao-ref="userDao"/>

自动装配

<bean id="userDao" class="com.feng.dao.UserDaoImpl"/>
<bean id="userService" class="com.feng.service.UserServiceImpl" autowire="byName"/>

2.4、注解介绍

使用注解可以简化 XML 配置,注意需要存在无参构造方法、Set 方法、或合适的有参构造方法。

配置 Bean 的注解

  • @Component:注解在类,将该类注入 Spring 容器 。
    • value:唯一标识符,默认类名小写。常用写法:@Component。
  • @Controller:@Component 的衍生注解,功能与其相同,用于 Web 层。
  • @Service:@Component 的衍生注解,功能与其相同,用于 Service 层。
  • @Repository:@Component 的衍生注解,功能与其相同,用于 Dao 层。
  • @Scope:注解在类,Bean 的作用域。注意需要配合 @Component、@Controller、@Service、@Repository 一起使用,否则无效。
    • value/scopeName:作用域名称,默认单例。常用写法:@Scope(“singleton”) 单例,@Scope(“prototype”) 原型。

想要激活以上注解,需要在 XML 中配置上下文组件扫描。上下文组件扫描包括上下文注解配置的功能。

<context:component-scan base-package="com.feng"/>

依赖注入的注解

  • @Autowired:注解在字段,根据类型自动注入。
    • required:该依赖的 Bean 是否必须存在,默认 true。为 true 时则不存在会报错,为 false 时则不存在会被忽略。常用写法:@Autowired。
  • @Qualifier:注解在字段,根据名称自动注入。
    • value:唯一标识符,默认空。常用写法:@Autowired@Qualifier(“XXX”)。
  • @Resource(Java 注解):注解在字段,根据名称/类型自动注入。常用写法:@Resource 先类型再名称,@Resource(name = “XXX”) 按名称,@Resource(type = XXX.class) 按类型。
  • @Value:注解在字段,只能注入基本类型或 String。
    • value:注入的值。常用写法:@Value(“XXX”)。

想要激活以上注解,需要在 XML 中配置上下文注解配置

<context:annotation-config/>

其他注解

  • @PostContruct(Java 注解):注解在方法,Bean 的初始化方法。常用写法:@PostContruct。
  • @PreDestroy(Java 注解):注解在方法,Bean 的销毁方法。常用写法:@PreDestroy。

想要激活以上注解,需要在 XML 中配置上下文注解配置

<context:annotation-config/>

2.5、方式二、使用 XML 与注解

Dao 接口与实现类

public interface UserDao {
    void save();
}
public class UserDaoImpl implements UserDao {
    @Override
    public void save() {
        System.out.println("Save User ...");
    }
}

Service 接口与实现类

public interface UserService {
    void save();
}
public class UserServiceImpl implements UserService {
    @Autowired
    private UserDao userDao;

    @Override
    public void save() {
        userDao.save();
    }
}

XML 配置文件

<bean id="userDao" class="com.feng.dao.UserDaoImpl"/>
<bean id="userService" class="com.feng.service.UserServiceImpl"/>
<context:annotation-config/>

2.6、方式三、完全使用注解

Dao 接口与实现类

public interface UserDao {
    void save();
}
@Component
public class UserDaoImpl implements UserDao {
    @Override
    public void save() {
        System.out.println("Save User ...");
    }
}

Service 接口与实现类

public interface UserService {
    void save();
}
@Component
public class UserServiceImpl implements UserService {
    @Autowired
    private UserDao userDao;

    @Override
    public void save() {
        userDao.save();
    }
}

XML 配置文件

<context:component-scan base-package="com.feng"/>

3、DI - 其他配置

3.1、注入不同数据类型

这里使用 Set 方法注入。Array、List、Set 的格式基本相同,Set、Map、Properties 会去重。

@Data
public class User {
    private String name;
    private String wife;
    private String[] books;
    private List<String> hobbies;
    private Set<String> games;
    private Map<String,String> card;
    private Properties info;
}
<?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="user" class="com.feng.pojo.User">
        <property name="name" value="张三"/>
        <property name="wife">
            <null/>
        </property>
        <property name="books">
            <array>
                <value>《西游记》</value>
                <value>《三国演义》</value>
                <value>《红楼梦》</value>
                <value>《红楼梦》</value>
            </array>
        </property>
        <property name="hobbies">
            <list>
                <value>篮球</value>
                <value>跑步</value>
                <value>足球</value>
                <value>足球</value>
            </list>
        </property>
        <property name="games">
            <set>
                <value>LoL</value>
                <value>Dota</value>
                <value>WoW</value>
                <value>WoW</value>
            </set>
        </property>
        <property name="card">
            <map>
                <entry key="身份证" value="123456"/>
                <entry key="银行卡" value="123456"/>
                <entry key="社保卡" value="123456"/>
                <entry key="社保卡" value="456789"/>
            </map>
        </property>
        <property name="info">
            <props>
                <prop key="性别"></prop>
            </props>
        </property>
    </bean>
</beans>
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
User user = applicationContext.getBean(User.class);
System.out.println(user);

3.2、Bean 的初始化与销毁方法

public class UserServiceImpl implements UserService {
    private UserDao userDao;

    public void setUserDao(UserDao userDao) {
        this.userDao = userDao;
    }

    @Override
    public void save() {
        userDao.save();
    }

    // 初始化方法
    public void init() {
        System.out.println("初始化 UserServiceImpl ...");
    }

    // 销毁方法
    public void destroy() {
        System.out.println("销毁 UserServiceImpl ...");
    }
}
<bean id="userDao" class="com.feng.dao.UserDaoImpl"/>
<bean id="userService" class="com.feng.service.UserServiceImpl" init-method="init" destroy-method="destroy">
    <property name="userDao" ref="userDao"/>
</bean>
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
UserDao userDao = context.getBean(UserDao.class);
userDao.save();

3.3、Abstract Bean

@Setter
@ToString
public class Son {
    private String name;
    private Integer age;
}
<bean id="parent" abstract="true">
    <property name="name" value="张三"/>
    <property name="age" value="18"/>
</bean>
<bean id="son" class="com.feng.util.Son" parent="parent"/>
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
Son son = context.getBean(Son.class);
System.out.println(son);

3.4、FactoryBean

可以将方法的返回值注入 Spring 容器,分为静态工厂与实例工厂。

  • 静态工厂:直接配置 FactoryBean 与静态方法。
  • 实例工厂:因为需要先实例化 FactoryBean,所以先配置 FactoryBean 再配置实例方法。
public class MyFactoryBean {
    // 静态工厂
    public static UserDao getUserDaoInstance() {
        return new UserDaoImpl();
    }

    // 实例工厂
    public UserDao getUserDao() {
        return new UserDaoImpl();
    }
}
<!--静态工厂-->
<bean id="userDao" class="com.feng.factory.MyFactoryBean" factory-method="getUserDaoInstance"/>

<!--实例工厂-->
<bean id="userDao" factory-bean="myFactoryBean" factory-method="getUserDao"/>
<bean id="myFactoryBean" class="com.feng.factory.MyFactoryBean"/>
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
UserDao userDao = context.getBean(UserDao.class);
userDao.save();

4、DI - Spring 配置类

4.1、Spring 配置类介绍

Spring 配置类(SpringConfig.java)用于配置 Spring 容器。

使用 Java 类搭配注解实现,等价于 Spring 配置文件(applicationContext.xml)。

Spring 配置类的注解

  • @Configuration:注解在类,表示该类为 Java 配置类。常用写法:@Configuration。
  • @ComponentScan:注解在类,用于上下文组件扫描。常用写法:@ComponentScan(“com.feng”)。
  • @PropertySource:注解在类,用于加载 .properties 配置文件。常用写法:@PropertySource(“classpath:jdbc.properties”)。
  • @Import:注解在类,用于导入其他 Spring 配置类。等价于 XML 中的 import 标签。常用写法:@Import(Config.class)。
  • @Bean:注解在方法,将方法的返回值注入 Spring 容器。常用写法:@Bean、@Bean(“id”)。

4.2、使用数据库连接池

常见的数据库连接池有 DBCP、C3P0、Druid 等,都依赖 MySQL。

Maven 依赖

<!--MySQL-->
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>8.0.26</version>
</dependency>
<!--DBCP-->
<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-dbcp2</artifactId>
    <version>2.8.0</version>
</dependency>
<!--C3P0-->
<dependency>
    <groupId>com.mchange</groupId>
    <artifactId>c3p0</artifactId>
    <version>0.9.5.5</version>
</dependency>
<!--Druid-->
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid</artifactId>
    <version>1.2.8</version>
</dependency>

Druid 使用

driver=com.mysql.cj.jdbc.Driver
url=jdbc:mysql://localhost:3306/mybatis?serverTimezone=Asia/Shanghai&useSSL=true&useUnicode=true&characterEncoding=UTF-8
user=root
password=test123!
// 一、读取 jdbc.properties
ResourceBundle jdbc = ResourceBundle.getBundle("jdbc");
String driver = jdbc.getString("driver");
String url = jdbc.getString("url");
String username = jdbc.getString("user");
String password = jdbc.getString("password");
// 二、获取 DruidDataSource
DruidDataSource dataSource = new DruidDataSource();
dataSource.setDriverClassName(driver);
dataSource.setUrl(url);
dataSource.setUsername(username);
dataSource.setPassword(password);
// 三、获取数据库
Connection connection = dataSource.getConnection();
System.out.println(connection);
connection.close();

4.3、Spring 配置数据库连接池

XML 配置文件

<context:property-placeholder location="classpath:jdbc.properties"/>
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
    <property name="driverClassName" value="${driver}"/>
    <property name="url" value="${url}"/>
    <property name="username" value="${user}"/>
    <property name="password" value="${password}"/>
</bean>
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
DataSource dataSource = applicationContext.getBean(DataSource.class);
Connection connection = dataSource.getConnection();
System.out.println(connection);
connection.close();

Spring 配置类

@Configuration
@PropertySource("classpath:jdbc.properties")
public class SpringConfig {
    @Value("${driver}")
    private String driver;
    @Value("${url}")
    private String url;
    @Value("${user}")
    private String user;
    @Value("${password}")
    private String password;

    @Bean
    public DruidDataSource getDruidDataSource() {
        DruidDataSource dataSource = new DruidDataSource();
        dataSource.setDriverClassName(driver);
        dataSource.setUrl(url);
        dataSource.setUsername(user);
        dataSource.setPassword(password);
        return dataSource;
    }
}
ApplicationContext applicationContext = new AnnotationConfigApplicationContext(Context.class);
DataSource dataSource = applicationContext.getBean(DruidDataSource.class);
Connection connection = dataSource.getConnection();
System.out.println(connection);
connection.close();

4.4、Junit 集成 Spring

在前面的测试类中,从 Spring 容器获取 Bean 实例,一直使用 getBean() 方法,比较麻烦。

Junit 集成 Spring 之后,使用 @Autowired 等注解,直接注入需要的 Bean 实例即可。

Junit 集成 Spring 的注解

  • @RunWith(SpringJUnit4ClassRunner.class):注解在类,集成 Spring Junit。
  • @ContextConfiguration(“classpath:applicationContext.xml”):注解在类,指定 Spring 配置文件。
  • @ContextConfiguration(classes = SpringConfig.class):注解在类,指定 Spring 配置类。

Maven 依赖

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-test</artifactId>
    <version>5.3.8</version>
</dependency>

示例

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class T01_JUnitSpring {
    // 集成之前
    @Test
    public void test01() {
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
        UserService userService = applicationContext.getBean(UserService.class);
        userService.save();
    }

    // 集成之后
    @Autowired
    private UserService userService;

    @Test
    public void test02() {
        userService.save();
    }
}
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = SpringConfig.class)
public class T02_JUnitSpring {
    // 集成之前
    @Test
    public void test01() {
        ApplicationContext applicationContext = new AnnotationConfigApplicationContext(SpringConfig.class);
        UserService userService = applicationContext.getBean(UserService.class);
        userService.save();
    }

    // 集成之后
    @Autowired
    private UserService userService;

    @Test
    public void test02() {
        userService.save();
    }
}

5、AOP 应用

5.1、AOP 介绍

AOP(Aspect Oriented Programming,面向切面编程)是通过预编译的方式与运行期动态代理,实现程序功能统一维护的一种技术。AOP 是软件开发中的热点,是对 OOP(Object Oriented Programming,面向对象编程)的延续与补充。

Spring AOP 可以对业务逻辑的各个部分进行隔离,降低业务逻辑各个部分之间的耦合度,提高程序的可重用性与开发效率。应用场景:事务处理、日志记录、性能统计、安全控制、异常处理等。

Spring AOP 同时使用 JDK 动态代理与 CGLIB 动态代理。

名词解释

  • 通知(Advice):是指需要增强的方法,就是 AOP 的具体应用,与业务逻辑无关。
  • 目标对象(Target):是指需要被增强的对象,就是真正的业务逻辑。
  • 切入点(PointCut):是指目标对象中需要被增强的方法
  • 连接点(JoinPoint):是指目标对象中需要被增强的方法前后,或者抛出异常等等。
  • 切面(Aspect):是指切入点 + 通知,就是需要被增强的方法 + 需要增强的方法
  • 代理(Proxy):是指 AOP 的实现原理,就是通过代理模式。
  • 织入(Weaving):是指将通知应用到目标对象的过程,就是 AOP 的实现过程。

三种实现方式

  • 方式一、实现通知接口。
  • 方式二、自定义通知类。
  • 方式三、注解自动织入。

5.2、准备内容

Maven 依赖

<!--AspectJ Weaver 切面织入-->
<dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjweaver</artifactId>
    <version>1.9.4</version>
</dependency>

Service 接口与实现类

public interface UserService {
    void insert();

    void select();
}
public class UserServiceImpl implements UserService {
    @Override
    public void insert() {
        System.out.println("增加一个用户");
    }

    @Override
    public void select() {
        System.out.println("查询一个用户");
    }
}

XML 配置文件

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

测试

ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
UserService userService = applicationContext.getBean(UserService.class);
userService.insert();

5.3、方式一、实现通知接口

通知接口

  • 前置通知:目标方法执行之前调用,实现接口 org.springframework.aop.MethodBeforeAdvice。
  • 后置通知:目标方法返回结果之后调用,实现接口 org.springframework.aop.AfterReturningAdvice。
  • 环绕通知:目标方法执行前后调用,实现接口 org.aopalliance.intercept.MethodInterceptor。
  • 异常抛出通知:目标方法抛出异常之后调用,实现接口 org.springframework.aop.ThrowsAdvice。
  • 引介通知:在目标类中添加属性与方法,实现接口 org.springframework.aop.IntroductionInterceptor。

AOP 标签

aop:config 标签用于配置 AOP。

  • aop:pointcut:切入点。id 为唯一标识符,expression 为切入点表达式。
  • aop:advisor:通知。id 为唯一标识符,advice-ref 为引用通知 Bean 的 ID,pointcut 为具体切入点,pointcut-ref 为引用切入点 ID。
  • aop:aspect:切面。id 为唯一标识符,ref 为引入切面类的 Bean。
  • aop:before:前置通知。method 为切面类中的方法,pointcut 为具体切入点,pointcut-ref 为引用切入点 ID。
  • aop:after:后置通知。method 为切面类中的方法,pointcut 为具体切入点,pointcut-ref 为引用切入点 ID。
  • aop:around:环绕通知。method 为切面类中的方法,pointcut 为具体切入点,pointcut-ref 为引用切入点 ID。
  • aop:after-returning:返回之后通知。method 为切面类中的方法,pointcut 为具体切入点,pointcut-ref 为引用切入点 ID。
  • aop:after-throwing:异常抛出通知。method 为切面类中的方法,pointcut 为具体切入点,pointcut-ref 为引用切入点 ID。
  • aop:declare-parents:引介通知。types-matching 为切入点表达式,implement-interface 为引介接口的全限定类名,default-impl 为引介接口的默认实现(全限定类名),delegate-ref 为引介接口的默认实现(引用 Bean),default-impl 与 delegate-ref 二选一。
<aop:config proxy-target-class="[true|false]" expose-proxy="[true|false]">
    <aop:pointcut id="" expression="execution(* com.feng.service.*.*(..))"/>
    <aop:advisor id="" advice-ref="" pointcut="" pointcut-ref=""/>
    <aop:aspect id="" ref="">
        <aop:pointcut id="" expression="execution(* com.feng.service.*.*(..))"/>
        <aop:before method="" pointcut="" pointcut-ref=""/>
        <aop:after method="" pointcut="" pointcut-ref=""/>
        <aop:around method="" pointcut="" pointcut-ref=""/>
        <aop:after-returning method="" pointcut="" pointcut-ref=""/>
        <aop:after-throwing method="" pointcut="" pointcut-ref=""/>
        <aop:declare-parents types-matching="" implement-interface="" default-impl="" delegate-ref=""/>
    </aop:aspect>
</aop:config>

示例

  • 实现通知接口(这里使用 MethodBeforeAdvice、AfterReturningAdvice)。
  • 配置 AOP(配置**“切入点 + 通知”组成切面**)。
public class MethodBeforeLog implements MethodBeforeAdvice {
    @Override
    public void before(Method method, Object[] args, Object target) throws Throwable {
        System.out.println(method.getName() + " 方法执行之前通知");
    }
}
public class AfterReturningLog implements AfterReturningAdvice {
    @Override
    public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
        System.out.println(method.getName() + " 方法返回之后通知");
    }
}
<!--方式一-->
<bean id="methodBeforeLog" class="com.feng.log.interfaceImpl.MethodBeforeLog"/><!--配置通知 Bean-->
<bean id="afterReturningLog" class="com.feng.log.interfaceImpl.AfterReturningLog"/><!--配置通知 Bean-->
<aop:config><!--配置 AOP-->
    <aop:pointcut id="pointcut" expression="execution(* com.feng.service.*.*(..))"/><!--配置切入点-->
    <aop:advisor pointcut-ref="pointcut" advice-ref="methodBeforeLog"/><!--配置通知-->
    <aop:advisor pointcut-ref="pointcut" advice-ref="afterReturningLog"/><!--配置通知-->
</aop:config>
insert 方法执行之前通知
增加一个用户
insert 方法返回之后通知

5.4、方式二、自定义通知类

  • 自定义通知类(前置通知方法、后置通知方法)。
  • 配置 AOP(使用切面,内部配置**“切入点 + 通知”组成切面**)。
public class DiyAdvice {
    public void before() {
        System.out.println("自定义前置通知");
    }

    public void after() {
        System.out.println("自定义后置通知");
    }
}
<!--方式二-->
<bean id="diyAdvice" class="com.feng.log.diy.DiyLog"/><!--配置通知 Bean-->
<aop:config><!--配置 AOP-->
    <aop:aspect ref="diyAdvice"><!--配置切面-->
        <aop:pointcut id="pointcut" expression="execution(* com.feng.service.*.*(..))"/><!--配置切入点-->
        <aop:before method="before" pointcut-ref="pointcut"/><!--配置前置通知-->
        <aop:after method="after" pointcut-ref="pointcut"/><!--配置后置通知-->
    </aop:aspect>
</aop:config>
前置通知
增加一个用户
后置通知

5.5、方式三、注解自动织入

AOP 注解

  • @Aspect:注解在类,切面。
  • @PointCut:注解在方法,切入点。
  • @Before:注解在方法,前置通知。
  • @After:注解在方法,后置通知。
  • @Around:注解在方法,环绕通知。注意:只有环绕通知支持对象传递(ProceedingJoinPoint,继续连接点),其他通知会报错 ProceedingJoinPoint is only supported for around advice。
  • @AfterReturning:注解在方法,方法返回之后通知。
  • @AfterThrowing:注解在方法,抛出异常之后通知。注意:在目标方法中如果存在 try-catch-finally 主动处理异常则不会触发该通知,如果使用 throws 抛出异常则可以触发。

想要激活以上注解,需要开启切面织入自动代理

<aop:aspectj-autoproxy/>

示例

  • 注解自动织入。
  • 使用注解配置切面、切入点、前置通知、后置通知、环绕通知。
@Aspect// 切面
public class AnnotationLog {
    @Before("execution(* com.feng.service.*.*(..))")// 前置通知,指定切入点
    public void before() {
        System.out.println("前置通知");
    }

    @After("execution(* com.feng.service.*.*(..))")// 后置通知,指定切入点
    public void after() {
        System.out.println("后置通知");
    }

    @Around("execution(* com.feng.service.*.*(..))")// 环绕通知,指定切入点
    public Object around(ProceedingJoinPoint p) throws Throwable {
        System.out.println("环绕前置通知");
        Object o = p.proceed();// 执行目标方法
        System.out.println(p.getSignature());// 获取目标方法签名
        System.out.println(o);// 获取目标方法返回值
        System.out.println("环绕后置通知");
        return o;
    }
}
<!--方式三-->
<bean id="annotationLog" class="com.feng.log.annotation.AnnotationLog"/><!--配置通知 Bean-->
<aop:aspectj-autoproxy/><!--切面织入自动代理-->
环绕前置通知
前置通知
增加一个用户
后置通知
void com.feng.service.UserService.insert()
null
环绕后置通知

5.6、AOP 应用 - 声明式事务

传统事务与声明式事务的区别

  • 传统事务:事务代码写在业务逻辑代码中,成功则提交,失败则回滚。缺点:所有的业务逻辑代码中,都需要添加事务代码。
  • 声明式事务:将事务代码从业务逻辑代码中单独分离出来,使用 AOP 织入事务。优点:可以降低业务逻辑代码中冗余的事务代码。

事务的传播特性(详见 TransactionDefinition 接口)

  • REQUIRED 需要的(默认):支持使用当前事务,如果当前事务不存在,则创建一个新事务。支持事务,如果当前事务存在则使用,否则创建一个新事务。
  • SUPPORTS 支持的:支持使用当前事务,如果当前事务不存在,则不使用事务。支持事务,如果当前事务存在则使用,否则不使用事务。
  • MANDATORY 强制的:支持使用当前事务,如果当前事务不存在,则抛出 Exception。
  • REQUIRES_NEW 需要新的:创建一个新事务,如果当前事务存在,则把当前事务挂起。
  • NOT_SUPPORTED 不支持的:不支持事务执行,如果当前事务存在,则把当前事务挂起。
  • NEVER 从不的:不支持事务执行,如果当前事务存在,则抛出 Exception。
  • NESTED 嵌套的:嵌套事务,如果当前事务存在,则在新的嵌套事务中执行。如果当前事务不存在,则与 REQUIRED 一样。

示例

  • 配置数据源事务管理器,注入数据源。
  • 配置事务管理器通知,配置事务的传播特性。
  • 配置 AOP(配置**“切入点 + 通知”组成切面**)。
<!--配置数据源事务管理器-->
<bean id="tx" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <property name="dataSource" ref="dataSource"/><!--注入数据源-->
</bean>

<!--配置事务管理器通知-->
<tx:advice id="txAdvice" transaction-manager="tx">
    <tx:attributes>
        <tx:method name="*" propagation="REQUIRED"/><!--配置事务的传播特性-->
    </tx:attributes>
</tx:advice>

<!--配置 AOP-->
<aop:config>
    <aop:pointcut id="txPointcut" expression="execution(* com.feng.service.*.*(..))"/><!--配置切入点-->
    <aop:advisor pointcut-ref="txPointcut" advice-ref="txAdvice"/><!--配置通知-->
</aop:config>

6、Spring 总结

6.1、Mavan 依赖

  • Spring IoC
<!--Spring WebMVC-->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-webmvc</artifactId>
    <version>5.3.13</version>
</dependency>
<!--Junit-->
<dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>4.12</version>
</dependency>
<!--Lombok-->
<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>1.18.12</version>
</dependency>
  • 数据库连接池
<!--MySQL-->
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>8.0.26</version>
</dependency>
<!--DBCP-->
<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-dbcp2</artifactId>
    <version>2.8.0</version>
</dependency>
<!--C3P0-->
<dependency>
    <groupId>com.mchange</groupId>
    <artifactId>c3p0</artifactId>
    <version>0.9.5.5</version>
</dependency>
<!--Druid-->
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid</artifactId>
    <version>1.2.8</version>
</dependency>
  • Spring Test
<!--Spring Test-->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-test</artifactId>
    <version>5.3.8</version>
</dependency>
  • Spring AOP
<!--AspectJ Weaver 切面织入-->
<dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjweaver</artifactId>
    <version>1.9.4</version>
</dependency>

6.2、BeanFactory 与 FactoryBean 的区别

  • BeanFactory:Spring 容器的基本接口,提供配置、获取 Bean 等方法。使用工厂模式,属于 Spring 核心 API。
  • FactoryBean:可以将方法的返回值注入 Spring 容器。分为静态工厂与实例工厂,属于创建 Bean 的一种方式。

6.3、@Component 与 @Bean 的区别

  • @Component:注解在类,将该类注入 Spring 容器。多用于自己开发的类。
  • @Bean:注解在方法,将方法的返回值注入 Spring 容器。多用于第三方工具的类。
Logo

更多推荐