本文已参与「新人创作礼」活动,一起开启掘金创作之路。
Spring
1. Spring简介
1.1 Spring是什么
Spring是分层的 Java SE/EE应用 full-stack 轻量级开源框架,以 IoC(Inverse Of Control:反转控制)和 AOP(Aspect Oriented Programming:面向切面编程)为内核。
提供了展现层 SpringMVC 和持久层 Spring JDBCTemplate 以及业务层事务管理等众多的企业级应用技术 ,还能整合开源世界众多著名的第三方框架和类库,逐渐成为使用最多的Java EE 企业应用开源框架。
1.2 Spring的优势
-
方便解耦,简化开发
通过 Spring 提供的 IoC容器,可以将对象间的依赖关系交由 Spring 进行控制,避免硬编码所造成的过度耦合。 用户也不必再为单例模式类、属性文件解析等这些很底层的需求编写代码,可以更专注于上层的应用。
-
AOP 编程的支持
通过 Spring的 AOP 功能,方便进行面向切面编程,许多不容易用传统 OOP 实现的功能可以通过 AOP 轻松实现。
-
声明式事务的支持
可以将我们从单调烦闷的事务管理代码中解脱出来,通过声明式方式灵活的进行事务管理,提高开发效率和质量。
-
方便程序的测试
可以用非容器依赖的编程方式进行几乎所有的测试工作,测试不再是昂贵的操作,而是随手可做的事情。
-
集成各种优秀框架
Spring秀框架(Struts、Hibernate、Hessian、Quartz等)的支持。
-
降低avaEE API 的使用难度
Spring对 JavaEE API(如 JDBC、JavaMail、远程调用等)进行了薄薄的封装层,使这些 API 的使用难度大为降低。
2. Spring快速入门
2.1 Spring程序开发步骤
2.1.1 导入 Spring 开发的基本包坐标
<properties>
<spring.version>5.0.5.RELEASE</spring.version>
</properties>
<dependencies>
<!--导入spring的context坐标,context依赖core、beans、expression-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${spring.version}</version>
</dependency>
</dependencies>
2.1.2 编写 Dao 接口和实现类
public interface UserDao {
public void save();
}
public class UserDaoImpl implements UserDao {
@Override
public void save() {
System.out.println("UserDao save method running....");
}
}
2.1.3 创建 Spring 核心配置文件
在类路径下(resources)创建applicationContext.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"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
</beans>
2.1.4 在 Spring 配置文件中配置 UserDaoImpl
<?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.itheima.dao.impl.UserDaoImpl"></bean>
</beans>
2.1.5 使用 Spring 的 API 获得 Bean 实例
@Test
public void test1(){
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
UserDao userDao = (UserDao) applicationContext.getBean("userDao");
userDao.save();
}
3. Spring配置文件
3.1 Bean标签基本配置
用于配置对象交由Spring 来创建。
默认情况下它调用的是类中的无参构造函数,如果没有无参构造函数则不能创建成功。
基本属性:
- id:Bean实例在Spring容器中的唯一标识
- class:Bean的全限定名
3.2Bean标签的配置范围
scope:指对象的配置范围
| 取值范围 | 说明 |
|---|---|
| singleton | 默认的 |
| prototype | 多例的 |
| request | WEB 项目中,Spring 创建一个 Bean 的对象,将对象存入到 request 域中 |
| session | WEB 项目中,Spring 创建一个 Bean 的对象,将对象存入到 session 域中 |
| global session | WEB 项目中,应用在 Portlet 环境,如果没有 Portlet 环境那么globalSession 相当 于 session |
-
当scope的取值为singleton时
Bean的实例化个数:1个
Bean的实例化时机:当Spring核心文件被加载时,实例化配置的Bean实例
-
Bean的生命周期:
- 对象创建:当应用加载,创建容器时,对象就被创建了
- 对象运行:只要容器在,对象一直活着
- 对象销毁:当应用卸载,销毁容器时,对象就被销毁了
-
当scope的取值为prototype时 Bean的实例化个数:多个 Bean的实例化时机:当调用getBean()方法时实例化Bean
- 对象创建:当使用对象时,创建新的对象实例
- 对象运行:只要对象在使用中,就一直活着
- 对象销毁:当对象长时间不用时,被 Java 的垃圾回收器回收了
3.3 Bean生命周期配置
- init-method:指定类中的初始化方法名称
- destroy-method:指定类中销毁的方法名称
<bean id="userDao" class="com.itheima.dao.impl.UserDaoImpl" scope="singleton" init-method="" destroy-method=""></bean>
3.4 Bean实例化的三种方式
-
无参构造方法实例化
它会根据默认无参构造方法来创建类对象,如果bean中没有默认无参构造函数,将会创建失败
<bean id="userDao" class="com.itheima.dao.impl.UserDaoImpl" scope="singleton" init-method="init" destroy-method="destory"></bean> -
工厂静态方法实例化
public class DynamicFactoryBean { public static UserDao createUserDao(){ return new UserDaoImpl(); } }<bean id="userDao" class="com.itheima.factory.StaticFactory" factory-method="getUserDao"></bean> -
工厂实例方法实例化
<bean id="factory" class="com.itheima.factory.DynamicFactory"></bean>
<bean id="getUser" factory-bean="factory" factory-method="getUserDao"></bean>
3.5 Bean的依赖注入入门
- 创建 UserService,UserService 内部在调用 UserDao的save() 方法
public class UserServiceImpl implements UserService {
@Override
public void save() {
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
UserDao userDao = (UserDao) applicationContext.getBean("userDao");
userDao.save();
}
}
-
将 UserServiceImpl 的创建权交给 Spring
<bean id="userService" class="com.itheima.service.impl.UserServiceImpl"></bean> -
从 Spring 容器中获得 UserService 进行操作
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml"); UserService userService = (UserService) applicationContext.getBean("userService"); userService.save();
3.6 Bean的依赖注入分析
目前UserService实例和UserDao实例都存在与Spring容器中,当前的做法是在容器外部获得UserService 实例和UserDao实例,然后在程序中进行结合。
因为UserService和UserDao都在Spring容器中,而最终程序直接使用的是UserService,所以可以在 Spring容器中,将UserDao设置到UserService内部。
3.7 Bean的依赖注入概念
-
依赖注入(Dependency Injection):
它是 Spring 框架核心 IOC 的具体实现。
-
在编写程序时,通过控制反转,把对象的创建交给了 Spring,但是代码中不可能出现没有依赖的情况。 IOC 解耦只是降低他们的依赖关系,但不会消除。例如:业务层仍会调用持久层的方法。
- 那这种业务层和持久层的依赖关系,在使用 Spring 之后,就让 Spring 来维护了。 简单的说,就是坐等框架把持久层对象传入业务层,而不用我们自己去获取。
3.7.1 Bean依赖注入方式(setting)—>使用较多
在UserServiceImpl中添加setUserDao方法
public class UserServiceImpl implements UserService {
private UserDao userDao;
public void setUserDao(UserDao userDao) {
this.userDao = userDao;
}
@Override
public void save() {
userDao.save();
}
}
- xml配置方式一:
<bean id="userDao" class="com.itheima.dao.impl.UserDaoImpl"/>
<bean id="userService" class="com.itheima.service.impl.UserServiceImpl">
<property name="userDao" ref="userDao"/>
</bean>
-
xml配置方式二:
P命名空间注入
本质也是set方法注入,但比起上述的set方法注入更加方便,主要体现在配置文件中。
如下: 首先,需要引入P命名空间:
xmlns:p="http://www.springframework.org/schema/p"其次,修改配置方式
<bean id="userService" class="com.itheima.service.impl.UserServiceImpl" p:userDao-ref="userDao"/>
3.7.2 Bean的依赖注入方式(构造方法)
创建有参构造和无参构造
public class UserServiceImpl implements UserService {
@Override
public void save() {
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
UserDao userDao = (UserDao) applicationContext.getBean("userDao");
userDao.save();
}
}
xml配置
构造方法注入
<bean id="userDao" class="com.itheima.dao.impl.UserDaoImpl"/>
<bean id="userService" class="com.itheima.service.impl.UserServiceImpl">
<constructor-arg name="userDao" ref="userDao"></constructor-arg>
</bean>
3.8 Bean的依赖注入的数据类型
上面的操作,都是注入的引用Bean,除了对象的引用可以注入,普通数据类型,集合等都可以在容器中进行注入。
注入数据的三种数据类型
- 普通数据类型
- 引用数据类型
- 集合数据类型
3.8.1 普通数据类注入
public class UserDaoImpl implements UserDao {
private String company;
private int age;
public void setCompany(String company) {
this.company = company;
}
public void setAge(int age) {
this.age = age;
}
public void save() {
System.out.println(company+"==="+age);
System.out.println("UserDao save method running....");
}
}
<bean id="userDao" class="com.itheima.dao.impl.UserDaoImpl">
<property name="company" value="传智播客"></property>
<property name="age" value="15"></property>
</bean>
3.8.2 集合数据类型(List)的注入
public class UserDaoImpl implements UserDao {
private List<String> strList;
public void setStrList(List<String> strList) {
this.strList = strList;
}
public void save() {
System.out.println(strList);
System.out.println("UserDao save method running....");
}
}
<bean id="userDao" class="com.itheima.dao.impl.UserDaoImpl">
<property name="strList">
<list>
<value>aaa</value>
<value>bbb</value>
<value>ccc</value>
</list>
</property>
</bean>
3.8.3 集合数据类型( Map )的注入
public class UserDaoImpl implements UserDao {
private Map<String,User> userMap;
public void setUserMap(Map<String, User> userMap) {
this.userMap = userMap;
}
public void save() {
System.out.println(userMap);
System.out.println("UserDao save method running....");
}
}
<bean id="userDao" class="com.itheima.dao.impl.UserDaoImpl">
<property name="map">
<map>
<entry key="u1" value-ref="user1"/>
<entry key="u2" value-ref="user2"/>
<entry key="u3" value-ref="user3"/>
</map>
</property>
</bean>
<bean id="user1" class="com.itheima.domain.User">
<property name="name" value="tom"/>
<property name="age" value="18"/>
</bean>
<bean id="user2" class="com.itheima.domain.User">
<property name="name" value="jack"/>
<property name="age" value="19"/>
</bean>
<bean id="user3" class="com.itheima.domain.User">
<property name="name" value="jerry"/>
<property name="age" value="20"/>
</bean>
3..8.4 集合数据类型(Properties)的注入
public class UserDaoImpl implements UserDao {
private Properties properties;
public void setProperties(Properties properties) {
this.properties = properties;
}
public void save() {
System.out.println(properties);
System.out.println("UserDao save method running....");
}
}
<bean id="userDao" class="com.itheima.dao.impl.UserDaoImpl">
<property name="properties">
<props>
<prop key="p1">aaa</prop>
<prop key="p2">bbb</prop>
<prop key="p3">ccc</prop>
</props>
</property>
</bean>
3.9 引入其他配置文件(分模块开发)
实际开发中,Spring的配置内容非常多,这就导致Spring配置很繁杂且体积很大,所以,可以将部分配置拆解到其他 配置文件中,而在Spring主配置文件通过import标签进行加载。
<import resource="applicationContext-xxx.xml"/>
3.10 总结
<bean>标签
id属性:在容器中Bean实例的唯一标识,不允许重复
class属性:要实例化的Bean的全限定名
scope属性:Bean的作用范围,常用是Singleton(默认)和prototype
<property>标签:属性注入
name属性:属性名称
value属性:注入的普通属性值
ref属性:注入的对象引用值
<list>标签
<map>标签
<properties>标签
<constructor-arg>标签
<import>标签:导入其他的Spring的分文件
4. Spring相关api
4.1 ApplicationContext的继承体系
applicationContext:接口类型,代表应用上下文,可以通过其实例获得 Spring 容器中的 Bean 对象
4.2 ApplicationContext的实现类
-
ClassPathXmlApplicationContext
它是从类的根路径下加载配置文件 推荐使用这种
-
FileSystemXmlApplicationContext
它是从磁盘路径上加载配置文件,配置文件可以在磁盘的任意位置。
-
AnnotationConfigApplicationContext
当使用注解配置容器对象时,需要使用此类来创建 spring 容器。它用来读取注解
4.3 getBean()方法的使用
源码
public Object getBean(String name) throws BeansException { assertBeanFactoryActive(); return getBeanFactory().getBean(name);}public <T> T getBean(Class<T> requiredType) throws BeansException { assertBeanFactoryActive(); return getBeanFactory().getBean(requiredType);}
其中,当参数的数据类型是字符串时,表示根据Bean的id从容器中获得Bean实例,返回是Object,需要强转。
当参数的数据类型是Class类型时,表示根据类型从容器中匹配Bean实例,当容器中相同类型的Bean有多个时, 则此方法会报错。
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");UserService userService1 = (UserService) applicationContext.getBean("userService");UserService userService2 = applicationContext.getBean(UserService.class);
4.4 重点api
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");UserService userService1 = (UserService) applicationContext.getBean("userService");UserService userService2 = applicationContext.getBean(UserService.class);
5. Spring配置数据源
常用数据源(连接池):DBCP /C3P0 /BoneCP /Driud等
5.1 数据源的开发步骤
- maven中导入数据源的坐标和驱动
- 创建数据源对象
- 设置数据源的基本连接数据
- 使用数据源获取连接资源和归还连接资源
5.1.1 数据源的手动创建
- 在maven中导入数据源
<dependency>
<groupId>c3p0</groupId>
<artifactId>c3p0</artifactId>
<version>0.9.1.1</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.10</version>
</dependency>
-
导入数据库驱动
<dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.6</version> </dependency> -
创建c3p0连接池
// 测试创建c3p0数据源 @Test public void test01() throws Exception { // 创建德鲁伊连接池同理操作,只是把ComboPooledDataSource修改为德鲁伊连接池所对应的 ComboPooledDataSource dataSource = new ComboPooledDataSource(); dataSource.setDriverClass("com.mysql.jdbc.Driver"); dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/mybatis"); dataSource.setUser("root"); dataSource.setPassword("123456"); Connection connection = dataSource.getConnection(); System.out.println(connection); connection.close(); }
5.1.2 配置文件创建数据源
-
编写配置文件
db.properties
driver=com.mysql.jdbc.Driver url=jdbc:mysql://localhost:3306/mybatis?useSSL=false&useUnicode=true&characterEncoding=UTF-8 username=root password=123456 -
读取配置文件创建数据源
@Test public void test02() throws Exception{ // 读取配置文件 ResourceBundle rb = ResourceBundle.getBundle("db"); String driver = rb.getString("driver"); String url = rb.getString("url"); String username = rb.getString("username"); String password = rb.getString("password"); // 创建数据源对象 ComboPooledDataSource dataSource = new ComboPooledDataSource(); dataSource.setDriverClass(driver); dataSource.setJdbcUrl(url); dataSource.setUser(username); dataSource.setPassword(password); Connection connection = dataSource.getConnection(); System.out.println(connection); connection.close(); }5.1.3 Spring配置数据源
可以将DataSource的创建权交由Spring容器去完成
- DataSource有无参构造方法,而Spring默认就是通过无参构造方法实例化对象的
- DataSource要想使用需要通过set方法设置数据库连接信息,而Spring可以通过set方法进行字符串注入
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="driverClass" value="com.mysql.jdbc.Driver"/>
<property name="jdbcUrl" value="jdbc:mysql://localhost:3306/mybatis?useSSL=false&useUnicode=true&characterEncoding=UTF-8"/>
<property name="user" value="root"/>
<property name="password" value="123456"/>
</bean>
测试从容器当中获取数据源
ApplicationContext applicationContext = new
ClassPathXmlApplicationContext("applicationContext.xml");
DataSource dataSource = (DataSource)
applicationContext.getBean("dataSource");
Connection connection = dataSource.getConnection();
System.out.println(connection);
5.1.3 抽取jdbc配置文件
applicationContext.xml加载jdbc,properties配置文件获取连接信息。
首先要引入context命名空间和约束路径:
-
命名空间:
xmlns:context="http://www.springframework.org/schema/context" -
约束路径:
http://www.springframework.org/schema/context/spring-context.xsd
<context:property-placeholder location="classpath:jdbc.properties"/>
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="driverClass" value="${jdbc.driver}"/>
<property name="jdbcUrl" value="${jdbc.url}"/>
<property name="user" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</bean>
Spring容器加载properties文件
<context:property-placeholder location="xx.properties"/>
<property name="" value="${key}"/>
采用了el表达式
6. Spring注解开发
6.1 Spring原始注解
spring原始注解主要是为了替代的配置
| 注解 | 说明 |
|---|---|
| @Component | 使用在类上用于实例化Bean |
| @Controller | 使用在web层类上用于实例化Bean |
| @Service | 使用在service层类上用于实例化Bean |
| @Repository | 使用在dao层类上用于实例化Bean |
| @Autowired | 使用在字段上用于根据类型依赖注入 |
| @Qualifier | 结合@Autowired一起使用用于根据名称进行依赖注入 |
| @Resource | 相当于@Autowired+@Qualifier,按照名称进行注入 |
| @Value | 注入普通属性 |
| @Scope | 标注Bean的作用范围 |
| @PostConstruct | 使用在方法上标注该方法是Bean的初始化方法 |
| @PreDestroy | 使用在方法上标注该方法是Bean的销毁方法 |
当使用注解开发时,需要在xml中配置组件扫描
<!-- 组件扫描-->
<context:component-scan base-package="com.itheima"/>
6.1.1 @Compont
使用 @Compont 或 @Repoistory标识UserDaoImpl需要Spring进行实例化
// <bean id="userDao" class="com.itheima.dao.impl.UserDaoImpl"/>
@Component("userDao")
public class UserDaoImpl implements UserDao {
@Override
public void save() {
System.out.println("save方法实现了");
}
}
6.1.2 @Autowired @Qulifer 进行注入
使用@Autowired @Qulifer 进行userDao的注入
// <bean id="userService" class="com.itheima.service.impl.UserServiceImpl">
@Component("userService")
public class UserServiceImpl implements UserService {
// <property name="userDao" ref="userDao"></property>
@Autowired // 按照数据类型从Spring容器中进行匹配
@Qualifier("userDao") // 是按照id从容器中进行匹配的,但是必须结合 @Autowired 一起使用
@Resource(name = "userDao") // 可以替代上面两个注解 相当于 @Autowired + @Qualifier("userDao")
private UserDao userDao;
// 注意 使用注解配置的话 不在需要set方法
public void setUserDao(UserDao userDao) {
this.userDao = userDao;
}
@Override
public void save() {
userDao.save();
}
}
6.1.3 使用@Value进行字符串的注入
@Component("userDao")
public class UserDaoImpl implements UserDao {
@Value("注入普通数据")
private String str;
@Value("${driver}")
private String driver;
@Override
public void save() {
System.out.println(str);
System.out.println(driver);
System.out.println("save方法实现了");
}
}
6.1.4 使用@Scope标注Bean的范围
@Scope("prototype")
@Scope("singleton")
public class UserDaoImpl implements UserDao {
//此处省略代码
}
6.1.5 @PostConstruct 标注初始化方法和 @PreDestroy 标注销毁方法
@PostConstruct
public void init(){
System.out.println("初始化方法....");
}
@PreDestroy
public void destroy(){
System.out.println("销毁方法.....");
}
6.2 Spring新注解
使用Spring原始注解还不能代替所有的xml配置文件。
还需要使用注解代替的配置如下。
非自定义的bean配置
加载配置文件properties的配置 context:property-placeholder
组件扫描配置 context:component-scan
引入其他文件
| 注解 | 说明 |
|---|---|
| @ComponentScan | 用于指定 Spring 在初始化容器时要扫描的包。 作用和在 Spring 的 xml 配置文件中的 一样 |
| @Configuration | 用于指定当前类是一个 Spring 配置类,当创建容器时会从该类上加载注解 |
| @Bean | 使用在方法上,标注将该方法的返回值存储到 Spring 容器中 |
| @PropertySource | 用于加载.properties 文件中的配置 |
| @Import | 用于导入其他配置类 |
//加载配置文件
//<context:property-placeholder location="db.properties"/>
@PropertySource("classpath:db.properties")
public class DataSourceConfiguration {
@Value("${driver}")
private String driver;
@Value("${url}")
private String url;
@Value("${username}")
private String username;
@Value("${password}")
private String password;
@Bean("dataSource") // Spring 会将当前方法的返回值以指定名字存储到Spring容器中
public DataSource getDataSource() throws Exception {
ComboPooledDataSource dataSource = new ComboPooledDataSource();
dataSource.setDriverClass(driver);
dataSource.setJdbcUrl(url);
dataSource.setUser(username);
dataSource.setPassword(password);
return dataSource;
}
}
//标志该类是Sping的核心配置类
@Configuration
//<!-- 组件扫描-->
//<context:component-scan base-package="com.itheima"/>
@ComponentScan("com.itheima")
@Import({DataSourceConfiguration.class})
public class SpringCofiguration {
//.......
}
测试加载核心配置类创建Spring容器
public class UserController {
public static void main(String[] args) throws SQLException {
// ApplicationContext app = new ClassPathXmlApplicationContext("applicationContext.xml");
ApplicationContext applicationContext = new
AnnotationConfigApplicationContext(SpringCofiguration.class);
UserService userService = (UserService)
applicationContext.getBean("userService");
userService.save();
DataSource dataSource = (DataSource)
applicationContext.getBean("dataSource");
Connection connection = dataSource.getConnection();
System.out.println(connection);
}
}
6.3 Spring集成Junit
① 导入spring集成Junit的坐标
② 使用@Runwith注解替换原来的运行期
③ 使用@ContextConfiguration指定配置文件或配置类
④ 使用@Autowired注入需要测试的对象
⑤ 创建测试方法进行测试
- 导入spring集成Junit的坐标
<!--此处需要注意的是,spring5 及以上版本要求 junit 的版本必须是 4.12 及以上-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency
- 使用@Runwith注解替换原来的运行期
@RunWith(SpringJUnit4ClassRunner.class)
public class SpringJunitTest {
}
- 使用@ContextConfiguration指定配置文件或配置类
@RunWith(SpringJUnit4ClassRunner.class)
//加载spring核心配置文件
//@ContextConfiguration(value = {"classpath:applicationContext.xml"})
//加载spring核心配置类
@ContextConfiguration(classes = {SpringConfiguration.class})
public class SpringJunitTest {
}
- 使用@Autowired注入需要测试的对象
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = {SpringConfiguration.class})
public class SpringJunitTest {
@Autowired
private UserService userService;
}
- 创建测试方法进行测试
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = {SpringConfiguration.class})
public class SpringJunitTest {
@Autowired
private UserService userService;
@Test
public void testUserService(){
userService.save();
}
}
7. Spring集成web环境
7.1 ApplicationContext应用上下文获取方式
- 应用上下文对象是通过new ClasspathXmlApplicationContext(spring配置文件) 方式获取的,但是每次从 容器中获得Bean时都要编写new ClasspathXmlApplicationContext(spring配置文件) ,这样的弊端是配置 文件加载多次,应用上下文对象创建多次。
- 在Web项目中,可以使用ServletContextListener监听Web应用的启动,我们可以在Web应用启动时,就加 载Spring的配置文件,创建应用上下文对象ApplicationContext,在将其存储到最大的域servletContext域 中,这样就可以在任意位置从域中获得应用上下文ApplicationContext对象了。
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>applicationContext.xml</param-value>
</context-param>
@WebListener
public class ContextLoaderListener implements ServletContextListener {
@Override
public void contextInitialized(ServletContextEvent sce) {
ServletContext servletContext = sce.getServletContext();
// 读取web.xml中的参数
String contextConfigLocation = servletContext.getInitParameter("contextConfigLocation");
// ApplicationContext app = new ClassPathXmlApplicationContext("applicationContext.xml");
ApplicationContext app = new ClassPathXmlApplicationContext(contextConfigLocation);
// 将spring的应用上下文存储到ServletContext的域中
sce.getServletContext().setAttribute("app",app);
System.out.println("Spring容器创建完毕");
}
}
@WebServlet("/userServlet")
public class UserServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
ServletContext servletContext = this.getServletContext();
ApplicationContext app = (ApplicationContext) servletContext.getAttribute("app");
UserService userService = app.getBean(UserService.class);
userService.save();
}
}
7.2 Spring提供获取应用上下文的工具
上面的7.1分析不用手动实现,Spring提供了一个监听器ContextLoaderListener就是对上述功能的封装,该监听器内部加载Spring配置文件,创建应用上下文对象,并存储到ServletContext域中,提供了一个客户端工 具WebApplicationContextUtils供使用者获得应用上下文对象。
- web.xml中配置ContextLoaderListener监听器(导入spring-web坐标)
- 使用WebApplicationContextUtils获得应用上下文对象ApplicationContext
<!--在pom.xml中配置-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>5.2.12.RELEASE</version>
</dependency>
<!--在web.xml中配置-->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:applicationContext.xml</param-value>
</context-param>
<listener>
<listener-class>org.springframework.web.context.ContextLoader</listener-class>
</listener>
@WebServlet("/userServlet")
public class UserServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
ServletContext servletContext = this.getServletContext();
WebApplicationContext app = WebApplicationContextUtils.getWebApplicationContext(servletContext);
UserService userService = app.getBean(UserService.class);
userService.save();
}
}
6、Spring 的 AOP 简介
6.1 什么是 AOP
AOP 为 Aspect Oriented Programming 的缩写,意思为面向切面编程,是通过预编译方式和运行期动态代理 实现程序功能的统一维护的一种技术。 AOP 是 OOP 的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍 生范型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序 的可重用性,同时提高了开发的效率。
AOP术语
- 连接点(Joinpoint) 程序执行的某个特定位置,如某个方法调用前,调用后,方法抛出异常后,这些代码中的特定点称为连接点。简单来说,就是在哪加入你的逻辑增强。连接点表示具体要拦截的方法,上面切点是定义一个范围,而连接点是具体到某个方法
- 切点(PointCut) 每个程序的连接点有多个,如何定位到某个感兴趣的连接点,就需要通过切点来定位。比如,连接点--数据库的记录,切点--查询条件。切点用于来限定Spring-AOP启动的范围,通常我们采用表达式的方式来设置,所以关键词是范围
- 增强(Advice) 增强是织入到目标类连接点上的一段程序代码。在Spring中,像BeforeAdvice等还带有方位信息 通知是直译过来的结果,我个人感觉叫做“业务增强”更合适 对照代码就是拦截器定义的相关方法,通知分为如下几种:
- 前置通知(before):在执行业务代码前做些操作,比如获取连接对象
- 后置通知(after):在执行业务代码后做些操作,无论是否发生异常,它都会执行,比如关闭连接对象
- 异常通知(afterThrowing):在执行业务代码后出现异常,需要做的操作,比如回滚事务
- 返回通知(afterReturning),在执行业务代码后无异常,会执行的操作
- 环绕通知(around),这个目前跟我们谈论的事务没有对应的操作,所以暂时不谈
- 目标对象(Target) 需要被加强的业务对象
- 织入(Weaving) 织入就是将增强添加到对目标类具体连接点上的过程。织入是一个形象的说法,具体来说,就是生成代理对象并将切面内容融入到业务流程的过程。
- 代理类(Proxy) 一个类被AOP织入增强后,就产生了一个代理类。
- 切面(Aspect) 切面由切点和增强组成,它既包括了横切逻辑的定义,也包括了连接点的定义,SpringAOP就是将切面所定义的横切逻辑织入到切面所制定的连接点中。比如上文讨论的数据库事务,这个数据库事务代码贯穿了我们的整个代码,我们就可以这个叫做切面。 SpringAOP将切面定义的内容织入到我们的代码中,从而实现前后的控制逻辑。 比如我们常写的拦截器Interceptor,这就是一个切面类。
6.2 AOP的作用及其优势
- 作用:在程序运行期间,在不修改源码的情况下对方法进行功能增强
- 优势:减少重复代码,提高开发效率,并且便于维护
6.3 AOP 的底层实现
实际上,AOP 的底层是通过 Spring 提供的的动态代理技术实现的。在运行期间,Spring通过动态代理技术动态 的生成代理对象,代理对象方法执行时进行增强功能的介入,在去调用目标对象的方法,从而完成功能的增强。
6.4 AOP动态代理技术
常用的动态代理技术
- JDK 代理 : 基于接口的动态代理技术
- cglib 代理:基于父类的动态代理技术
6.5 基于 XML 的 AOP 开发
快速入门
① 导入 AOP 相关坐标
② 创建目标接口和目标类(内部有切点)
③ 创建切面类(内部有增强方法)
④ 将目标类和切面类的对象创建权交给 spring
⑤ 在 applicationContext.xml 中配置织入关系
⑥ 测试代码
- ① 导入 AOP 相关坐标
<!--导入spring的context坐标,context依赖aop-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.0.5.RELEASE</version>
</dependency>
<!-- aspectj的织入 -->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.8.13</version>
</dependency>
② 创建目标接口和目标类(内部有切点)
public interface TargetInterface {
public void method();
}
public class Target implements TargetInterface {
@Override
public void method() {
System.out.println("Target running....");
}
}
③ 创建切面类(内部有增强方法)
public class MyAspect {
//前置增强方法
public void before(){
System.out.println("前置代码增强.....");
}
}
//Proceeding JoinPoint: 正在执行的连接点===切点
public Object around(ProceedingJoinPoint pjp) throws Throwable {
System.out.println("环绕前增强....");
Object proceed = pjp.proceed();//切点方法
System.out.println("环绕后增强....");
return proceed;
}
④ 将目标类和切面类的对象创建权交给 spring
<!--配置目标类-->
<bean id="target" class="com.itheima.aop.Target"></bean>
<!--配置切面类-->
<bean id="myAspect" class="com.itheima.aop.MyAspect"></bean>
⑤ 在 applicationContext.xml 中配置织入关系
导入aop命名空间
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
配置切点表达式和前置增强的织入关系
<aop:config>
<!--引用myAspect的Bean为切面对象-->
<aop:aspect ref="myAspect">
<!--配置Target的method方法执行时要进行myAspect的before方法前置增强-->
<aop:before method="before" pointcut="execution(public void com.itheima.aop.Target.method())"></aop:before>
</aop:aspect>
</aop:config>
测试
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class AopTest {
@Autowired
private TargetInterface target;
@Test
public void test1(){
target.method();
}
}
6.5.1切点表达式的写法
语法:
execution([修饰符] 返回值类型 包名.类名.方法名(参数))
访问修饰符可以省略
返回值类型、包名、类名、方法名可以使用星号* 代表任意
包名与类名之间一个点 . 代表当前包下的类,两个点 .. 表示当前包及其子包下的类
参数列表可以使用两个点 .. 表示任意个数,任意类型的参数列表
例如:
execution(public void com.itheima.aop.Target.method())
execution(void com.itheima.aop.Target.*(..))
execution(* com.itheima.aop.*.*(..)) —————>{常用方法}
execution(* com.itheima.aop..*.*(..))
execution(* *..*.*(..))
6.5.2 通知的类型
通知的配置语法
<aop:通知类型 method=“切面类中方法名” pointcut=“切点表达式"></aop:通知类型>
6.5.3 切点表达式的抽取
<aop:config>
<!--引用myAspect的Bean为切面对象-->
<aop:aspect ref="myAspect">
<aop:pointcut id="myPointcut" expression="execution(* com.itheima.aop.*.*(..))"/>
<aop:before method="before" pointcut-ref="myPointcut"></aop:before>
</aop:aspect>
</aop:config>
6.6 基于注解的开发
① 创建目标接口和目标类(内部有切点)
② 创建切面类(内部有增强方法)
同上
③ 将目标类和切面类的对象创建权交给 spring
@Component("target")
public class Target implements TargetInterface {
@Override
public void method() {
System.out.println("Target running....");
}
}
@Component("myAspect")
public class MyAspect {
public void before(){
System.out.println("前置代码增强.....");
}
}
④ 在切面类中使用注解配置织入关系
@Component("myAspect")
@Aspect
public class MyAspect {
@Before("execution(* com.itheima.aop.*.*(..))")
public void before(){
System.out.println("前置代码增强.....");
}
}
⑤ 在配置文件中开启组件扫描和 AOP 的自动代理
<!--组件扫描-->
<context:component-scan base-package="com.itheima.aop"/>
<!--aop的自动代理-->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
-
注解通知的类型
-
切点表达式的抽取
同 xml 配置 aop 一样,我们可以将切点表达式抽取。抽取方式是在切面内定义方法,在该方法上使用@Pointcut 注解定义切点表达式,然后在在增强注解中进行引用。具体如下:
@Component("myAspect") @Aspect public class MyAspect { @Before("MyAspect.myPoint()") public void before(){ System.out.println("前置代码增强....."); } @Pointcut("execution(* com.itheima.aop.*.*(..))") public void myPoint(){} }
8. 声明式事务控制
8.1 编程式事务控制相关对象
8.1.1 PlatformTransactionManager
PlatformTransactionManager 接口是 spring 的事务管理器,它里面提供了我们常用的操作事务的方法。
| 方法 | 说明 |
|---|---|
| TransactionStatus getTransaction(TransactionDefination defination) | 获取事务的状态信息 |
| void commit(TransactionStatus status) | 提交事务 |
| void rollback(TransactionStatus status) | 回滚事务 |
8.1.2 TransactionDefinition
TransactionDefinition 是事务的定义信息对象,里面有如下方法:
| 方法 | 声明 |
|---|---|
| int getIsolationLevel() | 获得事务的隔离级别 |
| int getIsolationLevel() | 获得事务的传播行为 |
| int getTimeout() | 获得超时时间 |
| boolean isReadOnly() | 是否只读 |
-
事务的隔离级别
设置事务的隔离级别,可以解决事务并发产生的问题,比如脏读,不可重复读和虚读。
- ISOLATION_DEFAULT
- SOLATION_READ_UNCOMMITTED
- ISOLATION_READ_COMMITTED
- ISOLATION_REPEATABLE_READ
- ISOLATION_SERIALIZABLE
-
事务传播行为
- REQUIRED:如果当前没有事务,就新建一个事务,如果已经存在一个事务中,加入到这个事务中。一般的选择(默认值)
- SUPPORTS:支持当前事务,如果当前没有事务,就以非事务方式执行(没有事务)
- MANDATORY:使用当前的事务,如果当前没有事务,就抛出异常
- REQUERS_NEW:新建事务,如果当前在事务中,把当前事务挂起。
- NOT_SUPPORTED:以非事务方式执行操作,如果当前存在事务,就把当前事务挂起
- NEVER:以非事务方式运行,如果当前存在事务,抛出异常 NESTED:如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则执行 REQUIRED 类似的操作
- 超时时间:默认值是-1,没有超时限制。如果有,以秒为单位进行设置
- 是否只读:建议查询时设置为只读
8.1.3 TransactionStatus
TransactionStatus 接口提供的是事务具体的运行状态,方法介绍如下。
| 方法 | 说明 |
|---|---|
| boolean hasSavepoint() | 是否存储回滚点 |
| boolean isCompleted() | 事务是否完成 |
| boolean isNewTransaction() | 是否是新事务 |
| boolean isRollbackOnly() | 事务是否回滚 |
8.2 基于xml的声明式事务控制
8.2.1 简介
Spring 的声明式事务顾名思义就是采用声明的方式来处理事务。这里所说的声明,就是指在配置文件中声明 ,用在 Spring 配置文件中声明式的处理事务来代替代码式的处理事务。
声明式事务处理的作用
- 事务管理不侵入开发的组件。具体来说,业务逻辑对象就不会意识到正在事务管理之中,事实上也应该如 此,因为事务管理是属于系统层面的服务,而不是业务逻辑的一部分,如果想要改变事务管理策划的话, 也只需要在定义文件中重新配置即可
- 在不需要事务管理的时候,只要在设定文件上修改一下,即可移去事务管理服务,无需改变代码重新编译 ,这样维护起来极其方便
注意:Spring 声明式事务控制底层就是AOP。
8.2.2 声明式事务控制的实现
- 谁是切点
- 谁是通知
- 配置切面
① 引入tx命名空间
xmlns:tx="http://www.springframework.org/schema/tx"
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd
② 配置事务增强
<!--配置平台事务管理器-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<!--通知 事务的增强-->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<!--设置事务的属性信息的-->
<tx:attributes>
<tx:method name="transfer" isolation="REPEATABLE_READ" propagation="REQUIRED" read-only="false"/>
<tx:method name="save" isolation="REPEATABLE_READ" propagation="REQUIRED" read-only="false"/>
<tx:method name="findAll" isolation="REPEATABLE_READ" propagation="REQUIRED" read-only="true"/>
<tx:method name="update*" isolation="REPEATABLE_READ" propagation="REQUIRED" read-only="true"/>
<tx:method name="*"/>
</tx:attributes>
</tx:advice>
③ 配置事务 AOP 织入
<!--配置事务的aop织入-->
<aop:config>
<aop:pointcut id="txPointcut" expression="execution(* com.itheima.service.impl.*.*(..))"/>
<aop:advisor advice-ref="txAdvice" pointcut-ref="txPointcut"/>
</aop:config>
④ 测试事务控制转账业务代码
@Override
public void transfer(String outMan, String inMan, double money) {
accountDao.out(outMan,money);
int i = 1/0;
accountDao.in(inMan,money);
}
\