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 容器。多用于第三方工具的类。