Spring
前言:
本文作为本人学习spring,springMVC框架的学习整理记录,学习内容来自b站黑马程序员视频:【黑马程序员SSM框架教程|Spring+SpringMVC+MyBatis全套教程(spring+springmvc+mybatis)】 https://www.bilibili.com/video/BV1WZ4y1P7Bp/?p=198&share_source=copy_web&vd_source=9160ab6c26053cede7451cdb5549c8ab
1. 简介
1.1 定义
Spring 是分层的 Java SE/EE 应用 full-stack(全栈) 轻量级开源框架,以LoC(Inverse Of Control:反转控制) 和
**AOP(Aspect Oriented Programming:面向切面编程)**为内核。
EJB是Spring的前身
2017.9发布spring最新通用版本Spring5.0(GA)
1.2 Spring优势
1.2.1 方便解耦,简化开发
通过 Spring 提供的 LoC 容器,可以将对象间的依赖关系交由 Spring 进行控制,避免硬编码所造成的过度耦合 。用户也不必再为单例模式类,属性文件等这些很底层的需求编写代码,可以更专注于上层的应用。
1.2.2 AOP 编程的支持
通过 Spring 的 AOP 功能,方便进行面向切面编程,许多不容易用OOP 实现的功能可以通过 AOP 轻松实现 。
1.2.3 声明式事务的支持
可以将我们从单调烦闷的事务里代码中解脱出来,通过声明式方法灵活的进行事务管理,提高开发效率和质量 。
1.2.4 方便程序的测试
可以用非容器依赖的编程方式进行几乎所有的测试工作(Junit),测试不再是昂贵的操作,而是随手可做啲事情。
1.2.5 方便集成各种优秀框架
Spring对各种优秀框架(Struts、Hibernate、Hessian、Quartz)的支持。
1.2.6 降低JavaEE API 的使用难度
Spring对JavaEE API (如JDBC、JavaMail、远程调用等)进行了薄薄的封装层,时这些API的使用难度大为降低。
1.2.7 Java源码是经典学习范例
略
1.3 Spring体系结构
目前,Spring框架按照功能组织成大约20个模块。这些模块分为核心容器、数据访问/集成、Web、AOP(面向切面的编程)、instrument(支持和类加载器的实现来在特定的应用服务器上使用)、消息和测试,如下图所示。
上图中,除了Test表示的是所有的内容都可以通过Test测试,其他皆是由下到上的递进依赖关系。
Spring主要分成六个模块:
1.Spring核心容器:核心容器是Spring框架的重要组成部分,也可以说是Spring框架的基础。他在整个框架中的作用是负责管理对象的创建,管理,配置等操作。其主要包含spring-core、spring-beans、spring-context、spring-expression(SpEl–Spring Expression Language)组件。
2.面向切面编程(AOP):Spring框架还提供了面向切面编程的能力,利用面向切面编程,可以实现一些面向对象编程无法很好实现的操作。例如,将日志,事务与具体的业务逻辑解耦。其主要包含spring-aop、spring-aspects组件。
3.Instrumentation:该模块提供了为JVM添加代理的功能,该模块包含spring-instrument、spring-instrument-tomcat组件,**使用较少,**不必过分关注。
4.数据访问与集成:Spring框架为了简化数据访问的操作,包装了很多关于数据访问的操作,提供了相应的模板。同时还提供了使用ORM框架的能力,可以与很多流行的ORM框架进行整合,如hibernate,mybatis等等的著名框架。还实现了数据事务的能力,能够支持事务。包含spring-jdbc、spring-tx、spring-orm、spring-oxm、spring-jms、spring-messaging组件。
5.Web和远程调用:Spring框架支持Web开发,以及与其他应用远程交互调用的方案。包含spring-web、spring-webmvc、spring-websocket、spring-webmvc-portlet组件。
6.Spring测试:Spring框架提供了测试的模块,可以实现单元测试,集成测试等等的测试流程,整合了JUnit或者TestNG测试框架。包含spring-test组件。
2. 入门使用
2.1 开发步骤
- 导入Spring开发的基本包坐标
- 编写Dao接口和实现类
- 编写Spring核心配置文件(applicationContext.xml)
- 在Spring配置文件中配置接口全限名
- 使用SpringAPI获取Bean实例
在这个开发过程中,取消了new改用SpringAPI去获取Bean实例,实现了解耦,在开发过程中若需要改变使用的Dao类,可以直接修改xml配置文件(编译前编译后xml不变)完成Dao类的转换
3. Spring配置文件
3.1 Bean标签基本配置
用于配置对象交由Spring创建,默认情况下调用该类的无参构造,若不存在无参构造则不能创建成功。
id: 唯一标识,用于标记Spring创建的Bean对象(实例)。(使用单驼峰,后面代码可能有不规范)
class: Bean全限名。
3.2 Bean标签范围配置
scope: 指对象的作用范围,取值如下
singleton
使用singleton作为scope ,在项目中获取的Spring 创建 的Bean实例为同一个,即为单例。
实例:1
创建时机:Spring核心文件被加载时(及创建Spring容器时)。
生命周期:
创建:当应用加载,容器被创建时,对象就被创建了。
运行:只要容器存在,对象一直存在。
销毁:当应用卸载,容器被销毁时,对象被销毁。
prototype
使用prototype ,则存在多个Bean实例,即为多例。
实例:n
创建时机:当调用getBean()方法时实例化Bean。
生命周期:
创建:当使用对象时,创建新的对象实例。
运行:只要对象在使用中,就一直存活。
销毁:当对象长时间不使用时,被Java垃圾回收器回收。
3.3 Bean生命周期配置
init-method:指定类中的初始化方法名称
destroy-method:指定类中销毁方法名称
在实例被创建后才初始化方法
3.4 Bean实例化三种方式
1.无参构造方法实例化(主要)
<bean id="BeanDao" class="com.com.clown.dao.impl.BeanDaoImpl"></bean>
2.工厂静态方法实例化
<bean id="BeanDao" class="com.com.clown.factory.StaticFactory" factory-method="getBeanDao"></bean>
package com.clown.factory;
import com.clown.dao.BeanDao;
import com.clown.dao.impl.BeanDaoImpl;
public class StaticFactory {
public static BeanDao getBeanDao(){
return new BeanDaoImpl();
}
}
3.工厂实例方法实例化
<bean id="Factory" class="com.com.clown.factory.DynamicFactory"></bean>
<bean id="BeanDao" factory-bean="Factory" factory-method="getBeanDao"></bean>
package com.clown.factory;
import com.clown.dao.BeanDao;
import com.clown.dao.impl.BeanDaoImpl;
public class DynamicFactory {
public BeanDao getBeanDao(){
return new BeanDaoImpl();
}
}
3.5 依赖注入
定义:依赖注入(Dependency Injection)是Spring框架核心IOC的具体实现。
在编写程序时,通过控制反转,把对象的创建交给了Spring,但是代码中不可能出现没有依赖的情况(依赖表示代码依赖Spring创建的实例)。
IOC解耦只是降低他们的依赖关系,但不会消除。
通过依赖注入的方式构建的依赖关系,在使用Spring后会让Spring来维护,不需要我们自己去获取(不需要再次创建Spring容器去获取实例).
3.5.1 场景分析
当外部(Controller)调用Spring服务时,Spring程序存在多个Dao,被多个Service调用,若不使用依赖注入,会出现如下代码耦合情况
Service内部如下
package com.clown.service.impl;
import com.clown.dao.BeanDao;
import com.clown.service.BeanService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class BeanServiceImpl implements BeanService {
@Override
public void test() {
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
BeanDao beanDao = (BeanDao) applicationContext.getBean("BeanDao");
beanDao.test();
}
}
Controller如下
package com.clown.demo;
import com.clown.service.BeanService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class SpringController {
public static void main(String[] args) {
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
BeanService beanService = (BeanService) applicationContext.getBean("BeanService");
beanService.test();
}
}
可以看到,加载了两次Spring容器
使用依赖注入的目的即将实例关系改变为如下
3.5.2 set方式注入
Service代码
package com.clown.service.impl;
import com.clown.dao.BeanDao;
import com.clown.service.BeanService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class BeanServiceImpl implements BeanService {
private BeanDao beanDao;
//注意,依赖注入xml标签中的name值指的是set后面的单词
public void setBeanDao(BeanDao beanDao) {
this.beanDao = beanDao;
}
@Override
public void test() {
// ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
// BeanDao beanDao = (BeanDao) applicationContext.getBean("BeanDao");
beanDao.test();
}
}
applicationContext.xml
使用property标签的方式:
name指注入属性的名称,指向set后面的单词,但单驼峰
ref(对象引用)指向注入的Bean实例id
<bean id="BeanService" class="com.com.clown.service.impl.BeanServiceImpl">
<property name="beanDao" ref="BeanDao"></property>
</bean>
使用p命名空间的方式:
<bean id="BeanService" class="com.com.clown.service.impl.BeanServiceImpl" p:beanDao-ref="BeanDao"/>
注意在xml头部加入
xmlns:p="http://www.springframework.org/schema/p"
3.5.3 构造方法方式注入
Service代码
package com.clown.service.impl;
import com.clown.dao.BeanDao;
import com.clown.service.BeanService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class BeanServiceImpl implements BeanService {
private BeanDao beanDao;
public BeanServiceImpl(BeanDao beanDao) {
this.beanDao = beanDao;
}
//注意,依赖注入xml标签中的name值指的是set后面的单词
public void setBeanDao(BeanDao beanDao) {
this.beanDao = beanDao;
}
@Override
public void test() {
// ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
// BeanDao beanDao = (BeanDao) applicationContext.getBean("BeanDao");
beanDao.test();
}
}
applicationContext.xml
name指向有参构造中属性名
ref指向注入Bean实例名
<bean id="BeanService" class="com.com.clown.service.impl.BeanServiceImpl">
<constructor-arg name="beanDao" ref="BeanDao"></constructor-arg>
</bean>
注入结果
可以看到,Spring容器只创建了一次,并且BeanService也可以通过注入的Dao来实现test()方法。
3.6 Bean依赖注入的数据类型
上述操作注入的都是引用Bean ,一共有三种数据类型可以被注入。
- 普通数据类型
- 引用数据类型
- 集合数据类型
整合如下,使用的是set方式,构造方法方式同样通用
Dao代码,其中存在两种数据类型(也可以说内嵌了一种,一共三种)
package com.clown.dao.impl;
import com.clown.dao.BeanDao;
import com.clown.entity.UserEntity;
import java.util.List;
import java.util.Map;
import java.util.Properties;
public class BeanDaoImpl implements BeanDao {
private int id;
private String name;
private List<Integer> list;
private Map<String, UserEntity> map;
private Properties properties;
public void setId(int id) {
this.id = id;
}
public void setName(String name) {
this.name = name;
}
public void setList(List<Integer> list) {
this.list = list;
}
public void setMap(Map<String, UserEntity> map) {
this.map = map;
}
public void setProperties(Properties properties) {
this.properties = properties;
}
public BeanDaoImpl() {
System.out.println("Bean实例被创建......");
}
public void init(){
System.out.println("Bean初始化方法......");
}
public void destroy(){
System.out.println("Bean销毁方法......");
}
@Override
public void test() {
System.out.println(id);
System.out.println(name);
System.out.println(list.toString());
System.out.println(map.toString());
System.out.println(properties.toString());
System.out.println("BeanTest......");
}
}
UserEntity
package com.clown.entity;
public class UserEntity {
private int userId;
private String userName;
public int getUserId() {
return userId;
}
public void setUserId(int userId) {
this.userId = userId;
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
@Override
public String toString() {
return "UserEntity{" +
"userId=" + userId +
", userName='" + userName + '\'' +
'}';
}
}
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"
xmlns:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- <bean id="BeanDao" class="com.clown.dao.impl.BeanDaoImpl" scope="singleton" init-method="init" destroy-method="destroy"></bean>-->
<bean id="BeanDao" class="com.clown.dao.impl.BeanDaoImpl">
<property name="id" value="1"/>
<property name="name" value="bean1"/>
<property name="list">
<list>
<value>1</value>
<value>2</value>
<value>3</value>
</list>
</property>
<property name="map">
<map>
<entry key="user1" value-ref="userEntity1"/>
<entry key="user2" value-ref="userEntity2"/>
</map>
</property>
<property name="properties">
<props>
<prop key="prop1">p1</prop>
<prop key="prop2">p2</prop>
<prop key="prop3">p3</prop>
</props>
</property>
</bean>
<!-- <bean id="BeanDao" class="com.clown.factory.StaticFactory" factory-method="getBeanDao"></bean>-->
<!-- <bean id="Factory" class="com.clown.factory.DynamicFactory"></bean>-->
<!-- <bean id="BeanDao" factory-bean="Factory" factory-method="getBeanDao"></bean>-->
<!-- <bean id="BeanService" class="com.clown.service.impl.BeanServiceImpl">-->
<!-- <property name="beanDao" ref="BeanDao"></property>-->
<!-- </bean>-->
<!-- <bean id="BeanService" class="com.clown.service.impl.BeanServiceImpl" p:beanDao-ref="BeanDao"/>-->
<bean id="BeanService" class="com.clown.service.impl.BeanServiceImpl">
<constructor-arg name="beanDao" ref="BeanDao"></constructor-arg>
</bean>
<bean id="userEntity1" class="com.clown.entity.UserEntity">
<property name="userId" value="1"/>
<property name="userName" value="user1"/>
</bean>
<bean id="userEntity2" class="com.clown.entity.UserEntity">
<property name="userId" value="2"/>
<property name="userName" value="user2"/>
</bean>
</beans>
结果输出
3.7 引入其他配置文件(分模块开发)
由于Spring配置内容繁多,我们可以将不同模块的配置文件拆解,再在主配置文件中使用import标签引用加载
分解如下
applicationContext-beanDao.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">
<bean id="BeanDao" class="com.clown.dao.impl.BeanDaoImpl">
<property name="id" value="1"/>
<property name="name" value="bean1"/>
<property name="list">
<list>
<value>1</value>
<value>2</value>
<value>3</value>
</list>
</property>
<property name="map">
<map>
<entry key="user1" value-ref="userEntity1"/>
<entry key="user2" value-ref="userEntity2"/>
</map>
</property>
<property name="properties">
<props>
<prop key="prop1">p1</prop>
<prop key="prop2">p2</prop>
<prop key="prop3">p3</prop>
</props>
</property>
</bean>
<bean id="userEntity1" class="com.clown.entity.UserEntity">
<property name="userId" value="1"/>
<property name="userName" value="user1"/>
</bean>
<bean id="userEntity2" class="com.clown.entity.UserEntity">
<property name="userId" value="2"/>
<property name="userName" value="user2"/>
</bean>
</beans>
applicationContext-beanService.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">
<import resource="applicationContext-beanDao.xml"/>
<bean id="BeanService" class="com.clown.service.impl.BeanServiceImpl">
<constructor-arg name="beanDao" ref="BeanDao"></constructor-arg>
</bean>
</beans>
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"
xmlns:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<!--因为在beanService.xml中被引用,可以不写?-->
<import resource="applicationContext-beanDao.xml"/>
<import resource="applicationContext-beanService.xml"/>
</beans>
注意,applicationContext-beanDao.xml被两次引用,存在嵌套关系,但运行结果无异常,取消主配置文件中的引用也仍然可以运行。
3.8 小结
4. Spring相关API
4.1 ApplicationContext的继承体系
applicationContext:接口类型 ,代表应用上下文,可以通过其实例获得Spring容器中的Bean对象
4.2 ApplicationContext的实现类
1.ClassPathXmlApplicationContext
从类的根路径下加载配置文件 (推荐使用)。
2.FileSystemXmlApplicationContext
从磁盘路径上加载配置文件,配置文件可以在任何位置。
3.AnnotationConfigApplicationContext
当使用注解配置容器对象时,需要使用此类来创建spring容器,用来读取注解。
4.3 getBean()方法的使用
getBean存在两种使用方式:id 或者 Class类
使用id定位Bean实例,允许配置文件中存在多个同类型的Bean实例,因为存在唯一标识符id区分。
但使用Class类定位实例,只允许配置文件中存在一个该类型的Bean实例,出现多个会报错。
5. 数据源配置
5.1 数据源(连接池)的作用
- 数据源(连接池)为了提高程序性能而出现的
- 事先实例化数据源,初始化部分连接资源
- 使用连接资源时从数据源中获取
- 使用完毕后将连接资源归还数据源
常见的数据源(连接池):DBCP、C3P0、BoneCP、Druid等
5.2 数据源的开发步骤
- 导入数据源的坐标和数据源驱动坐标
- 创建数据源对象
- 设置数据源的基本连接数据
- 使用数据源获取连接资源和归还连接资源
1.pom坐标
<!-- c3po连接池-->
<dependency>
<groupId>c3p0</groupId>
<artifactId>c3p0</artifactId>
<version>0.9.1.2</version>
</dependency>
<!-- druid连接池-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.0.28</version>
</dependency>
<!-- jdbc驱动-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.25</version>
</dependency>
2.代码
//c3po连接
ComboPooledDataSource comboPooledDataSource = new ComboPooledDataSource();
comboPooledDataSource.setDriverClass("com.mysql.jdbc.Driver");
comboPooledDataSource.setJdbcUrl("#jdbc链接#");
comboPooledDataSource.setUser("#用户名#");
comboPooledDataSource.setPassword("#密码#");
Connection connection = comboPooledDataSource.getConnection();
System.out.println(connection);
connection.close();
//druid连接
//获取连接池对象
DruidDataSource druidDataSource = new DruidDataSource();
//设置连接信息
druidDataSource.setDriverClassName("com.mysql.jdbc.Driver");
druidDataSource.setUrl("#jdbc链接#");
druidDataSource.setUsername("#用户名#");
druidDataSource.setPassword("#密码#");
//获取连接资源
DruidPooledConnection connection1 = druidDataSource.getConnection();
System.out.println(connection1);
//关闭连接资源
connection1.close();
3.配置文件解耦
jdbc.properties
jdbc.driver = com.mysql.jdbc.Driver
jdbc.url = #jdbc链接#
jdbc.username = #用户名#
jdbc.password = #密码#
//解析配置文件
ResourceBundle jdbc = ResourceBundle.getBundle("jdbc");
String driver = jdbc.getString("jdbc.driver");
String url = jdbc.getString("jdbc.url");
String username = jdbc.getString("jdbc.username");
String password = jdbc.getString("jdbc.password");
//c3po连接
ComboPooledDataSource comboPooledDataSource = new ComboPooledDataSource();
comboPooledDataSource.setDriverClass(driver);
comboPooledDataSource.setJdbcUrl(url);
comboPooledDataSource.setUser(username);
comboPooledDataSource.setPassword(password);
Connection connection = comboPooledDataSource.getConnection();
System.out.println(connection);
connection.close();
5.3 Spring产生数据源对象
package com.clown.demo;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.SQLException;
public class SpringDataSourceDemo {
public static void main(String[] args) throws Exception {
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
//DataSource datasource = (DataSource) applicationContext.getBean("datasource");
DataSource datasource = applicationContext.getBean(DataSource.class);
Connection connection = datasource.getConnection();
System.out.println(connection);
}
}
5.3.1 直接配置
applicationContext.xml
<bean id="datasource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="driverClass" value="com.mysql.jdbc.Driver"/>
<property name="jdbcUrl" value="#jdbc链接#"/>
<property name="user" value="#用户名#"/>
<property name="password" value="#密码#"/>
</bean>
参考上文,我们配置了一个jdbc.properties的配置文件,一般来说,会将数据库连接的配置文件与spring的配置文件分开管理。因此我们抽取jdbc配置文件。
5.3.2 jdbc配置文件抽取
首先,需要引入context命名空间和约束路径
- 命名空间 xmlns:context=“http://www.springframework.org/schema/context”
- 约束路径 http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
使用 context:property-placeholder 加载配置文件
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
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>
</beans>
6. Spring注解开发
Spring存在两种注解,原始注解与新注解
6.1 原始注解
原始注解主要是用于替代Bean标签的配置
如下代码
BaoDaoImpl
package com.clown.dao.impl;
import com.clown.dao.BeanDao;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Repository;
//@Component("beanDao")
@Repository("beanDao")
@Scope("singleton")
public class BeanDaoImpl implements BeanDao {
@Override
public void function() {
System.out.println("Bean...function...");
}
}
BaoServiceImpl
package com.clown.service.impl;
import com.clown.dao.BeanDao;
import com.clown.service.BeanService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Service;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import javax.annotation.Resource;
//@Component("beanService")
@Service("beanService")
public class BeanServiceImpl implements BeanService {
// @Autowired //根据数据类型注入Bean
// @Qualifier("beanDao") //根据id去容器中匹配 需要结合Autowired使用
@Resource(name = "beanDao")
BeanDao beanDao;
@Value("${jdbc.url}")
String url;
@Override
public void function() {
beanDao.function();
}
@PostConstruct
public void urlDisplay(){
System.out.println(url);
}
@PreDestroy
public void destroy(){
System.out.println("Bean...Destroy...");
}
}
SpringAnnotDemo(模拟web层)
package com.clown.demo;
import com.clown.service.BeanService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.SQLException;
public class SpringAnnotDemo {
public static void main(String[] args) throws Exception {
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
BeanService bean = applicationContext.getBean(BeanService.class);
bean.function();
((ClassPathXmlApplicationContext)applicationContext).close();
}
}
输出
如上使用注解,applicationContext.xml里除了数据源配置外没有其他的Bean
但仍然存在无法被原始注解取代的标签
- 非自定义的Bean配置:<bean>
- 加载properties文件的配置:<context:property-placeholder>
- 组件扫描的配置:<context:component-scan>
- 引入其他文件:<import>
因此引入了新注解
6.2 新注解
SpringConfiguration
package com.clown.config;
import com.mchange.v2.c3p0.ComboPooledDataSource;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import javax.sql.DataSource;
//核心配置文件
@Configuration
//包扫描
@ComponentScan("com.clown")
//配置文件导入
@Import(JdbcConfiguration.class)
public class SpringConfiguration {
}
JdbcConfiguration
package com.clown.config;
import com.mchange.v2.c3p0.ComboPooledDataSource;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.PropertySource;
import javax.sql.DataSource;
import java.beans.PropertyVetoException;
@PropertySource("classpath:jdbc.properties")
public class JdbcConfiguration {
@Value("${jdbc.driver}")
String driver;
@Value("${jdbc.url}")
String url;
@Value("${jdbc.username}")
String username;
@Value("${jdbc.password}")
String password;
@Bean("dataSource")
public DataSource getDataSource() throws Exception {
ComboPooledDataSource comboPooledDataSource = new ComboPooledDataSource();
comboPooledDataSource.setDriverClass(driver);
comboPooledDataSource.setJdbcUrl(url);
comboPooledDataSource.setUser(username);
comboPooledDataSource.setPassword(password);
return comboPooledDataSource;
}
}
SpringAnnotDemo
package com.clown.demo;
import com.clown.config.SpringConfiguration;
import com.clown.service.BeanService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.SQLException;
public class SpringAnnotDemo {
public static void main(String[] args) throws Exception {
// ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
//注解方式获取配置文件
ApplicationContext applicationContext = new AnnotationConfigApplicationContext(SpringConfiguration.class);
BeanService bean = applicationContext.getBean(BeanService.class);
bean.function();
DataSource dataSource = applicationContext.getBean(DataSource.class);
Connection connection = dataSource.getConnection();
System.out.println(connection);
connection.close();
((AnnotationConfigApplicationContext)applicationContext).close();
}
}
运行结果
7. Spring集成Junit
7.1 Spring集成Junit步骤
- 导入Spring集成Junit的坐标
- 使用**@Runwith**注解替换原来的运行期
- 使用**@ContextConfiguration指定配置文件或配置类**
- 使用**@Autowired**注入需要测试的对象
- 创建测试方法进行测试
7.2 具体实现
1.pom坐标
<!-- springTest模块-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.3.8</version>
</dependency>
2.代码实现
package com.clown.test;
import com.clown.config.SpringConfiguration;
import com.clown.service.BeanService;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.SQLException;
@RunWith(SpringJUnit4ClassRunner.class)
//@ContextConfiguration("classpath:applicationContext.xml")
@ContextConfiguration(classes = {SpringConfiguration.class})
public class SpringJunitTest {
@Autowired
private BeanService beanService;
@Autowired
private DataSource dataSource;
@Test
public void beanServiceTest(){
beanService.function();
}
@Test
public void dataSource() throws Exception {
Connection connection = dataSource.getConnection();
System.out.println(connection);
connection.close();
}
}
3.运行结果
8. SpringMVC开发
8.1 Servlet监听器获取应用上下文
在上述项目中,模拟web层获取应用上下层是通过new ClasspathXmlApplicationContext()方式来获取的,但是这样每次获取Bean都需要获取应用上下文,配置文件被加载多次,应用上下文被创建多次。
在Web项目中,可以使用ServletContextListener监听器来监听web应用的启动,在启动web应用时,就加载Spring的配置文件,创建ApplicationContext应用上下文对象,将其存储到最大的域servletContext域中,后续任何位置都可以从该域内获取应用上下文,避免多次读取多次创建。
使用servlet需要在pom中写入如下坐标
<!-- servlet依赖-->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>4.0.1</version>
</dependency>
<dependency>
<groupId>javax.servlet.jsp</groupId>
<artifactId>javax.servlet.jsp-api</artifactId>
<version>2.3.1</version>
</dependency>
并且在web.xml中需要配置监听器
<listener>
<listener-class>自定义监听器类全限名</listener-class>
</listener>
具体实现略
8.2 SpringWeb模块集成应用上下文创建与获取
实际开发中,不需要我们进行自定义监听器,自定义Servlet的配置,Spring提供了ContextLoaderListener ,对上述功能继续了封装,改监听器内部加载Spring配置文件,创建应用上下文对象,并存储到ServletContext域中,提供了WebApplicationContextUtils供使用者获取应用上下文对象。
实际开发步骤如下
- pom.xml 在添加servlet两个坐标的基础上再添加springweb模块坐标
<!-- springWeb模块-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>5.3.8</version>
</dependency>
-
配置web.xml
<?xml version="1.0" encoding="UTF-8"?> <web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd" version="4.0"> <!--全局初始化参数--> <context-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:applicationContext.xml</param-value> </context-param> <!--监听器配置--> <listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener> <!--servlet映射配置--> <servlet> <servlet-name>ServletController</servlet-name> <servlet-class>com.clown.servlet.ServletController</servlet-class> </servlet> <servlet-mapping> <servlet-name>ServletController</servlet-name> <url-pattern>/servletController</url-pattern> </servlet-mapping> </web-app>
-
实现ServletController
package com.clown.servlet; import com.clown.dao.BeanDao; import org.springframework.context.ApplicationContext; import org.springframework.web.context.support.WebApplicationContextUtils; import javax.servlet.ServletContext; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; public class ServletController extends HttpServlet { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { ServletContext servletContext = req.getServletContext(); //通过WebApplicationContextUtils获取到存放在ServletContext域中的springApp上下文 ApplicationContext context = WebApplicationContextUtils.getWebApplicationContext(servletContext); BeanService bean = context.getBean(BeanService.class); bean.function(); } }
ContextConfigLocation:上下文配置监听器,该监听器继承了ServletContextListener监听器,监听服务启动。
WebApplicationContextUtils:springweb封装的获取应用上下文的工具类
8.3 SpringMVC简介
SpringMVC是一种基于Java实现MVC设计模型的请求驱动类型的轻量级Web框架,属于SpringFrameWork的后续产品,已经融合在Spring Web Flow中。
SpringMVC已经成为目前最主流的MVC框架之一,随着Spring3.0的发布,全面超越Struts2,成为最优秀的MVC框架。
它可以通过一套注解,让一个简单的Java类成为处理请求的控制器,而无序实现任何接口,同时它还支持RESTful编程风格的请求。
8.4SpringMVC开发步骤
- 导入SpringMVC相关坐标
- 配置SpringMVC核心控制器DispathcerServlet
- 创建Controller类和视图页面
- 使用注解配置Controller类中业务方法的映射地址
- 配置SpringMVC核心文件spring-mvc.xml
- 客户端发起请求测试
代码实现:
1.pom坐标配置
<!-- SpringMVC模块-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.3.8</version>
</dependency>
2.配置web.xml
<!-- spring-mvc核心控制器配置-->
<servlet>
<servlet-name>DispatcherServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<!-- 初始化参数配置-->
<!-- 加载spring-mvc配置文件,进行包扫描-->
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:spring-mvc.xml</param-value>
</init-param>
<!-- 启动服务器时加载-->
<load-on-startup>1</load-on-startup>
</servlet>
<!-- 控制器映射-->
<servlet-mapping>
<servlet-name>DispatcherServlet</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
3.Controller类
package com.clown.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
//web层Bean注解
@Controller
public class BeanController {
//请求映射注解
@RequestMapping("/test")
public String test(){
System.out.println("BeanController...test...");
return "testhtml.jsp";
}
}
<%--
Created by IntelliJ IDEA.
User: 61907
Date: 2023/4/13
Time: 14:35
To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Title</title>
</head>
<body>
<h1>TestSuccess</h1>
</body>
</html>
4.spring-mvc.xml配置
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
<context:component-scan base-package="com.clown.controller"/>
</beans>
5.请求测试
流程图
8.5 SpringMVC组件解析
8.5.1 SpringMVC执行流程
图解
文字
DispatcherServlet(前端控制器)更多是存在一个调度,分配任务的作用,
HandlerMappin(处理器映射器)相当于根据请求找到对应的Controller类位置并返回执行链(Chain),
HandlerApdapter(处理器适配器)根据执行链请求Controller(也叫Handler 处理器)类并得到响应返回视图与模型,
ViewReslover(视图解析器)解析视图与模型并返回视图对象(View),
最后DispatcherServlet再渲染视图返回响应给客户端。
8.5.2 SpringMVC注解解析
@RequestMapping:请求映射,用于建立请求URL和处理请求方法之间的对应关系
位置:
- 类上,请求URL的第一级访问目录。此处不写的话相当于应用的根目录
- 方法上,请求URL的第二级访问目录,与类上的注解一起组成虚拟访问路径。
属性:
- value:用于指定请求的URL。
- method:指定请求方式
- params:用于限制请求参数的条件,支持简单的表达式。
//Get请求方式,并且请求参数必须有accountName,且不为0
@RequestMapping(value = "/test",method = RequestMethod.GET,params = {"accountName","accountName!=0"})
8.5.3 SpringMVC组件扫描
除了直接限制扫描的包外,还可以通过配置文件限制扫描的注解类型。
<!--包扫描-->
<context:component-scan base-package="com.clown">
<!-- 只扫描Controller类的注解-->
<context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
<!-- 不扫描Controller类的注解-->
<!-- <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>-->
</context:component-scan>
8.5.4 SpringMVC的XML配置解析
在8.5.1中我们了解了SpringMVC的执行流程,这些流程的模块在spring包中的配置文件可以找到。
通过内部资源视图解析器InternalResourceViewResolver->UrlBasedViewResolver
可以发现存在
REDIRECT_URL_PREFIX:重定向url前缀(地址不变)
FORWARD_URL_PREFIX:转发url前缀(地址改变)
两个常量,通过解析Controller类返回的变量前缀,来创建对应的视图。
除了这两个常量,还存在两个私有属性
private String prefix = "";//前缀
private String suffix = "";//后缀
我们可以在spring-mvc.xml中配置内部资源视图解析器、来解耦,简化代码。
<bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<!-- <property name="prefix" value="/jsp/"/>-->
<property name="suffix" value=".jsp"/>
</bean>
@RequestMapping(value = "/test",method = RequestMethod.GET,params = {"accountName","accountName!=0"})
public String test(){
System.out.println("BeanController...test...");
// return "testhtml.jsp";
return "testhtml";
}
简化返回值
9. SpringMVC的数据响应与请求
9.1 SpringMVC数据响应方式
- 页面跳转
- 返回字符串
- 返回ModelAndView
- 回写数据
- 返回字符串
- 返回对象或集合
9.2 SpringMVC数据响应–页面跳转–字符串
直接返回字符串的形势会将返回值与视图解析器规定好的前后缀拼接后跳转
<bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<!-- <property name="prefix" value="/jsp/"/>-->
<property name="prefix" value="/WEB-INF/view"/>
<property name="suffix" value=".jsp"/>
</bean>
@RequestMapping("/res1")
public String stringRes(){
//默认转发
return "index";
//WEB-INF存在访问限制,无法直接重定向到目录下
}
9.3 SpringMVC数据响应–页面跳转–ModelAndView
9.2.1 自建ModelAndView返回
通过ModelAndView的方式进行页面跳转
@RequestMapping("/res2")
public ModelAndView modelAndViewRes1(){
ModelAndView modelAndView = new ModelAndView();
//设置视图名称
modelAndView.setViewName("resTest1");
//设置model数据
modelAndView.addObject("data","数据测试");
return modelAndView;
}
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Title</title>
</head>
<body>
<h1>${data}</h1>
</body>
</html>
9.2.1 Spring构建ModelAndView返回
除了自己新建一个ModelAdnView对象,还可以直接让Spring帮忙构建一个。
@RequestMapping("/res3")
public ModelAndView modelAndViewRes2(ModelAndView modelAndView){
//设置视图名称
modelAndView.setViewName("resTest1");
//设置model数据
modelAndView.addObject("data","数据测试");
return modelAndView;
}
效果相同。
9.2.1 Model结合String返回
@RequestMapping("/res4")
public String modelAndViewRes3(Model model){
//设置model数据
model.addAttribute("data","数据测试");
return "resTest1";
}
类似的,我们可以直接用request返回数据
@RequestMapping("/res5")
public String modelAndViewRes4(HttpServletRequest request){
request.setAttribute("data","数据测试");
return "resTest1";
}
可以发现,框架对形参的处理较为多样,一般可以想到的合理的形参,spring框架都可以帮助构建
9.4 SpringMVC数据响应–回写数据–字符串
一般来说,我们可以使用resq获取输入流来回写数据
@RequestMapping("/res6")
public void writeBackData1(HttpServletResponse response) throws IOException {
response.getWriter().println("dataTest");
}
但是这种方式存在一定的耦合,在框架中我们可以使用**@ResponseBody**回写数据
@RequestMapping("/res7")
@ResponseBody
public String writeBackData2(){
return "dataTest";
}
@ResponseBody的作用是告诉Spring框架不要进行页面跳转而是直接进行HTTP响应体返回。
9.5 SpringMVC数据响应–回写数据–返回JSON格式数据
在实际应用中,将数据封装在json格式数据中返回。
9.5.1 手动封装Json数据
@RequestMapping("/res8")
@ResponseBody
public String writeBackData3() throws JsonProcessingException {
BeanEntity entity = new BeanEntity(1,"xiaomin",1);
ObjectMapper objectMapper = new ObjectMapper();
String string = objectMapper.writeValueAsString(entity);
return string;
}
这里使用了jackson的json工具包,依赖如下
<!-- jackson核心-->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<version>2.12.3</version>
</dependency>
<!-- jackson数据绑定-->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.12.3</version>
</dependency>
<!-- jackson注解相关-->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
<version>2.12.3</version>
</dependency>
9.5.1 Spring辅助封装Json数据
手动封装存在耦合,我们使用Spring组件中的处理器映射器,配置其中的消息转换器来实现对象封装为json
<!-- 处理器映射器配置-->
<bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter">
<!-- 消息转换器配置-->
<property name="messageConverters">
<list>
<bean class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter"/>
</list>
</property>
</bean>
配置后可以直接返回对象,消息转换器会将其处理为json格式。
@RequestMapping("/res9")
@ResponseBody
public BeanEntity writeBackData4(){
BeanEntity entity = new BeanEntity(1,"xiaomin",1);
return entity;
}
但是配置后,手动的json格式设置返回的json数据会被处理为一个严格的String值
9.6 SpringMVC数据响应–注解驱动配置
在json格式返回配置中,我们需要去手动的配置处理器映射器的消息转换器,这种处理方式仍存在一定耦合。
我们可以直接配置注解驱动(优先级低于手动配置的组件,若重复配置,以手动配置的组件为主)
<mvc:annotation-driven/>
注意需要配置
- 命名空间:xmlns:mvc=“http://www.springframework.org/schema/mvc”
- 约束路径:http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd
并且这样处理后,手动配置的json格式数据返回恢复正常
9.7 SpringMVC获取请求参数类型
SpringMVC可以获取如下类型的参数
- 基本类型参数
- POJO类型参数
- 数组类型参数
- 集合类型参数
9.8 SpringMVC获取请求参数–基本类型参数
Controller中的业务方法的参数名称要和请求参数的name一致,参数值会自动映射匹配。
@RequestMapping("/req1")
@ResponseBody
public void BaseTypeReq(int id,String name){
System.out.println(id);
System.out.println(name);
}
合理的参数数据类型可以被Spring解析转换,如String->int。
这种获取方式,请求参数必须和方法参数匹配,否则报错。
9.9 SpringMVC获取请求参数–POJO类型参数
Controller中的业务方法的POJO参数的属性名要和请求参数的name一致,参数值会自动映射匹配。
@RequestMapping("/req2")
@ResponseBody
public void POJOTypeReq(BeanEntity beanEntity){
System.out.println(beanEntity);
}
允许存在部分部分参数为空的情况,如引用类型String参数为空,获取内容为null。
9.10 SpringMVC获取请求参数–数组类型参数
Controller中的业务方法数组名称要和请求参数的name一致,参数值会自动映射匹配。
@RequestMapping("/req3")
@ResponseBody
public void ArrayTypeReq(String[] strings){
for (String string : strings) {
System.out.println(string);
}
}
9.11 SpringMVC获取请求参数–集合类型参数
9.11.1 VO对象封装
集合类型参数的获取可以使用VO对象的方式存储。
VO(View Object):视图对象,用于展示层,它的作用是把某个指定页面(或组件)的所有数据封装起来。
package com.clown.entity;
import java.util.List;
public class VO {
private List<BeanEntity> beanEntityList;
@Override
public String toString() {
return "VO{" +
"beanEntityList=" + beanEntityList +
'}';
}
public VO() {
}
public VO(List<BeanEntity> beanEntityList) {
this.beanEntityList = beanEntityList;
}
public List<BeanEntity> getBeanEntityList() {
return beanEntityList;
}
public void setBeanEntityList(List<BeanEntity> beanEntityList) {
this.beanEntityList = beanEntityList;
}
}
<%--
Created by IntelliJ IDEA.
User: 61907
Date: 2023/4/17
Time: 20:33
To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Title</title>
</head>
<body>
<form action="${pageContext.request.contextPath}/req4" method="post">
<input type="text" name="beanEntityList[0].id">
<input type="text" name="beanEntityList[0].name">
<input type="text" name="beanEntityList[0].aut"><br>
<input type="text" name="beanEntityList[1].id">
<input type="text" name="beanEntityList[1].name">
<input type="text" name="beanEntityList[1].aut"><br>
<button type="submit">提交</button>
</form>
</body>
</html>
在前端页面中我们需要通过下标的方式指定VO中集合元素位置。
${pageContext.request.contextPath}:获取项目指定url前缀
@RequestMapping("/req4")
@ResponseBody
public void SetTypeReq(VO vo){
System.out.println(vo);
}
9.11.2 ajax方式提交
实际开发过程中,前端使用ajax提交json格式的数据。
使用这种方式提交则无需使用VO对象进行封装,但需要使用@RequestBody对参数进行声明
@RequestBody:常用来处理content-type不是默认的application/x-www-form-urlcoded编码的内容,比如说:application/json或者是application/xml等。一般情况下来说常用其来处理application/json类型。
并且在使用ajax前,我们需要将jq的js文件导入到项目中,并且由于spring对静态资源权限的限制,我们需要在配置文件中配置相应的映射来开放访问权限。
<%--
Created by IntelliJ IDEA.
User: 61907
Date: 2023/4/17
Time: 20:46
To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Title</title>
</head>
<script src="${pageContext.request.contextPath}/js/jquery.js"></script>
<script>
var beanList = new Array();
beanList.push({id:1,name:"lisi",aut:2});
beanList.push({id:2,name:"zhangsan",aut:1});
$.ajax({
type:"POST",
url:"${pageContext.request.contextPath}/req5",
data:JSON.stringify(beanList),
contentType:"application/json;charset=utf-8"
});
</script>
<body>
</body>
</html>
spring-mvc.xml配置,以下处理方法二选一。
<!--开放静态资源配置-->
<mvc:resources mapping="/js/**" location="/js/"/>
<!-- 默认无法被映射的路径交给Tomcat处理-->
<mvc:default-servlet-handler/>
mapping表示请求的格式,location表示对应的静态文件路径
@RequestMapping("/req5")
@ResponseBody
public void AjaxReq(@RequestBody List<BeanEntity> list){
System.out.println(list);
}
方法参数不需要和ajax匹配
9.12 获取参数乱码解决
中文数据获取存在乱码问题,可以通过配置一个全局的过滤器来解决
<!-- 全局过滤器配置-->
<filter>
<filter-name>CharacterEncodingFilter</filter-name>
<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
<init-param>
<param-name>encoding</param-name>
<param-value>UTF-8</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>CharacterEncodingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
配置后参数不再出现乱码问题
9.13 参数绑定注解
@RequestParam:参数绑定注解
@RequestMapping("/req6")
@ResponseBody
public void AjaxReq(@RequestParam(value = "username",required = false,defaultValue = "NoN")String name){
System.out.println(name);
}
- value:参数名称
- required:是否是必须的(默认为true)
- defaultValue:默认值
9.14 Restful风格的参数获取
上述例子中,user后跟随的参数,可以使用占位符的方式绑定。
@RequestMapping(value = "/req7/{name}",method = RequestMethod.GET)
@ResponseBody
public void PVReq(@PathVariable("name") String username){
System.out.println(username);
}
@PathVariable:将 URL 中占位符参数绑定到控制器处理方法的入参中。
通过规定method,来区分请求类型,完成Restful风格逻辑。
9.15 自定义类型转换器
SpringMVC中默认存在一些类型转换器可以帮助参数进行转换,如String转换为Int,但也有一些类型没有被写进去,如Date类型的数据。
我们可以通过自定义类型转换器的方式,来将参数类型进行转换。
步骤如下
- 实现Converter接口并重写方法。
package com.clown.converter;
import org.springframework.core.convert.converter.Converter;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
public class DateConverter implements Converter<String, Date> {
@Override
public Date convert(String s) {
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd");
Date parse = null;
try {
parse = simpleDateFormat.parse(s);
} catch (ParseException e) {
e.printStackTrace();
}
return parse;
}
}
2.配置文件中声明转换器
<bean id="conversionService" class="org.springframework.context.support.ConversionServiceFactoryBean">
<property name="converters">
<list>
<bean class="com.clown.converter.DateConverter"/>
</list>
</property>
</bean>
3.注解驱动中引用转换器
<mvc:annotation-driven conversion-service="conversionService"/>
4.请求
9.16 获取Servlet相关API
直接通过方法形参的方式获取
@RequestMapping("/req9")
@ResponseBody
public void ServletAPIReq(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, HttpSession httpSession){
System.out.println(httpServletRequest);
System.out.println(httpServletResponse);
System.out.println(httpSession);
}
9.17 请求头获取
@RequestMapping("/req10")
@ResponseBody
public void HerderReq(@RequestHeader(value = "User-Agent",required = false)String headers,
@CookieValue(value = "JSESSIONID",required = false)String cookie
){
System.out.println(headers);
System.out.println(cookie);
}
@RequestHeader:获取请求头数据
@CookieValue:获取Cookie数据
9.18 SpringMVC文件上传
9.18.1 文件上传客户端三要素
- 表单项type=“file”
- 表单的提交方式是post
- 表单的enctype属性是多部分表单形式,及enctype=“muliyipart/from-data”
<%--
Created by IntelliJ IDEA.
User: 61907
Date: 2023/4/18
Time: 19:57
To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Title</title>
</head>
<body>
<form action="${pageContext.request.contextPath}/req11" method="post" enctype="multipart/form-data">
名称:<input type="text" name="name"><br>
文件:<input type="file" name="file"><br>
<input type="submit" value="提交">
</form>
</body>
</html>
9.18.2 文件上传原理
- 当from表单修改为多部分表单时,request.getParameter()等API将失效
- 原因是如默认enctype = “application/x-www-form-urlencoded” 提交的表单数据是url键值对的格式 key=value&key1=value1
- 当 enctype="multipart/form-data"时 请求的正文就会改变为如下
9.18.3 文件上传步骤
多文件与单文件上传的区别只是参数接收用数组或单个属性。
-
导入fileupload和io坐标
<!-- fileUpload坐标 io坐标--> <dependency> <groupId>commons-fileupload</groupId> <artifactId>commons-fileupload</artifactId> <version>1.4</version> </dependency> <dependency> <groupId>commons-io</groupId> <artifactId>commons-io</artifactId> <version>2.7</version> </dependency>
-
配置文件上传解析器
<!-- 文件上传解析器(多部分请求解析器)--> <bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver"> <!-- 上传文件总大小--> <property name="maxUploadSize" value="5242800"/> <!-- 上传单个文件大小--> <property name="maxUploadSizePerFile" value="5242800"/> <!-- 上传文件的编码类型--> <property name="defaultEncoding" value="UTF-8"/> </bean>
-
编写文件上传代码
@RequestMapping(value = "/req11",method = RequestMethod.POST) @ResponseBody public void FileReq(String name, MultipartFile file){ System.out.println(name); //获取文件名 String filename = file.getOriginalFilename(); try { //保存文件 file.transferTo( new File("C:\\Users\\61907\\Desktop\\School\\Note\\Spring\\SpringDemo\\Day04\\src\\main\\resources\\" +filename)); } catch (IOException e) { e.printStackTrace(); } }
10. JdbcTemplate的使用
10.1 JdbcTemplate概述
JdbcTemplate是spring框架中提供的一个对象,对原始的jdbcAPI进行简单的封装,spring提供了很多操作模板类。操作关系型数据库的JdbcTemplate和HibernateTemplate,操作nosql数据库的RedisTemplate,以及操作消息队列的JmsTemplate等等。
10.2 JdbcTemplate开发步骤
-
导入spring-jdbc和spring-tx(事务)坐标
(jdbc驱动和连接池等驱动略)
<!-- spring-jdbc JdbcTemplate相关依赖--> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-jdbc</artifactId> <version>5.3.8</version> </dependency> <!-- spring-tx 数据库事务相关--> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-tx</artifactId> <version>5.3.8</version> </dependency>
-
创建数据库表和实体
package com.clown.entity;
public class Teacher {
private int id;
private String name;
private int age;
private String sex;
@Override
public String toString() {
return "Teacher{" +
"id=" + id +
", name='" + name + '\'' +
", age=" + age +
", sex='" + sex + '\'' +
'}';
}
public Teacher() {
}
public Teacher(int id, String name, int age, String sex) {
this.id = id;
this.name = name;
this.age = age;
this.sex = sex;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex;
}
}
-
创建JdbcTemplate对象
手动创建JdbcTemplate对象(这里的使用了注解的方式,让spring去注入了dataSource,详见Spring注解开发)
package com.clown.test; import com.clown.config.SpringConfiguration; import com.mchange.v2.c3p0.ComboPooledDataSource; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import javax.sql.DataSource; @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(classes = {SpringConfiguration.class}) public class JdbcTest { @Autowired DataSource dataSource; @Autowired JdbcTemplate jdbcTemplate; @Test public void originalJdbcTemplateTest(){ JdbcTemplate jdbcTemplate = new JdbcTemplate(); jdbcTemplate.setDataSource(dataSource); jdbcTemplate.update("insert into teacher values (?,?,?,?)",3,"MR.Test",29,"MAN"); } }
package com.clown.config; import com.mchange.v2.c3p0.ComboPooledDataSource; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.PropertySource; import org.springframework.jdbc.core.JdbcTemplate; import javax.sql.DataSource; @PropertySource("classpath:jdbc.properties") public class JdbcConfiguration { @Value("${jdbc.driver}") String driver; @Value("${jdbc.url}") String url; @Value("${jdbc.username}") String username; @Value("${jdbc.password}") String password; @Autowired DataSource dataSource; @Bean("dataSource") public DataSource getDataSource() throws Exception { ComboPooledDataSource comboPooledDataSource = new ComboPooledDataSource(); comboPooledDataSource.setDriverClass(driver); comboPooledDataSource.setJdbcUrl(url); comboPooledDataSource.setUser(username); comboPooledDataSource.setPassword(password); return comboPooledDataSource; } @Bean("jdbcTemplate") public JdbcTemplate getJdbcTemplate(){ JdbcTemplate jdbcTemplate = new JdbcTemplate(); jdbcTemplate.setDataSource(dataSource); return jdbcTemplate; } }
也可以使用xml文件配置
<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> <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate"> <property name="dataSource" ref="datasource"/> </bean>
然后java代码使用getBean的方式去获取jdbcTemplate对象(或将@ContextConfiguration的值改成xml文件,也可以进行注解注入)。
-
执行数据库操作
10.3 JdbcTemplate常用API
-
update():执行增、删、改操作,返回影响数据库的行数
-
queryForObject(String sql, Class requiredType): 针对单行单列查询,返回单个值,参数2是返回值类型对应的class对象
Integer count = jdbcTemplate.queryForObject(sql, Integer.class);
-
queryForObject(String sql, RowMapper rowMapper, Object… args): 针对单行多列查询,返回一个javaBean对象。
RowMapper<User> rowMapper = new BeanPropertyRowMapper<User>(User.class); User user = jdbcTemplate.queryForObject(sql, rowMapper, params);
-
query(String sql, RowMapper rowMapper, Object… args):针对多行多列查询,返回javaBean对象的集合
RowMapper<User> rowMapper = new BeanPropertyRowMapper<User>(User.class); List<User> list = jdbcTemplate.query(sql, rowMapper, params);
-
queryForList(String sql, Class elementType) :针对多行单列的查询,返回一个简单类型的集合,比如List
String sql="select name from t_user"; List<String> names = jdbcTemplate.queryForList(sql, String.class); return names;
注意:如果是多表连接查询返回多行多列结果,需要用RowMapper自己手动封装结果集
参考:JdbcTemplate常用API举例与总结_无垠苍茫的博客-CSDN博客,JDBCTemplate常用API_jdbctemplate api_ElegantCodingWH的博客-CSDN博客
11. SpringMVC拦截器
11.1 拦截器(interceptor)作用
SpringMVC的拦截器类似Servlet的Filter过滤器,用于对处理器进行预处理和后处理。
和Filter过滤器存在过滤器链一样,拦截器存在拦截器链(interceptorChain)在访问被拦截的过程中,会根据拦截器链中的定义的顺序调用拦截器。
拦截器是AOP(面向切面)思想的具体实现
11.2 拦截器与过滤器的区别
11.3 拦截器开发步骤
-
创建拦截器实现HandlerInterceptor接口
package com.clown.Interceptor; import org.springframework.web.servlet.HandlerInterceptor; import org.springframework.web.servlet.ModelAndView; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; public class MyInterceptor implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { System.out.println("preHandle..."); return true; } @Override public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { System.out.println("postHandle..."); } @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { System.out.println("afterCompletion..."); } }
-
配置拦截器
<mvc:interceptors> <mvc:interceptor> <mvc:mapping path="/**"/> <bean class="com.clown.interceptor.MyInterceptor"/> </mvc:interceptor> </mvc:interceptors>
11.3 拦截器方法详细
package com.clown.interceptor;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class MyInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("preHandle...");
if (request.getParameter("flag").equals("admin")){
return true;
}else {
return false;
}
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
System.out.println("postHandle...");
assert modelAndView != null;
System.out.println("modelAndView...");
System.out.println(modelAndView);
/**
* 渲染视图,前提是modelAndView不为空
*/
modelAndView.setViewName("index");
// modelAndView.setViewName("resTest1");
// modelAndView.addObject("data","postHandleTest");
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
System.out.println("afterCompletion...");
}
}
12. SpringMVC异常处理机制
12.1 简单异常映射解析器
<!-- 简单异常映射解析器-->
<bean class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">
<!-- 默认异常-->
<property name="defaultErrorView" value="error"/>
<!-- 指定异常map-->
<property name="exceptionMappings">
<map>
<entry key="java.lang.NullPointerException" value="nullError"/>
</map>
</property>
</bean>
上文中配置的拦截器获取不到view,报空指针异常被异常处理器捕获
12.2 自定义异常处理器
- 创建自定义异常处理实现HandlerExceptionResolver接口
package com.clown.resolver;
import org.springframework.web.servlet.HandlerExceptionResolver;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class MyExceptionResolver implements HandlerExceptionResolver {
@Override
public ModelAndView resolveException(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) {
ModelAndView modelAndView = new ModelAndView();
modelAndView.setViewName("errorModel");
modelAndView.addObject("data",e.getClass());
return modelAndView;
}
}
-
配置自定义异常处理器
<bean class="com.clown.resolver.MyExceptionResolver"/>
-
编写页面
<%--
Created by IntelliJ IDEA.
User: 61907
Date: 2023/4/25
Time: 22:08
To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Title</title>
</head>
<body>
<h1>${data}</h1>
</body>
</html>
13. AOP
13.1 AOP简介
相关定义
- AOP为Aspect Oriented Programming的缩写,意为面向切面编程,是通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。
- AOP与LOC(反转控制)是spring两个重要内核。
- AOP是OOP(面向对象)的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范式。利用AOP可以对业务逻辑的各个部分进行隔离,从而实现业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。
作用优势
- 作用:在程序运行期间,在不修改源码的情况下对方法进行功能增强。
- 方案大概是将基本功能源码与增强功能源码分开,在运行时,通过配置文件的配置将需要增强的基础功能与增强功能结合。
- 优势:减少重复代码,提高开发效率,并且便于维护。
底层实现
实际上,AOP的底层是通过Spring提供的动态代理技术实现的,在运行期间,Spring通过动态代理技术动态的生成代理对象,代理对象方法执行时进行增强功能的介入,再去调用目标对象的方法,从而完成功能的增强。
注:动态代理对应的静态代理存在如下优缺点
虽然静态代理实现简单,且不侵入原代码,但是,当场景稍微复杂一些的时候,静态代理的缺点也会暴露出来。
1、 当需要代理多个类的时候,由于代理对象要实现与目标对象一致的接口,有两种方式:
- 只维护一个代理类,由这个代理类实现多个接口,但是这样就导致代理类过于庞大
- 新建多个代理类,每个目标对象对应一个代理类,但是这样会产生过多的代理类
2、 当接口需要增加、删除、修改方法的时候,目标对象与代理类都要同时修改,不易维护。
常用的动态代理技术
- JDK代理:基于接口的动态代理技术
- cglib代理:基于父类的动态代理技术
13.2 动态代理-JDK
详见:深入理解Java动态代理 - 知乎 (zhihu.com)
范例:
接口
package com.clown.dao;
public interface AopDao {
public void function();
}
实现类
package com.clown.dao.impl;
import com.clown.dao.AopDao;
public class AopDaoImpl implements AopDao {
@Override
public void function() {
System.out.println("基本功能...");
}
}
工具类
package com.clown.dao;
public class Enhance {
public void before(){
System.out.println("AOP...before");
}
public void after(){
System.out.println("AOP...after");
}
}
动态代理实现增强实现类
package com.clown.dao;
import com.clown.dao.impl.AopDaoImpl;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class AopJDKProxy {
public static void main(String[] args) {
AopDaoImpl aopDao = new AopDaoImpl();
Enhance enhance = new Enhance();
AopDao instance = (AopDao) Proxy.newProxyInstance(
//获取目标类的类加载器
aopDao.getClass().getClassLoader(),
//获取目标类的所有接口
aopDao.getClass().getInterfaces(),
//代理类调用处理程序,使用匿名内部类的方式
new InvocationHandler() {
//任何调用代理类方式的行为实际上都是调用invoke
//
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
enhance.before();
Object invoke = method.invoke(aopDao, args);
enhance.after();
return invoke;
}
}
);
instance.function();
}
}
13.3 动态代理-Cglib
详见:深入理解Java动态代理 - 知乎 (zhihu.com)
Cglib在高版本的Sprin中已被集成,无需引入。
范例:
增强对象
package com.clown.dao;
public class AopCglibServices {
public void function(){
System.out.println("CglibService...");
}
}
增强工具
package com.clown.dao;
public class Enhance {
public void before(){
System.out.println("AOP...before");
}
public void after(){
System.out.println("AOP...after");
}
}
动态代理实现增强实现类
package com.clown.dao;
import com.clown.dao.AopCglibServices;
import com.clown.dao.Enhance;
import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
public class AopCglibProxy {
public static void main(String[] args) {
//增强对象
AopCglibServices aopCglibServices = new AopCglibServices();
//增强工具
Enhance enhance = new Enhance();
//Cglib提供的强化工具类
Enhancer enhancer = new Enhancer();
//设置增强父类
enhancer.setSuperclass(aopCglibServices.getClass());
//设置回调方法
enhancer.setCallback(new MethodInterceptor() {
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
enhance.before();
// Object invoke = methodProxy.invokeSuper(o, objects);
Object invoke = method.invoke(aopCglibServices, objects);
enhance.after();
return invoke;
}
});
//创建代理
AopCglibServices o = (AopCglibServices)enhancer.create();
o.function();
}
}
13.4 AOP相关概念与注意事项
1.需要编写的内容
- 编写核心业务代码(目标类的目标方法)
- 编写切面类,切面类中有通知(增强功能方法)
- 在配置文件中,配置织入关系,既将哪些通知与哪些连接点进行结合
2.AOP技术实现的内容
Spring框架监控切入点方法的执行。一旦监控到切入点方法被运行,使用代理机制,动态创建目标对象的代理对象,根据通知类别,在代理对象的对应位置,将通知对应的功能织入,完成完整的代码逻辑运行。
3.AOP底层选用代理方法
根据目标类是否实现接口判断使用JDK或Cglib。
13.4 AOP开发–XML方式
开发步骤
-
导入AOP相关坐标
<!-- AOP配置(第三方)--> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>1.9.5</version> </dependency>
-
创建目标接口和目标类(内部有切点)
package com.clown.aop; //目标类 public class Target { //连接点 public void function(){ System.out.println("Target...Function..."); } }
-
创建切面类(内部有增强方法)
package com.clown.aop; import org.aspectj.lang.ProceedingJoinPoint; public class MyAspect { public void before(){ System.out.println("AOP...before"); } public void afterReturning(){ System.out.println("AOP...afterReturning"); } /** * 环绕类增强方法需要使用 * ProceedingJoinPoint:运行中连接点,即切点。 * 作为参数传递并最终返回 * @param proceedingJoinPoint * @return * @throws Throwable */ public Object around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable { System.out.println("AOP...around...begin"); Object proceed = proceedingJoinPoint.proceed(); System.out.println("AOP...around...end"); return proceed; } public void afterThrowing(){ System.out.println("AOP...afterThrowing"); } public void after(){ System.out.println("AOP...after"); } }
-
将目标类和切面类的对象创建权交给spring
<!-- 目标类与切面类托管spring--> <bean id="target" class="com.clown.aop.Target"/> <bean id="myAspect" class="com.clown.aop.MyAspect"/>
-
在配置文件中配置织入关系
<!-- aop配置--> <aop:config> <!-- 切面配置--> <aop:aspect ref="myAspect"> <!-- 切点表达式抽取--> <!-- <aop:pointcut id="pointcut" expression="execution(* com.clown.aop.*.*(..))"/>--> <!-- 使用抽取示范--> <!-- <aop:before method="before" pointcut-ref="pointcut"/>--> <!-- 前置--> <aop:before method="before" pointcut="execution(* com.clown.aop.*.*(..))"/> <!-- 后置--> <aop:after-returning method="afterReturning" pointcut="execution(* com.clown.aop.*.*(..))"/> <!-- 环绕--> <aop:around method="around" pointcut="execution(* com.clown.aop.*.*(..))"/> <!-- 异常--> <aop:after-throwing method="afterThrowing" pointcut="execution(* com.clown.aop.*.*(..))"/> <!-- 最终--> <aop:after method="after" pointcut="execution(* com.clown.aop.*.*(..))"/> </aop:aspect> </aop:config>
通知的配置与种类:
切点表达式写法:
切点表达式抽取:
-
测试
package com.clown.test; import com.clown.aop.Target; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration("classpath:applicationContext.xml") public class AopTest { @Autowired Target target; @Test public void test(){ target.function(); } }
13.5 AOP开发–注解方式
注解方式开发与xml开发差别不大,主要有以下几点
1.切面注解,通知注解,切点表达式抽取注解
package com.clown.aop.anno;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
/**
* 注解方式开发AOP
*/
@Component("myAspect")
@Aspect//切面注解
public class MyAspect {
//切点表达式抽取
@Pointcut("execution(* com.clown.aop.anno.*.*(..))")
public void pointcut(){}
//五种通知(增强)注解
@Before("pointcut()")
public void before(){
System.out.println("AOP...before");
}
@AfterReturning("pointcut()")
public void afterReturning(){
System.out.println("AOP...afterReturning");
}
/**
* 环绕类增强方法需要使用
* ProceedingJoinPoint:运行中连接点,即切点。
* 作为参数传递并最终返回
* @param proceedingJoinPoint
* @return
* @throws Throwable
*/
@Around("pointcut()")
public Object around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
System.out.println("AOP...around...begin");
Object proceed = proceedingJoinPoint.proceed();
System.out.println("AOP...around...end");
return proceed;
}
@AfterThrowing("pointcut()")
public void afterThrowing(){
System.out.println("AOP...afterThrowing");
}
@After("execution(* com.clown.aop.anno.*.*(..))")
public void after(){
System.out.println("AOP...after");
}
}
2.目标类注解托管spring
package com.clown.aop.anno;
import org.springframework.stereotype.Component;
//目标类
@Component("target")
public class Target {
//连接点
public void function(){
System.out.println("Target...Function...");
// int x = 1/0;
}
}
3.配置文件配置组件扫描与aop自动代理
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
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
">
<!-- 包扫描-->
<context:component-scan base-package="com.clown.aop.anno"/>
<!-- aop自动代理-->
<aop:aspectj-autoproxy/>
</beans>
14.事务控制
14.1 事务简介
事务(Transactional) 就是把多个要做的操作组合成一个整体.利用事务的特性来保证操作的安全性,如果一个事务做到一半出现任何错误,就会进行回滚操作.来恢复成最初的模样.
事务的特性 (具有ACID的特性)
-
A 原子性(atomicity) : 事务是一个不可分割的工作单位,事务中的操作要么都修改,要么都不修改。
-
C 一致性(consistency):事务在完成时,必须是所有的数据都保持一致状态。
-
I 隔离性(isolation):一个事务的执行不能被其他事务所影响。
-
D 持久性(Durability): 持久性是指一个事务一旦被提交,它对数据库中数据的改变就是永久性的.
14.2 编程式事务控制
编程式事务控制是声明式事务控制的前置基础,spring通过声明式控制事务的底层代码即编程式事务控制。
编程式事务控制存在如下相关对象
14.2.1 PlatformTransactionManager(平台事务管理器)
该对象是一个接口,提供了常用的操作事务的方法。
接口的实现由spring完成,我们需要声明使用的实现类,不同的Dao层技术存在不同的实现类,如Dao层使用jdbc或mybatis与hibernate的时实现类不同。
14.2.2 TransactionDefinition(事务定义对象)
该对象作为事务定义信息的存储对象,存储事务信息。
该对象需要我们在配置文件中声明,设置给spring
-
隔离级别
设置事务的隔离级别可以解决事务并发产生的问题,如脏读,不可重复读,虚读。
- ISOLATION_DEFAULT:默认。
- ISOLATION_READ_UNCOMMITTED:读未提交。
- ISOLATION_READ_COMMITTED:读已提交,解决脏读。
- ISOLATION_REPEATABLE_READ:可重复读,解决不可重复读问题。
- ISOLATION_SERIALIZABLE:串行化,可解决大部分事务问题(性能较低)。
-
传播行为
传播行为解决业务间互相调用时的事务统一性问题。
14.2.3 TransactionStatus(事务状态对象)
该对象维护不同时间点的事务状态信息。
该对象由spring生成,不需要我们声明设置。
14.3 声明式事务控制
Spring声明式事务控制即采用声明的方式来处理事务,使用配置文件声明的方式代替代码式硬编程的事务控制。
作用:事务管理不入侵开发的组件,具体来说,业务逻辑对象就不会意识到正在事务管理中,满足服务分层的原则,将处于系统层的事务控制与应用层的业务逻辑隔离,也具有松耦合的作用,若需要改变(剔除)事务管理策略,只需要在配置文件中重新配置,方便维护。
从作用即可发现,spring声明式事务控制的底层就是AOP。属于AOP的子集。
14.4 声明式事务控制–XML方式
使用经典场景,多次修改业务。
在该场景中,AOP角色如下:
- 切点:修改业务
- 通知:事务控制
使用声明式事务控制需要依赖:
<!-- spring-tx 事务相关-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>5.3.8</version>
</dependency>
开发步骤:
在配置文件中配置事务相关声明
需要先引入如下坐标
xmlns:tx="http://www.springframework.org/schema/tx"
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd
主要配置,dao层已使用注解方式声明,配置文件也可。
<!-- 事务管理器-->
<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="updateAge" isolation="REPEATABLE_READ" propagation="REQUIRED" read-only="false"/>
<tx:method name="*"/>
</tx:attributes>
</tx:advice>
<!-- 织入-->
<aop:config>
<aop:advisor advice-ref="txAdvice" pointcut="execution(* com.clown.service.impl.*.*(..))"/>
</aop:config>
Dao层:
package com.clown.dao;
import com.clown.entity.Teacher;
import java.util.List;
public interface TeacherDao {
public List<Teacher> selectAll();
public int updateAge(int id,int age);
}
package com.clown.dao.impl;
import com.clown.dao.TeacherDao;
import com.clown.entity.Teacher;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.stereotype.Repository;
import java.util.List;
@Repository
public class TeacherDaoImpl implements TeacherDao {
@Autowired
JdbcTemplate jdbcTemplate;
@Override
public List<Teacher> selectAll() {
RowMapper<Teacher> rowMapper = new BeanPropertyRowMapper<Teacher>(Teacher.class);
List<Teacher> query = jdbcTemplate.query("select * from teacher", rowMapper);
return query;
}
@Override
public int updateAge(int id, int age) {
int update = jdbcTemplate.update("update teacher set age = ? where id = ?", age, id);
return update;
}
}
Service层:
package com.clown.service;
public interface TeacherService {
public void selectAll();
public boolean updateAge();
}
package com.clown.service.impl;
import com.clown.dao.TeacherDao;
import com.clown.dao.impl.TeacherDaoImpl;
import com.clown.entity.Teacher;
import com.clown.service.TeacherService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
@Service("teacherService")
public class TeacherServiceImpl implements TeacherService {
@Autowired
TeacherDao teacherDao;
@Override
public void selectAll() {
List<Teacher> teacherList = teacherDao.selectAll();
for (Teacher teacher : teacherList) {
System.out.println(teacher);
}
}
@Override
public boolean updateAge() {
int a = teacherDao.updateAge(1, 1);
int x = 1/0;
int b = teacherDao.updateAge(2, 2);
return a!=0 && b!=0;
}
}
测试类:
package com.clown.test;
import com.clown.entity.Teacher;
import com.clown.service.TeacherService;
import com.clown.service.impl.TeacherServiceImpl;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import java.util.List;
@RunWith(SpringJUnit4ClassRunner.class)
//@ContextConfiguration(classes = {SpringConfiguration.class})
@ContextConfiguration(value = "classpath:applicationContext.xml")
public class TransactionTest {
@Autowired
TeacherService teacherService;
@Test
public void Test1(){
// teacherService.selectAll();
teacherService.updateAge();
}
}
测试结果:
由于在updateAge方法中存在错误
在事务控制下,a即使在运行前仍回滚,两行记录都不改变。
14.5声明式事务控制–注解方式
注解方式将事务信息对象声明的方式由配置文件配置转换为@Transactional注解。
但事务管理器的配置仍然需要,并且需要声明<tx:annotation-driven/>事务注解驱动。
package com.clown.service.impl;
import com.clown.dao.TeacherDao;
import com.clown.dao.impl.TeacherDaoImpl;
import com.clown.entity.Teacher;
import com.clown.service.TeacherService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
@Service("teacherService")
@Transactional(isolation = Isolation.REPEATABLE_READ)
public class TeacherServiceImpl implements TeacherService {
@Autowired
TeacherDao teacherDao;
@Override
@Transactional(isolation = Isolation.READ_COMMITTED,propagation = Propagation.REQUIRED)
public void selectAll() {
List<Teacher> teacherList = teacherDao.selectAll();
for (Teacher teacher : teacherList) {
System.out.println(teacher);
}
}
@Override
@Transactional(isolation = Isolation.READ_COMMITTED,propagation = Propagation.REQUIRED)
public boolean updateAge() {
int a = teacherDao.updateAge(1, 1);
int x = 1/0;
int b = teacherDao.updateAge(2, 2);
return a!=0 && b!=0;
}
}
<!-- 事务管理器-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="datasource"/>
</bean>
<!-- 事务注解驱动-->
<tx:annotation-driven/>