框架整合
基础框架Spring Framework
- Spring Framework 是Spring生态圈最基础的项目,是其他Spring框架的根基
核心容器
核心概念(IoC/DI) 目标:充分解耦
-
IoC(Inversion of Control)控制反转
-
对象的创建控制权由程序转移到外部(使用对象时,由主动new产生的对象转换未由外部提供对象,此过程中对象创建控制器由程序转移到了外部,此思想称为控制反转)
-
Spring 技术对IoC思想进行了实现,提供一个
容器(IoC)
,用来充当外部
-
IoC容器负责对象的创建,初始化等一些列工作,被创建或者被管理的对象在IoC容器中统称为
Bean
-
-
DI(Dependency Injection) 依赖注入
- 在容器中建立Bean和Bean之间的依赖关系的整个过程,称为依赖注入
IoC入门
1.导入xml创建的Maven依赖(导入Spring的坐标spring-context)
<!-- https://mvnrepository.com/artifact/org.springframework/spring-context -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.10.RELEASE</version>
</dependency>
2.配置bean
<?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">
<!--2.配置bean
bean标签标示配置bean
id属性标示给bean的名字
class属性表示给bean定义类型-->
<bean id="bookDao" class="com.itheima.dao.impl.BookDaoImpl"/>
<bean id="bookService" class="com.itheima.service.impl.BookServiceImpl"/>
</beans>
3.获取IoC容器
4.获取bean
//3.获取IoC容器
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
//4.获取bean
//BookDao bookDao = (BookDao) ctx.getBean("bookDao");
//bookDao.save();
BookService bookService = (BookService) ctx.getBean("bookService");
bookService.save();
DI入门
5.删除new创建dao方法
6.提供对应的set方法
public class BookServiceImpl implements BookService {
//private BookDao bookDao = new BookDaoImpl();
//5.删除new创建dao方法
private BookDao bookDao;
//6.提供对应的set方法
public void setBookDao(BookDao bookDao) {
this.bookDao = bookDao;
}
@Override
public void save() {
System.out.println("book service save...");
bookDao.save();
}
}
7.配置server与 dao的关系
<!--2.配置bean
bean标签标示配置bean
id属性标示给bean的名字
class属性表示给bean定义类型-->
<bean id="bookDao1" class="com.itheima.dao.impl.BookDaoImpl"/>
<bean id="bookService" name="service service2" class="com.itheima.service.impl.BookServiceImpl">
<!--7.配置server与 dao的关系-->
<!--property标签表示配置当前bean的属性
name属性标识配置哪一个具体的属性(setBookDao的名字部分)
ref属性标识参照哪一个bean-->
<property name="bookDao" ref="bookDao1"/>
</bean>
//bookService service效果是一样的,可以取id值也能取name值
BookService bookService = (BookService) ctx.getBean("service4");
bookService.save();
//bean名称错误:Exception in thread "main" org.springframework.beans.factory.NoSuchBeanDefinitionException: No bean named 'service4' available
Bean作用范围配置
本质是对象
- scope
- 默认为singleton单例模式(从IoC获取的多个对象地址相同)
- 可以改成prototype非单例模式
<bean id="bookService" name="service service2" class="com.itheima.service.impl.BookServiceImpl" scope="singleton">
适合交给容器进行管理的bean
- 表现层对象
- 业务层对象
- 数据层对象
- 工具对象
不适合交给容器进行管理的bean
- 封装实体的域对象(因为可能要存不同的值)
bean实例化(三种方式)
常用-构造方法
<!--构造方法实现-->
<bean id="bookDao" class="com.itheima.dao.impl.BookDaoImpl"/>
public class BookDaoImpl implements BookDao {
public BookDaoImpl() {
System.out.println("book constructor is running...");
}
@Override
public void save() {
System.out.println("book dao save...");
}
}
无参构造方法如果不存在,将抛出异常BeanCreationException
分析Spring的错误信息
接下来,我们主要研究下Spring的报错信息来学一学如阅读。
- 错误信息从下往上依次查看,因为上面的错误大都是对下面错误的一个包装,最核心错误是在最下面
- Caused by: java.lang.NoSuchMethodException: com.itheima.dao.impl.BookDaoImpl.
<init>
()- Caused by 翻译为
引起
,即出现错误的原因 - java.lang.NoSuchMethodException:抛出的异常为
没有这样的方法异常
- com.itheima.dao.impl.BookDaoImpl.
<init>
():哪个类的哪个方法没有被找到导致的异常,<init>
()指定是类的构造方法,即该类的无参构造方法
- Caused by 翻译为
如果最后一行错误获取不到错误信息,接下来查看第二层:
Caused by: org.springframework.beans.BeanInstantiationException: Failed to instantiate [com.itheima.dao.impl.BookDaoImpl]: No default constructor found; nested exception is java.lang.NoSuchMethodException: com.itheima.dao.impl.BookDaoImpl.<init>
()
- nested:嵌套的意思,后面的异常内容和最底层的异常是一致的
- Caused by: org.springframework.beans.BeanInstantiationException: Failed to instantiate [com.itheima.dao.impl.BookDaoImpl]: No default constructor found;
- Caused by:
引发
- BeanInstantiationException:翻译为
bean实例化异常
- No default constructor found:没有一个默认的构造函数被发现
- Caused by:
看到这其实错误已经比较明显,给大家个练习,把倒数第三层的错误分析下吧:
Exception in thread “main” org.springframework.beans.factory.BeanCreationException: Error creating bean with name ‘bookDao’ defined in class path resource [applicationContext.xml]: Instantiation of bean failed; nested exception is org.springframework.beans.BeanInstantiationException: Failed to instantiate [com.itheima.dao.impl.BookDaoImpl]: No default constructor found; nested exception is java.lang.NoSuchMethodException: com.itheima.dao.impl.BookDaoImpl.<init>
()。
至此,关于Spring的构造方法实例化就已经学习完了,因为每一个类默认都会提供一个无参构造函数,所以其实真正在使用这种方式的时候,我们什么也不需要做。这也是我们以后比较常用的一种方式。
方式二:静态工厂实例化bean(了解)
<!--方式二:使用静态工厂实例化bean-->
<bean id="orderDao"
class="com.itheima.factory.OrderDaoFactory"
factory-method="getOrderDao"/>
public class OrderDaoImpl implements OrderDao {
public void save() {
System.out.println("order dao save ...");
}
}
-----------------------------------------
public class OrderDaoFactory {
public static OrderDao getOrderDao(){
System.out.println("factory setup....");
return new OrderDaoImpl();
}
}
方式三:实例工厂(了解)
<!--方式三:使用实例工厂实例化bean-->
<!--先创建工厂bean-->
<bean id="userFactory" class="com.itheima.factory.UserDaoFactory"/>
<bean id="userDao" factory-bean="userFactory" factory-method="getUserDao"/>
public class UserDaoFactory {
public UserDao getUserDao(){
return new UserDaoImpl();
}
}
public class AppForInstanceUser {
public static void main(String[] args) {
// //创建实例工厂对象
// UserDaoFactory userDaoFactory = new UserDaoFactory();
// //通过实例工厂对象创建对象
// UserDao userDao = userDaoFactory.getUserDao();
// userDao.save();
//类路径获取xml文件
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
//文件绝对路径访问
//ApplicationContext ctx2 = new FileSystemXmlApplicationContext("D:\\MineFile\\zuoye\\code\\spring_quickstart\\src\\main\\resources\\applicationContext.xml");
UserDao userDao1 = (UserDao) ctx.getBean("userDao");
UserDao userDao2 = (UserDao) ctx.getBean("userDao");
System.out.println(userDao1);
System.out.println(userDao2);
userDao1.save();
}
}
方式四:使用FactoryBean实例化bean(实用)
方式三的变种
<!--方式四:方式三的变种,使用FactoryBean实例化bean-->
<bean id="userDao" class="com.itheima.factory.UserDaoFactoryBean"/>
造出来的是getObject对象
public class UserDaoFactoryBean implements FactoryBean<UserDao> {
//代替原始实例工厂中创建对象的方法
public UserDao getObject() throws Exception {
return new UserDaoImpl();
}
public Class<?> getObjectType() {
return UserDao.class;
}
public boolean isSingleton() {
return true;//为true则为非单例模式
}
}
-----------------
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
UserDao userDao1 = (UserDao) ctx.getBean("userDao");
UserDao userDao2 = (UserDao) ctx.getBean("userDao");
System.out.println(userDao1);
System.out.println(userDao2);//单例
userDao1.save();
bean生命周期
配置:
init-method
destroy-method
- 初始化容器
- 创建对象(内存分配)
- 执行构造方法
- 执行属性注入(set操作)
- 执行bean初始化方法
- 使用bean
- 执行业务操作
- 关闭/销毁容器
- 执行bean销毁方法
- 手动关闭容器
- ConfigurableApplicationContext接口close()操作
- ClassPathXmlApplicationContext继承自ConfigurableApplicationContext
- 注册关闭钩子,在虚拟机退出前先关闭容器再退出虚拟机
- ConfigurableApplicationContext接口registerShutdownHook()操作
依赖注入
方式
-
setter注入
-
简单类型、
-
引用类型
-
public class BookDaoImpl implements BookDao { private int connectionNum; private String databaseName; private BookDao bookDao; public void setConnectionNum(int connectionNum) { this.connectionNum = connectionNum; } public void setDatabaseName(String databaseName) { this.databaseName = databaseName; } public void setBookDao(BookDao bookDao) { this.bookDao = bookDao; } }
<bean id="bookDao" class="com.itheima.dao.impl.BookDaoImpl"> <!--使用value注入简单类型数据和引用类型--> <property name="databaseName" value="mysql"/> <property name="bookDao" ref="bookDao"/> <property name="connectionNum" value="10"/> </bean>
-
-
构造器注入
-
简单类型
-
引用类型
-
public class BookDaoImpl implements BookDao { private int connectionNum; private String databaseName; public BookDaoImpl(int connectionNum, String databaseName) { this.connectionNum = connectionNum; this.databaseName = databaseName; System.out.println("book constructor is running..." + connectionNum + ", " + databaseName); } }
-
标准格式
<bean id="bookDao" class="com.itheima.dao.impl.BookDaoImpl"> <constructor-arg name="connectionNum" value="100"/> <constructor-arg name="databaseName" value="mysql2"/> </bean> <bean id="bookService" name="service service2" class="com.itheima.service.impl.BookServiceImpl" scope="singleton"> <constructor-arg name="bookDao" ref="bookDao"/> <constructor-arg name="userDao" ref="userDao"/> </bean>
-
优化形参名称万一不一样问题(name改为type)
-
<bean id="bookDao" class="com.itheima.dao.impl.BookDaoImpl"> <constructor-arg type="int" value="100"/> <constructor-arg type="java.lang.String" value="mysql2"/> </bean> <bean id="bookService" name="service service2" class="com.itheima.service.impl.BookServiceImpl" scope="singleton"> <constructor-arg name="bookDao" ref="bookDao"/> <constructor-arg name="userDao" ref="userDao"/> </bean>
-
优化形参类型可能重复问题(加索引)
-
<bean id="bookDao" class="com.itheima.dao.impl.BookDaoImpl"> <constructor-arg index="0" value="100"/> <constructor-arg index="1" value="mysql2"/> </bean> <bean id="bookService" name="service service2" class="com.itheima.service.impl.BookServiceImpl" scope="singleton"> <constructor-arg name="bookDao" ref="bookDao"/> <constructor-arg name="userDao" ref="userDao"/> </bean>
-
如果有必要可以两者同时使用,用构造器完成强制依赖
的注入,setter完成可选依赖
的注入
自己开发的模块推荐使用setter注入
依赖自动装配
只能用于引用类型的依赖注入,不能对简单类型进行操作
自动装配优先级低于setter和构造器,同时出现会自动装配失效
-
按类型(常用)
-
必须保证相同类型的bean唯一(推荐)
-
注:相应的类(impl实现类)要有setter方法
- xml配置文件里要有对应的实现bean()
- 不能有两个相同的dao层实现类(<bean
id="bookDao"
class=“com.itheima.dao.impl.BookDaoImpl”/>和<beanid="bookDao2"
class=“com.itheima.dao.impl.BookDaoImpl”/>)
-
<bean class="com.itheima.dao.impl.BookDaoImpl"/> <!--autowire属性:开启自动装配,通常使用按类型装配--> <bean id="bookService" class="com.itheima.service.impl.BookServiceImpl" autowire="byType"/>
-
-
按名称
- 要保证实现dao的bean有对应id的名称在impl类里
- 变量名和配置耦合(不推荐)
-
按构造方法
-
不启用自动装配
集合注入
数组,List,Set,Map,Properties
注:name属性是对应dao类的成员变量的值
里面那个才是类型
<bean id="orderDao" class="com.itheima.dao.impl.OrderDaoImpl">
<property name="array">
<array>
<value>100</value>
<value>200</value>
<value>300</value>
</array>
</property>
<property name="list">
<list>
<value>itcast</value>
<value>itheima</value>
<value>boxuegu</value>
</list>
</property>
<property name="set">
<set>
<value>100</value>
<value>200</value>
<value>300</value>
</set>
</property>
<property name="map">
<map>
<entry key="country" value="china"/>
<entry key="province" value="henan"/>
<entry key="city" value="kaifeng"/>
</map>
</property>
<property name="properties">
<props>
<prop key="county">china</prop>
<prop key="province">henan</prop>
<prop key="city">kaifeng</prop>
</props>
</property>
</bean>
加载properties文件
需要修改xml文件的位置,新增一个内容空间
<!--1.开启context命名空间-->
<context:property-placeholder location="jdbc.properties" system-properties-mode="NEVER"/>
<!--不使用系统的变量模块,防止设置的变量名一样导致读取不正确-->
<!--2.使用context空间加载properties文件-->
<bean class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassLoader" value="${jdbc.driver}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</bean>
容器创建
方式一:类路径加载配置文件
//类路径获取xml文件
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
方式二:文件路径加载配置文件
ApplicationContext ctx2 = new FileSystemXmlApplicationContext("D:\\...\\applicationContext.xml");
加载多个配置文件
ApplicationContext ctx = new ClassPathXmlApplicationContext("bean1.xml", "bean2.xml");
获取Bean
方式1:使用bean名称
//方式1:使用bean名称
BookDao bookDao = (BookDao) ctx.getBean("bookDao");
方式二:使用bean名称获取指定类型
//方式二:使用bean名称获取指定类型
BookDao bookDao = ctx.getBean("bookDao", BookDao.class);
方式三:使用bean类型获取,适用于只有一个同类型的bean
//方式三:使用bean类型获取,适用于只有一个同类型dao
BookDao bookDao = ctx.getBean(BookDao.class);
容器类层次结构图
核心容器总结
容器相关
-
BeanFactory是IoC容器的顶层接口,初始化BeanFactory对象时,加载的bean
延迟加载
-
ApplicationContext
接口是Spring容器的核心接口,初始化时bean立即加载
-
ApplicationContext提供基础的bean操作相关方法,通过
其他接口
扩展功能 -
ApplicationContext常用初始化类
- ClassPathXmlApplicationContext
- FileSystemXmlApplicationContext
bean相关
依赖注入相关
注解开发定义bean
有对应名称的注解
xml配置,注解开发用的是context容器
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
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
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
<!-- <bean id="bookDao" class="com.itheima.dao.impl.BookDaoImpl"/>-->
<!--注解开发用扫描-->
<context:component-scan base-package="com.itheima.dao.impl"/>
<!--可改成扫描范围大一点,service包就也能扫描到-->
<context:component-scan base-package="com.itheima"/>
</beans>
@Component("bookDao")
public class BookDaoImpl implements BookDao {
}
---
@Component//组件
public class BookServiceImpl implements BookService {
}
---------------------
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
BookDao bookDao = (BookDao) ctx.getBean("bookDao");
衍生注解
@Controller:用于表现层bean定义
@Serveice:用于业务层bean定义
@Repository:用于数据层bean 定义
@Repository("bookDao")
public class BookDaoImpl implements BookDao {
}
@Serveice
public class BookDaoImpl implements BookDao {
}
纯注解开发(Spring3.0开始)
- 使用了java类替代配置文件,开启了Spring快速开发赛道
- java类代替Spring核心配置文件
右图的注解等价于左边灰的一片配置代码
<context:component-scan base-package=“com.itheima”/>该段等价于第二个注解
代替的java文件
//声明当前类为Spring配置类
@Configuration
//设置bean扫描路径,多个路径书写为字符串数组格式
@ComponentScan({"com.itheima.service","com.itheima.dao"})
//读取properties文件,可以直接使用里面的键值对,但不能使用通配符 *.properties
@PropertySource("jdbc.properties")
public class SpringConfig {
}
-
创建方法
-
//3.获取IoC容器 ApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig.class); //4.获取bean BookDao bookDao = (BookDao) ctx.getBean("bookDao"); bookDao.save();
注解的bean生命周期和多例
@Repository //等价于@Component("bookDao")
@Scope("prototype")//非单例模式开启
public class BookDaoImpl implements BookDao {
@Override
public void save() {
System.out.println("book dao save...");
}
@PostConstruct
public void init() {
System.out.println("构造之后执行...");
}
@PreDestroy
public void destroy() {
System.out.println("销毁之前执行...");
}
}
------------------------
//3.获取IoC容器
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig.class);
ctx.close();//该方法调用销毁前
注解开发的依赖注入
@Autowired//该注解可以删除setter方法,该注解会自己创建
@Qualifier("bookDao") //如果有多个相同类型的dao类,则用该注释指定
private BookDao bookDao;
private UserDao userDao;
//6.提供对应的set方法
// public void setBookDao(BookDao bookDao) {
// this.bookDao = bookDao;
// }
@Override
public void save() {
System.out.println("book service save...");
bookDao.save();
}
注:自动装配基于反射设计创建对象并暴力反射
对应属性为私有属性初始化数据,因此无需提供setter方法
自动装配建议使用无参构造方法创造对象(默认),如果不提供对应构造方法,请提供唯一的构造方法
赋值
@Repository //等价于@Component("bookDao")
@Scope("prototype")//非单例模式开启
public class BookDaoImpl implements BookDao {
@Value("itheima")//赋值
private String name;
@Override
public void save() {
System.out.println("book dao save..." + name);
}
管理第三方bean(mysql)
- 不推荐把所有config的java文件放在SpringConfig文件里
- 推荐使用导入法
Jdbc配置文件
//声明当前类为Spring配置类
//@Configuration
public class JdbcConfig {
//定义一个方法获得要管理的对象
//添加bean,表示当前方法返回的是一个bean
@Bean
public DataSource dataSource() {
DruidDataSource ds = new DruidDataSource();
ds.setDriverClassName("com.mysql.jdbc.Driver");
ds.setUrl("jdbc:mysql://localhost:3306/db1");
ds.setName("root");
ds.setPassword("root");
return ds;
}
}
主Config文件
//声明当前类为Spring配置类
@Configuration
//设置bean扫描路径,多个路径书写为字符串数组格式
@ComponentScan({"com.itheima.service","com.itheima.dao" //,"com.itheima.config"})
@Import({JdbcConfig.class})//导入配置
public class SpringConfig {
}
简单依赖注入(用成员变量)
引用类型依赖注入(用方法形参)
XML配置比对注解配置
整合MyBatis
- spring整合数据层技术MyBatis
导入坐标
<!-- https://mvnrepository.com/artifact/org.springframework/spring-context -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.10.RELEASE</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.16</version>
</dependency>
<dependency>
<groupId>org.apache.tomcat</groupId>
<artifactId>tomcat-annotations-api</artifactId>
<version>7.0.47</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.6</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.34</version>
</dependency>
<dependency>
<!--spring操作jdbc需要的坐标-->
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.2.10.RELEASE</version>
</dependency>
<!--mybatis和spring整合需要的坐标-->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>1.3.0</version>
</dependency>
jdbc.properties文件
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/spring_db?useSSL=false
jdbc.username=root
jdbc.password=root
JdbcConfig文件
public class JdbcConfig {
@Value("${jdbc.driver}")
private String driver;
@Value("${jdbc.url}")
private String url;
@Value("${jdbc.username}")
private String userName;
@Value("${jdbc.password}")
private String password;
@Bean
public DataSource dataSource(){
DruidDataSource ds = new DruidDataSource();
ds.setDriverClassName(driver);
ds.setUrl(url);
ds.setUsername(userName);
ds.setPassword(password);
return ds;
}
}
在com.itheima.domain包里的
public class Account implements Serializable {
//在com.itheima.domain包里的
private Integer id;
private String name;
private Double money;
MybatisConfig文件
public class MybatisConfig {
//定义bean,SqlSessionFactoryBean,用于产生SqlSessionFactory对象
@Bean
public SqlSessionFactoryBean sqlSessionFactory(DataSource dataSource){
SqlSessionFactoryBean ssfb = new SqlSessionFactoryBean();
//扫描数据需要存放的类型的包
ssfb.setTypeAliasesPackage("com.itheima.domain");
//在spring容器中自动装配的
ssfb.setDataSource(dataSource);
return ssfb;
}
//定义bean,返回MapperScannerConfigurer对象
//等价于xml配置文件里的
// <mappers>
// <!--扫描mapper-->
// <package name="com.itheima.mapper"/>
// </mappers>
@Bean
public MapperScannerConfigurer mapperScannerConfigurer(){
MapperScannerConfigurer msc = new MapperScannerConfigurer();
//扫描映射的包,数据交互层的和数据库交互的包,具体看下面的注释举例
msc.setBasePackage("com.itheima.dao");
return msc;
//public interface AccountDao {
//@Insert("insert into tbl_account(name,money)values(#{name},#{money})")
//void save(Account account);
}
}
SpringConfig主文件
//声明为配置bean文件
@Configuration
//扫描包
@ComponentScan("com.itheima")
//@PropertySource:加载类路径jdbc.properties文件
@PropertySource("classpath:jdbc.properties")
//导入第三方bean
@Import({JdbcConfig.class,MybatisConfig.class})
public class SpringConfig {
}
整合JUnit
test目录下的单元测试
//设置类运行器
@RunWith(SpringJUnit4ClassRunner.class)
//设置Spring环境对应的配置类
@ContextConfiguration(classes = SpringConfig.class)
public class AccountServiceTest {
//支持自动装配注入bean
@Autowired
private AccountService accountService;
AOP(面向切面编程)
Aspect Oriented Programming 面向切面编程,一种编程范式
OOP(Object Oriented programming)面向对象编程
核心概念
-
在不惊动原始设计的基础上为其进行功能增强
-
spring理念:无入侵式/无侵入式
-
连接点(JoinPoint):程序执行过程中的任意位置,粒度为执行方法,抛出异常,设置变量等
- 在SpringAOP中,理解为方法的执行
-
切入点(Pointcut)匹配连接点的式子
- 在SpringAOP中,一个切入点可以只描述一个具体的方法,也可以匹配多个方法
- 一个具体方法:BookDao接口中的无形参,无返回值的save方法
- 匹配多个方法:所有的save方法,所有get开头的方法,所有以Dao结尾的方法,所有带一个参数的方法
- 在SpringAOP中,一个切入点可以只描述一个具体的方法,也可以匹配多个方法
-
通知(Advice):在切入点处执行的操作,也就是共性功能
- 功能最终以方法的形式表现
-
通知类:定义通知的类
-
切面(Aspect):描述通知与切入点的对应关系
AOP基础操作
在SpringConfig文件中配置使用AOP的注解
@Configuration
@ComponentScan("com.itheima")
//开启注解开发AOP功能
@EnableAspectJAutoProxy
public class SpringConfig {
}
写通知文件,用切面联系切入点
切入点定义依托一个不具有实际意义的方法进行,即无参数,无返回值,方法体无实际逻辑
//通知类必须配置成Spring管理的bean
@Component
//设置当前类为切面类类
@Aspect
public class MyAdvice {
//设置切入点,要求配置在方法上方
// execution 执行
@Pointcut("execution(void com.itheima.dao.BookDao.update())")
private void pt(){}
//设置在切入点pt()的前面运行当前操作(前置通知)
@Before("pt()")
public void method(){
System.out.println(System.currentTimeMillis());
}
}
AOP工作流程
-
spring容器启动
-
读取所有切面配置的切入点
-
初始化bean,判定bean对应的类中的方法是否匹配到任意切入点
- 匹配失败,创建对象
- 匹配成功,创建原始对象(目标对象)的代理对象
-
获取bean执行方法
-
获取bean,调用方法并执行
-
bean是代理对象时,根据代理对象的运行模式运行原始方法与增强的内容,完成操作
-
核心概念
-
目标对象(Target )︰原始功能去掉共性功能对应的类产生的对象,这种对象是无法直接完成最终工作的
-
代理( Proxy ):目标对象无法直接完成工作,需要对其进行功能回填,通过原始对象的代理对象实现
AOP切入点的表达式方式
标准格式
方式一:执行com.itheima.dao包下的BookDao.update()
- @Pointcut(“execution(void com.itheima.dao.BookDao.update())”)
private void pt(){}
方式二:执行com.itheima.dao.impl包下的BookDaoImpl类中的无参数update方法
- @Pointcut(“execution(void com.itheima.dao.impl.BookDaoImpl.update())”)
private void pt(){}
通配符
-
*
:单个独立的任意符号,可独立,可匹配前后缀- execution(public * com.itheima.*.UserService.Find*(*))
-
..
:简化包名和参数的- execution(public User com…UserService.FindByid(…))
-
+
:专用于匹配子类类型- 匹配业务层的所有方法
- execution(* * …*Service+.*(…))
书写技巧
AOP通知类型(5种)
抽取的共性功能的位置不同,运行代码加入的位置也不同
5种类型:
@Pointcut("execution(void com.itheima.dao.BookDao.update())")//无返回值
private void pt(){}
@Pointcut("execution(int com.itheima.dao.BookDao.select())")//返回值是int类型
private void pt2(){}
- 前置通知
//@Before:前置通知,在原始方法运行之前执行
@Before("pt()")
public void before() {
System.out.println("before advice ...");
}
- 后置通知
//@After:后置通知,在原始方法运行之后执行
@After("pt()")
public void after() {
System.out.println("after advice ...");
}
- 环绕通知(重点)
//@Around:环绕通知,在原始方法运行的前后执行
@Around("pt()")
public Object around(ProceedingJoinPoint pjp) throws Throwable {
System.out.println("around before advice ...");
//表示对原始操作的调用
Object ret = pjp.proceed();
System.out.println("around after advice ...");
return ret;
}
@Around("pt2()")
//方法声明加上Object
public Object aroundSelect(ProceedingJoinPoint pjp) throws Throwable {
System.out.println("around before advice ...");
//表示对原始操作的调用
Integer ret = (Integer) pjp.proceed();
System.out.println("around after advice ...");
return ret;//返回值
}
- 返回后通知(了解)
//@AfterReturning:返回后通知,在原始方法执行完毕后运行,且原始方法执行过程中未出现异常现象
// @AfterReturning("pt2()")
public void afterReturning() {
System.out.println("afterReturning advice ...");
}
- 抛出异常通知(了解)
//@AfterThrowing:抛出异常后通知,在原始方法执行过程中出现异常后运行
@AfterThrowing("pt2()")
public void afterThrowing() {
System.out.println("afterThrowing advice ...");
}
}
@Around注意事项
AOP通知获取数据
获取切入点方法的参数
- JoinPoint:适用于前置,后置,返回后,抛出异常后通知
- proceedJoinPoint:适用于环绕通知
获取切入点方法运行异常信息
- 抛出异常后通知
- 环绕通知
AOP总结
核心概念
- 代理(Proxy ) : SpringAOP的核心本质是采用
代理模式
实现的 - 连接点( JoinPoint ) :在SpringAOP中,理解为任意方法的执行
- 切入点( Pointcut ):匹配连接点的式子,也是具有
共性功能
的方法描述 - 通知( Advice ):若干个方法的共性功能,在切入点处执行,最终体现为一个方法
- 切面( Aspect )︰描述
通知
与切入点
的对应关系 - 目标对象(Target )︰被代理的
原始对象
成为目标对象
Spring事务
- 事务作用:在数据层保证一系列的数据库操作同时成功和失败
- Spring事务作用:在数据层或业务层保证一系列的数据库操作同成功同失败
开启注解式事务
@Configuration
@ComponentScan("com.itheima")
@PropertySource("classpath:jdbc.properties")
@Import({JdbcConfig.class,MybatisConfig.class})
//开启注解式事务驱动
@EnableTransactionManagement
public class SpringConfig {
}
配置事务位置,一般写在接口,实现类也行
public interface AccountService {
/**
* 转账操作
* @param out 传出方
* @param in 转入方
* @param money 金额
*/
//配置当前接口方法具有事务
@Transactional
public void transfer(String out,String in ,Double money) ;
}
事务相关配置
都可以在注解里添加属性
业务追加日志
无论操作是否成功,均进行转账操作的日志留痕
要让该日志不回滚,要开启新事务
public interface LogService {
//propagation设置事务属性:传播行为设置为当前操作需要新事务
@Transactional(propagation = Propagation.REQUIRES_NEW)
void log(String out, String in, Double money);
}
@Service
public class AccountServiceImpl implements AccountService {
@Autowired
private AccountDao accountDao;
@Autowired
private LogService logService;
public void transfer(String out,String in ,Double money) {
try{
accountDao.outMoney(out,money);
// int i = 1/0;
accountDao.inMoney(in,money);
}finally {
logService.log(out,in,money);
}
}
}
SpringMVC
基于spring的javaweb技术
- springMVC技术和Servlet技术功能等同,均属于web层开发技术
- 使用简单,开发便捷(相较于servlet)
基本使用
- 使用流程
-
导入SpringMVC坐标和servlet坐标
<dependency> <groupId>javax.servlet</groupId> <artifactId>javax.servlet-api</artifactId> <version>3.1.0</version> <scope>provided</scope> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>5.2.10.RELEASE</version> </dependency>
-
创建SpringMVC控制器类(等同于Servlet功能)
//定义表现层控制器bean
@Controller
public class UserController {
//设置映射路径为/save,即外部访问路径
@RequestMapping("/save")
//设置当前操作返回结果为指定json数据(本质上是一个字符串信息)
@ResponseBody
public String save(){
System.out.println("user save ...");
return "{'info':'springmvc'}";
}
}
- 初始化SpringMVC环境(同Spring环境),设定加载对应的bean
//springmvc配置类,本质上还是一个spring配置类
@Configuration
@ComponentScan("com.itheima.controller")
public class SpringMvcConfig {
}
- 初始化Servlet容器,加载SpringMVC环境,并设置SpringMVC技术处理的请求
//web容器配置类
public class ServletContainersInitConfig extends AbstractDispatcherServletInitializer {
//加载springmvc配置类,产生springmvc容器(本质还是spring容器)
protected WebApplicationContext createServletApplicationContext() {
//初始化WebApplicationContext对象
AnnotationConfigWebApplicationContext ctx = new AnnotationConfigWebApplicationContext();
//加载指定配置类
ctx.register(SpringMvcConfig.class);
return ctx;
}
//设置由springmvc控制器处理的请求映射路径
protected String[] getServletMappings() {
return new String[]{"/"};
}
//加载spring配置类
protected WebApplicationContext createRootApplicationContext() {
return null;
}
}
Bean的加载控制
-
SpringMVC相关bean(表现层bean)
-
Spring控制的bean
- 业务bean(Service)
- 功能bean(DataSource等)
-
排除SpringMVC扫描的包,避免重复
-
SpringMVC相关bean加载控制
- springMVC加载的bean对应的包均在com.itheima.controller包内
-
Spring相关bean加载控制
- 方式一:Spring加载的bean设定扫描范围为com.itheima,但要排除掉controller包内的bean
@Configuration @ComponentScan({"com.itheima.service","com.itheima.dao"}) public class SpringConfig { } ---------------------- @ComponentScan("com.itheima.controller") public class SpringMvcConfig { }
- 方式二:Spring加载的bean设定扫描范围为精准范围,例如service包,dao包等
@Configuration //@ComponentScan({"com.itheima.service","com.itheima.dao"}) //设置spring配置类加载bean时的过滤规则,当前要求排除掉表现层对应的bean //excludeFilters属性:设置扫描加载bean时,排除的过滤规则 //type属性:设置排除规则,当前使用按照bean定义时的注解类型进行排除 //classes属性:设置排除的具体注解类,当前设置排除@Controller定义的bean @ComponentScan(value="com.itheima", excludeFilters = @ComponentScan.Filter( //筛选类型为ANNOTATION(注解)的 type = FilterType.ANNOTATION, //选中Controller注解 classes = Controller.class ) ) public class SpringConfig { } ------------------ @Configuration //@ComponentScan("com.itheima.controller") public class SpringMvcConfig { }
-
PostMan
模拟HTTP协议快速发送请求
请求与响应
请求映射路径
- @RequestMapping
- 在方法上注解 再在类上注解
- 设置当前控制器方法请求访问路径
@Controller
//类上方配置的请求映射与方法上面配置的请求映射连接在一起,形成完整的请求映射路径
@RequestMapping("/user")
public class UserController {
//请求路径映射
@RequestMapping("/save")
@ResponseBody
public String save(){
System.out.println("user save ...");
return "{'module':'user save'}";
}
//请求路径映射
@RequestMapping("/delete")
@ResponseBody
public String delete(){
System.out.println("user delete ...");
return "{'module':'user delete'}";
}
}
Get请求和Post请求发送普通参数
包含处理中文乱码问题
处理json数据格式
- @RequestBody和@RequestParam
- 区别
- @RequestParam用于接受url地址传参,表单传参【application/x-www-form-urlencoded】
- @RequestBod用于接收json数据【application/json】
- 应用
- 后期开发发送json格式数据为主
- 发送非json格式数据用@RequestParam
导入坐标
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.9.0</version>
</dependency>
扫描控制器类以及开启json自动转换
@Configuration
@ComponentScan("com.itheima.controller")
//开启json数据类型自动转换
@EnableWebMvc
public class SpringMvcConfig {
}
ServletContainersInitConfig配置以及开启对前台请求的乱码处理
public class ServletContainersInitConfig extends AbstractAnnotationConfigDispatcherServletInitializer {
protected Class<?>[] getRootConfigClasses() {
return new Class[0];
}
protected Class<?>[] getServletConfigClasses() {
return new Class[]{SpringMvcConfig.class};
}
protected String[] getServletMappings() {
return new String[]{"/"};
}
//乱码处理,过滤器
@Override
protected Filter[] getServletFilters() {
CharacterEncodingFilter filter = new CharacterEncodingFilter();
filter.setEncoding("UTF-8");
return new Filter[]{filter};
}
}
控制器类
//请求参数
@Controller
public class UserController {
//普通参数:请求参数与形参名称对应即可完成参数传递
@RequestMapping("/commonParam")
@ResponseBody
public String commonParam(String name ,int age){
System.out.println("普通参数传递 name ==> "+name);
System.out.println("普通参数传递 age ==> "+age);
return "{'module':'common param'}";
}
//普通参数:请求参数名与形参名不同时,使用@RequestParam注解关联请求参数名称与形参名称之间的关系
//使用name注入形参userName
@RequestMapping("/commonParamDifferentName")
@ResponseBody
public String commonParamDifferentName(@RequestParam("name") String userName , int age){
System.out.println("普通参数传递 userName ==> "+userName);
System.out.println("普通参数传递 age ==> "+age);
return "{'module':'common param different name'}";
}
//POJO参数:请求参数与形参对象中的属性对应即可完成参数传递
@RequestMapping("/pojoParam")
@ResponseBody
public String pojoParam(User user){
System.out.println("pojo参数传递 user ==> "+user);
return "{'module':'pojo param'}";
}
//嵌套POJO参数:嵌套属性按照层次结构设定名称即可完成参数传递
@RequestMapping("/pojoContainPojoParam")
@ResponseBody
public String pojoContainPojoParam(User user){
System.out.println("pojo嵌套pojo参数传递 user ==> "+user);
return "{'module':'pojo contain pojo param'}";
}
//数组参数:同名请求参数可以直接映射到对应名称的形参数组对象中
@RequestMapping("/arrayParam")
@ResponseBody
public String arrayParam(String[] likes){
System.out.println("数组参数传递 likes ==> "+ Arrays.toString(likes));
return "{'module':'array param'}";
}
//集合参数:同名请求参数可以使用@RequestParam注解映射到对应名称的集合对象中作为数据
@RequestMapping("/listParam")
@ResponseBody
public String listParam(@RequestParam List<String> likes){
System.out.println("集合参数传递 likes ==> "+ likes);
return "{'module':'list param'}";
}
-----------------------------------------------------------
重点json
//集合参数:json格式
//1.开启json数据格式的自动转换,在配置类中开启@EnableWebMvc
//2.使用@RequestBody注解将外部传递的json数组数据映射到形参的集合对象中作为数据
@RequestMapping("/listParamForJson")
@ResponseBody
//先在配置文件里开启json自动转换,再使用@RequestBody注解就可以存入json到list了
public String listParamForJson(@RequestBody List<String> likes){
System.out.println("list common(json)参数传递 list ==> "+likes);
return "{'module':'list common for json param'}";
}
//POJO参数:json格式
//1.开启json数据格式的自动转换,在配置类中开启@EnableWebMvc
//2.使用@RequestBody注解将外部传递的json数据映射到形参的实体类对象中,要求属性名称一一对应
@RequestMapping("/pojoParamForJson")
@ResponseBody
public String pojoParamForJson(@RequestBody User user){
System.out.println("pojo(json)参数传递 user ==> "+user);
return "{'module':'pojo for json param'}";
}
//集合参数:json格式
//1.开启json数据格式的自动转换,在配置类中开启@EnableWebMvc
//2.使用@RequestBody注解将外部传递的json数组数据映射到形参的保存实体类对象的集合对象中,要求属性名称一一对应
@RequestMapping("/listPojoParamForJson")
@ResponseBody
public String listPojoParamForJson(@RequestBody List<User> list){
System.out.println("list pojo(json)参数传递 list ==> "+list);
return "{'module':'list pojo for json param'}";
}
//日期参数
//使用@DateTimeFormat注解设置日期类型数据格式,默认格式yyyy/MM/dd
@RequestMapping("/dataParam")
@ResponseBody
public String dataParam(Date date,
@DateTimeFormat(pattern="yyyy-MM-dd") Date date1,
@DateTimeFormat(pattern="yyyy/MM/dd HH:mm:ss") Date date2){
System.out.println("参数传递 date ==> "+date);
System.out.println("参数传递 date1(yyyy-MM-dd) ==> "+date1);
System.out.println("参数传递 date2(yyyy/MM/dd HH:mm:ss) ==> "+date2);
return "{'module':'data param'}";
}
}
响应
控制器类(其他类和请求的一样)
@Controller
public class UserController {
//响应页面/跳转页面
//返回值为String类型,设置返回值为页面名称,即可实现页面跳转
@RequestMapping("/toJumpPage")
public String toJumpPage(){
System.out.println("跳转页面");
return "page.jsp";
}
//响应文本数据
//返回值为String类型,设置返回值 为任意字符串信息,即可实现返回指定字符串信息,需要依赖@ResponseBody注解
@RequestMapping("/toText")
@ResponseBody
public String toText(){
System.out.println("返回纯文本数据");
return "response text";
}
//响应POJO对象
//返回值为实体类对象,设置返回值为实体类类型,即可实现返回对应对象的json数据,需要依赖@ResponseBody注解和@EnableWebMvc注解
@RequestMapping("/toJsonPOJO")
@ResponseBody
public User toJsonPOJO(){
System.out.println("返回json对象数据");
User user = new User();
user.setName("itcast");
user.setAge(15);
return user;
}
//响应POJO集合对象
//返回值为集合对象,设置返回值为集合类型,即可实现返回对应集合的json数组数据,需要依赖@ResponseBody注解和@EnableWebMvc注解
@RequestMapping("/toJsonList")
@ResponseBody
public List<User> toJsonList(){
System.out.println("返回json集合数据");
User user1 = new User();
user1.setName("传智播客");
user1.setAge(15);
User user2 = new User();
user2.setName("黑马程序员");
user2.setAge(12);
List<User> userList = new ArrayList<User>();
userList.add(user1);
userList.add(user2);
return userList;
}
}
REST风格
- REST(Representational State Transfer),表现形式状态转换
- 传统风格资源描述形式
- http://localhost/user/getById?id=1
- http://localhost/user/saveUser
- REST风格描述形式
- http://localhost/user/1
- http://localhost/user
优点
- 隐藏资源的访问行为,无法通过地址得知对资源是何种操作
- 书写简化
@RequestBody @Requestparam @PathVariable
根据REST风格对资源进行访问称为RESTful
一样的前置
- 先在SpringMvcConfig配置文件扫描controller的文件夹
- 再开启JSON自动转换
- 然后到ServletContainersInitConfig配置中获取Servlet配置文件(SpringMvcConfig)路径
- 记得开启乱码处理中文
- 下面就是Controller主要测试代码
@Controller
public class UserController {
//设置当前请求方法为POST,表示REST风格中的添加操作
@RequestMapping(value = "/users",method = RequestMethod.POST)
@ResponseBody
public String save(){
System.out.println("user save...");
return "{'module':'user save'}";
}
//设置当前请求方法为DELETE,表示REST风格中的删除操作
//@PathVariable注解用于设置路径变量(路径参数),要求路径上设置对应的占位符,并且占位符名称与方法形参名称相同
@RequestMapping(value = "/users/{id}",method = RequestMethod.DELETE)
@ResponseBody
public String delete(@PathVariable Integer id){
System.out.println("user delete..." + id);
return "{'module':'user delete'}";
}
//设置当前请求方法为PUT,表示REST风格中的修改操作
@RequestMapping(value = "/users",method = RequestMethod.PUT)
@ResponseBody
public String update(@RequestBody User user){
System.out.println("user update..."+user);
return "{'module':'user update'}";
}
//设置当前请求方法为GET,表示REST风格中的查询操作
//@PathVariable注解用于设置路径变量(路径参数),要求路径上设置对应的占位符,并且占位符名称与方法形参名称相同
@RequestMapping(value = "/users/{id}" ,method = RequestMethod.GET)
@ResponseBody
public String getById(@PathVariable Integer id){
System.out.println("user getById..."+id);
return "{'module':'user getById'}";
}
//设置当前请求方法为GET,表示REST风格中的查询操作
@RequestMapping(value = "/users",method = RequestMethod.GET)
@ResponseBody
public String getAll(){
System.out.println("user getAll...");
return "{'module':'user getAll'}";
}
}
REST快速开发
优化共同拥有的注解,不同方法使用相对应的注解
//@Controller
//@ResponseBody配置在类上可以简化配置,表示设置当前每个方法的返回值都作为响应体
//@ResponseBody
@RestController //使用@RestController注解替换@Controller与@ResponseBody注解,简化书写
@RequestMapping("/books")
public class BookController {
// @RequestMapping( method = RequestMethod.POST)
@PostMapping //使用@PostMapping简化Post请求方法对应的映射配置
public String save(@RequestBody Book book){
System.out.println("book save..." + book);
return "{'module':'book save'}";
}
// @RequestMapping(value = "/{id}" ,method = RequestMethod.DELETE)
@DeleteMapping("/{id}") //使用@DeleteMapping简化DELETE请求方法对应的映射配置
public String delete(@PathVariable Integer id){
System.out.println("book delete..." + id);
return "{'module':'book delete'}";
}
// @RequestMapping(method = RequestMethod.PUT)
@PutMapping //使用@PutMapping简化Put请求方法对应的映射配置
public String update(@RequestBody Book book){
System.out.println("book update..."+book);
return "{'module':'book update'}";
}
// @RequestMapping(value = "/{id}" ,method = RequestMethod.GET)
@GetMapping("/{id}") //使用@GetMapping简化GET请求方法对应的映射配置
public String getById(@PathVariable Integer id){
System.out.println("book getById..."+id);
return "{'module':'book getById'}";
}
// @RequestMapping(method = RequestMethod.GET)
@GetMapping //使用@GetMapping简化GET请求方法对应的映射配置
public String getAll(){
System.out.println("book getAll...");
return "{'module':'book getAll'}";
}
}
基于RESTful页面数据交互
- 制作SpringMVC控制器,并通过PostMan测试接口功能
@RestController
@RequestMapping("/books")
public class BookController {
//新增
@PostMapping
public String save(@RequestBody Book book){
System.out.println("book save ==> "+ book);
return "{'module':'book save success'}";
}
//查询
@GetMapping
public List<Book> getAll(){
System.out.println("book getAll is running ...");
List<Book> bookList = new ArrayList<Book>();
Book book1 = new Book();
book1.setType("计算机");
book1.setName("SpringMVC入门教程");
book1.setDescription("小试牛刀");
bookList.add(book1);
//模拟数据...
return bookList;
}
}
- 设置对静态资源的访问放行
- 记得要把该配置放入扫描
@Configuration
public class SpringMvcSupport extends WebMvcConfigurationSupport {
//设置静态资源访问过滤,当前类需要设置为配置类,并被扫描加载
@Override
protected void addResourceHandlers(ResourceHandlerRegistry registry) {
//当访问/pages/????时候,从/pages目录下查找内容
registry.addResourceHandler("/pages/**").addResourceLocations("/pages/");
registry.addResourceHandler("/js/**").addResourceLocations("/js/");
registry.addResourceHandler("/css/**").addResourceLocations("/css/");
registry.addResourceHandler("/plugins/**").addResourceLocations("/plugins/");
}
}
- 前端页面通过异步提交访问后台控制器
//添加
saveBook () {
axios.post("/books",this.formData).then((res)=>{
});
},
//主页列表查询
getAll() {
axios.get("/books").then((res)=>{
this.dataList = res.data;
});
},
SSM整合(Spring+SpringMVC+MyBatis)
整合的配置(项目)
- 首先创建目录结构
0.pom.xml
<!--servlet和springMVC的整合-->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
<scope>provided</scope>
</dependency>
<!--包括aop,beans,context.core,expression,web-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.2.10.RELEASE</version>
</dependency>
<!--mybatis和spring的整合-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.2.10.RELEASE</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.6</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>1.3.0</version>
</dependency>
<!--test单元测试和spring的整合-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.2.10.RELEASE</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.47</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.16</version>
</dependency>
<!--用来做json转换-->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.9.0</version>
------
<plugin>
<groupId>org.apache.tomcat.maven</groupId>
<artifactId>tomcat7-maven-plugin</artifactId>
<version>2.1</version>
<configuration>
<port>80</port>
<path>/</path>
</configuration>
</plugin>
1.SpringConfig 13.开启事务
//设置为配置文件,核心配置类
@Configuration
//设置扫描包
@ComponentScan({"com.itheima.service"})
//载入properties文件
@PropertySource("classpath:jdbc.properties")
//加载jdbcConfig和MyBatisConfig
@Import({JdbcConfig.class, MyBatisConfig.class})
//13.开启事务
@EnableTransactionManagement
public class SpringConfig {
}
2.jdbc.properties
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/db1
jdbc.username=root
jdbc.password=root
3.JdbcConfig 14.事务处理
public class JdbcConfig {
//从jdbc.properties中得到
@Value("${jdbc.driver}")
private String driver;
@Value("${jdbc.url}")
private String url;
@Value("${jdbc.username}")
private String username;
@Value("${jdbc.password}")
private String password;
//声明为bean容器
@Bean
public DataSource dataSource() {
DruidDataSource dataSource = new DruidDataSource();
dataSource.setDriverClassName(driver);
dataSource.setUrl(url);
dataSource.setUsername(username);
dataSource.setPassword(password);
return dataSource;
}
//14.事务处理bean,管理器
@Bean
public PlatformTransactionManager transactionManager(DataSource dataSource) {
DataSourceTransactionManager ds = new DataSourceTransactionManager();
ds.setDataSource(dataSource);
return ds;
}
}
4.MybatisConfig
public class MyBatisConfig {
//定义bean,SqlSessionFactoryBean,用于产生SqlSessionFactory对象
//该bean存入容器里
@Bean
public SqlSessionFactoryBean sqlSessionFactory(DataSource dataSource) {
SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();
//在spring容器中自动装配的
factoryBean.setDataSource(dataSource);
//扫描数据需要存放的类型的包
factoryBean.setTypeAliasesPackage("com.itheimna.domain");
return factoryBean;
}
//定义bean,返回MapperScannerConfigurer对象
@Bean
public MapperScannerConfigurer mapperScannerConfigurer() {
MapperScannerConfigurer msc = new MapperScannerConfigurer();
//扫描映射的包,数据交互层的和数据库交互的包,具体看下面的注释举例
msc.setBasePackage("com.itheima.dao");
return msc;
//public interface AccountDao {
//@Insert("insert into tbl_account(name,money)values(#{name},#{money})")
//void save(Account account);
}
}
5.SpringMvcConfig
//核心配置类
@Configuration
//设置扫描servlet路径
@ComponentScan("com.itheima.controller")
//功能很多,主要用来处理JSON自动转换
@EnableWebMvc
public class SpringMvcConfig {
}
6.ServletConfig
public class ServletConfig extends AbstractAnnotationConfigDispatcherServletInitializer {
//根配置
@Override
protected Class<?>[] getRootConfigClasses() {
return new Class[]{SpringConfig.class};
}
//Mvc配置,Spring不能访问MVC,但MVC能访问spring的容器
@Override
protected Class<?>[] getServletConfigClasses() {
return new Class[]{SpringMvcConfig.class};
}
@Override
protected String[] getServletMappings() {
return new String[]{"/"};
}
//根据是否需要处理乱码问题来重写方法
//一般传json数据是不需要用到该方法
//json自动转换会处理中文
@Override
protected Filter[] getServletFilters() {
CharacterEncodingFilter filter = new CharacterEncodingFilter();
filter.setEncoding("utf-8");
return new Filter[]{filter};
}
}
7.Book
public class Book {
private Integer id;
private String type;
private String name;
private String description;
//getter和setter方法
}
8.BookDao
public interface BookDao {
// @Insert("insert into tbl_book values(null,#{type},#{name},#{description})")
@Insert("insert into tbl_book (type,name,description) values(#{type},#{name},#{description})")
public void save(Book book);
@Update("update tbl_book set type = #{type}, name = #{name}, description = #{description} where id = #{id}")
public void update(Book book);
@Delete("delete from tbl_book where id = #{id}")
public void delete(Integer id);
@Select("select * from tbl_book where id = #{id}")
public Book getById(Integer id);
@Select("select * from tbl_book")
public List<Book> getAll();
}
9.BookService 15.加事务
//15.加事务
@Transactional
public interface BookService {
/**
* 保存
* @param book
* @return
*/
public boolean save(Book book);
/**
* 修改
* @param book
* @return
*/
public boolean update(Book book);
/**
* 删除
* @param id
* @return
*/
public boolean delete(Integer id);
/**
* 查询单个
* @param id
* @return
*/
public Book getById(Integer id);
/**
* 查询全部
* @return
*/
public List<Book> getAll();
}
10.BookServiceImpl
- 实现自动装配时@Autowired报错则需要修改报错检查
- 报错是因为dao包里可能没有对应的BookDao
@Service
public class BookServiceImpl implements BookService {
//自动装配到该变量
@Autowired
private BookDao bookDao;
@Override
public boolean save(Book book) {
bookDao.save(book);
return true;
}
@Override
public boolean update(Book book) {
bookDao.update(book);
return true;
}
@Override
public boolean delete(Integer id) {
bookDao.delete(id);
return true;
}
@Override
public Book getById(Integer id) {
return bookDao.getById(id);
}
@Override
public List<Book> getAll() {
return bookDao.getAll();
}
}
11.BookController
//使用@RestController注解替换@Controller与@ResponseBody注解,简化书写
@RestController
@RequestMapping("/books")
public class BookController {
//自动装配
@Autowired
private BookService bookService;
//根据业务操作不同,实现不同的Mapping
@PostMapping
//根据形参类型选中形参注入,从json中取出
public boolean save(@RequestBody Book book) {
return bookService.save(book);
}
@PutMapping
public boolean update(@RequestBody Book book) {
return bookService.update(book);
}
@DeleteMapping("/{id}")
public boolean delete(@PathVariable Integer id) {
return bookService.delete(id);
}
@GetMapping("/{id}")
public Book getById(@PathVariable Integer id) {
return bookService.getById(id);
}
@GetMapping
public List<Book> getAll() {
return bookService.getAll();
}
}
12.开发后台完成之后要测试
两个环节
-
业务层
接口开发完进行junit
单元测试@RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(classes = SpringConfig.class) public class BookServiceTest { @Autowired private BookService bookService; @Test public void testGetById() { Book book = bookService.getById(1); System.out.println(book); } @Test public void testGetAll() { List<Book> all = bookService.getAll(); System.out.println(all); } }
-
表现层
接口用postman
测试
表现层和前端数据
- 表现层数据封装
- 设置统一数据返回结果类
Result
public class Result {
//数据
private Object data;
//操作的码
private Integer code;
//操作失败的提示信息
private String msg;
}
public Result() {
}
public Result( Integer code,Object data) {
this.data = data;
this.code = code;
}
public Result(Integer code, Object data, String msg) {
this.data = data;
this.code = code;
this.msg = msg;
}
//get和set方法
Code
public class Code {
public static final Integer SAVE_OK = 20011;
public static final Integer DELETE_OK = 20021;
public static final Integer UPDATE_OK = 20031;
public static final Integer GET_OK = 20041;
public static final Integer SAVE_ERR = 20010;
public static final Integer DELETE_ERR = 20020;
public static final Integer UPDATE_ERR = 20030;
public static final Integer GET_ERR = 20040;
}
BookController更新
//使用@RestController注解替换@Controller与@ResponseBody注解,简化书写
@RestController
@RequestMapping("/books")
public class BookController {
//自动装配
@Autowired
private BookService bookService;
//根据业务操作不同,实现不同的Mapping
@PostMapping
//根据形参类型选中形参注入,从json中取出
public Result save(@RequestBody Book book) {
boolean flag = bookService.save(book);
return new Result(flag ? Code.SAVE_OK : Code.SAVE_ERR, flag);
}
@PutMapping
public Result update(@RequestBody Book book) {
boolean flag = bookService.update(book);
return new Result(flag ? Code.UPDATE_OK : Code.UPDATE_ERR, flag);
}
@DeleteMapping("/{id}")
public Result delete(@PathVariable Integer id) {
boolean flag = bookService.delete(id);
return new Result(flag ? Code.DELETE_OK : Code.DELETE_ERR, flag);
}
@GetMapping("/{id}")
public Result getById(@PathVariable Integer id) {
Book date = bookService.getById(id);
Integer code = date != null ? Code.GET_OK : Code.GET_ERR;
String msg = date != null ? "" : "数据查询失败,请重试!";
return new Result(code, date, msg);
}
@GetMapping
public Result getAll() {
List<Book> all = bookService.getAll();
Integer code = all != null ? Code.GET_OK : Code.GET_ERR;
String msg = all != null ? "" : "数据查询失败,请重试!";
return new Result(code, all, msg);
}
}
异常处理器
出现异常现象的常见位置与诱因如下:
- 框架内部抛出的异常:因使用不合规导致
- 数据层抛出的异常:因外部服务器故障导致(例如:服务器访问超时)
- 业务层抛出的异常:因业务逻辑书写错误导致(例如:遍历业务书写操作,导致索引异常等)
- 表现层抛出的异常:因数据收集、校验等规则导致(例如:不匹配的数据类型间导致异常)
- 工具类抛出的异常:因工具类书写不严谨不够健壮导致(例如:必要释放的连接长期未释放等)
各个层级均出现异常,所以异常处理代码书写在表现层
可以全部进行处理
ProjectExceptionAdvice
该异常接受类写在controller里,要被MVC扫描到才行
对所有异常进行捕获测试
//定义异常处理通知
@RestControllerAdvice
public class ProjectExceptionAdvice {
//处理哪种异常
@ExceptionHandler(Exception.class)
public Result doException(Exception e) {
System.out.println("嘿嘿,异常,我的异常!");
return new Result(666,null, " 异常了");
}
}
项目异常分类
项目异常处理方案
创建自定义异常包exception存储自定义异常类
在Controller类try…catch…异常并给ProjectExceptionAdvice处理
@RestControllerAdvice
public class ProjectExceptionAdvice {
//系统问题异常
@ExceptionHandler(SystemException.class)
public Result doSystemException(SystemException e) {
//记录日志
//发送消息给运维
//发送邮件给开发人员,e对象发送给开发人员
return new Result(e.getCode(), null, e.getMessage());
}
//恶意调用异常
@ExceptionHandler(BusinessException.class)
public Result doBusinessException(BusinessException e) {
return new Result(e.getCode(), null, e.getMessage());
}
//未知异常
@ExceptionHandler(Exception.class)
public Result doException(Exception e) {
System.out.println("嘿嘿,异常,我的异常!");
return new Result(Code.SYSTEM_UNKNOWN_ERR,null, "系统繁忙,请稍后再试!");
}
}
前后台协议联调的vue
<!DOCTYPE html>
<html>
<head>
<!-- 页面meta -->
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<title>SpringMVC案例</title>
<meta content="width=device-width,initial-scale=1,maximum-scale=1,user-scalable=no" name="viewport">
<!-- 引入样式 -->
<link rel="stylesheet" href="../plugins/elementui/index.css">
<link rel="stylesheet" href="../plugins/font-awesome/css/font-awesome.min.css">
<link rel="stylesheet" href="../css/style.css">
</head>
<body class="hold-transition">
<div id="app">
<div class="content-header">
<h1>图书管理</h1>
</div>
<div class="app-container">
<div class="box">
<div class="filter-container">
<el-input placeholder="图书名称" v-model="pagination.queryString" style="width: 200px;"
class="filter-item"></el-input>
<el-button @click="getAll()" class="dalfBut">查询</el-button>
<el-button type="primary" class="butT" @click="handleCreate()">新建</el-button>
</div>
<el-table size="small" current-row-key="id" :data="dataList" stripe highlight-current-row>
<el-table-column type="index" align="center" label="序号"></el-table-column>
<el-table-column prop="type" label="图书类别" align="center"></el-table-column>
<el-table-column prop="name" label="图书名称" align="center"></el-table-column>
<el-table-column prop="description" label="描述" align="center"></el-table-column>
<el-table-column label="操作" align="center">
<template slot-scope="scope">
<el-button type="primary" size="mini" @click="handleUpdate(scope.row)">编辑</el-button>
<el-button type="danger" size="mini" @click="handleDelete(scope.row)">删除</el-button>
</template>
</el-table-column>
</el-table>
<!-- 新增标签弹层 -->
<div class="add-form">
<el-dialog title="新增图书" :visible.sync="dialogFormVisible">
<el-form ref="dataAddForm" :model="formData" :rules="rules" label-position="right"
label-width="100px">
<el-row>
<el-col :span="12">
<el-form-item label="图书类别" prop="type">
<el-input v-model="formData.type"/>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="图书名称" prop="name">
<el-input v-model="formData.name"/>
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col :span="24">
<el-form-item label="描述">
<el-input v-model="formData.description" type="textarea"></el-input>
</el-form-item>
</el-col>
</el-row>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button @click="dialogFormVisible = false">取消</el-button>
<el-button type="primary" @click="handleAdd()">确定</el-button>
</div>
</el-dialog>
</div>
<!-- 编辑标签弹层 -->
<div class="add-form">
<el-dialog title="编辑检查项" :visible.sync="dialogFormVisible4Edit">
<el-form ref="dataEditForm" :model="formData" :rules="rules" label-position="right"
label-width="100px">
<el-row>
<el-col :span="12">
<el-form-item label="图书类别" prop="type">
<el-input v-model="formData.type"/>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="图书名称" prop="name">
<el-input v-model="formData.name"/>
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col :span="24">
<el-form-item label="描述">
<el-input v-model="formData.description" type="textarea"></el-input>
</el-form-item>
</el-col>
</el-row>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button @click="dialogFormVisible4Edit = false">取消</el-button>
<el-button type="primary" @click="handleEdit()">确定</el-button>
</div>
</el-dialog>
</div>
</div>
</div>
</div>
</body>
<!-- 引入组件库 -->
<script src="../js/vue.js"></script>
<script src="../plugins/elementui/index.js"></script>
<script type="text/javascript" src="../js/jquery.min.js"></script>
<script src="../js/axios-0.18.0.js"></script>
<script>
var vue = new Vue({
el: '#app',
data: {
pagination: {},
dataList: [],//当前页要展示的列表数据
formData: {},//表单数据
dialogFormVisible: false,//控制表单是否可见
dialogFormVisible4Edit: false,//编辑表单是否可见
rules: {//校验规则
type: [{required: true, message: '图书类别为必填项', trigger: 'blur'}],
name: [{required: true, message: '图书名称为必填项', trigger: 'blur'}]
}
},
//钩子函数,VUE对象初始化完成后自动执行
created() {
this.getAll();
},
methods: {
//列表
getAll() {
//发送ajax请求
axios.get("/books").then((res => {
this.dataList = res.data.data;
}))
},
//弹出添加窗口
handleCreate() {
this.dialogFormVisible = true;
this.resetForm();
},
//重置表单
resetForm() {
this.formData = {}
},
//添加
handleAdd() {
//发送ajax请求
axios.post("/books", this.formData).then((res => {
console.log(res)
//如果操作成功,关闭弹窗,再次查询数据
if (20011 == res.data.code) {
this.dialogFormVisible = false;
this.$message.success("添加成功");
} else if (20010 == res.data.code) {
this.$message.error("添加失败");
} else {
this.$message.error(res.data.msg);
}
})).finally(() => {
//无论成功都要执行查询
this.getAll();
})
},
//弹出编辑窗口
handleUpdate(row) {
console.log(row)
//根据id查询数据
axios.get("/books/" + row.id).then((res) => {
//查询到了才进入
if (20041 == res.data.code) {
this.formData = res.data.data;
this.dialogFormVisible4Edit = true;
} else {
this.$message.error(res.data.msg);
}
})
//展示弹窗,加载数据
},
//编辑
handleEdit() {
axios.put("/books", this.formData).then((res) => {
if (20031 == res.data.code) {
this.$message.success("修改成功");
this.dialogFormVisible4Edit = false;
} else if (20030 == res.data.code) {
this.$message.error("修改失败");
} else {
this.$message.error(res.data.msg);
}
}).finally(() => {
this.getAll();
})
},
// 删除
handleDelete(row) {
//1.弹出提示框
this.$confirm("此操作会永久删除当前数据,是否继续?", "提示", {
type: 'info'
}).then(() => {
axios.delete("/books/" + row.id).then((res) => {
//2.做删除业务
if (20021 == res.data.code) {
this.$message.success("删除成功");
} else if (20020 == res.data.code) {
this.$message.error("删除失败");
} else {
this.$message.error(res.data.msg);
}
})
}).catch(() => {
//3.取消删除
this.$message.success("取消删除");
}).finally(() => {
this.getAll();
})
}
}
})
</script>
</html>
拦截器
写在controller层/interceptor
- 拦截器(Interceptor)是一种动态拦截方法调用的机制,在SpringMVC中动态拦截控制器方法的执行
- 作用:
- 在指定的方法调用前后执行预先设定的代码
- 阻止原始方法的执行
拦截器和过滤器区别
- 归属不同:Filter属于Servlet技术,Interceptor属于SpringMvc技术
- 拦截内容不同:Filter对所有访问进行增强,Interceptor仅针对SpringMVC的访问进行增强
执行流程
SpringMvcSupport载入拦截器设置,记得把该配置文件放入springConfig里扫描
@Configuration
public class SpringMvcSupport extends WebMvcConfigurationSupport {
@Autowired
private ProjectInterceptor projectInterceptor;
//过滤器
@Override
protected void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/pages/**").addResourceLocations("/pages/");
}
@Override
protected void addInterceptors(InterceptorRegistry registry) {
//配置拦截器
//拦截器对象在容器里,需要自动装配容器注入
registry.addInterceptor(projectInterceptor).addPathPatterns("/books","/books/*");
}
}
SpringMvcSupport可以替换到MVC配置文件中
@Configuration
@ComponentScan({"com.itheima.controller"})
@EnableWebMvc
//实现WebMvcConfigurer接口可以简化开发,但具有一定的侵入性
public class SpringMvcConfig implements WebMvcConfigurer {
@Autowired
private ProjectInterceptor projectInterceptor;
@Autowired
private ProjectInterceptor2 projectInterceptor2;
@Override
public void addInterceptors(InterceptorRegistry registry) {
//配置多拦截器
//执行顺序和拦截器载入顺序有关
//先进1,后进2 先出2,后出1
registry.addInterceptor(projectInterceptor).addPathPatterns("/books","/books/*");
registry.addInterceptor(projectInterceptor2).addPathPatterns("/books","/books/*");
}
}
多拦截器执行顺序
需要载入的拦截器类
@Component
//定义拦截器类,实现HandlerInterceptor接口
//注意当前类必须受Spring容器控制
public class ProjectInterceptor implements HandlerInterceptor {
@Override
//原始方法调用前执行的内容
//返回值类型可以拦截控制的执行,true放行,false终止'
//如果为false,则只会执行preHandle,后面的不会执行了
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
String contentType = request.getHeader("Content-Type");
HandlerMethod hm = (HandlerMethod)handler;
System.out.println("preHandle..."+contentType);
return true;
}
@Override
//原始方法调用后执行的内容
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
System.out.println("postHandle...");
}
@Override
//原始方法调用完成后(postHandle执行完)执行的内容
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
System.out.println("afterCompletion...");
}
}
SpringBoot
打包时最好清除clean
之前的打包文件
注意:基于idea开发SpringBoot程序必须要联网才能加载到程序框架结构
-
SpringBoot是由Pivotal团队提供的全新框架,其设计目的是用来简化Spring应用的初始搭建以及开发过程
-
最简Spring Boot程序包含的基础文件
- pom.xml
- web依赖是创建时打勾载入的
- Application类
SpringBoot可以使用官网创建一个项目,idea的创建也是基于官网创建的
- Spring Boot的jar包快速启动
- 注:先通过idea打包
- 运行环境jdk版本要和jar包一样
执行启动指令
java -jar springboot.jar
Spring boot如何自动加载版本
为了不频繁切换坐标依赖和插件的版本,spring boot的父项的父项加载了全部版本的依赖和插件
- 根据spring boot的版本来自动加载不同版本的依赖和插件
- 整个spring boot项目就是基于该方法加载的
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<!--指定了一个父工程,父工程中的东西在该工程中可以继承过来使用-->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.5.0</version>
</parent>
<groupId>com.itheima</groupId>
<artifactId>springboot_01_quickstart</artifactId>
<version>0.0.1-SNAPSHOT</version>
<!--JDK 的版本-->
<properties>
<java.version>8</java.version>
</properties>
<dependencies>
<!--该依赖就是我们在创建 SpringBoot 工程勾选的那个 Spring Web 产生的-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--这个是单元测试的依赖,我们现在没有进行单元测试,所以这个依赖现在可以没有-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<!--这个插件是在打包时需要的,而这里暂时还没有用到-->
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
Springboot起步依赖
spring-boot-starter-parent
包含starter的全是起步依赖
配置文件格式(3种)比如修改端口号
-
resources目录下的application.properties文件里
-
自建的yml格式文件
-
自建的yaml文件
三种配置文件如果都配置了不同的端口
- 优先级properties文件权限大于yml大于yaml
打包之后如何启动包
进入 jar
包所在位置,在 命令提示符
中输入如下命令
jar -jar springboot_01_quickstart-0.0.1-SNAPSHOT.jar
概述
起步依赖
我们使用 Spring Initializr
方式创建的 Maven
工程的的 pom.xml
配置文件中自动生成了很多包含 starter
的依赖
探索父工程
父工程中包含所有version版本,根据需要会自动选择对应的版本
-
所有
SpringBoot
项目要继承的项目,定义了若干个坐标版本号(依赖管理,而非依赖),以达到减少依赖冲突的目的 -
spring-boot-starter-parent
(2.5.0)与spring-boot-starter-parent
(2.4.6)共计57处坐标版本不同
实际开发
-
使用任意坐标时,仅书写GAV中的G和A,V由SpringBoot提供
G:groupid
A:artifactId
V:version
-
如发生坐标错误,再指定version(要小心版本冲突)
切换web服务器
现在我们启动工程使用的是 tomcat
服务器,那能不能不使用 tomcat
而使用 jetty
服务器,jetty
在我们 maven
高级时讲 maven
私服使用的服务器。而要切换 web
服务器就需要将默认的 tomcat
服务器给排除掉,怎么排除呢?使用 exclusion
标签
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<exclusions>
<exclusion>
<artifactId>spring-boot-starter-tomcat</artifactId>
<groupId>org.springframework.boot</groupId>
</exclusion>
</exclusions>
</dependency>
现在我们运行引导类会不行,因为没有服务器了,所以引入服务器
程序直接停止了,为什么呢?那是因为排除了 tomcat
服务器,程序中就没有服务器了。所以此时不光要排除 tomcat
服务器,还要引入 jetty
服务器。在 pom.xml
中因为 jetty
的起步依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jetty</artifactId>
</dependency>
接下来再次运行引导类,在日志信息中就可以看到使用的是 jetty
服务器
配置文件修改
我们现在启动服务器默认的端口号是 8080
,访问路径可以书写为
http://localhost:8080/books/1
在线上环境我们还是希望将端口号改为 80
,这样在访问的时候就可以不写端口号了,如下
http://localhost/books/1
而 SpringBoot
程序如何修改呢?SpringBoot
提供了多种属性配置方式
-
application.properties
server.port=80
-
application.yml
server: port: 81
-
application.yaml
server: port: 82
注意:
SpringBoot
程序的配置文件名必须是application
,只是后缀名不同而已。
三种配合文件的优先级
在三种配合文件中分别配置不同的端口号,启动服务查看绑定的端口号。用这种方式就可以看到哪个配置文件的优先级更高一些
application.properties
文件内容如下:
server.port=80
application.yml
文件内容如下:
server:
port: 81
application.yaml
文件内容如下:
server:
port: 82
启动服务,在控制台可以看到使用的端口号是 80
。说明 application.properties
的优先级最高
注释掉 application.properties
配置文件内容。再次启动服务,在控制台可以看到使用的端口号是 81
,说明 application.yml
配置文件为第二优先级。
从上述的验证结果可以确定三种配置文件的优先级是:
application.properties
> application.yml
> application.yaml
注意:
SpringBoot
核心配置文件名为application
SpringBoot
内置属性过多,且所有属性集中在一起修改,在使用时,通过提示键+关键字修改属性例如要设置日志的级别时,可以在配置文件中书写
logging
,就会提示出来。配置内容如下logging: level: root: info
yaml
YAML(YAML Ain’t Markup Language),一种数据序列化格式
优点
- 容易阅读
- 容易与脚本语言交互
- 以数据为核心,重数据轻格式
YAML文件拓展名
- .yml(主流)
- .yaml
语法规则
每行多个值
yaml数组数据
- 数组数据在数据书写位置下方使用减号做数据开始符号
- 注意空格
yaml数据读取方式(三种)
方式一和二
- 一是用@Value一个一个读取
- 二是用@Autowired自动装配到Environment类型的变量里
- 三是先把yaml里的数据封装到对应的user对象里,再自动装配到对应的对象里使用
第一种和第二种
使用@Value
封装全部数据到Environment对象
application.yaml
lesson: SpringBoot
server:
port: 80
enterprise:
name: itcast
age: 16
tel: 400000444
subject:
- java
- 前端
- web
- 大数据
BookController.java
@RestController
@RequestMapping("/books")
public class BookController {
@Value("${lesson}")
private String lesson;
@Value("${server.port}")
private Integer port;
@Value("${enterprise.subject[0]}")
private String subject_00;
//自动装配全部数据
@Autowired
private Environment environment;
@GetMapping("/{id}")
public String getById(@PathVariable Integer id) {
System.out.println(lesson);
System.out.println(port);
System.out.println(subject_00);
System.out.println("id ==>" + id);
System.out.println("--------------------------");
System.out.println(environment.getProperty("lesson"));
System.out.println(environment.getProperty("server.port"));
System.out.println(environment.getProperty("server"));
System.out.println(environment.getProperty("enterprise.age"));
System.out.println(environment.getProperty("enterprise.subject[1]"));
return "hello ,spring boot!";
}
}
自定义对象封装指定数据
第三种
多环境开发
有这样的场景,我们开发完毕后需要测试人员进行测试,由于测试环境和开发环境的很多配置都不相同,所以测试人员在运行我们的工程时需要临时修改很多配置,如下
java –jar springboot.jar –-spring.profiles.active=test --server.port=85 --server.servlet.context-path=/heima --server.tomcat.connection-timeout=-1 …… …… …… …… ……
针对这种情况,SpringBoot
定义了配置文件不同的放置的位置;而放在不同位置的优先级时不同的。
SpringBoot
中4级配置文件放置位置:
- 1级:classpath:application.yml
- 2级:classpath:config/application.yml
- 3级:file :application.yml
- 4级:file :config/application.yml
==说明:==级别越高优先级越高
- 生产环境
- 开发环境
- 测试环境
yaml环境
#设置启用的环境
spring:
profiles:
active: dev
---
#开发
spring:
config:
activate:
on-profile: dev
server:
port: 80
---
#生产
spring:
config:
activate:
on-profile: pro
server:
port: 81
---
#测试
spring:
config:
activate:
on-profile: test
server:
port: 82
---
application.properties环境
- 需要创建多个properties文件,通过主的properties文件调用
- 该图片调用的是application-pro.properties文件
多环境命令行启动参数设置
- 执行打包前最好clean一下,以免前次的东西影响此次打包
- 打包前还需要确认打包编码,要不然也会打包出错
打包之后在包所在的文件目录打开cmd窗口
- 先启动服务器java -jar demo1-0.0.1-SNAPSHOT.jar
- 带参数启动Springboot
- 再改变启用的环境jjava -jar demo1-0.0.1-SNAPSHOT.jar --spring.profiles.active=test
命令行的优先级更高
maven中设置多环境属性
SpringBoot中引用Maven属性
执行Maven打包指令
失败
对资源文件开启对默认占位符的解析
配置文件分类
idea里的yml文件属于最低级(4级)
和jar包放在同一个目录下的application.yml文件优先级比jar包里的大
而config里的又更大
整合第三方技术
整合JUnit
整合MyBatis
2.4.2之前都有数据库连接url时区问题
设置时区解决
- url: jdbc:mysql://localhost:3306/db1?serverTimezone=UTC
案例
总结
MyBatis-Plus
简称MP,是一个MyBatis的增强工具,在MyBatis基础上只做增强不做改变,为简化开发,提升效率而生
框架结构
快速入门
导入依赖
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.1</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
</dependencies>
连接数据库配置
配置application.yml
# DataSource Config
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/mybatis_plus?useSSL=false&useUnicode=true&characterEncoding=utf-8&serverTimezone=GMT%2B8
username: root
password: 1234
启动类
在Spring Boot启动类中添加@MapperScan注解,扫描mapper包
@MapperScan("com.itheima.mapper")
@SpringBootApplication
public class MybatisPlusStudyApplication {
public static void main(String[] args) {
SpringApplication.run(MybatisPlusStudyApplication.class, args);
}
}
添加实体类
@Data//lombok注解
public class User {
private Long id;
private String name;
private Integer age;
private String email;
}
添加mapper
BaseMapper是MyBatis-Plus提供的模板mapper,其中包含了基本的CRUD方法,泛型为操作的 实体类型
public interface UserMapper extends BaseMapper<User> {
}
测试
@Autowired
private UserMapper userMapper;
@Test
void test01(){
List<User> users = userMapper.selectList(null);
for (User user : users) {
System.out.println(user);
}
}
思考
SQL语句:Mybatis-Plus写的
方法:也是MybatisPlus写的
添加日志,查看sql执行语句
我们所有的sql现在是不可见的,我们希望知道他是怎么执行的,所以我们必须要看日志!
- 在application.yml中配置日志输出
# 配置日志
mybatis-plus:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
mapper-locations:
增删改查
/**
* step3:测试
*
* 测试删除功能,真正执行的是修改
*
* UPDATE t_user SET is_deleted=1 WHERE id=? AND is_deleted=0
*
* 测试查询功能,被逻辑删除的数据默认不会被查询
*
* SELECT id,username AS name,age,email,is_deleted FROM t_user WHERE is_deleted=0
*/
/**
* 查询所有
*/
@Test
void test01(){
List<User> users = userMapper.selectList(null);
for (User user : users) {
System.out.println(user);
}
}
/**
* 插入
*/
@Test
void insert(){
//id值默认使用雪花算法生成,且主键默认是id,如果要让主键变为uid就要用@TableId("uid")来设置
//在实体类user里设置
User user = new User(null, "fx", 2, "aaa@qq.com");
int insert = userMapper.insert(user);
System.out.println("受影响行数"+insert);
//1511332162436071425
System.out.println(user.getId());
}
/**
*根据id查询
*/
@Test
void testDeleteById(){
//DELETE FROM user WHERE id=?
int result = userMapper.deleteById(1);
System.out.println("受影响行数:"+result);
}
/**
*批量删除
*/
@Test
void testDeleteBatchIds(){
//DELETE FROM user WHERE id IN ( ? , ? , ? )
List ids = new ArrayList();
ids.add(1);
ids.add(2);
int result = userMapper.deleteBatchIds(ids);
System.out.println("受影响行数:"+result);
}
/**
*根据Map删除
*/
@Test
void testDeleteByMap(){
//DELETE FROM user WHERE name = ? AND age = ?
Map<String,Object> map=new HashMap<>();
map.put("age",12);
map.put("name","lisi");
int result = userMapper.deleteByMap(map);
System.out.println("受影响行数:"+result);
}
/**
*更新
*/
@Test
void testUpdateById(){
//SELECT id,name,age,email FROM user WHERE id=?
User user = new User(1739925106510225409L, "hello", 12, null);
int result = userMapper.updateById(user);
//注意:updateById参数是一个对象
System.out.println("受影响行数:"+result);
}
/**
*批量插入
*/
@Test
void testSaveBatch(){
// SQL长度有限制,海量数据插入单条SQL无法实行,
// 因此MP将批量插入放在了通用Service中实现,而不是通用Mapper
ArrayList<User> users = new ArrayList<>();
for (int i = 0; i < 5; i++) {
User user = new User();
user.setName("lyl"+i);
user.setAge(20+i);
users.add(user);
}
//SQL:INSERT INTO t_user ( username, age ) VALUES ( ?, ? )
userService.saveBatch(users);
}
/**
*条件查询
*/
@Test
void test1(){
//查询用户名包含a,年龄在20到30之间,并且邮箱不为null的用户信息
//SELECT id,username AS name,age,email,is_deleted FROM t_user WHERE
// is_deleted=0 AND (username LIKE ? AND age BETWEEN ? AND ? AND email IS NOT NULL)
QueryWrapper<User>queryWrapper=new QueryWrapper<>();
queryWrapper.like("name","a")
.between("age",20,30)
.isNotNull("email");
List<User> userList = userMapper.selectList(queryWrapper);
userList.forEach(System.out::println);
}
/**
* 测试分页
*/
@Test
void testPage(){
//设置分页参数
Page<User>page=new Page<>(1,3);
//page里面不设计的话默认是当前页1, 每页显示的记录条数是10
userMapper.selectPage(page,null);
//获取分页数据
List<User> list = page.getRecords();
list.forEach(System.out::println);
System.out.println("当前页:"+page.getCurrent());
System.out.println("每页显示的条数:"+page.getSize());
System.out.println("总记录数:"+page.getTotal());
System.out.println("总页数:"+page.getPages());
System.out.println("是否有上一页:"+page.hasPrevious());
System.out.println("是否有下一页:"+page.hasNext());
}
自动填充
数据库插入时间字段和更新时间字段
- 只需要设定默认值
CURRENT_TIMESTAMP
,就能产生时间
方法二
1.删除数据库默认值
2.实体类的字段属性上需要加注解
@TableField(fill = FieldFill.INSERT)
private Date createTime;
@TableField(fill = FieldFill.INSERT_UPDATE)
private Date updateTime;
12345
3、编写处理器处理注解
@Component public class MyMetaObjectHandler implements MetaObjectHandler { @Override public void insertFill(MetaObject metaObject) { // 起始版本 3.3.0(推荐使用) this.strictInsertFill(metaObject, "createTime", LocalDateTime.class, LocalDateTime.now()); } @Override public void updateFill(MetaObject metaObject) { // 起始版本 3.3.0(推荐) this.strictUpdateFill(metaObject, "updateTime", LocalDateTime.class, LocalDateTime.now()); } }
注:使用Mybatisplus查询的表数据不包括插入时间和更新时间
常用注解
@TableName
-
放在实体类
User
上,同步实体类名和数据库表名 -
@Data//lombok注解 @NoArgsConstructor//无参构造器 @TableName("t_user")//实体类类型的类名同步名称到要操作的表的表名 public class User { private Long id; @TableField("username")//同步数据库字段名 private String name; private Integer age; private String email;
-
也可以通过全局配置解决问题,最下面一行
-
# mybatis-plus配置 mybatis-plus: configuration: # 配置日志 log-impl: org.apache.ibatis.logging.stdout.StdOutImpl global-config: db-config: # 配置MyBatis-Plus操作表的默认前缀 table-prefix: t_
@TableId
- MyBatis-Plus默认将Id作为主键列,在插入数据时,默认基于雪花算法生成id
- 如果实体类和表中主键不是
id
(比如uid
)则需要自己指定主键 - 在属性id上添加注解
@TableId(“uid”)或 @TableId(value=“uid”)
就行
@TableField
- 若实体类中的属性使用的是驼峰命名风格,而表中的字段使用的是下划线命名风格
- 例如实体类属性userName,表中字段user_name 此时MyBatis-Plus会自动将下划线命名风格转化为驼峰命名风格
如果不满足上面条件
-
则需要添加注释手动同步
-
public class User { private Long id; @TableField("username")//同步数据库字段名 private String name; private Integer age; private String email;
@TableLogic
-
数据库中创建逻辑删除状态列,设置默认值为0
-
只是根据逻辑值查询,不会删除数据
-
实体类中添加逻辑删除属性
-
public class User { private Long id; @TableField("username")//同步数据库字段名 private String name; private Integer age; private String email; //装配时间到该变量 @TableField(fill = FieldFill.INSERT) private Date createTime; @TableField(fill = FieldFill.INSERT_UPDATE) private Date updateTime; //逻辑删除添加(其实是给一个标志位,为1就不查询) @TableLogic private Integer isDeleted; //getter setter
条件构造器和常用接口
wapper介绍
- Wrapper:条件构造器抽象类,最顶端父类
- AbstractWrapper : 用于查询条件封装,生成 sql 的 where 条件
- QueryWrapper : 查询条件封装
- UpdateWrapper : Update 条件封装
- AbstractLambdaWrapper : 使用Lambda 语法
- LambdaQueryWrapper :用于Lambda语法使用的查询Wrapper
- LambdaUpdateWrapper : Lambda 更新封装Wrapper
- AbstractWrapper : 用于查询条件封装,生成 sql 的 where 条件
QueryWrapper
组装查询
@Test
void test1(){
//查询用户名包含a,年龄在20到30之间,并且邮箱不为null的用户信息
//SELECT id,username AS name,age,email,is_deleted FROM t_user WHERE
// is_deleted=0 AND (username LIKE ? AND age BETWEEN ? AND ? AND email IS NOT NULL)
QueryWrapper<User>queryWrapper=new QueryWrapper<>();
queryWrapper.like("name","a")
.between("age",20,30)
.isNotNull("email");
List<User> userList = userMapper.selectList(queryWrapper);
userList.forEach(System.out::println);
}
组装排序条件
@Test
void test2(){
//按年龄降序查询用户,如果年龄相同则按id升序排列
// SELECT id,username AS name,age,email,is_deleted FROM t_user WHERE
// is_deleted=0 ORDER BY age DESC,id ASC
QueryWrapper<User>queryWrapper=new QueryWrapper<>();
queryWrapper.orderByDesc("age")//降序
.orderByAsc("id");//升序
List<User> userList = userMapper.selectList(queryWrapper);
userList.forEach(System.out::println);
}
组装删除条件
@Test
void test3(){
//删除email为空的用户
//DELETE FROM t_user WHERE (email IS NULL)
QueryWrapper<User>queryWrapper=new QueryWrapper<>();
queryWrapper.isNull("email");
int result = userMapper.delete(queryWrapper);
System.out.println("受影响的行数:" + result);
}
条件优先级
@Test
void test4(){
//将(年龄大于20并且用户名中包含有a)或邮箱为null的用户信息修改 年龄18,邮箱aaa@a.com
//UPDATE t_user SET age=?, email=? WHERE (username LIKE ? AND age > ?
// OR email IS NULL)
QueryWrapper<User>queryWrapper=new QueryWrapper<>();
queryWrapper.gt("age",20)
.like("name","a")
.or()
.isNull("email");
User user = new User();
user.setAge(18);
user.setEmail("aaa@a.com");
int result = userMapper.update(user, queryWrapper);
System.out.println("受影响的行数:" + result);
}
组装select子句
@Test
void test5(){
//查询用户信息的username和age字段
//SELECT username,age FROM t_user
QueryWrapper<User>queryWrapper=new QueryWrapper<>();
queryWrapper.select("name","age");
List<User> userList = userMapper.selectList(queryWrapper);
userList.forEach(System.out::println);
}
实现子查询
@Test
void test6(){
//查询id小于等于3的用户信息
//SELECT id,username AS name,age,email,is_deleted FROM t_user
// WHERE (id IN (select id from t_user where id <= 3))
QueryWrapper<User>queryWrapper=new QueryWrapper<>();
queryWrapper.inSql("id", "select id from user where id <= 3");
List<User> userList = userMapper.selectList(queryWrapper);
userList.forEach(System.out::println);
}
UpdateWrapper
@Test
void test7(){
//将(年龄大于20或邮箱为null)并且用户名中包含有a的用户信息修改
//组装set子句以及修改条件
UpdateWrapper<User>updateWrapper=new UpdateWrapper<>();
//lambda表达式内的逻辑优先运算
updateWrapper.set("age",16)
.set("name","sgadssa")
.like("name","a")
.and(i->i.gt("age",20).or().isNull("email"));
//这里必须要创建User对象,否则无法应用自动填充。如果没有自动填充,可以设置为null
//UPDATE t_user SET username=?, age=?,email=? WHERE (username LIKE ? AND (age > ? OR email IS NULL))
// User user = new User();
//user.setName("张三");
int result = userMapper.update(null, updateWrapper);
System.out.println(result);
}
condition
@Test
void test08UseCondition(){
//定义查询条件,有可能为null(用户未输入或未选择)
String name=null;
Integer ageBegin=10;
Integer ageEnd=20;
QueryWrapper<User>queryWrapper=new QueryWrapper<>();
//StringUtils.isNotBlank()判断某字符串是否不为空且长度不为0且不由空白符(whitespace)构成
queryWrapper.like(StringUtils.isNotBlank(name),"name","a")
.ge(ageBegin!=null,"age",ageBegin)//大于等于
.le(ageEnd!=null,"age",ageEnd);//小于等于
//SELECT id,username AS name,age,email,is_deleted FROM t_user WHERE (age >= ? AND age <= ?)
List<User> userList = userMapper.selectList(queryWrapper);
userList.forEach(System.out::println);
}
插件
分页插件
MyBatis Plus自带分页插件,只要简单的配置即可实现分页功能
添加配置
@Configuration
@MapperScan("cn.frozenpenguin.mapper")//可将主类中的注解移到这里
public class MybatisPlusConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
interceptor.addInnerInterceptor(new
PaginationInnerInterceptor(DbType.MYSQL));
return interceptor;
}
测试
@Test
void testPage(){
//设置分页参数
Page<User>page=new Page<>();
userMapper.selectPage(page,null);
//获取分页数据
List<User> list = page.getRecords();
list.forEach(System.out::println);
System.out.println("当前页:"+page.getCurrent());
System.out.println("每页显示的条数:"+page.getSize());
System.out.println("总记录数:"+page.getTotal());
System.out.println("总页数:"+page.getPages());
System.out.println("是否有上一页:"+page.hasPrevious());
System.out.println("是否有下一页:"+page.hasNext());
}
该查询不会返回插入时间和更新时间
测试结果:
User(id=5, name=Billie, age=24, email=test5@baomidou.com)
User(id=1511332162436071425, name=lisi, age=2, email=aaa@qq.com)
User(id=1511527300890456066, name=lisi, age=2, email=aaa@qq.com)
User(id=1511527301301497858, name=lyl0, age=20, email=null)
User(id=1511527301301497859, name=lyl1, age=21, email=null)
User(id=1511527301301497860, name=lyl2, age=22, email=null)
User(id=1511527301368606722, name=lyl3, age=23, email=null)
User(id=1511527301368606723, name=asgag, age=23, email=null)
当前页:1
每页显示的条数:10
总记录数:8
总页数:1
是否有上一页:false
是否有下一页:false