框架 02 - Spring 5

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 DAOJDBC 与 DAO 模块支持异常统一处理(例如打开与关闭连接),极大降低代码数量。
Spring ORM对象关系映射支持多种 ORM 框架,包括 JDO、Hibernate、IBatis SQL Map 等。
Spring WebWeb 模块基于 Spring Context,提供 Web 应用上下文。支持 Web 开发,包括处理请求、表单、异常等。
Spring Web MVCMVC 模块全功能的创建 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 容器。多用于第三方工具的类。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值