SSM
文章目录
- SSM
- Spring Framework
- SpringMVC
- Maven
- Springboot
- MyBatisPlus(简称MP)
Spring Framework
注意事项
Spring的注解是要写在实现类上的,接口编程只是用接口来创建对象,但实际操作的注解必须在实现类和实现类方法上
出现异常可以先不处置,直接抛出去,把异常全抛到表现层在表现层处理(个人喜好哈,你也可以抛给用户处理(开个玩笑))
自己写的异常类如果继承RuntimeException可以把异常自动网上抛,否则就需要一直throws 异常类名
前言:Dao,Service,Controller
Dao
Dao层用来跟数据库交互进行CRUD的操作,只关注数据不关注功能
- 封装了所有数据库访问细节,如 SQL 语句、ORM 框架(MyBatis、Hibernate 等)的使用。
- 只关注数据存取,不包含业务逻辑。
Service
Service层用来调用Dao层实现一个一个的功能
- 依赖 DAO 层获取数据,对数据进行加工处理,实现业务规则。
- 可以调用多个 DAO 方法,或调用其他 Service 方法组合业务。
Controller
Controller层用来接收客户端请求,调用 Service 层处理业务,返回结果给客户端
- 负责请求参数验证、解析,以及响应数据的格式化(如 JSON、HTML)。
- 不处理业务逻辑,仅作为请求入口和响应出口。
核心容器-IoC容器
spring实现IoC思想的外部
目标
充分解耦
->使用IoC容器管理bean
->在IoC容器中将由依赖关系的bean进行关系绑定
效果
使用对象可以直接从IoC容器中获取,且获取到的bean已经绑定了所有的依赖关系
IoC思想(Inversion of Control)控制反转
对象的创建控制权由程序转移到外部,为了解耦,在程序内部只创建一个指针不实例化对象,操作正常进行,在调用程序的时候再对这个指针进行实例化
bean
IoC容器负责对象的创建,初始化等一系列工作,被创建或被管理的对象在IoC容器中统称为bean
注意spring创建bean时候调用的是无参的构造方法(注意我说的是构造方法不是方法),如果重构有参的构造方法而不写无参的构造方法会报错无法调用
配置bean方式一:构造方法配置
配置bean在spring配置文件applicationContext.xml中配置,具体看下面案例
正常单独配置的话用自闭合标签
要注入依赖关系的话用
前后标签中用标签添加依赖配置信息
bean标签的属性
id
用于对这个bean进行标识,便于在引入bean的时候能找到需要的bean
name
用法与id完全相同,但name可以配置多个,格式是name=“name1 name2 name3”,中间用空格逗号分号隔开均可
但我们一般建议用id配置
class
用于表明该bean携带的实体类的位置
scope
默认是singleton也就是单例bean(在一个Application对象下无论引入多少次这个bean得到的都是同一个对象而不是新对象)
可以指定为prototype,变成多例bean
但要注意用途,一般向dao,service这种bean他可以复用,一直用一个bean没什么问题,那用默认的单例就可以,能减轻spring的压力
但像实体类的bean就不能是单例的,要是多例的
->适合单例的:
表现层对象
业务层对象
数据层对象
工具对象
->适合多例的
实体类对象
property标签的属性
指定这个标签进行依赖注入是为了让一个类中不会引用另一个类的实例化对象而是只用接口指针,降低耦合度
name
用于指定当前bean对应实体类中要实例化的空对象,name的指定依据的是setter方法,比如给一个成员变量BookDao bookDao0
写的setter方法假如叫setBookDao1,那么名字就是bookDao1,只不过一般都是按标准写setBookDao0所以name一般跟成员变量名一样,这点要区分
ref
用于指定要实例化的对象bean,比如上面的bookDao1要实例化为一个BookDaoImpl对象,这个对象在配置文件中对应的bean的id为bookDao,那么就是ref=“bookDao”
配置bean方式二:静态工厂(一般是为了兼容早期的遗留系统)
假如有一个工厂类
public class OrderDaoFactory{
public static OrderDao getOrderDao(){
return new OrderDaoImpl;
}
}
配置的时候就是配置工厂类,然后用factory-method方法点明获得所需类的方法
<bean id="orderDao" class="com.Morgan.factory.OrderDaoFactory" factory-method="getOderDao"/>
配置bean方式三:实例工厂(一般是为了兼容早期遗留系统)
有一个工厂类
public class UserDaoFactory{
public UserDao getUserDao(){
return new UserDaoImpl;
}
}
然后配置,需要先配置一个工厂bean,然后再配置实例bean
<bean id="userFactory" class="com.Morgan.factory.UserDaoFactory"/>
<bean id="userDao" factory-method="getUserDao" factory-bean="userFactory"/>
重点:配置bean方式四:FactoryBean配置工厂
有一个工厂类,让他实现接口FactoryBean(注意这个是java 的接口不是自己写的接口),这里的E是你要实例化的对象泛型,然后实现接口方法
这三个接口方法一个用来获得实例化对象,一个用来获得实例化对象的类型,一个用来规定造出来的实例化对象是不是单例的(不实现这个方法默认是单例),
相当于是方法三,spring把工厂获得实例化对象的格式给你规定好了让你直接用
public class UserDaoFactoryBean implements FactoryBean<UserDao>{
public UserDao getObject() throws Exception{
return new UserDaoImpl();
}
public Class<?> getObjectType(){
return UserDao.class;
}
// true说明是单例,false说明非单例
public boolean isSingleton(){
return true;
}
}
然后配置这个bean,直接配置工厂bean类就能得到想要的实例化对象UserDaoImpl
<bean id="userDao" class="com.Morgan.factory.UserDaoFactoryBean"/>
注意区分,实例工厂是先得到一个工厂的实例化对象,然后获取工厂方法创造的实例化对象
工厂bean是直接获取工厂方法创造的那个实例化对象
bean生命周期
注意一般spring用来开发web,关闭操作通常都由tomcat代劳了,这些只是了解一下
第一种:配置文件配置开关方法
在BookDaoImpl中写init方法和destroy方法,代表初始化方法和关闭方法,然后在对应bean里用init-method属性和destroy-method属性指明初始化方法和关闭方法,但由于java虚拟机关闭的时候来不及执行destroy方法,所以需要在java虚拟机关闭之前先关闭容器
由于这两个方法在根接口ApplicationContext中是没有的,他是子接口中的,所以我们要使用这个方法的话会通常使用它的实现类来创造对象
而非直接用ApplicationContext
第一种:用关闭钩子方法registerShutdownHook(),相当于是提前告诉虚拟机关闭之前先关闭容器
App
public class App{
public static void main(String[] args){
ClassPathXmlApplicationContext ctx=new ClassPathXmlApplicationContext("applictionContext.xml");
// 由于这一句只要在虚拟机关闭之前执行就可以,所以可以放在程序任意位置
ctx.registerShutdownHook();
BookDao bookDao=(BookDao) ctx.getBean("bookDao");
bookDao.save();
}
}
第二种:用关闭方法close关闭
public class App{
public static void main(String[] args){
ClassPathXmlApplicationContext ctx=new ClassPathXmlApplicationContext("applictionContext.xml");
BookDao bookDao=(BookDao) ctx.getBean("bookDao");
bookDao.save();
// 由于这句话是关闭容器所以必须放在容器操作完成之后
ctx.close();
}
}
第二种:用规定的格式配置
配置文件中不需要再写init-method这些属性,而是在类中用接口方法的方式写
BookServiceImpl
public class BookServiceImpl implements BookService,InitializingBean,DisposableBean{
// 结束方法
public void destroy() throws Exception{
}
// 初始化方法,注意名字,在类里面的属性设置完之后才会运行这个初始化方法
public void afterPropertiesSet() throws Exception{
}
}
DI(Dependency Injection)依赖注入
在容器中建立bean与bean之间的依赖关系的整个过程交依赖注入,例如控制基础工具的Dao层和对应调用的功能层service层都是bean,他们中若有依赖关系,则两个bean建立依赖关系的过程就是DI
注入方式一:使用setter方法注入
见bean配置的property标签,在实现类中写setter方法然后在property标签中用属性进行依赖注入
简单注入(直接注入值)是用value属性
引用注入(注入对象)是用ref属性
注入方式二:构造器注入(也就是构造方法)
在类中不再写setter方法,而是写含参构造方法,然后通过配置文件往含参构造方法中传对象,进而传给属性
BookServiceImpl
public class BookServiceImpl implements BookService {
// 在这里面实例化对象才能调用对象方法
BookDao bookDao0;
public BookServiceImpl(BookDao bookDao1){
this.bookDao0=bookDao1;
}
@Override
public void save() {
bookDao0.save();
}
}
注意配置文件中标签是constructor-arg表示把实体类引入到构造方法中,name属性用的是构造方法的形参的名字,ref属性标记的是要传的实体类,相当于是把实体类传给构造方法进而传给属性
applicationContext.xml
<bean id="bookService" class="com.Morgan.service.impl.BookServiceImpl">
<constructor-arg name="bookDao1" ref="bookDao"/>
</bean>
传递简单类型也和上面setter方法传递类似,只不过换成constructor-arg
解耦
注意,以上的传递都仍然存在耦合度过高的问题,因为需要直接把参数名给传过来
就像下面有几种可以解耦的情况(但其实都存在一定弊端谨慎使用)
通过这些方式就可以避免参数名的传递,进行有效的解耦
1.要传递的参数只有一个的
就拿构造函数举例,构造器传参只有一个参数像这样
public class BookServiceImpl implement BookService{
UserDao userDao;
public BookServiceImpl(UserDaoImpl userDao1){
this.userDao=userDao1;
}
}
那么参数在配置的时候完全可以省略掉name="userDao1"这个属性,因为只有一个参数,能明确的指向这个变量
<bean id="bookService" class="com.Morgan.service.impl.BookServiceImpl">
<constructor-arg ref="bookDao"/>
</bean>
要传递的参数有多个且类型不同(可以被下面的替代)
要传递的参数有多个,但是是不同类型的参数
public class BookDaoImpl implement BookDao{
int num;
String name;
public BookDaoImpl(int num1,String name1){
this.num=num1;
this.name=name1;
}
}
那么拿参数类型来进行指向也能明确的指向对应变量
<bean id="bookDao" class="com.Morgan.dao.impl.BookDaoImpl">
<constructor-arg type="int" value="5"/>
<constructor-arg type="java.lang.String" value="saf"/>
</bean>
多个参数而且类型有重复
要传递的参数有多个而且有重复类型的
public class BookDaoImpl implement BookDao{
int num;
int nameNum;
public BookDaoImpl(int num1,int nameNum1){
this.num=num1;
this.nameNum=nameNum1;
}
}
拿下标来进行指向能明确的指向对应变量
<bean id="bookDao" class="com.Morgan.dao.impl.BookDaoImpl">
<constructor-arg index="0" value="5"/>
<constructor-arg index="1" value="17"/>
</bean>
选择:setter/构造器
1.强制依赖使用构造器进行,因为构造器依靠的是构造方法,强制要求你注入,不注入根本创建不了对象,非常可靠,而setter本质上只是一个成员方法,不会强制注入,使用setter注入有概率不进行注入导致null对象出现
2.可选依赖使用setter注入进行,灵活性更强
3.Spring框架倡导使用构造器,第三方框架内部大多数采用构造器注入的形式进行数据初始化,相对严谨
4.如果有必要可以同时使用,用构造器完成强制依赖的注入,用setter完成可选依赖的注入
5.实际开发中如果受控对象没有提供setter方法就必须构造器注入
6.自己开发的模块推荐setter注入(实际开发中很少会重写构造方法)
依赖自动注入–主要用按类型注入
依赖的自动注入前提是所有的属性都给出了对应的标准的setter方法
下面给出一个示例:
BookServiceImpl
public class BookServiceImpl{
BookDao bookDao;
public void setBookDao(BookDao bookDao){
this.bookDao=bookDao;
}
public void save(){
}
}
在所有属性都有配套的setter方法的前提下可以进行依赖自动注入
autowire属性有五种值,主要用byType和byName
重要:byType意思是依据类型来自动注入,例如示例中的setter方法参数类型是BookDao,那么spring就会在配置文件配置的这些bean中搜索BookDao(包括实现类)的bean然后注入依赖,以后主要用的(注意不要配置重复的bean)
byName意思是依据名字来自动注入,这里依据名字是依据的setter方法名,也就是以上文为例,setter方法名为setBookDao,那搜索依据就是bookDao,spring会在配置文件中搜索id为bookDao的bean
<bean id="bookDao" class="com.Morgan.dao.impl.BookDaoImpl"/>
<bean id="bookService" class="com.Morgan.service.impl.BookServiceImpl" autowire="byName"/>
注意事项
1.自动装配只用于引用类型依赖注入,不能对简单类型进行操作
2.使用按类型装配时候必须保证容器中相同类型的bean唯一
3.使用按名称装配时必须保障容器中具有指定名称的bean,因为变量名和配置耦合,不推荐
4.自动装配优先级低于setter注入和构造器注入,同时出现的时候自动装配配置失效
集合注入
集合注入分为数组(array),列表(list),集合(set),map,properties
当前有实体类BookImpl,所有setter方法均齐全
BookImpl
package com.Morgan.entity.impl;
import java.util.*;
public class BookImpl implements com.Morgan.entity.Book {
int[] array1;
List<String> list1;
Set<String> set1;
Map<String,String> map1;
Properties properties1;
@Override
public void setArray1(int[] array1) {
this.array1 = array1;
}
@Override
public void setList1(List<String> list1) {
this.list1 = list1;
}
@Override
public void setSet1(Set<String> set1) {
this.set1 = set1;
}
@Override
public void setMap1(Map<String, String> map1) {
this.map1 = map1;
}
@Override
public void setProperties1(Properties properties1) {
this.properties1 = properties1;
}
@Override
public int[] getArray1() {
return array1;
}
@Override
public List<String> getList1() {
return list1;
}
@Override
public Set<String> getSet1() {
return set1;
}
@Override
public Map<String, String> getMap1() {
return map1;
}
@Override
public Properties getProperties1() {
return properties1;
}
@Override
public String toString() {
return "BookImpl{" +
"array1=" + Arrays.toString(array1) +
", list1=" + list1 +
", set1=" + set1 +
", map1=" + map1 +
", properties1=" + properties1 +
'}';
}
}
Book
package com.Morgan.entity;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
public interface Book {
void setArray1(int[] array1);
void setList1(List<String> list1);
void setSet1(Set<String> set1);
void setMap1(Map<String, String> map1);
void setProperties1(Properties properties1);
int[] getArray1();
List<String> getList1();
Set<String> getSet1();
Map<String, String> getMap1();
Properties getProperties1();
}
在配置文件中配置bean
applicationContext.xml
<bean id="book" class="com.Morgan.entity.impl.BookImpl" scope="prototype">
<!-- property的name都指向的是setter方法的名字,也就是setUserName,name就是userName-->
<property name="array1">
<!-- 数组标签是array,值标签是value-->
<array>
<value>5</value>
<value>6</value>
</array>
</property>
<property name="list1">
<!-- 列表标签list,值标签value-->
<list>
<value>zhangsan</value>
<value>lisi</value>
<value>wangwu</value>
</list>
</property>
<property name="set1">
<!-- set标签set,值标签value,注意set不允许重复所以值标签重复项会直接被过滤掉-->
<set>
<value>zzz</value>
<value>zzz</value>
<value>aaa</value>
</set>
</property>
<property name="map1">
<!-- map集合标签就是map,但赋值的时候需要用entry,然后直接在自闭和标签中指定value和key-->
<map>
<entry key="yi" value="zhangsan"/>
<entry key="er" value="lisi"/>
</map>
</property>
<property name="properties1">
<!-- properties标签是props,key在prop标签内指定,value被prop标签包裹-->
<props>
<prop key="yi">zhangsan</prop>
<prop key="er">lisi</prop>
</props>
</property>
</bean>
然后再App中跟之前一样调用运行就行
引用注入(一般很少用)
在类标签(list标签,array标签…)下一级(也就是跟value级别标签写在一起)写ref标签,然后用bean属性指定引用类型的bean的id
<array>
<ref bean="beanId"/>
</array>
第三方bean的管理
以alibaba的druid数据池的bean为例,由于DruidDataSource类并没有提供所需的构造方法只提供了充分的setter方法,所以只能用setter注入
<!--跟导入自己的包形式完全一样,使用别人的包本质上就是把别人写的模块导入到本地然后当作本地的来正常使用-->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/数据库名称"/>
<property name="username" value="用户名"/>
<property name="password" value="密码"/>
</bean>
外部配置文件引用
但数据库这些配置一般不会直接写在spring配置文件中,而是单独写一个properties存放,在spring配置文件中引用
jdbc.properties
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://127.0.0.1:3306/模式名
jdbc.username=用户名
jdbc.password=密码
在配置文件applicationContext.xml中,前几行是配置头
applicationContext.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
</beans>
现在要把里面带有beans的语句都在写一份然后把新的里面的beans改成context,xmlns=也要改成xmlns:context=达到新建一个context命名空间的目的,然后引入配置(注意配置头里面不允许写注释),最后用占位符引入配置
又三种加载配置的方式:标准方式是加载项目中所有的properties文件(不包括引入的jar包里的)location=“classpath:*.properties”
第二种是加载所有properties(包括引入jar包中的)location=“classpath*😗.properties”
第三种是加载指定的properties文件location=“jdbc.properties”
<?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命名空间,使用context空间加载properties文件,这种是直接加载项目所有properties文件(外部引入的jar包里的不算)
也可以指定单个文件
-->
<context:property-placeholder location="classpath:*.properties"/>
<!--这种是连jar包里的也加载-->
<context:property-placeholder location="classpath*:*.properties"/>
<!--这种是只加载指定文件-->
<context:property-placeholder location="jdbc.properties"/>
<!--用占位符引入配置-->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="${jdbc.driver}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</bean>
</beans>
注意事项
配置文件起名之所以起jdbc.username这样的形式是因为系统有一些自己的环境变量,比如username这个变量系统自己就有,那如果变量名起username就会被系统的变量覆盖掉,如果不想被覆盖可以在context:property-placeholder标签中加一个属性system-properties-mode设置为NEVER表示不使用系统变量,这样所有的变量都会优先考虑自己的
<context:property-placeholder location="jdbc.properties" system-properties-mode="NEVER"/>
注解开发bean
定义bean
在bean对应的实体类上加注解@Component(“bean名称”),(注意bean名称加上的话调用bean用的是按名称调用,如果不加直接写@Component调用bean用的是按类型调用,还是BookDao.class,注意用的是接口的类,会自动扫描接口实现类,所以有多个实现类就不能用这个)(Ps:之所以不用实现类的类名来查找是因为耦合度太高)
BookDaoImpl
//要按名称调用的话这样写
@Component("bookDao")
//如果准备按类调用写@Component
public class BookDaoImpl implements BookDao {
@Override
public void save() {
System.out.println("yes");
}
}
项目结构示例:
|----main
|--------java
|------------com
|----------------Morgan
|--------------------dao
|------------------------impl
|----------------------------BookDaoImpl
|--------resources
|------------applicationContext.xml
在配置文件中配置注解的位置以便spring对注解进行扫描,可以直接配置到具体类的上一级包,这样就会扫描具体类的配置信息,也可以只配置到总包,这样就会自动扫描包下所有的类的注解
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: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命名空间之后对bean注解进行扫描-->
<!--扫描总包-->
<context:component-scan base-package="com.Morgan"/>
<!--扫描具体的包-->
<context:component-scan base-package="com.Morgan.dao.impl"
App
// 获取IoC容器(引入配置文件)
ApplicationContext ctx=new ClassPathXmlApplicationContext("applicationContext.xml");
// 根据注解配置的id来获取bean,得到的是一个Object对象,需要转成对应的对象
BookDao bookDao=(BookDao) ctx.getBean("bookDao");
// 如果是按类型获取bean这样写(只适用实现接口的类只有一个的情况,有多个用id)
BookDao bookDao=ctx.getBean(BookDao.class)
衍生注解(不是强制使用,用@Component也行,不过这样更规范)
@Service
用于Service层bean的开发,可以用来替代@Component,用法完全一样没有任何区别,相当于只是换个名字好认
@Repository
用于数据层dao层bean的开发,跟@Service一样
@Controller
用于控制层Controller层bean的开发,跟@Service一样
纯注解开发(不再包含配置文件)
纯注解开发把配置文件换成了配置类
在上面注解定义过bean的基础上,去掉配置文件,增加一个配置类SpringConfig,用注解@Configuration表明这个类是配置类 然后用注解@ComponentScan({“bean包1”,“bean包2”})来指向要扫描的包(包尽量写的详细,别写总包,对以后的开发有帮助)
SpringConfig
@Configuration
@ComponentScan({"com.Morgan.dao.impl","com.Morgan.service.impl"})
public class SpringConfig{
}
写了配置类之后配置文件就不需要了,调用配置的时候把原来引入配置文件的语句
ApplicationContext ctx=new ClassPathXmlApplicationContext("applicationContext.xml");
改为引入配置类
ApplicationContext ctx=new AnnotationConfigApplicationContext(SpringConfig.class);
功能注解
实现多例bean:在bean注解下面写注解@Scope(“prototype”)
管理bean生命周期:写了init方法和destroy方法(名字自己起的,Spring是靠注解识别初始化和结束方法的不是名字)
在init方法上面写@PostConstruct注解(翻译:构造方法之后)
在destroy方法上面写@PreDestroy注解(翻译:结束之前)
@Repository("bookDao")
@Scope("prototype")
public class BookDaoImpl implements BookDao {
@Override
public void save() {
System.out.println("yes");
}
@PostConstruct
public void init(){
System.out.println("init...")
}
@PreDestroy
public void destroy(){
System.out.println("destroy...")
}
}
然后再App启动类中写关闭钩子
注意这里被注解的初始化和结束方法不需要写在接口是因为有注解Spring在容器创建的时候就自动扫描实现了,而save方法是自己调用所以接口中必须有
依赖注入
注意
注解进行依赖注入是内部通过暴力反射强行获取私有属性,所以才不需要setter方法来调用私有属性
自动装配需要提供无参构造方法(不重写构造方法就行),要不然造不出对象
简单类型也尽量不要直接在类中用String name="Morgan"这种方式写死而是通过注解注入,因为注解中的值也可能来自配置文件,尽量不要写死
按类型注入
假设要把BookDaoImpl类注入BookServiceImpl类中
之前是setter方法注入,注解开发中可以不用setter方法,BookServiceImpl类中的setter方法去掉,然后在要注入依赖的成员变量上面添加@Autowired注解
比如之前的BookServiceImpl类如下
@Service
public class BookServiceImpl implements BookService {
BookDao bookDao0;
@Override
public void save() {
bookDao0.save();
}
public void setBookDao0(BookDao bookDao0) {
this.bookDao0 = bookDao0;
}
}
现在去掉setter方法用注解代替
@Service
public class BookServiceImpl implements BookService {
@Autowired
BookDao bookDao0;
}
这个是按类型注入,其余的类都和之前没有区别,只有要注入依赖的Service有区别
但如果一个Service类需要注入多个bean就不太行了就需要按id注解
按id注入
如果要按id注入,就在@Autowired注解下面添加@Qualifier(“bean对应的id名”)注解
@Service
public class BookServiceImpl implements BookService {
@Autowired
@Qualifier("bookDao1")
BookDao bookDao0;
@Autowired
@Qualifier("userDao1")
UserDao userDao0;
}
注入简单类型
直接@Value(“值”)注解就行,不需要写@Autowired注解
@Service
public class BookServiceImpl implements BookService {
@Autowired
@Qualifier("bookDao1")
BookDao bookDao0;
@Value("Morgan")
private String name;
}
引入配置文件中
在配置类中添加@PropertySource(“配置文件”)注解然后在要用配置文件中的值的地方直接用${键}就行
jdbc.properties
name="Morgan"
SpringConfig
@Configuration
@ComponentScan({"com.Morgan.dao.impl","com.Morgan.service.impl"})
@PropertiesSource("jdbc.properties")
public class SpringConfig{
}
BookServiceImpl
@Service
public class BookServiceImpl implements BookService {
@Value("${name}")
private String name;
}
配置文件也可以引入多个,同样是数组形式
@PropertySource({“jdbc.properties”,“jdbc1.properties”,“jdbc2.properties”})
但注意不允许使用通配符*.properties
第三方bean
管理第三方bean
比如德鲁伊连接池的配置,是jdbc配置,先写一个配置类JdbcConfig,然后在JdbcConfig中写一个获取第三方实体对象的方法然后把他获取到的这个对象定义为Bean,通过@Bean注解(按类型获取bean),若要按id获取bean则用@Bean(“id名”)获取bean
然后在Spring的配置类@Configuration下用@Import({JdbcConfig.class})(多个配置类用逗号隔开)上引入这个外部配置类
(注意外部配置类上面不写@Configuration注解)
(我们一般会把Spring的配置类和外部引入的分开写,但合在一起其实也能用只是太混杂了)
按类型:
JdbcConfig
public class JdbcConfig{
@Bean
public DataSource dataSource(){
DruidDataSource ds=new DruidDataSource();
ds.setDriverClassName("com.mysql.jdbc.Driver");
ds.setUrl("jdbc:mysql://localhost:3306/spring_db");
ds.setUsername("root");
ds.setPassword("root");
return ds;
}
}
SpringConfig
@Configuration
@Import({JdbcConfig.class})
public class SpringConfig{
}
App
ApplicationContext ctx=new AnnotationConfigApplicationContext(JdbcConfig.class);
ctx.getBean(DataSource.class);
按id:
JdbcConfig
@Configuration
public class JdbcConfig{
@Bean("dataSource")
public DataSource dataSource(){
DruidDataSource ds=new DruidDataSource();
ds.setDriverClassName("com.mysql.jdbc.Driver");
ds.setUrl("jdbc:mysql://localhost:3306/spring_db");
ds.setUsername("root");
ds.setPassword("root");
return ds;
}
}
SpringConfig
@Configuration
@Import({JdbcConfig.class})
public class SpringConfig{
}
App
ApplicationContext ctx=new AnnotationConfigApplicationContext(JdbcConfig.class);
ctx.getBean("dataSource");
第三方bean的依赖注入
简单类型注入,用成员变量和@Value(“值”)注解
@Configuration
public class JdbcConfig{
@Value("com.mysql.jdbc.Driver")
private String className;
@Value("jdbc:mysql://localhost:3306/spring_db")
private String url;
@Value("root")
private String username;
@Value("root")
private String password;
@Bean("dataSource")
public DataSource dataSource(){
DruidDataSource ds=new DruidDataSource();
ds.setDriverClassName(className);
ds.setUrl(url);
ds.setUsername(username);
ds.setPassword(password);
return ds;
}
}
引用类型的注入:直接作为参数传入
@Configuration
public class JdbcConfig{
@Bean("dataSource")
//依旧是用接口
public DataSource dataSource(BookService bookService){
DruidDataSource ds=new DruidDataSource();
ds.setDriverClassName(bookService.getClassName());
return ds;
}
}
案例
下面先看一个不好的示范:
创建一个maven项目,创建实体类及接口也就是将来要用的bean,建议结构:
----dao
--------impl
------------BookDaoImpl
--------BookDao
----service
--------impl
----------------BookServiceImpl
--------BookService
BookDao
package com.Morgan.dao;
public interface BookDao {
public void save();
}
BookDaoImpl
package com.Morgan.dao.impl;
import com.Morgan.dao.BookDao;
public class BookDaoImpl implements BookDao {
@Override
public void save() {
System.out.println("yes");
}
}
BookService
package com.Morgan.service;
public interface BookService {
public void save();
}
BookServiceImpl
package com.Morgan.service.impl;
import com.Morgan.dao.BookDao;
import com.Morgan.dao.impl.BookDaoImpl;
import com.Morgan.service.BookService;
public class BookServiceImpl implements BookService {
// 在这里面实例化对象才能调用对象方法
BookDao bookDao0=new BookDaoImpl();
@Override
public void save() {
bookDao0.save();
}
}
然后在resources下创建spring的配置文件applicationContext.xml
创建之前需要先在pom.xml中导入依赖
pom.xml
<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.morgan</groupId>
<artifactId>spring-study</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<maven.compiler.source>22</maven.compiler.source>
<maven.compiler.target>22</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<!-- 导入spring坐标spring-context-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<!-- 改成自己的稳定版本版本号-->
<version>6.2.7</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.2</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>
之后在resources下右键创建xml文件会有spring的xml文件的选项,创建applicationContext.xml(名字并非必须一样但由于后面运行类使用的主要类与这个名字相同最好写这个),注意xml配置bean时候bean指定属性可以有id,也可以有name,name可以配置多个,用空格逗号分号隔开均可,任何用id获取bean的地方用name获取也是一模一样的,但一般我们建议用id配置
applicationContext.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- 1.配置bean-->
<!-- bean标签标识配置bean-->
<!-- id属性表示给bean起名来区分bean方便使用-->
<!-- class属性表示给bean定义类型,定位到对应的实现类,注意定位的要是实现类而不是对应接口,因为要得到的是一个实例化对象-->
<!-- name之间可以用空格或者逗号或分号隔开,name的用法跟id等同-->
<bean id="bookDao" name="bookDao1 bookDao2 bookDao3" class="com.Morgan.dao.impl.BookDaoImpl"/>
<bean id="bookService" class="com.Morgan.service.impl.BookServiceImpl"/>
</beans>
配置完之后就可以创建自己的运行类App(名字自拟)了,一般与dao包平级
App
package com.Morgan;
import com.Morgan.dao.BookDao;
import com.Morgan.service.BookService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class App {
public static void main(String[] args) {
// 获取IoC容器(引入配置文件)
ApplicationContext ctx=new ClassPathXmlApplicationContext("applicationContext.xml");
// 根据在配置文件中配置的id来获取bean,得到的是一个Object对象,需要转成对应的对象
BookDao bookDao=(BookDao) ctx.getBean("bookDao");
bookDao.save();
BookService bookService=(BookService) ctx.getBean("bookService");
bookService.save();
}
}
最终运行得到yes yes
由于这种方式BookServiceImpl中仍然存在别的实例化对象,耦合度高不符合IoC思想的要求,所以需要把这个实例化对象也去掉,用配置文件来配置这个对象,注意去掉实例化对象只保留接口指针时必须要写setter方法才能用配置文件来配置实例化对象,要不然配置文件无法给对象实例化
BookServiceImpl
package com.Morgan.service.impl;
import com.Morgan.dao.BookDao;
import com.Morgan.service.BookService;
public class BookServiceImpl implements BookService {
BookDao bookDao0;
@Override
public void save() {
bookDao0.save();
}
public void setBookDao0(BookDao bookDao0) {
this.bookDao0 = bookDao0;
}
}
applicationContext.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- 1.配置bean-->
<!-- bean标签标识配置bean-->
<!-- id属性表示给bean起名来区分bean方便使用-->
<!-- class属性表示给bean定义类型,定位到对应的实现类-->
<!-- name之间可以用空格或者逗号或者分号隔开,name的用法跟id等同-->
<bean id="bookDao" name="bookDao1 bookDao2 bookDao3" class="com.Morgan.dao.impl.BookDaoImpl"/>
<bean id="bookService" class="com.Morgan.service.impl.BookServiceImpl">
<!-- 配置service与dao的关系-->
<!-- property标签配置当前service的属性-->
<!-- property的name都指向的是setter方法的名字,也就是setUserName,name就是userName-->
<!-- ref属性是根据id从配置文件中获取要配置给属性的实例化对象在这里就是获取上面的bookDao-->
<property name="bookDao0" ref="bookDao"/>
</bean>
</beans>
总结
注意事项
生命周期
无论哪一种引入bean的方式,要想管理bean的生命周期,创建bean管理容器的时候都必须用实现类创建而不能用接口创建(注意说的是创建容器不是引入bean)
也就是把
ApplicationContext ctx=new ClassPathXmlApplicationContext("applicationContext.xml");
ApplicationContext ctx=new AnnotationConfigApplicationContext("applicationContext.xml");
改成
ClassPathXmlApplicationContext ctx=new ClassPathXmlApplicationContext("applicationContext.xml");
AnnotationConfigApplicationContext ctx=new AnnotationConfigApplicationContext("applicationContext.xml");
当然如果不需要管理生命周期而是让他最后自然关闭就直接用接口创建就行
标签属性
bean属性
<bean
id="bookDao" bean的id
name="dao bookDaoImpl daoImpl" bean的别名
class="com.Morgan.dao.impl.BookDaoImpl" bean类型,静态工厂类,FactoryBean类
scope="singleton" 控制bean的实例数量(单例/多例)
init-method="destroy" 生命周期销毁方法
autowire="byType" 生命周期自动销毁方法
factory-method="getInstance" bean工厂方法,应用于静态工厂或实例工厂
factory-bean="com.Morgan.factory.BookDaoFactory" 实例工厂bean
lazy-init="true" 控制bean延迟加载
/>
依赖注入属性
<bean id="bookService" class="com.Morgan.service.impl.BookServiceImpl">
<constructor-arg name="bookDao" ref="bookDao"/> 构造器注入引用类型
<constructor-arg name="msg" value="WARN"/> 构造器注入简单类型
<constructor-arg type="java.lang.String" value="WARN"/> 构造器类型匹配
<constructor-arg index="0" value="WARN"/> 构造器索引匹配
<property name="bookDao" ref="bookDao"/> setter注入引用类型
<property name="msg" value="WARN"/> setter注入简单类型
setter注入集合类型:
<property name="array1"> setter注入数组类型
</property>
<property name="list1"> setter注入列表类型
<list> list集合
<value>itcast</value> 集合中是简单类型
<ref bean="dataSource"/> 集合中是引用类型
</list>
</property>
<property name="set1"> setter注入集合类型
<set>
<value>zzz</value> 重复的会自动过滤掉
<value>zzz</value>
<value>aaa</value>
</set>
</property>
<property name="map1"> setter注入map类型
<map>
<entry key="yi" value="zhangsan"/> 子标签用entry,key指明键,value指明值
<entry key="er" value="lisi"/>
</map>
</property>
<property name="properties1"> setter注入properties类型
<props>
<prop key="yi">zhangsan</prop> 和map类似,但key在里面,value在外面
<prop key="er">lisi</prop>
</props>
</property>
</bean>
容器
创建容器
方式一:类路径加载配置文件(推荐使用)(也就是从项目中找配置文件)
ApplicationContext ctx=new ClassPathXmlApplicationContext("applicationContext.xml");
方式二:文件路径加载配置文件(也就是从绝对路径找)
ApplicationContext ctx=new FileSystemXmlApplicationContext("D\\applicationContext.xml");
如果要一次性加载多个配置文件"applicationContext1.xml","applicationContext2.xml"这样用逗号隔开就行
获取bean
方式一:按id获取bean之后强制类型转换
BookDao bookDao=(BookDao)ctx.getBean("bookDao");
方式二:按id获取bean并直接通过第二个参数指定获取到的bean的类型
BookDao bookDao=ctx.getBean("bookDao",BookDao.class);
方式三:根据类型获取bean(谨慎使用,必须保证同类型bean只有一个)
BookDao bookDao=ctx.getBean(BookDao.class);
一种已经过时的创建容器并获取bean的方式(了解即可)
用BeanFactory创建容器并获取bean
举例:创建一个类AppForBeanFactory
public class AppForBeanFactory{
public static void main(String[] args){
Resource resourses=new ClassPathResource("applicationContext.xml");
BeanFactory bf=new XmlBeanFactory(resources);
BookDao bookDao=bf.getBean(BookDao.class);
bookDao.save();
}
}
BeanFactory是通过Resource来获取配置文件的,所以还需要先创建一个Resource对象,获取到bf之后的操作就跟ApplicationContext的操作完全一样了,但要注意的是,ApplictationContext是创建容器的时候就加载bean,也就是容器ctx被创建出来之后所有的bean的构造方法都已经被加载完了,举个例子
有一个bean对应的类是BookDao
public class BookDao{
public BookDao(){
System.out.println("yes");
}
}
那么在ctx被创建出来之后就会直接运行yes,但BeanFactory是延迟加载,也就是直到获取bean的那一步才会加载,运行构造方法
ApplicationContext想延迟加载可以在配置文件bean标签加一个属性lazy-init="true"达到同样效果
几种name属性起名依据
bean的name属性(自拟)
一般bean标识用id,用name就相当于配置别名,跟id用法一样,name是自己来起的,不过一般要遵守规范方便后续的引用
property的name属性(固定)
setter方法注入的
依据setter方法的名字起名,比如setter方法的名字叫setBookDao1(),那name就是bookDao1
构造器注入的
依据构造方法的形参名字起名,比如构造方法是public BookService(Book book1){},那么name就是book1
命名空间
beans
beans命名空间用于bean标签和bean标签对应的DI标签的开发
context标签
注解开发和引入外部properties配置文
整合
Spring整合mybatis
坐标导入
注意导入spring-jdbc和mybatis-spring的时候要注意版本,不同版本之间可能整合的不一样,比如mybatis包和mybatis-spring包之间版本依赖关系就很强
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.33</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.17</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>6.2.7</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>3.0.4</version>
</dependency>
配置修改
注意除了Spring配置类以外其他的配置类配置文件可以理解成一个一个工具,所以在mybatis配置类中用到Jdbc配置类的内容也不需要引入jdbc配置类,而是把所有的要用的东西都引入到Spring配置类中然后相互调用,因为最终在启动类中获取的是Spring配置类,所以这个就是总配置,其他的都只是工具
以注解开发为例:
传统mybatis有两个配置文件,jdbc配置文件和mybatis配置文件
jdbc.properties
jdbc_driver=com.mysql.cj.jdbc.Driver
jdbc_url=jdbc:mysql://localhost:3306/spring_db
jdbc_username=root
jdbc_password=root
MybatisConfig.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<!--引入的外部配置文件-->
<properties resource="jdbc.properties"></properties>
<!--为整个包下的所有类自动定义别名,默认是首字母小写(比如User定义为user)-->
<typeAliases>
<package name="com.Morgan.entity"/>
</typeAliases>
<!-- 配置数据库连接环境信息,在environments下配置可以配置多个数据库
id来指代不同的数据库,要调用哪个数据库就把default改成对应数据库的id就行
-->
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<!-- 数据库连接信息,下面四行的信息是连接信息,自己修改-->
<property name="driver" value="${jdbc_driver}"/>
<!-- 本地端口直接///加数据库就行-->
<property name="url" value="${jdbc_url}"/>
<property name="username" value="${jdbc_username}"/>
<property name="password" value="${jdbc_password}"/>
</dataSource>
</environment>
</environments>
<mappers>
<package name="com.Morgan.dao"></package>
</mappers>
</configuration>
现在整合为spring,首先是用一个JdbcConfig类把数据库配置装配为一个DataSource对象
但注意,此时用${}引用了properties文件中的配置信息,但配置文件并没有被引入,所以是找不到的,需要在spring配置类SpringConfig中引入
JdbcConfig
@Configuration
public class JdbcConfig{
@Value("${jdbc_driver}")
private String className;
@Value("${jdbc_url}")
private String url;
@Value("${jdbc_username}")
private String username;
@Value("${jdbc_password}")
private String password;
@Bean("dataSource")
public DataSource dataSource(){
DruidDataSource ds=new DruidDataSource();
ds.setDriverClassName(className);
ds.setUrl(url);
ds.setUsername(username);
ds.setPassword(password);
return ds;
}
}
新建一个mybatis配置类用以替换mybatis配置文件,将Configuration标签中包裹的内容替换到mybaties配置类中,其中DataSource的部分在JdbcConfig中已经配置完了可以直接用Bean获取到DataSource对象,其他的需要在配置类中配置,其中别名TypeAliasesPackage和DataSource配置成一个SqlSessionFactoryBean类的Bean,而要获取到各类Bean的Mapper配置成一个MapperScannerConfigurer类的Bean
MybatisConfig
public class MybatisConfig{
@Bean
//需要引入外部的Bean就放在参数上,在Spring配置类中引入的Jdbc配置类的Bean会自动装配上
public SqlSessionFactoryBean sqlSessionFactory(DataSource dataSource){
SqlSessionFactoryBean ssfb=new SqlSessionFactoryBean();
ssfb.setTypeAliasesPackage("com.Morgan.entity");
ssfb.setDataSource(dataSource);
return ssfb;
}
@Bean
public MapperScannerConfigurer mapperScannerConfigurer(){
MapperScannerConfigurer msc=new MapperScannerConfigurer();
msc.setBasePackage("com.Morgan.dao");
return msc;
}
}
在SpringConfig配置类中用@ComponentScan(“com.Morgan”)注解扫描包下的配置注解
然后用@PropertySource(“classpath:jdbc.properties”)引入jdbc配置文件以便JdbcConfig配置类可以引用配置文件的内容
然后用@Import({JdbcConfig.class,MybatisConfig.class})注解引入jdbc配置类和mybatis配置类
@Configuration
@ComponentScan("com.Morgan")
@PropertySource("classpath:jdbc.properties")
@Import({JdbcConfig.class,MybatisConfig.class})
public class SpringConfig{
}
Spring整合JUnit
坐标导入
导入junit包和spring-test包
pom.xml
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.2</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>6.2.7</version>
</dependency>
测试方法
初始的test包结构应当是
----test
--------java
对应的main包结构
----main
--------java
在test包的java包下创建要测试的类的同级类
要测试的类:
----main
--------java
------------com
----------------Morgan
--------------------service
------------------------AccountService
测试类:
----test
--------java
------------com
----------------Morgan
--------------------service
------------------------AccountServiceTest
AccountServiceTest
//Spring整合JUnit的专用类运行器
@RunWith(SpringJUnit4ClassRunner.class)
//引入Spring的配置类
@ContextConfiguration(classes = {SpringConfig.class})
public class BookServiceTest {
//注解引入Bean
@Autowired
private BookService bookService;
//每次测试某个方法都要@Test注明是测试类
@Test
public void saveTest(){
bookService.save();
}
}
AOP
AOP:面向切面编程,在不惊动原有代码的基础上做功能增强
书写技巧
1.所有代码必须按标准规范开发,否则会有些技巧会出问题,比如向对所有Service类添加功能,但有些Service类没有规范命名导致结尾不是以 Service结尾的,用通配符就用不了
2.描述切入点通常描述接口,而不描述现实类
3.访问控制修饰符针对接口开发均采用public描述(可省略访问控制修饰符描述)
4.返回值类型对于增删改类使用精准类型加速匹配,对于查询类使用*通配快速描述
5.包名的书写尽量不使用…匹配,效率过低,常用*做单个包描述匹配,或精准匹配
6.接口名/类名书写名称与模块相关的采用*匹配,例如UserService书写*Service,绑定业务层接口名
7.方法名书写以动词进行精准匹配,名词采用*匹配,例如getById书写成getBy*,selectAll书写成selectAll
8.参数规则较为复杂,根据业务方法灵活调整
9.通常不使用异常作为匹配规则
核心概念
举个例子,一个类Function中有一个方法func1()
现在有另一类BookDao,其中有三个方法save1(),save2(),save3()
现在想让save1(),save2()使用func1()方法,但不写在这两个方法里,那就需要另外一个东西连接Fuction类的func1方法和BookDao中的方法
这个东西就叫切面,Function就是通知类,func1()就是通知,save1,2,3都是切入点,save1,2是连接点
代理相当于通知类给Spring创建了一个原始对象的代理对象,把通知方法填入这个代理对象中,然后用这个代理对象进行功能实现
连接点
程序的任意执行方法,设置变量等操作,在SpringAOP中,理解为方法的执行
切入点
需要分配共同方法的连接点
在SpringAOP中,一个切入点可以只描述一个具体方法,也可以匹配多个方法
1.一个具体的方法:com.Morgan.dao包下的BookDao接口中的无形参无返回值的save方法
2.匹配多个方法:所有的save方法,所有get开头的方法,所有到结尾的接口中任意方法,所有带有一个参数的方法等等
通知
在切入点执行的操作,也就是共性功能
在SpringAOP中功能最终以方法的形式呈现
通知类
定义通知的类
切面
描述通知和切入点的对应关系
目标对象
原始功能去掉共性功能后对应的类产生的对象(相当于原本的Bean对象),这种对象无法直接完成最终工作
代理
目标对象无法直接完成最终工作,需要进行功能回填,通过原始对象的代理对象实现
AOP应用
1.导入坐标
AOP的坐标依赖于context坐标,导入context坐标之后AOP就自动导入过了
<!-- 导入spring坐标spring-context -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<!-- 改成自己的稳定版本版本号 -->
<version>6.2.7</version>
</dependency>
导入aspectjweaver坐标
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.4</version>
</dependency>
2.定义通知和切面
创建通知类aop.MyAdvice,然后创建通知方法,创建切面方法(切面方法是空体无参无返回值,仅为了做注解承载体使用)
然后在切面方法上用@Pointcut(“execution(访问修饰符 切入点返回值 切入点包名到方法名(参数)(例如com.Morgan.dao.BookDao.update() 异常名))”)定义切入点表达式,其中异常名和访问修饰符可省略
在通知方法上用@Before(“切面方法名()”)注解表示通知方法的内容在该连接点方法前执行(这是前置通知,其他的后面会细说)
在通知类上用@Component注解表示该类需要被Spring扫描
在通知类上用@Aspect注解表示是用注解开发的AOP,同时要在Spring配置类SpringConfig上用注解@EnableAspectJAutoProxy注解表示启动SpringAOP的扫描,相当于这个注解用来启动@Aspect注解,@Aspect注解用来表示AOP通知类
SpringConfig
@Configuration
@ComponentScan("com.Morgan")
@EnableAspectJAutoProxy
public class SpringConfig{
}
MyAdvice
@Component
@Aspect
public class MyAdvice{
@Pointcut("execution(void com.Morgan.dao.BookDao.update())")
private void pt(){}
@Before("pt()")
public void method(){
System.out.println(System.currentTimeMillis());
}
}
切入点表达式
1.
可以用接口定义切入点表达式,也可以用具体实现类定义切入点表达式,但为了解耦我们一般用接口
2.
可以用通配符描述切入点,用通配符可以实现诸如:给所有Service类的方法加功能,给某个包下所有类的方法加功能等等
*(常用) :单个独立的任意符号
可独立出现,也可以作为前缀或后缀的匹配符出现
//第一个*表示返回值任意,第二个*表示在Morgan包和UserService类之间还存在一个包且这个包为任意包,第三个*表示方法名是find开头的,最后一个*表示有一个参数且参数类型任意
execution(public * com.Morgan.*.UserService.find* (*))
…(常用) :多个连续的任意符号
可独立出现,常用于简化包名与参数的书写
//第一个..表示com包和UserService类之间还有任意个数的包,第二个..表示有任意个数个参数
execution(public User com..UserService.findById(..))
+(不常用) :专门用来匹配子类类型
写在类的后面表示匹配该类的子类
//表示匹配Service结尾的类的子类,当然写在接口后面表示接口的实现类
execution(* *..*Service+.*(..))
通知位置
前置通知@Before
在切入点方法之前前执行通知方法,注意不是切入点方法执行前执行而是切入点方法之前执行
@Component
@Aspect
public class MyAdvice{
@Pointcut("execution(void com.Morgan.dao.BookDao.update())")
private void pt(){}
@Before("pt()")
public void method(){
System.out.println("Before");
}
}
后置通知@After
在切入点方法之后执行,注意是切入点方法之后执行,即使切入点方法执行失败也会执行这个通知方法
@Component
@Aspect
public class MyAdvice{
@Pointcut("execution(void com.Morgan.dao.BookDao.update())")
private void pt(){}
@After("pt()")
public void method(){
System.out.println("After");
}
}
环绕通知@Around(重点!!!常用!!!)
用参数ProceedingJoinPoint pjp指代切入点方法,然后用pjp.proceed()方法执行切入点方法,这条语句放在哪切入点方法就在哪执行,相当于把切入点方法当作一段代码放在通知方法中作为通知方法的一部分
另外环绕通知特别的一点在于如果环绕通知的切入点方法有返回值,通知方法也要有返回值,类型要是Object,同时要写return抛出返回值,一般会抛出切入点方法的返回值,切入点方法的返回值就是pip.proceed()方法的结果,但注意这个结果都统一会返回为Object类型,所以需要强制类型转换
BookDao
public class BookDao{
public int update(){
System.out.println("切入点");
return 1;
}
}
MyAdvice
@Component
@Aspect
public class MyAdvice{
@Pointcut("execution(void com.Morgan.dao.BookDao.update())")
private void pt(){}
@Around("pt()")
public Object method(ProceedingJoinPoint pjp) throws Throwable{
System.out.println("Before1");
System.out.println("Before1");
Integer ret=(Integer)pjp.proceed();
System.out.println("After");
System.out.println(ret);
return ret;
}
}
得到结果Before1 Before2 切入点 After 1
返回后通知@AfterReturning(了解)
在方法执行之后执行,也就是如果方法执行出现问题那这个通知方法也不会执行
@Component
@Aspect
public class MyAdvice{
@Pointcut("execution(void com.Morgan.dao.BookDao.update())")
private void pt(){}
@AfterReturning("pt()")
public void method(){
System.out.println("AfterReturning");
}
}
异常通知@AfterThrowing(了解)
在抛异常的时候通知,不抛异常不通知
@Component
@Aspect
public class MyAdvice{
@Pointcut("execution(void com.Morgan.dao.BookDao.update())")
private void pt(){}
@AfterThrowing("pt()")
public void method(){
System.out.println("AfterThrowing");
}
}
AOP通知获取数据
获取的数据包括参数,返回值,异常三种
切入点方法的参数获取
JoinPoint(环绕通知里参数ProceedJoinPoint的父接口)
适用于前置,后置,返回后,抛出异常后通知
给通知方法传递一个JoinPoint类型的参数jp,用jp的方法jp.getArgs()获取切入点方法的参数,获取的结果是一个Object数组,切入点方法的所有参数存在这个数组中
@Component
@Aspect
public class MyAdvice{
@Pointcut("execution(void com.Morgan.dao.BookDao.update())")
private void pt(){}
@Before("pt()")
public void method(JoinPoint jp){
Object[] args=jp.getArgs();
System.out.println(Arrays.toString(args));
System.out.println("Before");
}
}
PreceedJoinPoint
适用于环绕通知
给通知方法传递一个PreceedJoinPoint类型的参数pjp,用方法pjp.getArgs()获取切入点方法的参数,用法跟上面的完全一样
若不修改切入点方法的参数那直接pjp.proceed()就可以了不管切入点方法有没有参数都一样
但如果要修改切入点方法的参数就要用刚刚用pjp.getArgs()获取到的参数数组args,给这个参数数组重新赋值比如args[0]=666然后把这个新参数数组作为参数传给pjp.proceed(args)这样达到修改参数的目的,通常用来对原方法中可能出现的错误进行修改来提高代码健壮性(比如参数要求一个数字,但传进来的有可能不是数字,那就在通知方法中补一个判断,如果不是数字就赋一个默认值给切入点方法)
@Component
@Aspect
public class MyAdvice{
@Pointcut("execution(void com.Morgan.dao.BookDao.update())")
private void pt(){}
@Around("pt()")
public Object method(ProceedingJoinPoint pjp) throws Throwable{
Object[] args=pjp.getArgs();
System.out.println("Before1");
System.out.println("Before1");
args[0]=666;
Integer ret=(Integer)pjp.proceed(args);
System.out.println("After");
System.out.println(ret);
return ret;
}
}
切入点方法返回值的获取
适用于返回后通知和环绕通知
返回后通知
@AfterReturning()注解本来是一个参数pt()即@AfterReturning(“pt()”),但其实这是缩写@AfterReturning(value=“pt()”),若要获取返回值需要先给通知方法传递一个参数public void method(Object ret),这个ret参数作为保存切入点方法返回值的对象,然后在注解中添加一个注解参数@AfterReturning(value=“pt()”,returning=“ret”),这个ret就是刚刚通知方法的ret,意思是将切入点方法的返回值存在这个ret对象中
注意这个存储返回值的参数ret的类型是由返回值类型决定的,当然写Object也可以不过如果要用这个返回值还需要强转
另外如果既要获取参数又要获取返回值也就是需要定义多个参数,JoinPoint jp和Object ret,那JoinPoint必须在第一个
@Component
@Aspect
public class MyAdvice{
@Pointcut("execution(void com.Morgan.dao.BookDao.update())")
private void pt(){}
@AfterReturning(value="pt()",returning="ret")
public void method(Object ret){
System.out.println("AfterReturning");
}
}
环绕通知
通知方法里pjp.proceed()的结果就是切入点方法的返回值
切入点方法的运行异常信息
环绕通知
直接用try-catch获取异常信息就行
抛出异常后通知
跟上面返回后通知获取返回值的方法差不多,给通知传一个参数Throwable t存储异常信息,然后给注解添加一个参数@AfterReturning(value=“pt()”,throwing=“t”)来把切入点方法的异常信息传递给t
@Component
@Aspect
public class MyAdvice{
@Pointcut("execution(void com.Morgan.dao.BookDao.update())")
private void pt(){}
@AfterReturning(value="pt()",throwing="t")
public void method(Throwable t){
System.out.println("AfterReturning");
}
}
事务
开启事务
Spring事务跟数据库事务非常相似,将某个方法定义为事务之后整个方法里所有的操作是一致的,若中间出现异常整个方法回滚,比如先给A加100元,然后1/0导致异常,然后给B减100元,若这个方法定义为事务了,那A和B余额都不变,否则就是A加了100,B没变
Spring事务用的是Jdbc配置
需要先在业务层接口上添加@Transactional注解注明是事务(注意是加在接口上不是实现类,降低耦合),如果是加在方法上的话说明是这个方法开启事务,如果是加在接口上说明接口里所有方法都开启事务
然后在Jdbc配置类中设置事务管理器,也就是设置一个返回值为PlatformTransactionManager的方法,并传给他一个DataSource类型的参数,由于Jdbc配置类中本身就有一个得到dataSource的方法被定义为Bean所以事务管理器被定义为Bean之后Spring会直接把这个dataSource给事务管理器,在事务管理器中定义DataSourceTransactionManager类型对象ptm并实例化,用ptm.setDataSource(dataSource)方法给ptm的dataSource赋值,最后返回这个ptm !!!注意!!!这个dataSource和Mybatis配置类造sql工厂Bean的时候用到的dataSource必须是同一个
最后在Spring配置类用@EnableTransactionManagement开启事务管理
AccountService
public interface AccountService{
@Transactional
public void transfer(String out,String in,Double money)
}
JdbcConfig
@Configuration
public class JdbcConfig{
@Value("${jdbc_driver}")
private String className;
@Value("${jdbc_url}")
private String url;
@Value("${jdbc_username}")
private String username;
@Value("${jdbc_password}")
private String password;
@Bean("dataSource")
public DataSource dataSource(){
DruidDataSource ds=new DruidDataSource();
ds.setDriverClassName(className);
ds.setUrl(url);
ds.setUsername(username);
ds.setPassword(password);
return ds;
}
@Bean
public PlatformTransactionManager transactionManager(DataSource dataSource){
DataSourceTransactionManager dtm=new DataSourceTransactionManager();
dtm.setDataSource(dataSource);
return dtm;
}
}
SpringConfig
@Configuration
@ComponentScan({"com.Morgan"})
@EnableAspectJAutoProxy
@EnableTransactionManagement
public class SpringConfig {
}
事务角色
事务管理员
发起事务方,在Spring中通常指代业务层开启事务的方法
比如一个业务层方法中用到了两个数据层方法,这个业务层方法定义为一个事务,那么这两个数据层方法就会被加入到这个事务中,也就是三个方法变成一个事务
事务协调员
加入事务方,在Spring中通常指代数据层方法,也可以是业务层方法
事务属性
事务属性类型
readOnly
作用:设置是否为只读事务
示例:readOnly=true只读事务
timeout
作用:设置事务超时时间
示例:timeout=-1(永不超时)
rollbackFor
作用:设置事务回滚异常
示例:rollbackFor={NullPointException.class}
例子(设置事务回滚异常)
正常Spring事务只有运行中异常和ERROR类异常可以回滚
其他的异常需要在事务的@Transactional注解加上rollbackFor参数来把这个异常添加到回滚的范围中
public interface AccountService{
@Transactional(rollbackFor={IOException.class})
public void transfer(String out,String in,Double money)
}
rollbackForClassName
作用:设置事务回滚异常
示例:同上但格式为字符串(也就是按id设置异常)
noRollbackFor
作用:设置事务不回滚异常
示例:noRollbackFor={NullPointException.class}
noRollbackForClassName
作用:设置事务不回滚异常
示例:同上但是用id设置,格式为字符串
propagation
作用:设置事务传播行为
比如现在有一个对数据库的操作,用try-finally结构记录这个操作和日志操作,想要让无论事务是否成功日志都记录,但由于操作和日志是在一个事务,所以操作若失败回滚日志操作也会回滚,那这样就不符合需求,就需要通过设置事务传播行为来配合需求
设置事务传播需求是通过传播属性设置的,比如上面例子中的log需要单独设置一个事务不跟随大事务回滚那就给log这个方法写上@Transactional注解来设置log方法为一个事务,但单单这样注解在大的事务中引用log方法的时候log还是会变成事务的一部分而不是单独的事务,需要引入传播属性即给log方法设置注解@Transaction(propagation=Propagation.REQUIRES_NEW)
public interface LogService{
//这样log在被大事务引用的时候就不会变成大事务的一部分而是一个独立的事务
@Transaction(propagation=Propagation.REQUIRES_NEW)
void log(String out,String in,Double money);
}
REQUIRED(默认)
事务管理员开启事务T的时候,加了这个传播属性的事务协调员会加入T变成T事务的一部分
事务管理员若没有开启事务,事务协调员会新建一个事务T2
REQUIRES_NEWS
事务管理员开启事务T的时候事务协调员新建一个T2,也就是作为一个单独事务而不是加入到大事务中
事务管理员不开启事务T事务协调员也一样是新建一个T2
SUPPORTS
事务管理员开启T事务协调员就加入T
不开启事务协调员也无操作
NOT_SUPPORTED
事务管理员开不开启T事务协调员都无操作也就是跟不给协调员设置事务差不多
MANDATORY
事务管理员开启T事务协调员就加入T
事务管理员不开启T事务协调员就报错
NEVER
事务管理员开启T事务协调员就报错
事务管理员不开启T事务协调员也无操作
NESTED
设置savePoint,一旦事务回滚,就会回滚到savePoint处,交由客户响应提交/回滚
SpringMVC
可以用于替代Servlet,也就是Servlet能做的SpringMVC都能做
参数注解(重要)
最新的spring控制层传参的时候如果参数是从url获取的,都要用参数注解注明参数名便于获取,区分各种类型的参数注解
@RequestBody
从json数据获取,也就是从请求体中获取的,参数前加@RequestBody注解
@RequestParam(“参数名”)
从url中获取(GET请求),需要指定参数名
@PathVariable(“参数名”)
从url中获取但是是REST风格的url
简介
概述
SpringMVC是一种基于Java实现MVC模型的轻量级Web框架,优点在于使用简单,开发便捷(相对于Servlet),且灵活性强
SpringMVC是一种表现层框架技术
SpringMVC用于进行表现层功能开发
入门案例
导入坐标
先导入servlet的坐标和springMVC的坐标
<dependencies>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>4.0.1</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>jakarta.servlet</groupId>
<artifactId>jakarta.servlet-api</artifactId>
<version>6.0.0</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>6.2.7</version>
</dependency>
</dependencies>
创建SpringMVC控制器类(等同Servlet功能)
先用@Controller注解把类定义为一个控制类Bean
然后用@RequestMapping(“/调用名称”)来给控制类的方法设置调用名称,调用名称一般在类上也用@RequestMapping注解加上前缀名,tomcat运行的时候完整的调用名称是前缀名加方法调用名(比如/user/save),因为BookController和UserController可能都有save方法,需要用前缀名加以区别
然后用@ResponseBody注解(这个注解相当于告诉SpringMVC该方法的返回值无需找视图,只需要转换成数据直接返回给客户端)
UserControllerImpl
@Controller
@RequestMapping("/user")
public class UserController {
//设置该方法的访问路径
@RequestMapping("/save")
// 设置当前操作的返回值类型(也就是把当前返回的东西作为响应数据传出去)
@ResponseBody
public String save(){
System.out.println("user-save...");
return "{'module':'springmvc'}";
}
}
创建SpringMVC配置类
创建springmvc配置类,跟spring配置类一样用@ComponentScan扫描Bean,用@Configuration声明配置身份
SpringMVCConfig
@Configuration
@ComponentScan("com.morgan.controller")
public class SpringMVCConfig {
}
创建servlet启动容器的配置类,在里面加载spring的配置
创建一个servlet容器启动的配置类,在里面加载spring的配置,继承AbstractDispatcherServletInitializer类,实现父类的三个方法(加载springmvc容器配置,设置归属springmvc处理的请求,加载spring配置)
加载springmvc容器配置的方法createServletApplicationContext的内容跟之前学spring-framework的时候加载spring差不多,但要注意之前的AnnotationConfigApplicationContext不适用web环境,需要换成AnnotationConfigWebApplicationContext,然后加载SpringMVC配置类
设置归属springmvc处理的请求把return的String[0]改成String[]{“/”}表示所有的请求都归springmvc处理
spring配置的加载暂时不需要动,跟原来一样return null就行
ServletContainerInitConfig
//定义一个servlet容器启动的配置类,在里面加载spring的配置
public class ServletContainerInitConfig extends AbstractDispatcherServletInitializer {
//加载springmvc容器配置
@Override
protected WebApplicationContext createServletApplicationContext() {
//原来那个Spring配置的ctx不适用于web环境,需要换成这个
AnnotationConfigWebApplicationContext ctx=new AnnotationConfigWebApplicationContext();
ctx.register(SpringMVCConfig.class);
return ctx;
}
//设置归属springmvc处理的请求
@Override
protected String[] getServletMappings() {
//所有请求都归springmvc处理
return new String[]{"/"};
}
//加载Spring容器配置(现在暂时return null就行后面会再写Spring配置)
@Override
protected WebApplicationContext createRootApplicationContext() {
return null;
}
}
静态页面的放行
因为后续会涉及到web的静态页面的放行问题,比如html应该由tomcat来管理但是由于在servlet启动容器配置类中把所有请求都归springmvc处理的,就会出问题,这时候就需要把这些静态资源放行
比如如果所有资源都是springmvc管理,那在controller类中定义方法跳转到jsp页面可以跳转,但在浏览器直接输jsp页面就进不去,因为这些本来应该由tomcat管理,但现在都交给springmvc管理了就导致直接在地址栏输入用不了
创建SpringMVCSurpport类,打上@Configuration注解,在里面配置不交由springmvc管理的模块
然后在SpringMVC配置类中@ComponentScan({“com.morgan.controller”,“com.morgan.config”}),加上config包来扫描到SpringMVCSupport类
SpringMVCConfig
@Configuration
@ComponentScan({"com.morgan.controller","com.morgan.config"})
@EnableWebMvc
public class SpringMVCConfig implements WebMvcConfigurer {
}
SpringMVCSupport
@Configuration
public class SpringMVCSupport extends WebMvcConfigurationSupport {
@Override
protected void addResourceHandlers(ResourceHandlerRegistry registry) {
//表示检测到前面形式的url的时候访问后面这个包,下面还可以以相同类型继续添加包
registry.addResourceHandler("/page/**").addResourceLocations("/page/");
}
运行
配置tomcat,启动,转到/save
bean的加载和控制
SpringMVC要加载的Bean只有controller层的(因为是servlet功能负责的前后端对接),其他的Bean仍由Spring管理,那就需要避免Spring加载到SpringMVC已经加载的Bean
SpringMVC只需要正常加载com.morgan.controller包就行
而Spring要想避免加载到SpringMVC已经加载的包有两种方式,精确的加载到service包,dao包等或者加载到com.morgan但排除掉controller包
先创建Spring配置类
然后在servlet启动容器配置类中加载Spring配置类
ServletContainerInitConfig
//定义一个servlet容器启动的配置类,在里面加载spring的配置
public class ServletContainerInitConfig extends AbstractDispatcherServletInitializer {
//加载springmvc容器配置
@Override
protected WebApplicationContext createServletApplicationContext() {
//原来那个Spring配置的ctx不适用于web环境,需要换成这个
AnnotationConfigWebApplicationContext ctx=new AnnotationConfigWebApplicationContext();
ctx.register(SpringMVCConfig.class);
return ctx;
}
//设置归属springmvc处理的请求
@Override
protected String[] getServletMappings() {
//所有请求都归springmvc处理
return new String[]{"/"};
}
//加载Spring容器配置
@Override
protected WebApplicationContext createRootApplicationContext() {
//原来那个Spring配置的ctx不适用于web环境,需要换成这个
AnnotationConfigWebApplicationContext ctx=new AnnotationConfigWebApplicationContext();
ctx.register(SpringConfig.class);
return ctx;
}
}
然后在spring中加载包
精确加载(常用)
@Configuration
@ComponentScan({"com.morgan.dao","com.morgan.service"})
public class SpringConfig {
}
排除加载
@Configuration
@ComponentScan({"com.morgan.dao","com.morgan.service"})
/*@ComponentScan(value = "com.morgan",
excludeFilters = @ComponentScan.Filter(
//意思是按注解排除
type = FilterType.ANNOTATION,
//意思是排除注解为@Contoller的Bean
classes = Controller.class
)
)*/
public class SpringConfig {
}
请求与响应
请求映射路径
用@RequestMapper()注解来写请求映射路径
写在类上的是给本类所有方法设置的前缀名\
写在方法上的是检索名
@Controller
@RequestMapping("/user")
public class UserControllerImpl implements UserController {
//设置该方法的访问路径
@RequestMapping("/save")
// 设置当前操作的返回值类型(也就是把当前返回的东西作为响应数据传出去)
@ResponseBody
@Override
public String save() {
System.out.println("user-save...");
return "{'module':'springmvc'}";
}
@RequestMapping("/delete")
@ResponseBody
@Override
public String delete() {
return "{'module':'springmvc-delete'}";
}
}
用请求发送参数
普通参数
GET请求
在url后面加上?参数1名=参数1值&参数2名=参数2值
然后在需要接收参数的方法中以方法参数的形式接收参数,注意在每个参数前面要加上@RequestParam(“参数名”)注解来注明要接收的参数名
@Controller
@RequestMapping("/user")
public class UserControllerImpl implements UserController {
//设置该方法的访问路径
@RequestMapping("/save")
// 设置当前操作的返回值类型(也就是把当前返回的东西作为响应数据传出去)
@ResponseBody
@Override
public String save() {
System.out.println("user-save...");
return "{'module':'springmvc'}";
}
@RequestMapping("/delete")
@ResponseBody
@Override
public String delete(@RequestParam("name") String name,@RequestParam("age") int age) {
System.out.println(name);
System.out.println(age);
return "{'module':'springmvc-delete'}";
}
}
url为http://localhost/mvc_example1_war_exploded/user/delete?name=sad&age=15
POST请求
POST请求需要在表单里提交,这里我们用Postman进行测试,提交数据,后端程序跟GET一样不用动
但POST需要处理中文乱码问题需要在servlet启动容器配置类中再实现一个方法getServletFilters(SpringMVC自己的方法不是自己写的),在方法里创建CharacterEncodingFilter对象filter作为过滤器,filter.setEncoding(“UTF-8”)来设定过滤器,最后返回new Filter[]{filter}数组来设定过滤器,如果有多个过滤器在大括号用逗号隔开往后加就行
ServletContainerInitConfig
//定义一个servlet容器启动的配置类,在里面加载spring的配置
public class ServletContainerInitConfig extends AbstractDispatcherServletInitializer {
//加载springmvc容器配置
@Override
protected WebApplicationContext createServletApplicationContext() {
//原来那个Spring配置的ctx不适用于web环境,需要换成这个
AnnotationConfigWebApplicationContext ctx=new AnnotationConfigWebApplicationContext();
ctx.register(SpringMVCConfig.class);
return ctx;
}
//设置归属springmvc处理的请求
@Override
protected String[] getServletMappings() {
//所有请求都归springmvc处理
return new String[]{"/"};
}
//加载Spring容器配置
@Override
protected WebApplicationContext createRootApplicationContext() {
//原来那个Spring配置的ctx不适用于web环境,需要换成这个
AnnotationConfigWebApplicationContext ctx=new AnnotationConfigWebApplicationContext();
ctx.register(SpringConfig.class);
return ctx;
}
@Override
protected Filter[] getServletFilters() {
CharacterEncodingFilter filter=new CharacterEncodingFilter();
filter.setEncoding("UTF-8");
return new Filter[]{filter};
}
}
实体类参数
假如要传的是一个实体类User
User
public class User {
private String name;
private int age;
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;
}
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
因为有getter和setter方法,要获取参数的controller方法就可以直接获取参数
UserControllerImpl
@RequestMapping("/getEntity")
@ResponseBody
@Override
public String getEntity(User user) {
System.out.println(user);
return "{'module':'springmvc-getEn'}";
}
在url中还是正常的传name和age两个成员变量的值
如果成员变量有实体类变量,比如User的一个变量是实体类Address变量address,那传参的时候用address.province这种形式,多层实体类变量就一直点就行
数组参数
定义数组参数,比如String[] likes,然后在url用likes传多个值,比如?likes=ad&likes=ac这样,注意key必须都是likes
注意加@RequestParam注解
UserControllerImpl
@RequestMapping("/getArray")
@ResponseBody
@Override
public String getArray(@RequestParam("likes") String[] likes) {
System.out.println(Arrays.toString(likes));
return "{'module':'springmvc-getArr'}";
}
集合参数
集合参数跟上面数组基本一模一样
@RequestMapping("/getList")
@ResponseBody
@Override
public String getList(@RequestParam("likes") List<String> likes) {
System.out.println(likes.toString());
return "{'module':'springmvc-getList'}";
}
json数据参数
先导入坐标
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.19.0</version>
</dependency>
在SpringMVC配置类上加上@EnableWebMvc注解开启json数据转换
SpringMVC
@Configuration
@ComponentScan("com.morgan.controller")
@EnableWebMvc
public class SpringMVCConfig {
}
把接收参数的地方参数前加上@RequestBody注解(相当于把@RequestParam改成这个,因为json数据是放在请求体里的而不是请求参数)
UserControllerImpl
@RequestMapping("/getJson")
@ResponseBody
@Override
public String getJson(@RequestBody List<String> likes) {
System.out.println(likes.toString());
return "{'module':'springmvc-getJson'}";
}
Postman里发送json数据用body里的raw的json格式发送就行
json数组
都跟上面一样,Postman里要测试json数据用中括号字符串数组就行[“a”,“b”]
@RequestMapping("/getJson")
@ResponseBody
@Override
public String getJson(@RequestBody List<String> likes) {
System.out.println(likes.toString());
return "{'module':'springmvc-getJson'}";
}
json的实体类对象
也跟上面一样,Postman里要测试json数据用大括号键值对,如果有多层实体类就是多层大括号键值对
{“name”:“张三”,
“age”:“15”,
“address”:{“province”:“taiwan”,“city”:“qinghai”}
}
@RequestMapping("/getJsonPojo")
@ResponseBody
@Override
public String getJsonPojo(@RequestBody User user) {
System.out.println(user);
return "{'module':'springmvc-getJsonPojo'}";
}
json的实体类数组
List类型的参数
就中括号里套大括号就行
[
{“name”:“张三”,
“age”:“15”,
“address”:{“province”:“taiwan”,“city”:“qinghai”}
},
{“name”:“李四”,
“age”:“12”,
“address”:{“province”:“taiwan”,“city”:“qinghai”}
}
]
@RequestMapping("/getJsonPojoList")
@ResponseBody
@Override
public String getJsonPojoList(@RequestBody List<User> list) {
System.out.println(list);
return "{'module':'springmvc-getJsonPojoList'}";
}
日期参数
传递日期型参数可以直接用Date参数,这样子传递的数据格式应当是yyyy/MM/dd默认格式,如果要其他格式用@DateTimeFormat(pattern=“格式”)注解,这样子改的格式既可以是年月日,还可以带上时间,当然@RequestParam注解也要加
@RequestMapping("/getDateParam")
@ResponseBody
@Override
public String getDateParam(@RequestParam("date1") Date date1,@RequestParam("date2") @DateTimeFormat(pattern="yyyy-MM-dd") Date date2,@RequestParam("date3") @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") Date date3) {
System.out.println(date1);
System.out.println(date2);
System.out.println(date3);
return "{'module':'springmvc-getDateParam'}";
}
响应
注意,如果没有@ResponseBody这个注解,SpringMVC管理controller方法默认都是返回一个字符串,然后把这个字符串解析为一个页面
所以无论是向返回字符串还是返回json数据都需要加@ResponseBody注解,只有要跳转的时候不加
响应页面
响应页面就只需要一个@RequestMapping(“/响应名”)注解就可以了,因为之前一直用的注解@ResponseBody的作用是把返回的内容作为文本数据返回而不解析
UserController
@Controller
@RequestMapping("/user")
public class UserController {
@RequestMapping("/toJumpPage")
public String toJumpPage(){
System.out.println("跳转中");
return "/page.jsp";
}
}
页面会跳转到jsp文件page上
响应字符串
加上@ResponseBody注解就行了
UserController
@RequestMapping("/toText")
@ResponseBody
public String toText(){
System.out.println("返回数据");
return "page.jsp";
}
最后返回字符串page.jsp而不是jsp页面
响应json数据
响应实体类对象
跟响应字符串差不多
User
public class User {
private String name;
private int age;
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;
}
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
UserController
@RequestMapping("/toPojo")
@ResponseBody
public User toPojo(){
System.out.println("返回pojo");
User user=new User();
user.setName("zhang");
user.setAge(15);
return user;
}
响应实体类集合
跟实体类差不多
UserController
@RequestMapping("/toPojoList")
@ResponseBody
public List<User> toPojoList(){
System.out.println("返回pojo");
//注意这里不要用一个User对象传两次,因为list.add方法往list中传user对象的时候传的是对象本事不是对象的副本
User user1 =new User();
user1.setName("zhang");
user1.setAge(15);
User user2=new User();
user2.setName("li");
user2.setAge(11);
List<User> list=new ArrayList<>();
list.add(user1);
list.add(user2);
return list;
}
REST
简介
格式
http://localhost/users 查询全部用户信息 GET(查询)
http://localhost/users/1 查询指定用户信息 GET(查询)
http://localhost/users 添加用户信息 POST(新增/保存)
http://localhost/users 修改用户信息 PUT(修改/更新)
http://localhost/users/1 删除用户信息 DELETE(删除)
注意描述模块的名称通常用复数表示此类资源而非单个资源,例如:users,books
根据REST风格对资源进行访问称为RESTful
优点
隐藏资源的访问行为,无法通过地址得知对资源是何种操作
书写简化
RESTful开发
普通开发
url传参是直接以/参数的形式
若不需要传参,按REST风格开发,@RequestMapping(value=“/users”,method=RequestMethod.GET)这种形式,同一个类所有的访问名都写成一样的(就像users这种风格),然后通过method来区分,这样只能限定到六个方法,基本满足简单开发,但实际开发更复杂,会扩展REST,这个后面再说
若要传参(查询指定用户信息或删除这种),在后端开发时在方法的参数前加上@PathVariable(“参数名”)表示这个参数是从url上获取的(这个用来替代@RequestParam注解),然后在访问名后面用/{参数名}的形式指定参数获取@RequestMapping(value=“/users/{id}”,method=RequestMethod.GET)
@Controller
public class RestController {
// 添加用的发送方式是post
@RequestMapping(value = "/users",method = RequestMethod.POST)
@ResponseBody
public String insert(){
System.out.println("insert");
return "insert";
}
// 在参数前面加上@PathVariable注解表示参数是从url路径中获取的,删除的发送方式是delete,/user/{id}表示/user路径后面的值传给id参数
@RequestMapping(value = "/users/{id}",method = RequestMethod.DELETE)
@ResponseBody
public String delete(@PathVariable("id") Integer id){
System.out.println(id);
return "delete";
}
// 查询用的是GET
@RequestMapping(value = "/users",method = RequestMethod.GET)
@ResponseBody
public String select(){
System.out.println("select");
return "select";
}
// 查询用的是GET,从路径上获取参数
@RequestMapping(value = "/users/{id}",method = RequestMethod.GET)
@ResponseBody
public String selectById(@PathVariable("id") Integer id){
System.out.println(id);
return "select";
}
// 修改用的是PUT
@RequestMapping(value = "/users",method = RequestMethod.PUT)
@ResponseBody
public String update(){
System.out.println("update");
return "update";
}
}
快速开发
由于访问名,@ResponseBody都是公用的,可以@RequestMapping(“/访问名”)和@ResponseBody直接放在类上表示整个类所有方法通用,然后@Controller注解和@ResponseBody注解可以合并成@RestController注解,方法注解@RequestMapping(method=RequestMethod.GET)可以直接简化为@GetMapping,如果是需要参数的就是@GetMapping(“/{参数名}”)
@RestController
@RequestMapping("/books")
public class BookController {
// 添加用的发送方式是post
@PostMapping
public String insert(){
System.out.println("insert");
return "insert";
}
// 在参数前面加上@PathVariable注解表示参数是从url路径中获取的,删除的发送方式是delete,/user/{id}表示/user路径后面的值传给id参数
@DeleteMapping({"/{id}"})
public String delete(@PathVariable("id") Integer id){
System.out.println(id);
return "delete";
}
// 查询用的是GET
@GetMapping
public String select(){
System.out.println("select");
return "select";
}
// 查询用的是GET,从路径上获取参数
@GetMapping({"/{id}"})
public String selectById(@PathVariable("id") Integer id){
System.out.println(id);
return "select";
}
// 修改用的是PUT
@PutMapping
public String update(){
System.out.println("update");
return "update";
}
}
SSM整合
整合配置
坐标导入
<dependencies>
<!-- springmvc-->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>4.0.1</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>jakarta.servlet</groupId>
<artifactId>jakarta.servlet-api</artifactId>
<version>6.0.0</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>6.2.7</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.19.0</version>
</dependency>
<!-- spring-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>6.2.7</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>6.2.7</version>
</dependency>
<!-- mybatis-->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.17</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>3.0.4</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.33</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.2.23</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.2</version>
<scope>test</scope>
</dependency>
</dependencies>
包框架
controller: 控制层,前后端交互
entity: 实体类,对应数据库的表
dao: 数据层,通过sql语句进行对数据库数据的操作
service: 功能层,通过对dao层的应用实现功能,需要子包impl(面向接口编程降低耦合度,dao层不需要是因为mybatis开发是自动代理直接用的接口不需要实现类,controller层不需要是因为需要应用mvc整合前后端,所有注解都基于实现类开发)
config: 配置,存放所有的配置类
配置
jdbc配置类(依赖于spring配置类)
通过外部properties文件导入数据库相关配置并存为一个DataSource类型的Bean方便后面mybatis配置使用
注意properties文件的导入不是直接导入到jdbc配置类中的而是导入到spring配置类中,然后jdbc配置类和mybatis配置类也导入spring配置类中,这三个都在一个位置就可以相互引用了,所以说jdbc配置类和mybatis配置类包括properties文件实际上都是spring配置类的一部分
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("dataSource")
public DataSource dataSource(){
DruidDataSource dataSource=new DruidDataSource();
dataSource.setDriverClassName(driver);
dataSource.setUrl(url);
dataSource.setUsername(username);
dataSource.setPassword(password);
return dataSource;
}
}
Mybatis配置类(依赖于Spring配置类)
造一个存放数据库信息的工厂bean和获取代理开发用的mapper的bean,注意数据库信息存储的DataSource对象是通过spring按类型直接从Jdbc配置类中按类型获取的bean
MybatisConfig
//mybatis配置类
public class MybatisConfig {
//造一个工厂Bean用来存放数据库信息
//DataSource对象是spring按类型直接从jdbc配置类中获取的bean
@Bean
public SqlSessionFactoryBean sqlSessionFactory(DataSource dataSource){
SqlSessionFactoryBean factoryBean=new SqlSessionFactoryBean();
factoryBean.setDataSource(dataSource);
//给实体类起别名,默认为类名首字母小写,如果不起就需要写完整包名很麻烦
factoryBean.setTypeAliasesPackage("com.morgan.entity");
return factoryBean;
}
// 配置一个类来扫描数据层dao包获取mapper进行代理开发
public MapperScannerConfigurer mapperScannerConfigurer(){
MapperScannerConfigurer mapperScannerConfigurer=new MapperScannerConfigurer();
mapperScannerConfigurer.setBasePackage("com.morgan.dao");
return mapperScannerConfigurer;
}
}
Spring配置类
SpringConfig
//Spring配置类
@Configuration
@ComponentScan({"com.morgan.service"})
//引入文件资源的注解
@PropertySource("classpath:jdbc.properties")
//导入相关需要依赖的配置类的注解
@Import({JdbcConfig.class, MybatisConfig.class})
public class SpringConfig {
}
SpringMVC配置类
SpringMVCConfig
//springmvc配置类
//配置类注解
@Configuration
//指定扫描的包的注解
@ComponentScan({"com.morgan.controller"})
//管理web相关操作的注解,比如json与字符串数据返回值转换等
@EnableWebMvc
public class SpringMVCConfig {
}
Web配置类
用来加载spring配置类和springmvc配置类并设定交给springmvc管理的请求
ServletConfig
//web配置类
public class ServletConfig extends AbstractAnnotationConfigDispatcherServletInitializer {
// 配置Spring配置类(根配置)
@Override
protected Class<?>[] getRootConfigClasses() {
return new Class[]{SpringConfig.class};
}
// 配置springmvc配置类(web相关配置)
@Override
protected Class<?>[] getServletConfigClasses() {
return new Class[]{SpringMVCConfig.class};
}
// 配置要交给springmvc配置的请求,在这里把所有的请求交给springmvc,若要交给tomcat再单独配置
@Override
protected String[] getServletMappings() {
return new String[]{"/"};
}
}
功能实现
1.用注解开发dao层
BookDao(接口)
public interface BookDao {
//注意id设置的自增不需要写,前面的type是数据库字段,后面的type是参数book里的变量,表示把变量的值给到数据库2
@Insert("insert into tbl_book (type,name,description) values (#{type},#{name},#{description})")
void insert(Book book);
@Update("update tbl_book set type=#{type},name=#{name},description=#{description} where id=#{id}")
void update(Book book);
@Delete("delete from tbl_book where id=#{id}")
void delete(Integer id);
@Select("select * from tbl_book")
List<Book> selectAll();
@Select("select * from tbl_book where id=#{id}")
Book selectById(Integer id);
}
2.根据dao层方法实现service层功能
public interface BookService {
/**
* 添加一组数据
* @param book
*/
boolean insert(Book book);
/**
* 按id修改某行数据
* @param book
*/
boolean update(Book book);
/**
* 按id删除某行数据
* @param id
*/
boolean delete(Integer id);
/**
* 查询所有的数据
* @return
*/
List<Book> selectAll();
/**
* 按id查询某行数据
* @param id
* @return
*/
Book selectById(Integer id);
}
BookServiceImpl
//注解定义为bean
@Service
public class BookServiceImpl implements BookService {
//这里book可能会出现红标因为自动装配导致idea检查的时候找不到bean,但这个红标不影响程序
//自动装配bean,这个Service类Bean依赖于BookDao类型的Bean要用这个注解进行依赖注入
@Autowired
private BookDao bookDao;
@Override
public boolean insert(Book book) {
bookDao.insert(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 List<Book> selectAll() {
return bookDao.selectAll();
}
@Override
public Book selectById(Integer id) {
return bookDao.selectById(id);
}
}
3.根据service层开发controller层
BookController
//@Controller注解和@ResponseBody注解的合体
@RestController
@RequestMapping("/books")
public class BookController {
@Autowired
private BookService bookService;
// 这种参数是从json数据请求体中获取的不是url所以不用@RequestParam
@PostMapping
public boolean insert(@RequestBody Book book) {
return bookService.insert(book);
}
@PutMapping
public boolean update(@RequestBody Book book) {
return bookService.update(book);
}
@DeleteMapping("/{id}")
public boolean delete(@PathVariable("id") Integer id) {
return bookService.delete(id);
}
@GetMapping
public List<Book> selectAll() {
return bookService.selectAll();
}
@GetMapping("/{id}")
public Book selectById(@PathVariable("id") Integer id) {
return bookService.selectById(id);
}
}
4.开启spring事务
在spring配置类上加上@EnableTransactionManager注解
SpringConfig
//Spring配置类
@Configuration
//开启spring事务
@EnableTransactionManagement
@ComponentScan({"com.morgan.service"})
//引入文件资源的注解
@PropertySource("classpath:jdbc.properties")
//导入相关需要依赖的配置类的注解
@Import({JdbcConfig.class, MybatisConfig.class})
public class SpringConfig {
}
由于事务是跟数据库相关的所以在Jdbc配置类中添加一个事务管理器为bean
JdbcConfig
//平台事务管理器
//注意datasource用参数调用不用方法调用,要不然就不是spring管理了
@Bean
public PlatformTransactionManager platformTransactionManager(DataSource dataSource){
DataSourceTransactionManager dataSourceTransactionManager=new DataSourceTransactionManager();
dataSourceTransactionManager.setDataSource(dataSource);
return dataSourceTransactionManager;
}
在Service层接口上添加@Transactional注解开启事务
BookService
//开启事务,里面每个方法都是一个事务
@Transactional
public interface BookService {
/**
* 添加一组数据
* @param book
*/
boolean insert(Book book);
/**
* 按id修改某行数据
* @param book
*/
boolean update(Book book);
/**
* 按id删除某行数据
* @param id
*/
boolean delete(Integer id);
/**
* 查询所有的数据
* @return
*/
List<Book> selectAll();
/**
* 按id查询某行数据
* @param id
* @return
*/
Book selectById(Integer id);
}
5.额外:测试
在test.java包里创建测试类测试service层的功能
com.morgan.BookServiceTest
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = SpringConfig.class)
public class BookServiceTest {
@Autowired
private BookService bookService;
// 注意测试方法是不能有参数的,参数数据都是直接在方法里给的
@Test
public void selectByIdTest(){
Book book=bookService.selectById(1);
System.out.println(book);
}
@Test
public void selectTest(){
List<Book> list=bookService.selectAll();
System.out.println(list);
}
}
前后端数据传输协议
由于前后端通信时可能出现各种问题,比如前端无法确定获取到的数据是一个还是多个,无法确定获取到的数据的用途(增删改查?),无法确定数据传输是否成功,所以前后端约定一套协议(自己约定的并非规范)来限定传达这些信息,一般将controller层各个方法的结果,传输信息(传输类型,传输结果等,比如传输类型用2001表示增,1表示成功,添加成功的code就是20011),错误信息设置为一个结果实体类Result的三个属性,然后所有的方法传递的都是一个Result类型的对象
注意由于data可能是列表,数组等等,所以定义要用Object
Result
public class Result {
// 传输信息
private Integer code;
// 传输数据,由于可能是各种类型数据所以用Object
private Object data;
// 错误信息
private String msg;
// 定义三个构造函数任挑选
public Result() {
}
public Result(Integer code, Object data, String msg) {
this.code = code;
this.data = data;
this.msg = msg;
}
public Result(Integer code, Object data) {
this.code = code;
this.data = data;
}
public Integer getCode() {
return code;
}
public void setCode(Integer code) {
this.code = code;
}
public Object getData() {
return data;
}
public void setData(Object data) {
this.data = data;
}
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
}
Code
public class Code {
//末尾数字区分成功(1)和失败(0),倒数第二位区分传输数据用途
public static final Integer INSERT_OK=200511;
public static final Integer DELETE_OK=200521;
public static final Integer UPDATE_OK=200531;
public static final Integer SELECT_OK=200541;
public static final Integer INSERT_ERR=200512;
public static final Integer DELETE_ERR=200522;
public static final Integer UPDATE_ERR=200532;
public static final Integer SELECT_ERR=200542;
}
BookController
//@Controller注解和@ResponseBody注解的合体
@RestController
@RequestMapping("/books")
public class BookController {
@Autowired
private BookService bookService;
// 这种参数是从json数据请求体中获取的不是url所以不用@RequestParam
@PostMapping
public Result insert(@RequestBody Book book) {
boolean data = bookService.insert(book);
return new Result(data?Code.INSERT_OK:Code.INSERT_ERR,data);
}
@PutMapping
public Result update(@RequestBody Book book) {
boolean data = bookService.update(book);
return new Result(data?Code.UPDATE_OK:Code.UPDATE_ERR,data);
}
@DeleteMapping("/{id}")
public Result delete(@PathVariable("id") Integer id) {
boolean data = bookService.delete(id);
return new Result(data?Code.DELETE_OK:Code.DELETE_ERR,data);
}
@GetMapping("/{id}")
public Result selectById(@PathVariable("id") Integer id) {
Book data = bookService.selectById(id);
Integer code=data!=null?Code.SELECT_OK:Code.SELECT_ERR;
String msg=data!=null?"":"数据查询失败,请重试";
return new Result(code,data,msg);
}
@GetMapping
public Result selectAll() {
List<Book> data = bookService.selectAll();
// 注意这里必须用data!=null只有这样才能证明是查询失败,如果用isEmpty有可能表本来就是空的
Integer code=data!=null?Code.SELECT_OK:Code.SELECT_ERR;
String msg=data!=null?"":"数据查询失败,请重试";
return new Result(code,data,msg);
}
}
异常处理
异常处理器
用的AOP思想对控制器类进行功能增强
在表现层controller层处理异常,用单独的异常类来进行异常的处理
给异常类加上@RestControllerAdvice:为Rest风格开发的控制器类做增强(并且这个注解自带@ResponseBody注解和Component注解)(注意由于一般异常类都放在表现层controller包下,如果不是,那还要在spring配置类中扫描到对应包)
给处理某一类异常对应的方法 加上@ExceptionHandler(异常类.class)注解来设置指定异常的处理方案,功能等同于控制器方法,出现异常后终止原始控制器执行,并转入当前方法执行,并且返回的也要是一个Result对象,这样可以反馈异常信息,并避免程序因异常直接不正常中断
异常码也需要规定
ExceptionAdvice
//对rest风格的控制器类做功能增强(其实这个就是一个AOP切面),这个注解包含自带@ResponseBody注解和Component注解相当于直接注册了
@RestControllerAdvice
public class ExceptionAdvice {
//设置该方法要处理的异常类型
@ExceptionHandler(Exception.class)
public Result doException(Exception e){
return new Result(Code.EXCEPTION,null,"发生Exception异常,请进行排查并重试");
}
}
项目异常处理
项目异常分类
业务异常(BusinessException)
规范的用户行为产生的异常
不规范的用户行为操作产生的异常
解决方案:发送对应消息传递给用户,提醒规范操作
系统异常(SystemException)
项目运行中可预计且无法避免的异常
解决方案:发送固定信息给用户,安抚用户; 发送特定信息给运维人员,提醒维护; 记录日志
其他异常(Exception)
编程人员未预期到的异常
解决方案:发送固定信息给用户,安抚用户; 发送特定信息给编程人员,提醒维护(纳入预期范围); 记录日志
项目异常处理
定义定义业务异常和系统异常的通知类(其他异常不需要,直接在写他的切面就行)
BusinessException
public class BusinessException extends RuntimeException{
// 定义一个code用来传递异常类型的信息
private Integer code;
public Integer getCode() {
return code;
}
// 定义自己需要的构造方法
public void setCode(Integer code) {
this.code = code;
}
public BusinessException(Integer code,String message) {
super(message);
this.code = code;
}
public BusinessException(Integer code, String message, Throwable cause) {
super(message, cause);
this.code = code;
}
public BusinessException(Integer code,Throwable cause) {
super(cause);
this.code = code;
}
}
SystemException
//定义一个系统性异常,继承RuntimeException这样可以自动把异常往上抛,不需要一直手动throw
public class SystemException extends RuntimeException{
// 定义一个code用来传递异常类型的信息
private Integer code;
public Integer getCode() {
return code;
}
// 定义自己需要的构造方法
public void setCode(Integer code) {
this.code = code;
}
public SystemException(Integer code,String message) {
super(message);
this.code = code;
}
public SystemException(Integer code, String message, Throwable cause) {
super(message, cause);
this.code = code;
}
public SystemException(Integer code,Throwable cause) {
super(cause);
this.code = code;
}
}
在Code类中添加三种异常对应的异常码
Code
//异常码,最后两位是对应异常编号
public static final Integer SYSTEM_EXCEPTION=50001;
public static final Integer EXCEPTION=50002;
public static final Integer BUSINESS_EXCEPTION=50003;
在异常处理器中添加业务类异常,系统性异常和其他异常的异常处理
ExceptionAdvice
//对rest风格的控制器类做功能增强(其实这个就是一个AOP切面),这个注解包含自带@ResponseBody注解和Component注解相当于直接注册了
@RestControllerAdvice
public class ExceptionAdvice {
// 系统性异常
@ExceptionHandler(SystemException.class)
public Result doSystemException(SystemException se){
//记录日志
//发消息给运维
//发送邮件给开发人员,异常对象发送给开发人员
return new Result(se.getCode(),null,se.getMessage());
}
// 业务类异常
@ExceptionHandler(BusinessException.class)
public Result doBusinessException(BusinessException be){
return new Result(be.getCode(),null,be.getMessage());
}
//设置该方法要处理的异常类型
// 其他异常,其他异常是无法预测的那些异常,所以不需要写通知类
@ExceptionHandler(Exception.class)
public Result doException(Exception e){
//记录日志
//发消息给运维
//发送邮件给开发人员,异常对象发送给开发人员
return new Result(Code.EXCEPTION,null,"服务器繁忙,请稍后再试");
}
}
最后在业务类中模拟一下异常
BookServiceImpl
@Override
public Book selectById(Integer id) {
//仅做测试异常功能是否可用作用,实际开发用try-catch包围可能出现异常的地方进行处理
if(id==10086){
throw new SystemException(Code.BUSINESS_EXCEPTION,"服务器超时,请重试");
}else if(id==10087){
throw new BusinessException(Code.BUSINESS_EXCEPTION,"连接超时,请重试");
}
return bookDao.selectById(id);
}
拦截器
概念
拦截器是一种动态拦截方法调用的机制,相当于在contoller方法前后上一道门,实行对应的方法,比如权限校验等
也是AOP
作用
在指定的方法调用前后执行预先设定好的代码
阻止原始方法的执行(比如禁止无权限用户的访问)
拦截器和过滤器的区别
归属不同:Filter属于Servlet技术,Interceptor属于SpringMVC技术
拦截内容不同:Filter对所有的访问进行增强,Interceptor仅针对SpringMVC的访问进行增强(因为Filter是在tomcat时进行的,Interceptor实在springmvc进行的)
拦截器设置
先在Contoller层定义一个通知类ProjectInterceptor,实现接口HandlerInterceptor
在通知类中实现三个通知方法preHandle,postHandle,afterCompletion(接口方法)
preHandle是原始方法执行前执行的,返回值true/false决定原始方法是否执行,postHandle在原始方法执行之后,afterCompletion在postHandle执行之后
用@Component把他注册为Bean以便后面拦截器调用他
ProjectInterceptor
//本质上也是一个通知类
@Component
public class ProjectInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// 原始方法执行前方法,这里只是模拟一下所以直接打印,实际开发写自己的需求,比如权限的校验等
System.out.println("preHandle...");
// return true表示继续执行原始方法,如果return false表示拦截原始方法的执行
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 {
// 在postHandle方法执行之后执行
System.out.println("afterCompletion...");
}
}
比较麻烦的设置方式
在配置包下的web增强类(之前用这个类配置不给springmvc管理的目录)加入拦截器的定义,规定拦截对象
SpringMVCSupport
//定义一个类来对springmvc进行功能增强
@Configuration
//继承webmvc增强类WebMvcConfigurationSupport
public class SpringMVCSupport extends WebMvcConfigurationSupport {
// 自动装配拦截器bean以便下面切面方法使用
@Autowired
private ProjectInterceptor projectInterceptor;
// 排除不想交给springmvc管理的包
@Override
protected void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/pages/**").addResourceLocations("/pages/");
registry.addResourceHandler("/css/**").addResourceLocations("/css/");
registry.addResourceHandler("/js/**").addResourceLocations("/js/");
registry.addResourceHandler("/plugins/**").addResourceLocations("/plugins/");
}
// 切面方法,对/books下的方法设置拦截器,注意**表示/books下所有包和子包,*表示/books下的所有包但不包括子包
@Override
protected void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(projectInterceptor).addPathPatterns("/books/**");
}
}
由于这个SpringMVCSupport类是配置类,需要被SpringMVC扫描到,所以在SpringMVC配置类的扫描注解中加上config包,并实现WebMvcConfigurer接口用于对springmvc进行功能增强,比如注册拦截器、配置视图解析器、处理静态资源等
SpringMVCConfig
//springmvc配置类
//配置类注解
@Configuration
//指定扫描的包的注解
@ComponentScan({"com.morgan.controller","com.morgan.config"})
//管理web相关操作的注解,比如json与字符串数据返回值转换等
@EnableWebMvc
//WebMVCConfigurer接口用于对springmvc进行功能增强,比如注册拦截器、配置视图解析器、处理静态资源
public class SpringMVCConfig implements WebMvcConfigurer {
}
简单的设置方式
直接在SpringMVC配置类实现接口WebMvcConfigurer,然后资源过滤和拦截器都可以直接定义在这里面
由于没有了增强类,也不需要再扫描config包
SpringMVCConfig
//springmvc配置类
//配置类注解
@Configuration
//指定扫描的包的注解
@ComponentScan({"com.morgan.controller"})
//管理web相关操作的注解,比如json与字符串数据返回值转换等
@EnableWebMvc
//WebMVCConfigurer接口用于对springmvc进行功能增强,比如注册拦截器、配置视图解析器、处理静态资源
public class SpringMVCConfig implements WebMvcConfigurer {
// 自动装配拦截器bean以便下面切面方法使用
@Autowired
private ProjectInterceptor projectInterceptor;
// 排除不想交给springmvc管理的包
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/pages/**").addResourceLocations("/pages/");
registry.addResourceHandler("/css/**").addResourceLocations("/css/");
registry.addResourceHandler("/js/**").addResourceLocations("/js/");
registry.addResourceHandler("/plugins/**").addResourceLocations("/plugins/");
}
// 切面方法,对/books下的方法设置拦截器,注意**表示/books下所有包和子包,*表示/books下的所有包但不包括子包
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(projectInterceptor).addPathPatterns("/books/**");
}
}
拦截器参数
前置处理
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// 原始方法执行前方法,这里只是模拟一下所以直接打印,实际开发写自己的需求,比如权限的校验等
System.out.println("preHandle...");
// return true表示继续执行原始方法,如果return false表示拦截原始方法的执行
return true;
}
request:请求对象
response:响应对象
handler:被调用的处理器对象,本质上是一个方法对象,对反射技术中的method对象进行了再包装
返回值为false,被拦截的处理器将不执行
完成后处理
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
// 在原始方法执行之后执行
System.out.println("postHandle...");
}
ex:如果处理器执行过程中出现异常对象可针对异常情况单独处理,但这个通常被自定义异常类替代了,没什么用
拦截器链(了解)
和过滤器一样,拦截器也可以配多个,配的顺序就是执行顺序
SpringMVCConfig
@Autowired
private ProjectInterceptor projectInterceptor1;
@Autowired
private ProjectInterceptor projectInterceptor2;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(projectInterceptor1).addPathPatterns("/books/**");
registry.addInterceptor(projectInterceptor2).addPathPatterns("/books/**");
}
拦截器前置处理是顺序,后置处理和后后置处理是倒序
比如依次三个拦截器interceptor1,interceptor2,interceptor3,执行的顺序是
pre1=>pre2=>pre3=>原始方法=>post3=>post2=>post1=>afterCompletion3=>afterCompletion2=>afterCompletion1
所以其实叫拦截器栈更合适,先进后出嘛
然后如果在某个pre处return false,那后面的pre方法,原始方法和所有的post方法都不会执行,afterCompletion会从return false的后一个继续执行到最后
比如pre3返回false,执行pre1=>pre2=>pre3=>afterCompoletion2=>afterCompletion1
pre2返回false,执行pre1=>pre2=>afterCompletion1
pre1返回false,执行pre1
Maven
多模块开发
新建模块进行开发之后用maven的install将模块下载到本地仓库中,然后在要使用这个模块的模块的依赖文件pom.xml中引入该模块的依赖
依赖冲突
a模块依赖b模块,b模块依赖c模块,那么b和c都属于a的依赖,这叫依赖的传递性
依赖可能出现版本冲突问题,遵循两个原则
依赖冲突的优先级
最短路径优先
按路径最短的依赖版本处理
第一声明优先
路径长度相同先声明的优先
比如一个模块依赖a和b两个模块,a依赖c的1.0版本,b依赖c的2.0版本
<dependencies>
<dependency>
<groupId>com.example</groupId>
<artifactId>libA</artifactId>
<version>1.0</version>
</dependency>
<dependency>
<groupId>com.example</groupId>
<artifactId>libB</artifactId>
<version>1.0</version>
</dependency>
</dependencies>
由于a声明在前,所以按a的来
覆盖原则
在父模块中定义的依赖版本,如果在子模块中没有明确依赖而是传递依赖(比如a依赖b,b依赖c,那a对c就是传递依赖),那按父模块为准
同模块冲突
一个模块直接引入两个不同版本的相同依赖,后写那个为准
手动设置依赖
可选依赖
在依赖项内部加上true标签,表示这个依赖只会在当前模块使用,不会传递到下一个模块,true表示对下一个模块隐藏,false表示不隐藏
<dependencies>
<dependency>
<groupId>com.example</groupId>
<artifactId>libA</artifactId>
<version>1.0</version>
<optional>true</optional>
</dependency>
<dependency>
<groupId>com.example</groupId>
<artifactId>libB</artifactId>
<version>1.0</version>
</dependency>
</dependencies>
排除依赖
有些模块并不是自己开发的而是使用的别人的模块,无法加标签,只能用在引用这个模块的模块中排除这个模块引用的依赖
比如a模块引用b模块,b模块引用c模块,现在要在a中排除c模块,注意不需要写版本,因为一排除就是直接排除这个包
a模块pom.xml
<dependencies>
<dependency>
<groupId>com.example</groupId>
<artifactId>libB</artifactId>
<version>1.0</version>
<exclusions>
<exclusion>
<groupId>com.example</groupId>
<artifactId>libC</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
继承与聚合
聚合
将多个模块组织成一个整体,同时进行项目构建
比如:假设a,b,c模块均依赖d模块,现在修改d模块,校验c合格,但是a和b不一定合格,那就又可能出现a,b在d修改后无法使用的情况,所以需要定义一个聚合工程(通常是一个不具有业务功能的空工程,有且仅有一个pom文件,也可以叫父工程,因为继承也用的这个)
在聚合工程的pom文件中先用pom标签将打包方式定义为pom(一般是jar或war,但这里是聚合工程,只是为了校验),然后用将所有的要测试的模块全部引入,最后构建这个项目,如果所有模块全部构建成功说明没问题
<!-- 声明为聚合工程-->
<packaging>pom</packaging>
<!-- 声明要测试模块-->
<modules>
<module>mvc-example1</module>
<module>nvc-response</module>
<module>springmvc-ssm</module>
</modules>
继承
父工程pom先用pom标签将打包方式定义为pom(声明为父工程),然后用引入子模块
<groupId>com.morgan</groupId>
<artifactId>SpringMVC</artifactId>
<version>1.0-SNAPSHOT</version>
<!-- 声明为父工程-->
<packaging>pom</packaging>
<!-- 声明子模块-->
<modules>
<module>mvc-example1</module>
<module>nvc-response</module>
<module>springmvc-ssm</module>
</modules>
子模块用标签引入父工程
<parent>
<groupId>com.morgan</groupId>
<artifactId>SpringMVC</artifactId>
<version>1.0-SNAPSHOT</version>
<!--快速定位父工程-->
<relativePath>../SpringMVC/pom.xml</relativePath>
</parent>
继承可以简化配置和减少版本冲突
所有子模块都要用的依赖继承
因为有可能有多个子工程存在相同的依赖,多次引入太麻烦,可用直接在父工程引入
在父工程引入的依赖在所有的子工程都可以使用
父工程
<dependencies>
<!-- springmvc-->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>4.0.1</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>jakarta.servlet</groupId>
<artifactId>jakarta.servlet-api</artifactId>
<version>6.0.0</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>6.2.7</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.19.0</version>
</dependency>
<!-- spring-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>6.2.7</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>6.2.7</version>
</dependency>
<!-- mybatis-->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.17</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>3.0.4</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.33</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.2.23</version>
</dependency>
</dependencies>
某些子模块要用的依赖继承
对于某些子模块要用的依赖,用
父工程
<dependencyManagement>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.2</version>
<scope>test</scope>
</dependency>
</dependencies>
</dependencyManagement>
然后在子工程引入依赖,引入的时候不需要写版本,因为用的是父工程的
子工程
<dependencyManagement>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</dependencyManagement>
属性
配置文件中某些依赖由于属于同一个工件等等原因他们的版本必须是一样的,当系统 更新需要修改版本依赖的时候由于依赖过多很可能出现依赖被漏了未修改导致出错,为了防止这种情况,把依赖版本用标签定义成属性,properties内部标签是自己定义的,比如要定义spring相关依赖的版本号就可以写6.2.7,然后在依赖项的版本号标签用${spring-version}来规定
<dependencies>
<!-- springmvc-->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>4.0.1</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>jakarta.servlet</groupId>
<artifactId>jakarta.servlet-api</artifactId>
<version>6.0.0</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>${spring-version}</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.19.0</version>
</dependency>
<!-- spring-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>${spring-version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>${spring-version}</version>
</dependency>
<!-- mybatis-->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.17</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>3.0.4</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.33</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.2.23</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.2</version>
<scope>test</scope>
</dependency>
</dependencies>
<properties>
<spring-version>6.2.7</spring-version>
</properties>
版本管理
工程版本
SNAPSHOT(快照版本)
项目开发过程中临时输出的版本称为快照版本
快照版本会随着开发的进展不断更新
RELEASE(发布版本)
项目开发进入阶段里程碑后向团队外部发布比较稳定的版本,这种版本所对应的构件文件是稳定的,即使进行功能的后续开发也不会改变当前发布版本的内容,称为发布版本
发布版本
alpha版
beta版
纯数字版
跳过测试
实际开发中可能出现想要验证更新的某些模块功能,但另一些模块功能还没更新好,那么已经写好的测试类肯定通不过
或者很确定这个项目没问题想要快速编译运行一遍
这时候就需要跳过测试
注意:跳过测试是跳过所有的测试类也就是test包下的,而不是ide编译不检查
跳过全部测试
在配置文件pom.xml中标签中定义插件
<build>
<finalName>springmvc-ssm</finalName>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>3.5.3</version>
<configuration>
<!-- true表示跳过全部测试,false表示不跳过测试-->
<skipTests>true</skipTests>
</configuration>
</plugin>
</plugins>
</build>
这样就可以跳过全部测试
跳过部分测试
把标签设为false,也就是整体上不跳过测试,然后在下面用标签排除,可以写多个多个排除
<build>
<finalName>springmvc-ssm</finalName>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>3.5.3</version>
<configuration>
<!-- true表示跳过全部测试,false表示不跳过测试-->
<skipTests>false</skipTests>
<excludes>
<!-- 可以写多个-->
<exclude>**/BookServiceTest.java</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
Springboot
!!!注意,springboot启动类必须放在代码的最外层,因为扫描包的时候springboot扫描的是启动类同级包和子包
比如所有的代码都写在目录com.morgan下,那启动类必须直接放在com.morgan下,不能再建一个包放
创建Springboot项目
创建新项目,选择springboot,做web项目的话在添加组件的时候把spring web加进去
springboot的引导类是项目的入口,运行main方法就可以启动项目
@SpringBootApplication
public class SpringbootExample01Application {
public static void main(String[] args) {
SpringApplication.run(SpringbootExample01Application.class, args);
}
}
程序快速启动
springboot程序被打成jar包之后可以给别人让别人直接用
在保存这个jar包的目录下启动cmd,运行指令java -jar jar包名.jar就可以直接启动了
这个能直接运行是依赖于springboot的插件
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
!!!注意:springboot用jar的打包方式,web项目也是jar的打包方式
更换依赖
因为springboot集成了非常多的依赖,所以很多时候要更换依赖只需要排除原依赖后直接继续从spring-boot-starter引入依赖就行,比如原依赖是org.springframework.boot
spring-boot-starter-tomcat,现在想把服务器换成jetty不需要从中央仓库下jetty而是直接用springboot中已经集成的org.springframework.boot
spring-boot-starter-jetty
springboot默认集成使用tomcat服务器
比如现在不想用tomcat服务器,要换成jetty服务器,按照排除依赖把tomcat排除然后引入springboot集成的jetty就行
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jetty</artifactId>
</dependency>
配置文件
比如想更换端口号,在application.properties中用他的server.port更改端口号
注意:因为这种配置文件配置的参数有两种,一种是自己定义的参数,自己拿去用,另一种是springboot自带的参数,比如这个server.port,修改这种参数就相当于是修改配置
如果三种配置文件中加载了同一个配置但值不一样,优先级为properties>yml>yaml
properties配置文件
格式是参数名=参数值
spring.application.name=springboot-example01
server.port=80
yaml配置文件
格式跟yml配置文件完全一样,相当于是yml的另一种格式
yml配置文件(主要使用)
格式是:
一级参数名:
二级参数名: 参数值
!!注意:参数值前必须有空格
比如参数server.port=80写在yml文件的格式是
server:
port: 80
语法规则
1.大小写敏感
2.属性层级关系使用多行描述,每行结尾使用冒号结束
3.使用缩进表示层级关系,同层级左侧对齐,只允许使用空格(不允许使用Tab键)
4.属性值前面添加空格(也就是属性名和属性值之间使用冒号+空格作为分隔)
5.#表示注释
6.数组数据:在数据书写位置的下方使用减号作为数据开始符号,每行书写一个数据,减号与数据间空格分隔
lesson: springboot
enterprise:
name: morgan
age: 16
subject:
- Java
- 前端
- 大数据
数据读取
使用@Value注解读取
跟Spring一样直接用@Value(“属性名”)就可以读取
application.yml
#配置属性
server:
port: 80
#自定义属性
lesson: springboot
enterprise:
name: morgan
age: 16
subject:
- Java
- 前端
- 大数据
BookController
@RestController
@RequestMapping("/books")
public class BookController {
@Value("${lesson}")
private String lesson;
// 二级参数
@Value("${enterprise.age}")
private Integer age;
// 数组参数
@Value("${enterprise.subject[0]}")
private String subject0;
@GetMapping("/{id}")
public String printHello(@PathVariable("id") Integer id){
System.out.println(lesson);
System.out.println(age);
System.out.println(subject0);
return "hello";
}
}
使用Environment对象读取
定义一个Environment对象env,给他加上@Autowired自动装配注解,他会自动把yml中的所有参数装配到env中
然后用env.getProperty(“参数名”)方法获取到对应参数
@RestController
@RequestMapping("/books")
public class BookController {
@Value("${lesson}")
private String lesson;
// 二级参数
@Value("${enterprise.age}")
private Integer age;
// 数组参数
@Value("${enterprise.subject[0]}")
private String subject0;
// 定义一个Environment对象用@Autowired注解自动装配yml配置文件的所有参数
@Autowired
private Environment env;
@GetMapping("/{id}")
public String printHello(@PathVariable("id") Integer id){
System.out.println(lesson);
System.out.println(age);
System.out.println(subject0);
System.out.println(env.getProperty("enterprise.name"));
System.out.println(env.getProperty("enterprise.subject[1]"));
return "hello";
}
}
使用实体类对象加载(常用)
定义一个实体类,一个实体类对应一个参数,属性就是二级参数
然后@Component把他加载为bean,用@ConfigurationProperties(prefix = “enterprise”)把配置里的enterprise参数加载进去
使用的时候只需要自动装配一个Enterprise实体类bean然后直接用这个bean的属性就行
application.yml
#配置属性
server:
port: 80
#自定义属性
lesson: springboot
enterprise:
name: morgan
age: 16
subject:
- Java
- 前端
- 大数据
Enterprise
@Component
@ConfigurationProperties(prefix = "enterprise")
public class Enterprise {
private String name;
private Integer age;
private String[] subject;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public String[] getSubject() {
return subject;
}
public void setSubject(String[] subject) {
this.subject = subject;
}
@Override
public String toString() {
return "Enterprise{" +
"name='" + name + '\'' +
", age=" + age +
", subject=" + Arrays.toString(subject) +
'}';
}
}
BookController
@RestController
@RequestMapping("/books")
public class BookController {
@Value("${lesson}")
private String lesson;
// 二级参数
@Value("${enterprise.age}")
private Integer age;
// 数组参数
@Value("${enterprise.subject[0]}")
private String subject0;
// 定义一个Environment对象用@Autowired注解自动装配yml配置文件的所有参数
@Autowired
private Environment env;
// 自动装配一个Bean
@Autowired
private Enterprise enterprise;
@GetMapping("/{id}")
public String printHello(@PathVariable("id") Integer id){
System.out.println(lesson);
System.out.println(age);
System.out.println(subject0);
System.out.println(env.getProperty("enterprise.name"));
System.out.println(env.getProperty("enterprise.subject[1]"));
System.out.println(enterprise);
return "hello";
}
}
多环境开发配置
开发,测试,发布使用的环境可能都不一样,比如开发的时候用的是本地数据库,发布的时候用中央数据库,所以需要分别配置,按使用环境切换
不同的配置之间用—隔开,公共环境写在最上面,用于设置公共的属性和指定当前要激活的环境,下面写不同的环境,包括环境的名和环境的特有配置
#公共环境
#设置要激活的配置
spring:
profiles:
active: dev
#共有属性
lesson: springboot
enterprise:
name: morgan
age: 16
subject:
- Java
- 前端
- 大数据
#用---分隔公共环境,测试,开发,生产环境
---
#标记为开发环境
spring:
config:
activate:
on-profile: dev
#开发环境配置
server:
port: 80
---
#标记为测试环境
spring:
config:
activate:
on-profile: test
#测试环境配置
server:
port: 81
---
#标记为生产环境
spring:
config:
activate:
on-profile: prod
#生产环境配置
server:
port: 82
多环境命令行启动参数设置
使用jar包启动springboot项目可以在命令行中修改参数,比如jar包给测试类测试,但忘记改激活环境的属性了,激活环境的属性的默认值仍然是开发环境,这时候可以在springboot启动的时候带参数启动,比如要把环境改成test,用java -jar 包名.jar --spring.profiles.active=test
再比如项目配置的端口号为80,但在电脑上80端口已经被占用,想改成81,可以用java -jar 包名.jar --server.port=81
也就是通过命令行对配置在jar包外进行修改
注意:jar包外对配置进行修改并不会修改jar包,只是改了配置,相当于用命令行参数覆盖掉配置参数,但配置参数并不会变,jar包是只读的
配置文件分类
一级配置文件:直接写在resources目录下的配置文件,优先级很低
二级配置文件:如果resources下有config目录,相较于一级配置文件,config目录中的配置文件优先级高
三级配置文件:在jar包同级创建一个application.yml文件,在里面写要修改的配置,这个文件里的配置优先级高于二级配置文件
四级配置文件:在jar包同级创建config包,在里面写的配置文件优先级高于三级配置文件
springboot整合Junit(测试)
原来测试类的两个注解被整合成立一个@SpringBootTest注解(原先spring需要定义spring配置类,springboot中起到配置类作用的就是启动类,启动类会扫描他所在包和所有子包)
BookServiceImpl
@Service
public class BookServiceImpl implements BookService {
@Override
public void save() {
System.out.println("yes");
}
}
SpringbootExample01ApplicationTests
@SpringBootTest
class SpringbootExample01ApplicationTests {
@Autowired
private BookService bookService;
@Test
void contextLoads() {
bookService.save();
}
}
springboot整合mybatis
创建项目的时候直接整合:勾选mybatis-framework和MySQL的依赖项(MySQL是我自己用的)
创建完项目再整合:自己导入依赖
<!-- mybatis依赖,根据springboot版本决定-->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>3.0.4</version>
</dependency>
<!-- mysql依赖,8.0之后版本改成j了不要用java,不用写版本号因为要用springboot中集成的,runtime一定要添加-->
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
配置
在yml配置文件中加上datasource的配置
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/example
username: root
password: admin
注意dao层要添加一个注解@Mapper,service和controller无改变
@Mapper
public interface BookDao {
@Select("select * from tbl_book where id=#{id}")
Book insert(Integer id);
}
springboot整合ssm
没什么大改动,注意dao层添加一个@Mapper注解,前端的静态资源css等放在resources包下的static包里,会自动放行不需要设置过滤器
如果想添加一个主界面(也就是springboot启动直接打开的界面),在static包下直接创建一个index.html,在里面引入主界面
index.html
<script>
document.location.href="pages/books.html";
</script>
MyBatisPlus(简称MP)
MyBatisPlus是基于MyBatis框架基础开发的增强型工具,只在简化开发,提高效率
控制台日志的隐藏
程序每次运行都会在控制台打印很多日志,把这些日志隐藏可以让程序更清晰
在resources目录下创建一个xml文件,打入一组空的configuration标签,这样就能隐藏控制台的日志了
logback.xml
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
</configuration>
两个大图标也可以关闭,在yml配置文件中给spring的banner设置为off,给mybatis的banner设置为false就关闭了
创建MyBatisPlus项目
创建springboot项目,选中mysql框架,创建完之后导入MyBatisPlus的依赖
pom.xml
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-spring-boot3-starter</artifactId>
<version>3.5.5</version>
</dependency>
在yml文件中配置数据库
application.yml
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/example
# 注意用druid需要导druid坐标
type: com.alibaba.druid.pool.DruidDataSource
username: root
password: admin
让dao层的接口继承接口BaseMapper,实现实体类泛型,继承之后那些基础的sql像查询某个id的数据这种的都不需要写了,自动都有可以直接用(当然你要在里面再写sql也没问题)
UserDao
@Mapper
public interface UserDao extends BaseMapper<User> {
}
UserDaoTest
@SpringBootTest
class UserDaoTest {
@Autowired
private UserDao userDao;
@Test
void contextLoads() {
User user = new User();
user.setName("morgan");
user.setAge(15);
user.setTel("12345678910");
user.setPassword("admin");
userDao.insert(user);
List<User> users = userDao.selectList(null);
System.out.println(users);
}
}
自增
注意:MyBatisPlus里实体类主键字段如果没有手动赋值,会使用雪花算法生成一个id,并且优先级高于数据库的自增策略,也就是如果数据库有一个id字段设置主键自增,然后添加数据的时候不给实体类的id赋值,那id会被赋值一个系统生成的随机id加入数据库中,这样就破坏了数据库的自增了
解决方法:在实体类主键属性上添加注解@TableId(type = IdType.AUTO)
User
public class User {
@TableId(type = IdType.AUTO)
private Long id;
private String name;
private String password;
private Integer age;
private String tel;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public String getTel() {
return tel;
}
public void setTel(String tel) {
this.tel = tel;
}
@Override
public String toString() {
return "User{" +
"id=" + id +
", name='" + name + '\'' +
", password='" + password + '\'' +
", age=" + age +
", tel='" + tel + '\'' +
'}';
}
}
表名列名识别
由于MyBatisPlus的基础sql并没有要求开发人员显式配置表名和列名,所以他直接通过实体类名映射表名,实体类属性名映射列名,所以开发的时候实体类和属性的名字一定要和数据库一样,如果不一样需要加注解@TableName来设置数据库表名
简介
MyBatisPlus特性
无侵入
只做增强不做改变,不会对现有工程产生影响
强大的CRUD操作
内置通用Mapper,少量配置即可实现单表CRUD
支持Lambda
编写查询条件无需担心字段写错
支持主键自动生成
映射匹配兼容性
表名字段名和实体类的类名属性名一样的时候会自动识别,但如果不一样就需要手动映射了
同一个注解的不同属性直接写在()里用逗号隔开,比如@TableField(value=“字段名”,select=false)
实体类某个属性是数据库表没有的
比如是否在线,这个不由数据库管,但可能会被设置成一个属性
在属性上加上@TableField(exist=false)注解,表示这个属性在数据库中不存在
属性名与字段名不一样
在属性上加上@TableField(value=“字段名”)注解
不想参与查询的字段
比如密码,获取权限的时候获取表里所有数据的话,密码就会包含在里面,有暴露风险
在属性上添加@TableField(select=false)注解,相当于告诉程序密码这个属性不参与查询
表名与实体类名称不匹配
在类上添加@TableName(“表名”)注解
MyBatisPlus操作
查询
分页查询
MyBatisPlus自带的有分页查询的插件,但是需要写拦截器,写配置文件,不好用
可以直接在dao类里写分页查询的方法,注意:limit的第一个参数是要查询的数据起始索引,这个索引是从0开始的,比如要查询的起始数据是第八条数据,那第一个参数就是7,第二个参数是每一页的数据条数
@Mapper
public interface UserDao extends BaseMapper<User> {
@Select("select * from User limit 1,1")
List<User> selectPage();
}
条件查询
条件查询用Wrapper对象封装查询条件,但Wrapper是一个抽象类,需要用他的子类,一共有三种按条件查询的方式
lt(也就是less than)方法表示小于,gt是大于,ge是大于等于,le是小于等于,具体的方法可以到[mybatis官网](SQL注入器 | MyBatis-Plus)上查询
查询方式
普通按条件查询
用QueryWrapper类对象封装查询条件,用这个的类中的方法来设置查询条件
@Test
void WrapperTest(){
// 定义QueryWrapper对象qw
QueryWrapper qw=new QueryWrapper();
// lt(就是less than)表示小于,第一个参数是列名,第二个参数是值,相当于age<18
qw.lt("age",18);
List<User> userList=userDao.selectList(qw);
System.out.println(userList);
}
Lambda按条件查询(用QueryWrapper对象)
因为普通按条件查询用字符串设置列名,又可能写错,所以可以用lambda格式写不会出错
@Test
void WrapperTest() {
// 定义QueryWrapper对象qw
QueryWrapper<User> qw = new QueryWrapper<User>();
// lt(就是less than)表示小于,第一个参数是列名,第二个参数是值,相当于age<18,直接连接表示和
// 也可以lqw.lt和lqw.gt分开写,一样的
qw.lambda().lt(User::getAge,30).gt(User::getAge,18);
List<User> userList = userDao.selectList(qw);
System.out.println(userList);
}
Lambda类按条件查询(常用)(用LambdaQueryWrapper对象)
直接用LambdaQueryWrapper对象来进行lambda查询
@Test
void WrapperTest() {
// 定义QueryWrapper对象qw
LambdaQueryWrapper<User> lqw = new LambdaQueryWrapper<User>();
// lt(就是less than)表示小于,第一个参数是列名,第二个参数是值,相当于age<18
lqw.lt(User::getAge, 30).or().gt(User::getAge, 18);
List<User> userList = userDao.selectList(lqw);
System.out.println(userList);
}
null值处理
给lqw方法现在的参数列表前面加一个判断参数比如null!=user.getAge()
lqw.lt(null!=user.getAge(),User::getAge,user.getAge);
表示不为空的时候才填进去
查询投影
查询某几个属性
如果只想查某几个属性,而不是全部属性,可以用LambdaQueryWrapper对象的select方法
LambdaQueryWrapper<User> lqw = new LambdaQueryWrapper<User>();
lqw.select(User::getAge,User::getId);
增添
id生成策略
数据表存id要用bigint不要用int,属性id用Long(包装类的long)
简介
不同的表应用不同的id生成策略
日志 :自增(1,2,3…)
购物订单:特殊规则(FQ23948AK3843)
外卖单:关联日期地区等信息
关系表:可省略id
操作
在id对应属性上添加@TableId(type = IdType.生成策略)注解,除了自增策略以外,其他的生成策略的前提都是数据库id字段不自增
@TableId(type = IdType.AUTO):自增策略,要保证数据库的id也开启了自增,两个有一个没开启都不行
@TableId(type = IdType.INPUT):手动指定id
@TableId(type = IdType.ASSIGN_ID):注意不要拼成ASSING了,如果id手动给了值就用手动给的,如果没给就用雪花算法生成一个
全局配置
如果所有的表对id的要求一样,可以在配置文件中直接设全局的策略,把mybatis-plus的id-type设置成要用的策略
还有假如所有的表名都有相同前缀tbl_,而实体类没有,比如表叫tbl_book,tbl_user等,实体类叫Book,User,那可以直接把配置文件mybatis-plus的table-prefix设置为这个前缀这样就不需要给每个类配表名了,程序会自动用前缀加类名当作表名
mybatis-plus:
global-config:
db-config:
# 配置全局的id策略
id-type: assign_id
# 配置全局的实体类前缀
table-prefix: tbl_
删除
多条删除
用lqw的deleteBatchIds(id列表)进行多条删除
逻辑删除
比如小王离职了,那他的销售记录按理说应该删除或称为垃圾数据,但年底做报表汇总收入的时候就可能导致没有汇总小王当时的而出现数据异常,所以就设置一个字段来进行逻辑删除,比如1代表删除,0代表没删,实际上数据库中数据仍然存在,但是我们可以识别这条数据是逻辑删除的
流程如下:
在数据表中添加一个字段deleted作为逻辑删除的表示,假设默认没删的话是0,删了的话是1
在实体类中添加相应属性,在属性上加上注释@TableLogic(value=“0”,delval=“1”)表示默认没删是0,删了是1
如果实体类设置了逻辑删除,那么通过MyBatisPlus的方法对这个表进行删除操作的时候就不会删除数据而是逻辑删除,因为检测到这个注解后delete方法底层会更换成update的sql
并且这样以来凡是用MyBatisPlus的方法执行的查询操作都会默认这条数据不存在,底层是sql加了个where deleted!=1的限定,如果想查全部数据就需要自己去写sql查
逻辑删除也可以用配置文件进行全局配置
mybatis-plus:
global-config:
db-config:
# 给每个实体类的deleted属性设置为逻辑删除的属性
logic-delete-field: deleted
# 不删除是1
logic-delete-value: 1
# 删除是0
logic-not-delete-value: 0
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public String getTel() {
return tel;
}
public void setTel(String tel) {
this.tel = tel;
}
@Override
public String toString() {
return "User{" +
"id=" + id +
", name='" + name + '\'' +
", password='" + password + '\'' +
", age=" + age +
", tel='" + tel + '\'' +
'}';
}
}
### 表名列名识别
由于MyBatisPlus的基础sql并没有要求开发人员显式配置表名和列名,所以他直接通过实体类名映射表名,实体类属性名映射列名,所以开发的时候实体类和属性的名字一定要和数据库一样,如果不一样需要加注解@TableName来设置数据库表名
## 简介
### MyBatisPlus特性
#### 无侵入
只做增强不做改变,不会对现有工程产生影响
#### 强大的CRUD操作
内置通用Mapper,少量配置即可实现单表CRUD
#### 支持Lambda
编写查询条件无需担心字段写错
#### 支持主键自动生成
## 映射匹配兼容性
表名字段名和实体类的类名属性名一样的时候会自动识别,但如果不一样就需要手动映射了
同一个注解的不同属性直接写在()里用逗号隔开,比如@TableField(value="字段名",select=false)
### 实体类某个属性是数据库表没有的
比如是否在线,这个不由数据库管,但可能会被设置成一个属性
在属性上加上@TableField(exist=false)注解,表示这个属性在数据库中不存在
### 属性名与字段名不一样
在属性上加上@TableField(value="字段名")注解
### 不想参与查询的字段
比如密码,获取权限的时候获取表里所有数据的话,密码就会包含在里面,有暴露风险
在属性上添加@TableField(select=false)注解,相当于告诉程序密码这个属性不参与查询
### 表名与实体类名称不匹配
在类上添加@TableName("表名")注解
## MyBatisPlus操作
### 查询
#### 分页查询
MyBatisPlus自带的有分页查询的插件,但是需要写拦截器,写配置文件,不好用
可以直接在dao类里写分页查询的方法,注意:limit的第一个参数是要查询的数据起始索引,这个索引是从0开始的,比如要查询的起始数据是第八条数据,那第一个参数就是7,第二个参数是每一页的数据条数
```java
@Mapper
public interface UserDao extends BaseMapper<User> {
@Select("select * from User limit 1,1")
List<User> selectPage();
}
条件查询
条件查询用Wrapper对象封装查询条件,但Wrapper是一个抽象类,需要用他的子类,一共有三种按条件查询的方式
lt(也就是less than)方法表示小于,gt是大于,ge是大于等于,le是小于等于,具体的方法可以到[mybatis官网](SQL注入器 | MyBatis-Plus)上查询
查询方式
普通按条件查询
用QueryWrapper类对象封装查询条件,用这个的类中的方法来设置查询条件
@Test
void WrapperTest(){
// 定义QueryWrapper对象qw
QueryWrapper qw=new QueryWrapper();
// lt(就是less than)表示小于,第一个参数是列名,第二个参数是值,相当于age<18
qw.lt("age",18);
List<User> userList=userDao.selectList(qw);
System.out.println(userList);
}
Lambda按条件查询(用QueryWrapper对象)
因为普通按条件查询用字符串设置列名,又可能写错,所以可以用lambda格式写不会出错
@Test
void WrapperTest() {
// 定义QueryWrapper对象qw
QueryWrapper<User> qw = new QueryWrapper<User>();
// lt(就是less than)表示小于,第一个参数是列名,第二个参数是值,相当于age<18,直接连接表示和
// 也可以lqw.lt和lqw.gt分开写,一样的
qw.lambda().lt(User::getAge,30).gt(User::getAge,18);
List<User> userList = userDao.selectList(qw);
System.out.println(userList);
}
Lambda类按条件查询(常用)(用LambdaQueryWrapper对象)
直接用LambdaQueryWrapper对象来进行lambda查询
@Test
void WrapperTest() {
// 定义QueryWrapper对象qw
LambdaQueryWrapper<User> lqw = new LambdaQueryWrapper<User>();
// lt(就是less than)表示小于,第一个参数是列名,第二个参数是值,相当于age<18
lqw.lt(User::getAge, 30).or().gt(User::getAge, 18);
List<User> userList = userDao.selectList(lqw);
System.out.println(userList);
}
null值处理
给lqw方法现在的参数列表前面加一个判断参数比如null!=user.getAge()
lqw.lt(null!=user.getAge(),User::getAge,user.getAge);
表示不为空的时候才填进去
查询投影
查询某几个属性
如果只想查某几个属性,而不是全部属性,可以用LambdaQueryWrapper对象的select方法
LambdaQueryWrapper<User> lqw = new LambdaQueryWrapper<User>();
lqw.select(User::getAge,User::getId);
增添
id生成策略
数据表存id要用bigint不要用int,属性id用Long(包装类的long)
简介
不同的表应用不同的id生成策略
日志 :自增(1,2,3…)
购物订单:特殊规则(FQ23948AK3843)
外卖单:关联日期地区等信息
关系表:可省略id
操作
在id对应属性上添加@TableId(type = IdType.生成策略)注解,除了自增策略以外,其他的生成策略的前提都是数据库id字段不自增
@TableId(type = IdType.AUTO):自增策略,要保证数据库的id也开启了自增,两个有一个没开启都不行
@TableId(type = IdType.INPUT):手动指定id
@TableId(type = IdType.ASSIGN_ID):注意不要拼成ASSING了,如果id手动给了值就用手动给的,如果没给就用雪花算法生成一个
全局配置
如果所有的表对id的要求一样,可以在配置文件中直接设全局的策略,把mybatis-plus的id-type设置成要用的策略
还有假如所有的表名都有相同前缀tbl_,而实体类没有,比如表叫tbl_book,tbl_user等,实体类叫Book,User,那可以直接把配置文件mybatis-plus的table-prefix设置为这个前缀这样就不需要给每个类配表名了,程序会自动用前缀加类名当作表名
mybatis-plus:
global-config:
db-config:
# 配置全局的id策略
id-type: assign_id
# 配置全局的实体类前缀
table-prefix: tbl_
删除
多条删除
用lqw的deleteBatchIds(id列表)进行多条删除
逻辑删除
比如小王离职了,那他的销售记录按理说应该删除或称为垃圾数据,但年底做报表汇总收入的时候就可能导致没有汇总小王当时的而出现数据异常,所以就设置一个字段来进行逻辑删除,比如1代表删除,0代表没删,实际上数据库中数据仍然存在,但是我们可以识别这条数据是逻辑删除的
流程如下:
在数据表中添加一个字段deleted作为逻辑删除的表示,假设默认没删的话是0,删了的话是1
在实体类中添加相应属性,在属性上加上注释@TableLogic(value=“0”,delval=“1”)表示默认没删是0,删了是1
如果实体类设置了逻辑删除,那么通过MyBatisPlus的方法对这个表进行删除操作的时候就不会删除数据而是逻辑删除,因为检测到这个注解后delete方法底层会更换成update的sql
并且这样以来凡是用MyBatisPlus的方法执行的查询操作都会默认这条数据不存在,底层是sql加了个where deleted!=1的限定,如果想查全部数据就需要自己去写sql查
逻辑删除也可以用配置文件进行全局配置
mybatis-plus:
global-config:
db-config:
# 给每个实体类的deleted属性设置为逻辑删除的属性
logic-delete-field: deleted
# 不删除是1
logic-delete-value: 1
# 删除是0
logic-not-delete-value: 0