spring
Spring
初始Spring
官网:spring.io
spring发展今天形成了一种生态圈,spring提供了若干个项目,每个项目用于完成特定的功能
- Spring Framework
- Spring Boot
- Spring Cloud
Spring Framework系统架构
Spring Framework是Spring生态圈中最基础的项目,是其他项目的根基
Spring模块:
- Core Containere:核心容器
- AOP Aspects:面向切面编程,AOP思想实现
- Data Access/Integration:数据访问,数据集成
- web:web开发
- Test:单元测试和集成测试
1. 核心概念
- IoC(Inversion of Control)控制反转
- 使用对象时,由主动new产生对象转换为由外部提供对象,此过程中对象创建权由程序转移到外部,此思想为控制反转
- Spring技术对IoC思想进行了实现
- Spring提供了一个容器,成为Ioc容器,用来充当IoC思想中的外部
- IoC容器负责对象的创建,初始化等一系列工作,被创建或被管理的对象在IoC容器中统称为Bean
- DI(Dependency Injection)依赖注入
- 在容器中建立Bean与Bean之间的依赖关系的整个过程,称为依赖注入
这些概念都是为了实现充分解耦
- 使用IoC容器管理bean(IoC)
- 在IoC容器内将有依赖关系的bean进行绑定(DI)
最终:使用对象时不仅可以直接从IoC容器中获取,并且获取到的bean已经绑定了所有的依赖关系
2.使用IOC案例(XML版)
- 创建一个Maven项目,并创建对应的dao接口和实现列一级service层的接口和实现类
- 导入spring坐标
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.3.23</version>
</dependency>
- 创建一个IOC容器的配置文件,里面用来存放bean
- 在配置文件中编写相应的bean配置
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- 1.导入spring的坐标spring-conteext-->
<!-- 2.配置bean-->
<!-- bean标签表示配置bean-->
<!-- id属性表示bean起名字-->
<!-- class表示bean的位置-->
<bean id="bookDao" class="com.peng.dao.impl.BookDaoImpl"> </bean>
<bean id="bookService" class="com.peng.service.Impl.BookServiceImpl"></bean>
</beans>
- 测试,先获取到IOC容器的对象,在从该对象中取出相应存储的对象。
//3.获取IoC容器
ApplicationContext ctx=new ClassPathXmlApplicationContext("applicationContext.xml");
//4.获取bean
BookDao bookDao = (BookDao)ctx.getBean("bookDao");
bookDao.save();
BookService bookService= (BookService)ctx.getBean("bookService");
bookService.save();
DI入门案例(XML)
为了符合最开始所说的控制反转,将对象创建由外部控制,所以在IOC(xml)配置文件配置后,其在serviceImpl中还有:
public class BookServiceImpl implements BookService {
private BookDao bookdao=new BookDaoImpl();
public void save(){
System.out.println("book service save...");
bookdao.save();
}
}
我们需要把其中的对象创建用相同的办法替换掉
- 去掉new来创建对象,改为只声明。在创建该属性的set方法
public class BookServiceImpl implements BookService {
//5.删除业务层中使用new的方式创建的dao对象
// private BookDao bookdao=new BookDaoImpl();
private BookDao bookdao;
public void save(){
System.out.println("book service save...");
bookdao.save();
}
public void setBookdao(BookDao bookdao) {
this.bookdao = bookdao;
}
}
- 在配置文件中配置
<bean id="bookService" class="com.peng.service.Impl.BookServiceImpl">
<!-- 7.配置service和dao的关系-->
<property name="bookdao" ref="bookDao"></property>
<!-- property标签表示配置当前bean的属性-->
<!-- name属性表示配置哪一个具体的属性-->
<!-- ref属性表示参照哪一个bean-->
</bean>
因为是在service中创建的dao对象,所有在配置时,就是在对应service的bean标签下创建使用property,属性标签来配置。
注意点:
bean的别名
如果定义名称不存在的话就会抛出:NoSuchBeanDefinitionException
bean作用范围
public static void main(String[] args) {
ClassPathXmlApplicationContext classPathXmlApplicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
BookDao bookDao1 = (BookDao)classPathXmlApplicationContext.getBean("bookDao");
BookDao bookDao2 = (BookDao)classPathXmlApplicationContext.getBean("bookDao");
System.out.println(bookDao1==bookDao2);
}
测试得到:
new两个对象的地址值是相同的,则是通过单例模式创建的。
单例模型复习
懒汉式:
public class Singleton {
private static Singleton instance;
private Singleton (){}
public static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
//线程不安全
饿汉式
public class Singleton {
private static Singleton instance = new Singleton();
private Singleton (){}
public static Singleton getInstance() {
return instance;
}
}
bean的作用范围选择:
<bean id="bookDao" name="dao book" class="com.peng.dao.impl.BookDaoImpl" scope="prototype"> </bean>
scope:
- singleton:单例(默认)
- prototype:非单例
bean的作用范围实际上是为了控制我们创建对象的数量。如果不用单例模式,则在调用一次bean就会创建一个对象,会大量重复创建照成资源浪费。
一般而言,对于表现层对象,业务层对象,数据层对象,工具层对象。时候交给容器来惊醒管理。
对于封装实体的域对象则不适合容器管理bean
bean的实例化
- bean本质上就是对象,创建bean使用构造方法完成。
实例化bean的三种方式--构造方法(常用)
构造方法实例化bean
- 提供可访问的构造方法
public class BookDaoImpl implements BookDao {
//无参构造省略
/*
或者加上,这里注意即使是私有的无参构造依然是可行的,原因就是,spring框架的底层本质上是使用的反射。
*/
private BookDaoImpl(){}
public void save(){
System.out.println("book dao save....");
}
}
- 配置
<bean id="bookDao" name="dao book" class="com.peng.dao.impl.BookDaoImpl" scope="singleton"> </bean>
注意:如果无参的构造方法不存在(没有显示定义无参且有有参构造方法时)将抛出异常BeanCreationException
静态化工厂实例化bean
在早些年是通过静态化工厂来创建对象来解耦合避免new对象的。
public class OrderDaoFactory {
public static OrderDao getOrderDao(){
return new OrderDaoImpl();
}
}
/*
然后通过这个类的静态方法来调用对象实现解耦。
*/
public class AppForInstanceOrder {
public static void main(String[] args) {
//通过静态工厂创建对象
OrderDao orderDao= OrderDaoFactory.getOrderDao();
orderDao.save();
}
}
当然我们也可以用容器来调用工厂里面的方法来掉对象:
<!-- 方式二:使用静态工厂实例化bean-->
<bean id="orderDao" class="com.peng.factory.OrderDaoFactory" factory-method="getOrderDao"></bean>
这里配置的class是工厂的类名,需要在配置上factory-method,得到工厂产生的对象。来得到对应的bean对象。
非静态工厂实例化bean
public class UserDaoFactory {
public UserDao getUserDao(){
return new UserDaoImpl();
}
}
原本的创建对象
public class AppForInstanceUser {
public static void main(String[] args) {
//创建实例工厂对象
UserDaoFactory userDaoFactory=new UserDaoFactory();
//通过实例工厂对象创建对象
UserDao userDao=userDaoFactory.getUserDao();
userDao.save();
}
}
使用Bean标签创建对象,需要配置两个bean标签
<!-- 方式三,使用实例工厂实例化Bean-->
<bean id="userFactory" class="com.peng.factory.UserDaoFactory"></bean>
<bean id="userDao" factory-method="getUserDao" factory-bean="userFactory"></bean>
非静态工厂与静态不同的是,需要先创建一个工厂的对象再用该对象调用里面的方法回去对象。
所以这里与上面的Bean标签不同。
非静态工厂实例化bean改良
- 创建一个FactroyBean接口的实现类,泛型设置对应返回值类型。再重写getObject和geObjectType方法,分别放回需要实例化的对象和对应类型的类的反射。
public class UserDaoFactoryBean implements FactoryBean<UserDao>{
//代替原始实例工厂中创建对象的方法
@Override
public Object getObject() throws Exception {
return new UserDaoImpl();
}
@Override
public Class<?> getObjectType() {
return UserDao.class;
}
}
<!-- 方式四,使用FactoryBean实例化-->
<bean id="userDao" class="com.peng.factory.UserDaoFactoryBean"></bean>
- FactoryBean的实现类还可以重写 isSingleton方法来设定设否为单例模式创建。
bean的生命周期
1.标签属性设置声明周期
- 建立一个Dao实现类,写一个初始化和销毁的方法。
public class BookDaoImpl implements BookDao {
@Override
public void save() {
System.out.println("bookDao save...");
}
@Override
public void init() {
System.out.println("init...");
}
@Override
public void destory() {
System.out.println("destory...");
}
}
写一个bean标签,init-method声明初始化方法,destroy-method声明销毁方法。
这两个属性就是配置bean的生命周期
<bean id="bookDao" class="com.peng.dao.impl.BookDaoImpl" init-method="init" destroy-method="destory"></bean>
public class app1 {
public static void main(String[] args){
ClassPathXmlApplicationContext classPathXmlApplicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
BookDao bookDao = (BookDao)classPathXmlApplicationContext.getBean("bookDao");
bookDao.save();
//手动关闭容器,暴力关闭,关闭后就不能在对容器进行操作
// classPathXmlApplicationContext.close();
//设置关闭容器的钩子
classPathXmlApplicationContext.registerShutdownHook();
}
}
2。接口方式实现生命周期设置
public class BookServiceImpl implements BookService, InitializingBean, DisposableBean {
private BookDao bookDao;
public void setBookDao(BookDao bookDao){
this.bookDao=bookDao;
}
public void save(){
System.out.println("book service save...");
bookDao.save();
}
//接口方式设置生命周期
@Override
public void destroy() throws Exception {
System.out.println("service destroy");
}
@Override
public void afterPropertiesSet() throws Exception {
System.out.println("service init");
}
}
可以看到输出结果,该类的bean创建是在属性创建过后才初始化bean的。
总结(bean生命周期):
- 初始化容器
- 创建对象(内存分配)
- 执行构造方法
- 执行属性注入(set操作)
- 执行bean初始化方法
- 使用bean
- 执行业务代码
- 关闭/销毁容器
- 执行bean销毁方法
3. 依赖注入方式
向一个类中传递数据的方式有几种
- 普通方法(set方法)
- 构造方法
依赖注入描述了容器中建立bean与bean之间依赖关系的过程,但是bean运行也可以传递数字或者字符串
- 引用类型
- 简单类型(基本数据类型与String)
依赖注入方式:
- setter注入
- 简单类型
- 引用类型
- 构造器注入
- 简单类型
- 引用类型
-
setter注入
-
引用类型
java类属性的生命和设置set方法
public class BookDaoImpl implements BookService {
private BookDao bookDao;
private UserDao userDao;
public void setBookDao(BookDao bookDao) {
this.bookDao = bookDao;
}
public void setUserDao(UserDao userDao) {
this.userDao = userDao;
}
@Override
public void save(){
System.out.println("service set...");
bookDao.save();
userDao.save();
}
}
<bean id="bookService" class="com.peng.service.Impl.BookDaoImpl">
<property name="bookDao" ref="BookDao"></property>
<property name="UserDao" ref="UserDao"></property>
</bean>
- 基本数据类型
也需要设置相应的set方法
public class BookDaoImpl implements BookDao {
private int id;
private String name;
public void setId(int id) {
this.id = id;
}
public void setName(String name) {
this.name = name;
}
@Override
public void save() {
System.out.println("save...."+id+","+name);
}
}
<bean id="BookDao" class="com.peng.dao.Impl.BookDaoImpl">
<property name="id" value="22"></property>
<property name="name" value="卢鹏"></property>
</bean>
这是setter注入中对于引用型数据与基本数据类型之间的唯一差别
- 构造器注入
也需要对应的构造方法
private BookDao bookDao;
private UserDao userDao;
private int id;
private String name;
public BookServiceImpl(BookDao bookDao, UserDao userDao) {
this.bookDao = bookDao;
this.userDao = userDao;
}
public BookServiceImpl(BookDao bookDao, UserDao userDao, int id, String name) {
this.bookDao = bookDao;
this.userDao = userDao;
this.id = id;
this.name = name;
}
配置和setter类似,只不过用的是constructor-arg标签。
也可以用index属性来指定用第几个属性赋值
<bean id="bookService" class="com.peng.service.Impl.BookServiceImpl">
<constructor-arg name="bookDao" ref="BookDao"/>
<constructor-arg name="userDao" ref="UserDao"/>
<constructor-arg index="2" value="20"/>
<constructor-arg index="3" value="卢鹏"/>
</bean>
依赖注入总结:
- 强制依赖使用构造器进行,使用setter注入有概率不进行注入导致null对象出现
- 可选依赖使用setter注入进行,灵活性强
- Spring框架倡导使用构造器,第三方框架内部大多都使用构造器注入的形式进行数据初始化,相对严谨
- 如果有必要可以两者同时使用,使用构造器注入完成强制注入依赖的注入,使用setter注入完成可选依赖的注入
- 实际开发过程中还要根据实际情况分析,如果受控对象没有提供setteer方法就必须使用构造器注入
- 自己开发的模块推荐使用setter注入
3. 依赖注入自动装配
- IoC容器根据bean所依赖的资源再容器中自动查找并注入到bean中的过程称为自动装配
自动装配方式:
- 按类型(常用)
- 按名称
- 按构造方法
- 不启用自动装配
public class BookServiceImpl implements BookService {
private BookDao bookDao;
@Override
public void save(){
System.out.println("service set...");
bookDao.save();
}
}
自动装配再对应bean标签中用 autowire属性来设置自动装配方式。
<bean id="bookService" class="peng.service.Impl.BookServiceImpl" autowire="byType">
自动装配特征
- 自动装配用于引用类型依赖注入,不能对肩带类型进行操作
- 使用按名称装配时必须保障容器中具有指定名称的bean,因变量名与配置耦合,不推荐使用
- 自动装配优先级低于setter注入与构造器注入,同时出现时自动装配失效。
4. 集合注入
给定集合的set方法。
public class BookDaoImpl implements BookDao {
private List listName;
private int[] arrayListName;
private Map map;
private Set set;
private Properties properties;
public void setListName(List listName) {
this.listName = listName;
}
public void setArrayListName(int[] arrayListName) {
this.arrayListName = arrayListName;
}
public void setMap(Map map) {
this.map = map;
}
public void setSet(Set set) {
this.set = set;
}
public void setProperties(Properties properties) {
this.properties = properties;
}
@Override
public void save() {
System.out.println("save....");
System.out.println(arrayListName);
System.out.println(listName);
System.out.println(set);
System.out.println(map);
System.out.println(properties);
}
}
<bean id="bookDao" class="dao.Impl.BookDaoImpl">
<property name="arrayListName">
<array>
<value>312</value>
<value>122</value>
<value>12</value>
</array>
</property>
<property name="listName">
<list>
<value>klagh</value>
<value>oiarg</value>
<value>weioru</value>
</list>
</property>
<property name="map">
<map>
<entry key="234" value="12"> </entry>
<entry key="df" value="dsf"> </entry>
</map>
</property>
<property name="set">
<set>
<value>21</value>
<value>21</value>
<value>34</value>
</set>
</property>
<property name="properties">
<props>
<prop key="key1">china</prop>
<prop key="key2">henan</prop>
<prop key="key3">kaifeng</prop>
</props>
</property>
</bean>
集合的bean标签配置类似与基本类型注入。
- 需要给定property标签的name属性
- 对于map和properties是由对应的键值对的赋值的,且他们的键值对赋值方式不同
- set集合还是符合唯一性的,相同的value会自动删除
数据源对象管理
- 加载数据源依赖
<!-- 加载druid依赖--> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>1.1.16</version> </dependency> <!-- c3p0依赖--> <dependency> <groupId>c3p0</groupId> <artifactId>c3p0</artifactId> <version>0.9.1.2</version> </dependency>
2. 加载mysql依赖
```xml
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.47</version>
</dependency>
- 配置相应的Bean标签
<!--管理DruidDataSource对象-->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
<property name="url" value="jdbc:mysql://localhost:3306/spring/spring_db"/>
<property name="name" value="root"></property>
<property name="password" value="1094148867g"></property>
</bean>
<!-- 管理c3p0对象-->
<bean id="dataSource2" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="driverClass" value="com.mysql.jdbc.Driver"></property>
<property name="jdbcUrl" value="jdbc:mysql://localhost:3306/spring/spring_db"></property>
<property name="user" value="root"></property>
<property name="password" value="1094148867g"></property>
</bean>
- 测试
ClassPathXmlApplicationContext classPathXmlApplicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
BookDao boookDao = (BookDao)classPathXmlApplicationContext.getBean("bookDao");
boookDao.save();
总结:
- 不同数据源的配置的名称是不同的
- Druid配置driverClass可以自动配置不用加载mysql驱动
- 要观察数据源中的有没有构造器来设定相关配置,如果没有就只能看有没有setter方法设定(一般都有),才能有对应的setter注入或者构造器注入。
加载配置文件
- 编写配置文件,db.properties
- 配置context命名空间在applicationContext.xml
- 使用context空间加载properties命名空间
<context:property-placeholder location="dp.properties"></context:property-placeholder>
- 使用属性占位符${} 读取propereties文件
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="${jdbc.driver}"></property>
<property name="url" value="${url}"/>
<property name="name" value="${username}"></property>
<property name="password" value="${password}"></property>
</bean>
还可以加载多个配置文件
<!-- <context:property-placeholder location="dp.properties"></context:property-placeholder>-->
<!-- 用,号形式分隔加载多个propereties文件 -->
<!-- <context:property-placeholder location="dp.properties,dp2.properties"></context:property-placeholder>-->
<!-- 用*.properties加载再reeesources下的所有properties文件 -->
<!-- <context:property-placeholder location="*.properties"></context:property-placeholder>-->
<!-- 用 classpath:*.properties加载该项目下的所有propeerties文件-->
<!-- <context:property-placeholder location="classpath:*.properties"></context:property-placeholder>-->
<!-- 用classpath*:*.properties可以加载其他jar包下的所有properties文件-->
<context:property-placeholder location="classpath*:*.properties"></context:property-placeholder>
容器其他操作
- 两种加载容器配置文件的方式
- 加载类路径
ClassPathXmlApplicationContext classPathXmlApplicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
- 从文件系统中加载配置文件
FileSystemXmlApplicationContext fileSystemXmlApplicationContext = new FileSystemXmlApplicationContext("E:\\spring-study\\spring-07\\src\\main\\resources\\applicationContext.xml");
- 三种加载bean的操作
- 直接getbean的得到bean标签的id
BookDao bookDao =(BookDao) classPathXmlApplicationContext.getBean("bookDao");
- 再getbeean中添加对应接口的反射
BookDao bookDao1 = classPathXmlApplicationContext.getBean("bookDao", BookDao.class);
- 只填写对应接口的反射。按类型查找对应的bean标签。
注意:如果再容器中有两个同类型的bean标签,那么用该方式就会报错```java BookDao bean = classPathXmlApplicationContext.getBean(BookDao.class); ```
<bean id="bookDao" class="com.peng.dao.Impl.BookDaoImpl"></bean> <bean id="bookDao1" class="com.peng.dao.Impl.BookDaoImpl"></bean>
Exception in thread "main" org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type 'com.peng.dao.BookDao' available: expected single matching bean but found 2: bookDao,bookDao1
- 使用以前老版本中加载bean的方法。就是ClassPathXmlApplicationContext的实现类的接口的最顶层接口。
BeanFactory接口。
Resource resource=new ClassPathResource("applicationContext.xml");
XmlBeanFactory xmlBeanFactory = new XmlBeanFactory(resource);
BookDao bean = xmlBeanFactory.getBean(BookDao.class);
bean.save();
该方式页原方式的区别,实在加载的bean的实际不同,前者ClassPathXmlApplicationContext是在启动容器后直接就加载了bean.
而BeeanFactory在启动容器后并没有直接加载bean。
前者是直接加载,后者是延迟加载。
总结
- 容器相关
- BeanFactory是IoC容器的顶层接口,初始化BeanFactory对象时,加载的bean延迟加载
- ApplicationContext接口时Spring容器的核心接口,初始化时bean立即加载
- ApplicationContext接口提供基础的bean操作相关方法,通过其他接口扩展其功能
- ApplicationContext接口常用初始化类
- ClassPathXmlApplicationContext
- FilSystemXmlApplicationContext
- bean相关属性
- 依赖注入相关
注解开发
使用Component注解实现容器的加载
- 在要注册的类中添加Component注解。Component()括号中填写对应id。相当于时bean标签中的id.
@Component("bookDao")
public class BookDaoImpl implements BookDao {
@Override
public void save() {
System.out.println("Book save...");
}
}
- 添加注解后要在xml配置文件中识别该注解,所以就需要添加对应标签.
且会扫描base-package下的类以及其子类。
<context:component-scan base-package="com.peng.dao"/>
以上操作就相当于是在xml中创建了一个bean标签。
- 如果不在Component的括号中填写对应的id名,则需要在获取getbean的时候用按类型的自动查找方式。
BookService bean = classPathXmlApplicationContext.getBean(BookService.class);
Spring提供了@Component注解的三个衍生注解
- @Controller:用于表现层bean定义
- @Service:用于业务层bean定义
- @Repository:用于数据层定义
三者与@Component的功能是等效的,只是为了便于区分。
用配置类替换applicationContext.xml配置文件实现纯注解开发
- 创建一个配置类,使用@Configuration和@ComponentScan("com.peng")
@Configuration
@ComponentScan("com.peng")
public class SpringConfig {
}
- 使用1AnnotationConfigApplicationContext获取相关配置信息
public static void main(String[] args) {
AnnotationConfigApplicationContext annotationConfigApplicationContext = new AnnotationConfigApplicationContext(SpringConfig.class);
BookDao bookDao = (BookDao)annotationConfigApplicationContext.getBean("bookDao");
bookDao.save();
BookService bean = annotationConfigApplicationContext.getBean(BookService.class);
bean.save();
}
@Configuration:用于设定当前类为配置类
@ComponentScan:用于设定扫描路径,此注解只能添加一次,多个数据请使用数组格式
@ComponentScan({"com.peng.dao","com.peng.Service"})
注解开发bean的作用范围和生命周期
- 单例模式更改
@Scope("prototype")
public class BookDaoImpl implements BookDao
- bean的初始化和销毁
依赖注入,制动装配
注入引用类型
使用@Autowired注解到需要添加的属性上。
@Service("bookService")
public class BookServiceImpl implements BookService {
@Autowired
private BookDao bookDao;
public void save(){
System.out.println("bookService ave...");
bookDao.save();
}
}
-
这种自动装配是按类型自动装配的也就是(BookDao.class)
- 但是到你的项目中有多个该类型的类时:
@Repository("bookDao") public class BookDaoImpl implements BookDao { @Override public void save() { System.out.println("Book save ..."); } } @Repository("bookDao2") public class BookDaoImpl2 implements BookDao{ @Override public void save() { System.out.println("Book save2 ..."); } }
默认是按照在BookService中的属性的名称来自动装配的。也就是说,如果在service类中的属性名叫bookDao2,那就就会注入对应的在@Repository("bookDao2")注解下的类。
-
当然处理这种多种类的默认注解情况我们还可以用@Qualifier("bookDao2")注解来开启指定名称注入。
@Service("bookService")
public class BookServiceImpl implements BookService {
@Autowired
@Qualifier("bookDao2")
private BookDao bookDao;
public void save(){
System.out.println("bookService ave...");
bookDao.save();
}
}
- 以上都是@Autowired默认通过按类型注解的情况,都是比较推荐的。
注意点:
- 自动装配基于反射设计创建对象并暴力反射对应属性为私有属性初始化数据,因此无需提供setter方法
- 自动装配建议使用无参构造方法创建对象(默认),如果不提供对应构造方法,请提供唯一的构造放啊发
- 使用@Qualifier注解开启指定名称装配
- @Qualifier注解无法单独使用,必须配合@Autowired注解使用
简单类型注入
使用@Value()
@Repository("bookDao2")
public class BookDaoImpl2 implements BookDao{
@Value("com.peng")
private String name;
@Override
public void save() {
System.out.println("Book save2 ..."+name);
}
}
读取外部配置文件
- 创建一个外部配置文件
- 在配置类中使用@PropertySource注解指明需要注入外部配置文件的位置。
@Configuration
@ComponentScan({"com.peng.dao" , "com.peng.service"})
@PropertySource("jdbc.properties")
public class SpringConfig {
}
- 用${}可以取出配置文件中对应的值。
@Repository("bookDao2")
public class BookDaoImpl2 implements BookDao{
@Value("${name}")
private String name;
@Override
public void save() {
System.out.println("Book save2 ..."+name);
}
}
- 查看结果
注意点:
- 当读取的配置文件不止一个时,可以用数组的方式加入多个配置文件,与上述所说的@ComponentScan({"com.peng.dao","com.peng.Service"})类似。
- 该@PropertySource()不支持通配符的使用,*.properties.
注解管理三方bean
- 加载三方jar包
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.2.16</version>
</dependency>
- 在配置类中添加一个要注册的bean的类型的方法,并在方法中手动添加
@Configuration
@ComponentScan("com.peng.dao")
public class SpringConfig {
//定义一个方法获得要管理的对象
//添加@Bean,将当前方法的返回值装入容器bean中实现三方bean的装配
@Bean
public DataSource data
Source(){
DruidDataSource ds=new DruidDataSource();
ds.setDriverClassName("com.mysql.jdbc.Driver");
ds.setUrl("jdbc:mysql://localhost:3306/mybatis");
ds.setDriverClassName("root");
ds.setPassword("1094148867g");
return ds;
}
}
- 当然这种添加bean的操作我们并不放在配置类中,而是单独创建一个类放在该类中。
-
但是需要识别到该类中的bean的话有以下两种操作
- 将该类加上@Configuration变为配置类,再再真正的配置类中用@ComponentScan("com.peng.config")扫描到该包
@Configuration public class jdbcConfig { //定义一个方法获得要管理的对象 //添加@Bean,表示当前方法的返回值是一个bean @Bean public DataSource dataSource(){ DruidDataSource ds=new DruidDataSource(); ds.setDriverClassName("com.mysql.jdbc.Driver"); ds.setUrl("jdbc:mysql://localhost:3306/mybatis"); ds.setDriverClassName("root"); ds.setPassword("1094148867g"); return ds; } }
但是不推荐这样使用,因为这样使用如果外部加载bean过多的话,而每个都创建了一个配置类,再扫描定位对应bean的话,不能清楚分辨再启动时加载的哪个配置类
2. 只在配置类中使用@Import注解
```java
@Import({jdbcConfig.class})
public class SpringConfig {} ``` 并在注解中添加对应bean加载的位置。@Import只能写一次,如果向添加多次,可用数组添加多个。
-
三方bean注入资源
- 简单类型注入
public class jdbcConfig {
@Value("com.mysql.jdbc.Driver")
private String driver;
@Value("jdbc:mysql://localhost:3306/mybatis")
private String url;
@Value("root")
private String username;
@Value("1094148867g")
private String password;
//定义一个方法获得要管理的对象
//添加@Bean,表示当前方法的返回值是一个bean
@Bean
public DataSource dataSource(BookDao bookDao){
System.out.println(bookDao);
DruidDataSource ds=new DruidDataSource();
ds.setDriverClassName(driver);
ds.setUrl(url);
ds.setDriverClassName(username);
ds.setPassword(password);
return ds;
}
}
用@Value即可。
2. 而对于引用类型。
这里把带注入的bookDao放到了该方法的形参中。实现引用类型注入。
因为再IoC容器中,把BookDao的bean和该三方bean的注册到了容器中,而在加载容器时,调用了该三方bean,而三方bean再创建时发现引用了容器中已经注册的bean,而自动装配了改bean,所以加载了本地的这个bean。
3. 而当我们把本地的bean的注册取消时
//@Repository
public class BookDaoImpl implements BookDao {
}
这时再加载会报错
Caused by: org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'com.peng.dao.BookDao'
可以看出时按类型自动装配的。
XML配置与注解配置比较
spring整合mybatis
- 创建一个项目,导入所需要的jar包
- 编写mybatis的配置文件以及连接数据库的propeerties文件
<?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="db.properties">
<property name="password" value="1094148867g"/>
</properties>
<environments default="development">
<environment id="development">
<!-- 事务管理-->
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="${driver}"/>
<property name="url" value="${url}"/>
<property name="username" value="${username}"/>
<property name="password" value="${password}"/>
</dataSource>
</environment>
<environment id="test">
<!-- 事务管理-->
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/mybatis?useUnicode=true&characterEncoding=utf-8"/>
<property name="username" value="root"/>
<property name="password" value="1094148867g"/>
</dataSource>
</environment>
</environments>
<!-- 绑定接口-->
<mappers>
<mapper class="com.peng.dao.UserDao"></mapper>
</mappers>
</configuration>
- 编写持久层与业务层
- 再数据层,业务层编写对应的sql语句注解(这里用注解替代了用Mapper.xml来实现的接口的方法)
public interface UserDao {
@Select("select * from user")
List<User> findAll();
@Insert("insert into user(id,name,pwd)values(#{name},#{pwd})")
void save();
@Update("update user set name=#{name},pwd=#{pwd} where id=#{id}")
void update(User user);
@Delete("delete from user where id=#{id}")
void delete(int id);
@Select("select * from where id=#{id}")
User findById(int id);
}
- 编写spring配置类
@Configuration
@ComponentScan("com.peng")
@PropertySource("classpath:db.properties")
@Import({JdbcConfig.class,MybatisConfig.class})
public class SpringConfig {
}
- 获取SqlSession对象
//1.创建SqlSessionFactoryBuilder对象
SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
//2.以流的形式加载mybatis-config.xml配置文件
InputStream resourceAsStream = Resources.getResourceAsStream("mybatis-config.xml");
//3.创建SqlSessionFactory对象
SqlSessionFactory build = sqlSessionFactoryBuilder.build(resourceAsStream);
//4.获取SqlSession
SqlSession sqlSession = build.openSession();
- 执行对应的sql语句操作
//5.执行SqlSession对象执行查询,获取结果
UserDao mapper = sqlSession.getMapper(UserDao.class);
List<User> all = mapper.findAll();
for (User user : all) {
System.out.println(user);
}
//6.释放资源
sqlSession.close();
以上操作就可以实现再spring中操作mybatis了.接下来对上面的部分操作进行一定优化(就是对于mybatis配置类将其改为与spring一样,将配置类改为java用类来保存,以及对于再获取SqlSession对象的操作中,将必要的对象保存再IoC容器中)
- 添加整合mybatis需要的两个jar包
<!-- //spring公司的jaar包 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.2.10.RELEASE</version>
</dependency>
<!-- //属于mybatis公司为整合mybatis的jar包 -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>1.3.0</version>
</dependency>
- 用类的方式加载mybatis-config.xml配置类。
再传统方法中我们是把该配置文件改为文件流,将文件流用sqlSessionFactoryBuilder对象中的build方法创建SqlSessionFactory对象,将配置信息加载到该对象中。
但是现在我们将该SqlSessionFactory要方法Ioc容器中,所以我们应该将配置信息一同加载到bean中。
再上面导入的Jar中提供了SqlSessionFactoryBean类,可以将信息加载到SqlSessionFactory中并创建该对象
下面我们来将配置文件中必须加载的信息分析
-
引入外部配置文件:我们在spring配置类中已经做了.
@Configuration @ComponentScan("com.peng") @PropertySource("classpath:db.properties") @Import({JdbcConfig.class,MybatisConfig.class}) public class SpringConfig { }
-
typeAliases:类型别名的加载。
可以看到有同名的设置方法,可以用setTypeAliasesPackage,将类所在的包放到加载进行扫描,也可以setTypeeAliases,直接将类的class对象加载。 -
然后就是环境的信息。也可以用set设置setEnvironment
-
然后就是事务管理,这里可以先不管,后面详解。其实对于这种配置,再spring框架中都会有一定的简化,就是使用默认配置,也就是说,对于一些固定格式的信息,例如环境的默认,事务管理类型的默认,以及数据源的默认都可以不用设置,而采用默认设置即可
-
再然后就是daaSource的设置,同样可以采用setDataSource设置,但是要注意的时,这里所传递的对象是一个DataSource对象。
我们再前节讲过,对于DataSource的配置我们用了一个JdbcConfig类写入,然后用@@Import({JdbcConfig.class,MybatisConfig.class})注解再spring配置类中定位到该bean实现注入。具体在三方bean的注入中提到。具体操作:
-
JdbcConfig类
public class JdbcConfig { @Value("${driver}") private String driver; @Value("${url}") private String url; @Value("${name}") private String name; @Value("${password}") private String password; @Bean public DataSource dataSource(){ DruidDataSource ds=new DruidDataSource(); ds.setDriverClassName(driver); ds.setUrl(url); ds.setUsername(name); ds.setPassword(password); return ds; } }
-
spring配置类注解
@Configuration @ComponentScan("com.peng") @PropertySource("classpath:db.properties") @Import({JdbcConfig.class,MybatisConfig.class}) public class SpringConfig { }
所以已经把DataSource的数据源加载到了bean中。我们就只需要再将该对象作为形参。再掉用该Mybtias的bean时就会自动扫描IoC容器中是否有DataSoruce对象的存在,我们也已经设置所以就会将容器中的bean带到形参中实现三方bean的引用类型注入。
具体操作如下
-
@Bean
public SqlSessionFactoryBean sqlSessionFactory(DataSource dataSource){
SqlSessionFactoryBean ssfb=new SqlSessionFactoryBean();
ssfb.setTypeAliasesPackage("com.peng.dao");
ssfb.setDataSource(dataSource);
ssfb.setEnvironment("development");
return ssfb;
}
- 最后一个红框时Mybatis的映射对应Mapper接口。而上面创建一个SqlSessionFactory的bean已经创建好了,现在就只需要将到接口映射的配置实现
@Bean
public MapperScannerConfigurer mapperScannerConfigurer(){
MapperScannerConfigurer mapperScannerConfigurer = new MapperScannerConfigurer();
mapperScannerConfigurer.setBasePackage("com.peng.dao");
return mapperScannerConfigurer;
}
- 最后测试
public class app2 {
public static void main(String[] args) {
AnnotationConfigApplicationContext annotationConfigApplicationContext = new AnnotationConfigApplicationContext(SpringConfig.class);
UserDao bean = annotationConfigApplicationContext.getBean(UserDao.class);
User byId = bean.findById(1);
System.out.println(byId);
}
}
Spring整合junit
- 加载所需要的jar包
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.3.23</version>
</dependency>
- 再test文件夹内创建一个测试类
- 再测试类中使用@RunWith(SpringJUnit4ClassRunner.class)类运行器注解和@ContextConfiguration(classes = SpringConfig.class)指定spring上下文配置类。
- 测试
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = SpringConfig.class)
public class UserDaoTest {
@Autowired
private UserDao userDao;
@Test
public void testFindById(){
System.out.println(userDao.findById(2));
}
}
AOP
简介:
- AOP(Aspect Oriented Programming)面向切面编程,一种编程范式,指导开发者如何组织程序结构
- OOP(Object Oriented Programming)面向对象编程
- 作用:再不惊动原始设计的基础1上为其进行功能增强
- Spring理念:无入侵式/无侵入式
核心概念:
现在创建一个需求,再进行增删改的时候追加一个计时功能.而查询没有。
- 左边的所有方法称为连接点
- 现在将这个计时的方法拿到外面去。让update和delete方法拥有其功能。在原有基础上添加功能的方法称为切入点
- 这个额外添加的功能的方法称为通知。
- 在通知和切入点之间的关系绑定称为切面
- 最后承载这个通知的类称为通知类
案例
目标:在接口执行前输入当前系统时间
- 导入要用的坐标
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.4</version>
</dependency>
spring-context坐标依赖spring-aop坐标
- 制作连接点的方法(原始操作,Dao接口与实现类)
连接点的制作
@Repository
public class BookDaoImpl implements BookDao {
@Override
public void save() {
System.out.println("BookDao save...");
}
@Override
public void update() {
System.out.println("book dao update...");
}
}
- 制作共性功能(通知类与通知)
public class MyAdvice {
public void method(){
System.out.println(System.currentTimeMillis());
}
}
- 定义切入点
//切入点
@Pointcut("execution(void com.peng.dao.BookDao.update())")
public void pt(){}
使用@Pointcur注解("execution(void com.peng.dao.BookDao.update())")为参数,指定连接点的返回值类型和连接点所在位置。将其注解再一个空壳方法上。
- 绑定切入点与通知的关系(切面)
//通知
@Before("pt()") //切面
public void method(){
System.out.println(System.currentTimeMillis());
}
使用@Before再连接点执行前执行该通知,("pt()") 参数方式切入点的方法,实现切入点与通知的绑定.
- 最后将通知类用注解@Component和@Aspect修饰
@Component
@Aspect
public class MyAdvice {
//切入点
@Pointcut("execution(void com.peng.dao.BookDao.update())")
public void pt(){}
//通知
@Before("pt()") //切面
public void method(){
System.out.println(System.currentTimeMillis());
}
}
@Configuration
@ComponentScan("com.peng.dao")
@EnableAspectJAutoProxy
public class SpringConfig {
}
加入@EnableAspectJAutoProxy注解启动@Aspect注解,spring识别该通知类实现Aop操作。
AOP工作流程
-
Spring容器启动
-
读取所有切面配置中的切入点
@Component @Aspect public class MyAdvice { //切入点 @Pointcut("execution(void com.peng.dao.BookDao.update())") public void pt(){} @Pointcut("execution(void com.peng.dao.BookDao.save())") public void ptx(){} //通知 @Before("pt()") //切面 public void method(){ System.out.println(System.currentTimeMillis()); } }
当有两个切入点,但是切面绑定了一个切面。那么再读取时只读取绑定的切面。
-
初始化bean,判定Bean对应的类中的方法是否匹配到切入点。
- 匹配失败,创建对象,初始化bean
- 匹配成功,创建原始对象 (目标对象)的代理对象
-
获取bean执行方法
- 获取bean,调用方法并执行,完成操作
- 获取bean是代理对象时,根据代理对象运行模式运行原始方法与增强的内容,完成操作。
AOP核心概念:
- 目标对象(Target):原始功能去掉共性功能对应的类产生的对象,这种对象是无法直接完成最终工作的
- 代理对象(Proxy):目标对象无法直接完成工作,需要对其进行功能回填,通过原始对象的实现
SpringAOP本质:代理对象
AOP切入点表达式
切入点:要进行增强的方法
切入点表达式:要进行增强的方法的描述方式
描述方式:用接口的update方法或者用接口实现类的update()方法
execution(void com.peng.dao.BookDao.update())
execution(void com.peng.dao.impl.BookDaoImpl.update())
切入点表达式标准格式:动作关键字(访问修饰符 返回值 包名.类/接口名.方法名(参数)异常名)
- 动作关键字:描述切入点的动作行为,例如execution表示执行到指定切入点。
- 访问修饰符:public,private等,可省略
- 异常名:方法定义中抛出指定异常,可以省略
可以使用通配符描述切入点,快速描述
- *:单个任意符号,可独立出现,可作为后缀或前缀
- ..:多个连续的任意符号。可以独立出现,常用于简化包名与参数的书写
- +:专用于匹配子类类型
execution(* *..*Service+.*(..))
AOP通知类型
- AOP通知描述了抽取的共性功能,根据共性功能抽取的位置不同,最终运行代码时要将其加入到合理的位置
- AOP通知共分为5中类型:
- 前置通知
- 后置通知
@Pointcut("execution(void com.peng.dao.BookDao.save())") public void pt(){} @Pointcut("execution(int com.peng.dao.BookDao.select())") public void ptselect(){} @Before("pt()") public void before(){ System.out.println("before advice..."); } @After("pt()") public void after(){ System.out.println("after advice..."); }
- 环绕通知(**)
@Pointcut("execution(void com.peng.dao.BookDao.save())")
public void pt(){}
@Pointcut("execution(int com.peng.dao.BookDao.select())")
public void ptselect(){}
// @Before("pt()")
public void before(){
System.out.println("before advice...");
}
// @After("pt()")
public void after(){
System.out.println("after advice...");
}
// @Around("pt()")
public void around(ProceedingJoinPoint pjp){
System.out.println("around before advice..");
try {
pjp.proceed();
} catch (Throwable e) {
e.printStackTrace();
}
System.out.println("around after advice...");
}
- 返回后通知(了解)
- 抛出异常后通知(了解)
//再连接点方法完全执行后且不抛出异常后才执行 //注意区别@After注解的区别,前者是再连接点方法执行后就马上执行不论它是否抛出异常。该注解是只有再连接点方法无异常执行后才运行。 public void afterReturning(){ System.out.println("afterReturning afvice..."); } //只有在连接点方法抛出异常后才执行 public void afterThrowing(){ System.out.println("afterThrowing advice..."); }
这里重点讲解环绕通知。
- 需要使用ProceedingJoinPoint类来声明一个对象。调用pjp.proceed()就表示对原始对象的操作。
- 如果连接点方法有返回值,那么需要设置一个返回值返回该切点方法。也可以直接返回pjp.proceed()的返回值。
- 所以一般正常写法是:
该方法的返回值写Object.哪怕连接点方法返回值是void,也可以这样写。@Around("pt()") public Object around(ProceedingJoinPoint pjp){ System.out.println("around before advice.."); Object proceed=null; try { proceed = pjp.proceed(); } catch (Throwable e) { e.printStackTrace(); } System.out.println("around after advice..."); return proceed; }
- 由于无法预知原始方法运行后是否会抛出异常,因此环绕通知方法必须抛出Throwable对象。
- 环绕通知必须依赖形参ProceedingJoinPoint才能实现对原始方法的调用,进而实现原始方法调用前后同时添加通知。
- 通知中如果未使用ProceedingJoinPoin对原始方法进行调用将跳过原始方法的执行。
Aop案例,测量业务层执行效率
需求:任意业务层接口执行均可显示其执行效率。
1.导入所需的所有jar包:
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.3.23</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.30</version>
</dependency>
<!-- 整合mybatis jar包-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.2.10.RELEASE</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>1.3.0</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.11</version>
</dependency>
<!-- 整合junitjar包-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.3.23</version>
</dependency>
<!-- 三方数据源jar包-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.2.16</version>
</dependency>
<!-- 整合aopjar包-->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.4</version>
</dependency>
<!-- 偷懒jar包-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.24</version>
</dependency>
</dependencies>
- 创建业务层数据层的接口和实现类
@Data
@AllArgsConstructor
public class User {
private int id;
private String name;
private String password;
}
@Repository
public interface UserDao {
@Insert("insert into user(id,name,pwd)values(#{id},#{name},#{password})")
public void save(User user);
@Update("update user set name=#{name},pwd=#{password} where id=#{id}")
public void update(User user);
@Delete("delete from user where id=#{id}")
public void delete(Integer id);
@Select("select * from user where id=#{id}")
public User findById(Integer id);
@Select("select * from user")
public List<User> findAll();
}
public interface UserService {
void save(User user);
void delete(Integer id);
void update(User user);
List<User> findall();
User findById(Integer id);
}
@Service
public class UserServiceImpl implements UserService {
@Autowired(required = false)
private UserDao userDao;
@Override
public void save(User user) {
userDao.save(user);
}
@Override
public void delete(Integer id) {
userDao.delete(id);
}
@Override
public void update(User user) {
userDao.update(user);
}
@Override
public List<User> findall() {
List<User> userAll = userDao.findAll();
return userAll;
}
@Override
public User findById(Integer id) {
User user = userDao.findById(id);
return user;
}
}
- 设置spring配置mybatis整合,jdbc导入。
@Configuration
@ComponentScan({"com.peng.dao","com.peng.service","com.peng.aop"})
@EnableAspectJAutoProxy
@PropertySource("classpath:jdbc.properties")
@Import({JdbcConfig.class,MybatisConfig.class})
public class SpringConfig {
}
//注入三方bean mysql登录信息
public class JdbcConfig {
@Value("${driver}")
private String driver;
@Value("${url}")
private String url;
@Value("${name}")
private String name;
@Value("${password}")
private String password;
@Bean
public DataSource dataSource(){
DruidDataSource druidDataSource = new DruidDataSource();
druidDataSource.setDriverClassName(driver);
druidDataSource.setUrl(url);
druidDataSource.setUsername(name);
druidDataSource.setPassword(password);
return druidDataSource;
}
}
public class MybatisConfig {
@Bean
public SqlSessionFactoryBean sqlSessionFactory(DataSource dataSource){
SqlSessionFactoryBean ssfb=new SqlSessionFactoryBean();
ssfb.setTypeAliasesPackage("com.peng.dao");
ssfb.setTypeAliasesPackage("com.peng.service");
ssfb.setDataSource(dataSource);
ssfb.setEnvironment("development");
return ssfb;
}
@Bean
public MapperScannerConfigurer mapperScannerConfigurer(){
MapperScannerConfigurer mapperScannerConfigurer = new MapperScannerConfigurer();
mapperScannerConfigurer.setBasePackage("com.peng.dao");
return mapperScannerConfigurer;
}
}
注意点:
- 再pojo类与数据库表字段名不同的情况再用注解进行sql操作时应看清对应关系
- spring配置中的注解关系,以及所需参数.
- @ComponentScan注解在扫描时不要忘记添加aop的类
- aop设置
@Component
@Aspect
public class ProjectAdvice {
//匹配业务层的所有方法
@Pointcut("execution(* com.peng.service.*Service.*(..))")
private void servicePt(){}
@Around("servicePt()")
public Object runSpeed(ProceedingJoinPoint pjp) throws Throwable {
long start=System.currentTimeMillis();
Object proceed=null;
for (int i = 0; i < 10000; i++) {
proceed = pjp.proceed();
}
Signature signature = pjp.getSignature();
System.out.println("方法名是:"+signature.getName());
System.out.println("方法信息是:"+signature.getDeclaringTypeName());
long end=System.currentTimeMillis();
System.out.println("业务层接口万次执行时间: "+(end-start)+"ms");
return proceed;
}
}
- 这里的pjp.getSignature()可以获取连接点方法的各种信息,这里获取了方法名及方法地址。
- 测试
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = SpringConfig.class)
public class UserServiceTest {
@Autowired
private UserService userService;
@Test
public void testById(){
User byId = userService.findById(1);
System.out.println(byId);
}
@Test
public void testFindAll(){
List<User> findall = userService.findall();
for (User user : findall) {
System.out.println(user);
}
}
@Test
public void testUpdate(){
User tkyd = new User(1, "tkyd", "324");
userService.update(tkyd);
List<User> findall = userService.findall();
for (User user : findall) {
System.out.println(user);
}
}
@Test
public void testSave(){
User tkyd2 = new User(6, "tkyd2", "3211444");
userService.save(tkyd2);
List<User> findall = userService.findall();
for (User user : findall) {
System.out.println(user);
}
}
}
AOP通知获取数据
- 获取切入点方法参数
- JoinPoint:适用于前置,后置,返回后,抛出异常后通知
- ProceedJoinPoint:适用于环绕通知
- 获取切入点方法返回值
- 返回后通知
- 环绕通知
- 获取切入点方法运行异常信息
- 抛出异常后通知
- 环绕通知
- 参数获取
JoinPoint获取参数测试:
Dao:
@Repository
public class UserDaoImpl implements UserDao {
@Override
public String save(int id, String name) {
System.out.println("save"+" "+id+" "+name);
return name;
}
@Override
public void select() throws Exception {
System.out.println("select exception");
}
}
advice:
@Component
@Aspect
public class Adivers {
@Pointcut("execution( void com.peng.dao.UserDao.save(..))")
public void Dao(){}
@Before("Dao()")
public void before(JoinPoint jp){
Object[] args = jp.getArgs();
System.out.println(Arrays.toString(args));
System.out.println("before advice");
}
测试:
public class app1 {
public static void main(String[] args) {
AnnotationConfigApplicationContext acac = new AnnotationConfigApplicationContext(springConfig.class);
UserDao user = acac.getBean(UserDao.class);
String tkud = user.save(1, "tkud");
System.out.println(tkud);
}
}
结果:
ProceedJoinPoint是JoinPoint的子接口,与JoinPoint一样可以调用getArgs获取方法参数。
@Around("Dao()")
public Object around(ProceedingJoinPoint pjp )throws Throwable{
Object[] args = pjp.getArgs();
System.out.println(Arrays.toString(args));
Object ret = pjp.proceed();
return ret;
}
除此之外.pjp.proceed()还可以传递参数。而pjp.proceed()代表的是原始连接点方法,而再该通知中我们又可以获取到原始方法的参数,那么我们是否可以再通知中更改原始连接点方法的参数呢?
@Around("Dao()")
public Object around(ProceedingJoinPoint pjp )throws Throwable{
Object[] args = pjp.getArgs();
args[0]=12341;
System.out.println(Arrays.toString(args));
Object ret = pjp.proceed(args);
return ret;
}
实验结果:
可以看到结果是可行的。
- 返回值获取
返回后通知接收返回值:
@AfterReturning(value="Dao()",returning = "ret")
public void afterReturning(Object ret){
System.out.println("afterReturning avice..."+ret);
}
注意
- @AfterRetruning的参数returning的值必须和所接收返回值的参数相同名。
- 再改通知的方法参数中JoinPoint jp的形参声明必须再String ret形参声明之前
环绕通知接收返回值,就是pjp.proceed()的返回值。
- 获取切入点方法运行异常信息
环绕通知接收异常。用try-catch捕获
@Around("Dao()")
public Object around(ProceedingJoinPoint pjp ){
Object[] args = pjp.getArgs();
args[0]=12341;
System.out.println(Arrays.toString(args));
Object ret = null;
try {
ret = pjp.proceed(args);
} catch (Throwable e) {
e.printStackTrace();
}
return ret;
}
抛出异常后通知接收异常
@AfterThrowing(value="Dao()",throwing = "t")
public void afterThrowing(Throwable t){
System.out.println("afterThrowing advice..."+t);
}
如果连接点方法出现异常:
AOP总结
概念:AOP面向切面编程,一种编程范式
作用:再不惊动原始设计的基础上为方法进行功能增强
核心概念:
- 代理(Proxy):SpringAop核心本质是采用代理模式实现的
- 连接点(JoinPoint):再SpringAOP中,理解为任意方法的执行
- 切入点(Pointcut):匹配连接点的式子,也是具有共性功能的方法描述
- 通知(Advice):若干个方法的共性功能,再切入点处执行,最终体现为一个方法
- 切面(Aspect):描述通知与切入点的对应关系
- 目标对象(Target):被代理的原始对象成为目标对象
Spring事务简介
事务作用:再数据层保障一系列的数据库操作同成功同失败
Spring事务作用:在数据层或业务层保障一系列的数据库操作同成功同失败
银行转账案例:
需求:实现任意两个账户间转账操作
需求微缩:A账户减少钱,B账户增加钱
分析:
- 数据层提供基础操作,指定账户减钱,指定账户加钱
- 业务层提供转账操作,调用减钱与加钱操作
- 提供2个账号和操作金额执行转账操作
- 基于Spring整合金额执行转账操作
- 基于Spring整合MyBatis环境搭建上述操作
- 导入指定的jar包
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.3.23</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.11</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.24</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.30</version>
</dependency>
<!-- spring整合mybatis的两个jar包-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.2.10.RELEASE</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>1.3.0</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.2.16</version>
</dependency>
<!-- 整合aopjar包-->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.4</version>
</dependency>
<!-- 整合junitjar包-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.3.23</version>
</dependency>
</dependencies>
- 编写数据层和业务层
dao
@Repository
public interface AccountDao {
@Update("update tablename1 set money=money+#{money} where name=#{name}")
void inMoney(Map map);
@Update("update tablename1 set money=money-#{money} where name=#{name}")
void outMoney(Map map);
}
pojo
@Data
public class Account {
private int id;
private String name;
private int money;
}
service
public interface AccountService {
public void transfer(String out,String in,int money);
}
@Service
public class AccountServiceImpl implements AccountService {
@Autowired
private AccountDao accountDao;
@Override
public void transfer(String out, String in, int money) {
Map<String,Object> outmap=new HashMap();
Map<String,Object> inmap=new HashMap();
outmap.put("name",out);
outmap.put("money",money);
inmap.put("money",money);
inmap.put("name",in);
accountDao.outMoney(outmap);
accountDao.inMoney(inmap);
}
}
- 编写指定的spring配置和mybatis配置
@ComponentScan("com.peng")
@Configuration
@PropertySource("classpath:jdbc.properties")
@Import({jdcbConfig.class,mybatisConfig.class})
public class springConfig {
}
public class jdcbConfig {
@Value("${driver}")
private String driver;
@Value("${url}")
private String url;
@Value("${name}")
private String name;
@Value("${password}")
private String password;
@Bean
public DataSource dataSource(){
DruidDataSource druidDataSource = new DruidDataSource();
druidDataSource.setDriverClassName(driver);
druidDataSource.setUrl(url);
druidDataSource.setUsername(name);
druidDataSource.setPassword(password);
return druidDataSource;
}
}
public class mybatisConfig {
@Bean
public SqlSessionFactoryBean sqlSessionFactoryBean(DataSource dataSource){
SqlSessionFactoryBean sfb = new SqlSessionFactoryBean();
sfb.setTypeAliasesPackage("com.peng.dao");
sfb.setDataSource(dataSource);
return sfb;
}
@Bean
public MapperScannerConfigurer mapperScannerConfigurer(){
MapperScannerConfigurer mapperScannerConfigurer = new MapperScannerConfigurer();
mapperScannerConfigurer.setBasePackage("com.peng.dao");
return mapperScannerConfigurer;
}
}
- 测试
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = springConfig.class)
public class AccountServiceTest {
@Autowired
private AccountService accountService;
@Test
public void testTransfer() throws IOException{
accountService.transfer("Tom","Jerry",100);
System.out.println(1);
}
}
- 运行结果
如果再转钱的过程中发生了异常.
@Service
public class AccountServiceImpl implements AccountService {
@Autowired
private AccountDao accountDao;
@Override
public void transfer(String out, String in, int money) {
Map<String,Object> outmap=new HashMap();
Map<String,Object> inmap=new HashMap();
outmap.put("name",out);
outmap.put("money",money);
inmap.put("money",money);
inmap.put("name",in);
accountDao.outMoney(outmap);
//模拟异常
int i=1/0;
accountDao.inMoney(inmap);
}
}
可以看到总金额减少了。
解决办法:
- 使用@Transactional注解
public interface AccountService { @Transactional public void transfer(String out,String in,int money); }
- 再jdbc配置中添加事务管理的bean
@Bean public PlatformTransactionManager transactionManager(DataSource dataSource){ DataSourceTransactionManager transactionManager=new DataSourceTransactionManager(); transactionManager.setDataSource(dataSource); return transactionManager; }
- 使用@EnableTransactionManagement注解告诉spring配置类使用了jdbc事务
@ComponentScan("com.peng") @Configuration @PropertySource("classpath:jdbc.properties") @Import({jdcbConfig.class,mybatisConfig.class}) @EnableTransactionManagement public class springConfig { }
此时模拟异常,执行service操作就不会改变money的数值。
注意事项:
- Spring注解式事务通常添加再业务层接口中而不会添加到业务层实现类中,降低耦合度
- 注解式事务可以添加到业务方法上表示当前方法开启事务,也可以添加到接口上表示当前接口所有方法开启事务。
- 事务管理器要根据实现技术进行选择
- MyBatis框架使用的式JDBC事务
Spring事务角色
- spring中的数据层的两个更新操作分别开启了两个事务,两个事务分别执行,当事务一或者事务二出现错误的时候其中的另一个事务就是停止执行,当时不会回滚,就导致了数据的总和发生改变。
-
现在再spring中的业务层开启一个事务,将上面的两个事务加入到该事务,到其中一个事务出现错误。另一个事务没执行,就可以直接回滚该事务将两个事务一起回滚,实现同成功同失败。
-
加入的事务称为事务协调员
- 加入事务方,在spring中通常代指数据层方法,也可以是业务层方法
-
开启的新事务称为事务管理员
- 发起是事务方,在spring中通常代指业务层开启事务的方法
Spring事务相关配置
该事务对于部分异常可能不会回滚(例如:IOException)
这时我们可已指定需要回滚的异常
public interface AccountService {
@Transactional(rollbackFor = IOException.class)
public void transfer(String out,String in,int money);
}
需求增加:任意两个账户之间的转账操作时,对每次转账操作在数据库进行留痕
-
基于转账操作案例添加日志模块,实现数据库中记录日志
-
业务层转账操作(transfer),调用减钱,加钱与记录日志功能
实现效果预期:
-
无论转账操作是否成功,均进行转账操作的日志留痕
步骤
- 在业务层接口上添加Spring事务,设置事务传播行为REQUIRES_NEW(需要新事务)
public interface LogService {
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void log(String out,String in,int money);
}
@Service
public class LogServiceImpl implements LogService {
@Autowired
private LogDao logDao;
@Override
public void log(String out, String in, int money) {
logDao.log("转账操作由"+out+"到"+in+",金额:"+money);
}
}
事务传播行为的参数:
SpringMVC
概述:
- SpringMVC技术与Servlet技术功能等同,均属于web层开发技术
入门案例
- 导入SpringMVC所需坐标
<!--导入springmvc所需jar包-->
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.1.9.RELEASE</version>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>4.0.1</version>
<scope>provided</scope>
</dependency>
</dependencies>
- 创建SpringMVC控制器类(等同于Servlet功能)
//2。定义Controller
//2.1使用过@Controller注解定义bean
@Controller
public class UserController {
//2.2设置当前操作的访问路劲
@RequestMapping("/save")
//2.3设置当前操作的返回值类型
@ResponseBody
public String save(){
System.out.println("user save....");
return "{'module':'springmvc'}";
}
}
- 初始化Spring1MVC环境(同Spring环境),设定SpringMVC加载对应bean
//3.设置SpringMVC的配置类,加载controller对应的bean
@Configuration
@ComponentScan("com.peng.controller")
public class SpringMvcConfig {
}
- 初始化Servlet容器,加载SpringMVC环境,并设置SpringMVC技术处理的求情.
//定义一个Servlet容器启动的配置类,加载spring的配置
public class ServletStart extends AbstractDispatcherServletInitializer {
//该方法时加载springMVC容器配置
protected WebApplicationContext createServletApplicationContext() {
AnnotationConfigWebApplicationContext acwac = new AnnotationConfigWebApplicationContext();
acwac.register(SpringMvcConfig.class);
return acwac;
}
//设置哪些请求归属springMVC处理
protected String[] getServletMappings() {
return new String[]{"/"};
}
//加载spring容器配置
protected WebApplicationContext createRootApplicationContext() {
return null;
}
}
注意
- 第四步,加载容器时,与spring中所学的容器不同
第一个是加载spring容器的类,第二是servlet容器的内(web)
新增注解:
- @Controller注解:位于SpringMVC控制器类定义的上方,设定SpringMVC的核心控制器bean
- @RequestMapping注解:位于SpringMVC控制器方法定义上方,设置当前控制器方法请求访问路径
- @ResponsBody注解:SpringMVC控制器方法定义上方,设置当前控制器方法响应内容为当前返回值。
SpringMVC工作流程:
- 服务器启动,执行ServletContainersInitConfig类,初始化wen容器
- 执行creatServletApplicationContext方法,创建了WebApplicationContext对象
- 加载SpringMvcConfig
- 执行@ComponentScan加载对饮bean
- 加载UserController,每个@RequestMapping的名称对应一个具体的方法
- 执行getServletMappings方法,定义所有的请求都通过SpringMVC
单次请求过程:
- 发送请求locahost/save
- web容器发现所有请求都经过SpringMVC,将请求交给SpringMVC处理
- 解析亲求路径/save
- 由/save匹配执行对应的方法save()
- 执行save()
- 检测到有@ResponseBody直接将save()方法的返回值作为响应体返回给请求方
SpringMVC容器bean的管理
springMVC与spring都有各自的容器加载bean
- springmvc加载controller层的相关Bean
- spring加载
- 业务bean(Service)
- 功能bean(DataSourece等)
那么因功能不同,如何避免Spring错误的加载到SpringMVC的bean?
- 加载Spring控制的bean的时候排除掉SpringMVC的bean
解决:
- 对于SpringMVC而言,它需要加载的bean所在的包是com.peng.controller下的包,避免加载spring容器所管理的包
- 而对于Spring的相关bean加载控制
- 方式一:Spring加载的Bean设定范围为com.peng,排除掉controller包内的bean
- 方式二:Spring加载的bean设定扫描范围为精准范围,例如service包,dao包等。
@ComponentScan注解中有关于排除bean的参数
- 设置该参数,设定其排除类型
- FilterType.ANNOTATION:按注解过滤
- classes = Controller.class,设定那种注解。
springMVC加载的配置bean:
public class ServletContainerslnitConfig extends AbstractDispatcherServletInitializer {
@Override
protected WebApplicationContext createServletApplicationContext() {
AnnotationConfigWebApplicationContext acwac = new AnnotationConfigWebApplicationContext();
acwac.register(SpringMvcConfig.class);
return acwac;
}
@Override
protected String[] getServletMappings() {
return new String[]{"/"};
}
@Override
protected WebApplicationContext createRootApplicationContext() {
AnnotationConfigWebApplicationContext acwac = new AnnotationConfigWebApplicationContext();
acwac.register(SpringConfig.class);
return acwac;
}
}
- createServletApplicationContext加载的springMVC的配置类
- createRootApplicationContext加载的spring的配置类
现在可以用AbstractDispatcherServletInitializer的子类AbstractAnnotationConfigDispatcherServletInitializer将上面的重复步骤简化。
public class ServletContainerslnitConfig extends AbstractAnnotationConfigDispatcherServletInitializer {
@Override
protected Class<?>[] getRootConfigClasses() {
return new Class[]{SpringConfig.class};
}
@Override
protected Class<?>[] getServletConfigClasses() {
return new Class[]{SpringMvcConfig.class};
}
@Override
protected String[] getServletMappings() {
return new String[]{"/"};
}
}
请求与响应
请求映射路径
- @RequestMapping,方法注解,类注解
- 作用在类上设置当前控制器方法请求访问路径前缀,作用在方法上设置请求访问路径
@Controller
@RequestMapping("/user")//定义请求路径前缀
public class UserController {
@RequestMapping("/save")
@ResponseBody
public String save(){
System.out.println("user save...");
return "{'module':'user save'}";
}
@RequestMapping("/delete")
@ResponseBody
public String delete(){
System.out.println("user delete...");
return "{'module':'user delete'}";
}
}
请求方式
- get
- post
普通参数传递
- 浏览器发送请求
- 控制层接收请求
@Controller
public class UserCommonParam {
@RequestMapping("/commonParam")
@ResponseBody
public String commonParam(String name,int age){
System.out.println("所接接收到的参数name==>"+name);
System.out.println("所接收到的参数age==>"+age);
return "{'module':common param'}";
}
}
变量名前面是我输出的乱码在idea中设置,我就先不设置了。这里主要解决后面从浏览器传递过来的参数乱码问题。
请求参数与响应方法接收参数不同名的情况
@RequestMapping("/commonParamDifferentName")
@ResponseBody
public String commonParamDifferentName(@RequestParam("name") String userName, int age){
System.out.println("普通参数传递 userName==>"+userName);
System.out.println("普通参数传递 age==>"+age);
return "{'module':'common param different name'}";
}
(@RequestParam("name") String userName, int age)使用该注解标明请求参数名所要传递的不同名参数。
POJO类型参数
//POJO参数
@RequestMapping("/pojoParam")
@ResponseBody
public String pojoParam(User user){
System.out.println("pojo参数传递 user==>"+user);
return "{'module':'pojo param'}";
}
所有,如果前面请请求送的参数名与响应方法参数中的pojo对象中的属性名一样,则就可以自动把请求发送对应的值装填到属性中。
POJO内嵌POJO类型参数
//嵌套POJO参数
@RequestMapping("/pojoContainPojoParam")
@ResponseBody
public String pojoContainPojoParam(User user){
System.out.println("pojo嵌套pojo参数传递 user==>"+user);
return "{'module':'pojo contain pojo param'}";
}
数组参数传递
//数组参数
@RequestMapping("/arrayParam")
@ResponseBody
public String arrayParam(String[] likes){
System.out.println("数组参数传递 likes==>"+ Arrays.toString(likes));
return "{'module':'array param'}";
}
必须保证发送请求的名称是一样的
集合参数传递
//集合参数
@RequestMapping("/listParam")
@ResponseBody
public String listParam(@RequestParam List<String> likes){
System.out.println("集合参数传递 likes==>"+likes);
return "{'module':'list param'}";
}
与数组不同的是,要使用 @RequestParam,来表示请求发送给的参数是作为响应体接收对象的值,而不是作为他的属性。
json数据的传递
通用操作:
- 导入读取json的jar包
<!-- 导入Json坐标-->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.9.0</version>
</dependency>
</dependencies>
- 在springmvc配置类中添加@EnableWebMvc注解
@Configuration
@ComponentScan("com.peng.controller")
//有将json数据转换为对象的可能
@EnableWebMvc
public class SpringMvcConfig {
}
- 用集合接收json数据
//集合参数:json格式
@RequestMapping("/listParamForJson")
@ResponseBody
public String listParamForJson(@RequestBody List<String> likes){
System.out.println("list common(json)参数传递 list==>"+likes);
return "{'module':'list common for json param'}";
}
发送请求
结果
- 用pojo接收json数据类型
//POJO参数:json格式
@RequestMapping("/pojoParamForJson")
@ResponseBody
public String pojoParamForJson(@RequestBody User user){
System.out.println("pojo(json)参数传递 user==>"+user);
return "{'module':'pojo for json param'}";
}
发送请求
- 如果集合中含有多个pojo对象的接收
//集合参数内含POJO
@RequestMapping("/listPojoParamForJson")
@ResponseBody
public String listPojoParamForJson(@RequestBody List<User> list){
System.out.println("list pojo(json)参数传递 list==>"+list);
return "{'module':'list pojo for json param'}";
}
注意点:
- 读取json数据需要用@RequestBody在方法参数前,与前面@RequestParam注解不同,因为,该注解是为了表明接收参数在请求的参数中localhost:8080/pojoParam?name=tkyd&age=21,而RequestBody是说明json数据是在请求的请求体中。
主要区别:
日期类型参数传递
//日期参数
@RequestMapping("/dataParam")
@ResponseBody
public String dataParam(Date date,
@DateTimeFormat(pattern = "yyyy-MMM-dd") Date date1,
@DateTimeFormat(pattern = "yyyy/MMM/dd HH:mm:ss") Date date2
){
System.out.println(date);
System.out.println(date1);
System.out.println(date2);
return "ok";
}
总结:
- java默认的日期格式是yyyy/mm/dd,如果不是该格式需要用 @DateTimeFormat注解来指定格式。
类型转换器
- Converter接口
public interface Converter<S,T>{
@Nullable
T conver(S var1);
}
具有很强大的类型转换格式功能
响应
- 响应页面
- 响应数据
- 文本数据
- json数据
- 跳转页面
//响应页面跳转
@RequestMapping("/toJumPage")
public String toJumPage(){
System.out.println("跳转页面");
return "page.jsp";
}
- 只需要添加一个 @RequestMapping指定要跳转时的路径即可,因为不用返回响应的具体值,所以不需要@ResponseBody注解。
- 且返回值就是所要跳转的页面名称
- 文本数据响应
//响应文本数据
@RequestMapping("/toText")
@ResponseBody
public String toText(){
System.out.println("返回纯文本数据");
return "response text";
}
所以需要有响应返回值的添加@ResponseBody,直接返回字符串类型。
- pojo数据响应
那么如果返回的是一个对象呢?
//响应POJO对象
@RequestMapping("/toJsonPOJO")
public User toJsonPOJO(){
System.out.println("返回json对象数据");
User user = new User();
user.setId(2);
user.setName("peng");
user.setPwd("23497218");
return user;
}
我们可以看到直接返回一个对象,返回的时一个json格式的数据
- 集合pojo数据响应
//响应POJO对象集合
@RequestMapping("toJsonList")
@ResponseBody
public List<User> toJsonList(){
System.out.println("返回json集合数据");
User user = new User();
user.setName("peng");
user.setId(3);
user.setPwd("324");
User user2 = new User();
user2.setName("pen");
user2.setId(2);
user2.setPwd("32gfh4");
User user3 = new User();
user3.setName("pe");
user3.setId(1);
user3.setPwd("3223524");
ArrayList<User> users = new ArrayList<>();
users.add(user);
users.add(user2);
users.add(user3);
return users;
}
REST风格
REST:表现形式状态转换
- 传统风格资源描述形式
- http://loclhost/user/getById?id=1
- REST风格描述形式
优点:
- 隐藏资源访问行为,无法通过地址得知对资源是如何操作的‘
- 书写简化
按照REST风格访问资源时使用行为动作区分对资源进行了何种操作
根据REST风格对资源进行访问称为RESTful
public class UserController {
@RequestMapping(value = "/users/{id}",method = RequestMethod.GET)
@ResponseBody
public String selectById(@PathVariable Integer id){
System.out.println("user"+id);
return null;
}
@RequestMapping(value = "/users",method = RequestMethod.GET)
@ResponseBody
public String select(){
System.out.println("save all...");
return null;
}
@RequestMapping(value = "/users",method = RequestMethod.POST)
@ResponseBody
public String save(){
System.out.println("user save...");
return "save";
}
@RequestMapping(value = "/users",method = RequestMethod.PUT)
@ResponseBody
public String update(){
System.out.println("user update...");
return "update";
}
@RequestMapping(value = "/users/{id}",method =RequestMethod.DELETE)
@ResponseBody
public String delete(@PathVariable Integer id){
System.out.println("user delete...");
return "delete";
}
}
REST风格步骤:
- 设定http请求动作,常用的有(post,get,put,delete)
格式:value = "/users",method = RequestMethod.PUT - 设定请求参数(路径变量)
- 路劲上要有这个变量的站位
- 在方法形参上要和这个变量名对应上
- 在方法形参上设定@PathVariable注解
@RequsetBody与RequsetParam与@PathVariable的区别
- @RequsetBody:用于接收url地址传参或表单路径
- @RequestParam:用于接收Json数据
- @PathVariable:用于接收路径参数,使用{参数名}描述路径参数
应用:
- 后期开发,发送请求参数超过一个,以json格式为主,@RequestBody应用较广
- 如果发送非json格式,选用@RequestParam接收请求参数
- 采用RESTful进行开发,当参数较少时,例如一个,采用@PathVariable接收路径变量,通常传递id值。
简化开发
//@Controller
//@RequestMapping("/books")
//@ResponseBody
@RestController("/books")
public class BookController {
// @RequestMapping(method = RequestMethod.POST)
@PostMapping
public String save(@RequestBody Book book){
System.out.println("book save...");
return "save";
}
// @RequestMapping(value = "/{id}",method = RequestMethod.DELETE)
@DeleteMapping("/{id}")
public String delete(@RequestBody Integer id){
System.out.println("book delete...");
return "delete";
}
// @RequestMapping(method = RequestMethod.PUT)
@PutMapping
public String update(@RequestBody Book book){
System.out.println("book update...");
return "update";
}
// @RequestMapping(value = "{id}",method = RequestMethod.GET)
@GetMapping("{id}")
public String select(@RequestBody Integer id){
System.out.println("book select...");
return "select";
}
// @RequestMapping(method = RequestMethod.GET)
@GetMapping
public String selectAll(){
System.out.println("all...");
return "all";
}
}
简化部分:
- 方法上的@PostMapping与RequestMapping("/books")可以放在类上,该类下的所有方法都有该注解
- 将@ResponseBody,@RequestMapping("/books"),@Controller组合放在了@RestController("/books")注解中
@Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented @Controller @ResponseBody public @interface RestController { String value() default ""; }
- 将@RequestMapping(method = RequestMethod.POST)用@PostMapping代替了,类似的get,delete一样。
SSM整合
- 初步整合,导入所需jar包和写响应配置类
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
<scope>test</scope>
</dependency>
<!-- spring容器所需坐标-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>4.3.11.RELEASE</version>
</dependency>
<!-- springmvc所需坐标-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>4.3.11.RELEASE</version>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.24</version>
</dependency>
<!-- mybatis所需坐标-->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.11</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>1.3.0</version>
</dependency>
<!-- 数据源jar包-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.2.16</version>
</dependency>
<!-- 导入Json坐标-->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.9.0</version>
</dependency>
</dependencies>
编写相应配置类**
- spring容器配置类
@Configuration
@ComponentScan({"com.peng.service"})
@PropertySource("jdbc.properties")
@Import({jdbcConfig.class,MybatisConfig.class})
public class SpringConfig {
}
分别是:声明该类为配置类,扫描业务层的包,导入外部配置文件,导入三方配置类
- 编写数据库相关的三方配置类,将有关对象放在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
public DataSource dataSource(){
DruidDataSource druidDataSource = new DruidDataSource();
druidDataSource.setDriverClassName(driver);
druidDataSource.setUrl(url);
druidDataSource.setUsername(username);
druidDataSource.setPassword(password);
return druidDataSource;
}
}
分别是,将数据源对象放到bean中,@Value导入加载的三方文件的配置信息注入到相关变量中。
MybatisConfig
public class MybatisConfig {
@Bean
public SqlSessionFactoryBean sqlSessionFactoryBean(DataSource dataSource){
SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
sqlSessionFactoryBean.setDataSource(dataSource);
sqlSessionFactoryBean.setTypeAliasesPackage("com.peng.pojo");
return sqlSessionFactoryBean;
}
@Bean
public MapperScannerConfigurer mapperScannerConfigurer(){
MapperScannerConfigurer msc=new MapperScannerConfigurer();
msc.setBasePackage("com.peng.dao");
return msc;
}
}
分别是:设定mybatis配置信息,必要的有类型别名,数据源的设定。然后返回SqlSessionFactoryBean对象加载到容器中。
再就设定mybatis映射配置。设定需要映射的数据层包,返回对象加载到bean中
接下来就是springmvc配置类
先是ServletConfig配置类
public class ServletConfig extends AbstractAnnotationConfigDispatcherServletInitializer {
@Override
protected Class<?>[] getRootConfigClasses() {
return new Class[]{SpringConfig.class};
}
@Override
protected Class<?>[] getServletConfigClasses() {
return new Class[]{SpringMvcConfig.class};
}
@Override
protected String[] getServletMappings() {
return new String[]{"/"};
}
}
直接继承AbstractAnnotationConfigDispatcherServletInitializer类重写其中的方法,getRootConfigClasses方法加载spring的配置类
getServletConfigClasses方法加载springmvc配置类
getServletMappings方法设定映射路径。
当然比较详细的可以继承AbstractDispatcherServletInitializer类。详细的展示出AnnotationConfigWebApplicationContext类是注册的spring与springmvc的配置类。
public class ServletContainerslnitConfig extends AbstractDispatcherServletInitializer {
@Override
protected WebApplicationContext createServletApplicationContext() {
AnnotationConfigWebApplicationContext acwac = new AnnotationConfigWebApplicationContext();
acwac.register(SpringMvcConfig.class);
return acwac;
}
@Override
protected String[] getServletMappings() {
return new String[]{"/"};
}
@Override
protected WebApplicationContext createRootApplicationContext() {
AnnotationConfigWebApplicationContext acwac = new AnnotationConfigWebApplicationContext();
acwac.register(SpringConfig.class);
return acwac;
}
}
最后是springmvc配置类
@Configuration
@ComponentScan("com.peng.controller")
@EnableWebMvc
public class SpringMvcConfig {
}
与spring配置类类似,与之不同的一点是用了@EnableWebMvc注解,该注解与web的开发有关系,比如前面所用到的将json数据转成对象。
- 设定各种的层级的类
先从底层数据层。DAO
设定pojo对象
@Data
public class Book {
private int id;
private String type;
private String name;
private String description;
}
设定DAO接口,用mybatis自动代理实现其实现类的sql语句.并加载到spring容器中
@Repository
public interface BookDao {
@Insert("insert into book(type,name,description) values(null,#{type},#{name},#{description}")
public void save(Book book);
@Update("update book set type=#{type},name=#{name},description=#{description} where id=#{id}")
public void update(Book book);
@Delete("delete from book where id=#{id}")
public void delete(Integer id);
@Select("select * from book where id=#{id}")
public Book getById(Integer id);
@Select("Select * from book")
public List<Book> getAll();
}
其次写业务层service
接口:加载到spring容器中
@Service
public interface BookService {
/**
* 保存
* @param book
* @return
*/
public boolean save(Book book);
/**
* 更新
* @param book
* @return
*/
public boolean update(Book book);
/**
* 删除
* @param id
* @return
*/
public boolean delete(Integer id);
/**
* 按id查询
* @param id
* @return
*/
public Book getById(Integer id);
/**
* 查询所有
* @return
*/
public List<Book> getAll();
}
预期impl实现类,自动装配DAO接口
public class BookServiceImpl implements BookService {
@Autowired
private BookDao bookDao;
@Override
public boolean save(Book book) {
bookDao.save(book);
return true;
}
@Override
public boolean update(Book book) {
bookDao.update(book);
return true;
}
@Override
public boolean delete(Integer id) {
bookDao.delete(id);
return true;
}
@Override
public Book getById(Integer id) {
return bookDao.getById(id);
}
@Override
public List<Book> getAll() {
return bookDao.getAll();
}
}
最后写控制层部分.
- 首先将其放在spring容器中
- 其次设定一个主路径@RequestMapping("/books")
- 然后自动装配service对象,调用service中的方法
- 然后用REST风格对其发送不同方式的请求
- 在其次对于请求过来的参数,以不同的方式接收。例如对于单个参数在url地址中的可以用@RequestParam,而对于传来的对象或json数据类型用@RequestBody接收。
@RestController
@RequestMapping("/books")
public class BookController {
@Autowired
private BookService bookService;
@PostMapping
public boolean save(@RequestBody Book book) {
bookService.save(book);
return true;
}
@PutMapping
public boolean update(@RequestBody Book book) {
bookService.update(book);
return true;
}
@DeleteMapping("/{id}")
public boolean delete(@RequestParam Integer id) {
bookService.delete(id);
return true;
}
@GetMapping("/{id}")
public Book getById(@RequestParam Integer id) {
return bookService.getById(id);
}
@GetMapping
public List<Book> getAll() {
return bookService.getAll();
}
}
表现层数据封装
对于后端传到前端的格式多种多样:
前端无法处理,需要对数据统一格式.
- code编码对应相关方法操作(关键字)
- data,将方法的放回值放到data中,前端人员只需取data中的值即可
- msg,返回相关信息。
可以用统一的返回结果类
public class Result{
private Object data;
private Integer code;
private String msg;
}
具体实现:
- 创建具体的结果类
@Data
public class Result {
private Object data;
private Integer code;
private String msg;
public Result(Object data, Integer code, String msg) {
this.data = data;
this.code = code;
this.msg = msg;
}
public Result(Object data, Integer code) {
this.data = data;
this.code = code;
}
public Result() {
}
}
- 创建具体的code类:
public class Code {
public static final Integer SAVE_OK=20011;
public static final Integer DELETE_OK=20021;
public static final Integer UPDATE_OK=20031;
public static final Integer SELECT_OK=20041;
public static final Integer SAVE_ERR=20010;
public static final Integer DELETE_ERR=20020;
public static final Integer UPDATE_ERR=20030;
public static final Integer SELECT_ERR=20040;
}
- 对于表现层是通过controller层传递到前端的,所以要对前端的返回值进行更改
@RestController
@RequestMapping("/books")
public class BookController {
@Autowired
private BookService bookService;
@PostMapping
public Result save(@RequestBody Book book) {
boolean flag=bookService.save(book);
return new Result(flag,flag ? Code.SAVE_OK:Code.SAVE_ERR);
}
@PutMapping
public Result update(@RequestBody Book book) {
boolean flag=bookService.update(book);
return new Result(flag,flag ? Code.UPDATE_OK:Code.UPDATE_ERR);
}
@DeleteMapping("/{id}")
public Result delete(@RequestParam Integer id) {
boolean flag=bookService.delete(id);
return new Result(flag,flag? Code.DELETE_OK:Code.DELETE_ERR);
}
@GetMapping("/{id}")
public Result getById(@RequestParam Integer id) {
Book book=bookService.getById(id);
Integer code=book!=null? Code.SELECT_OK:Code.SELECT_ERR;
String msg=book!=null ? "":"数据查询失败请重试";
return new Result(book,code,msg);
}
@GetMapping
public Result getAll() {
List<Book> list=bookService.getAll();
Integer code= list!=null?Code.SELECT_OK:Code.SELECT_ERR;
String msg= list!=null? "":"数据查询失败";
return new Result(list,code,msg);
}
}
最后进行增删改查操作,返回的就是结果类对象,前端根据code码或者nmsg信息判断是否取出数据,如果取出成功,则可以在data属性中举出对应数据。
异常处理器
对于这么多的异常我们应该统一处理,所有异常均抛到表现层进行处理
表现层异常处理,每一个方法中单独书写,代码书写量大且意义不大,应该通过AOP思想解决。
Springmvc中就给了一个异常处理器,运用AOP思想用统一书写格式处理异常。
- 集中的,统一的处理项目中从出现的异常。
具体操作
//处理REST风格Controller的通知
@RestControllerAdvice
public class ProjectExceptionAdvice {
//处理Exception异常
@ExceptionHandler(Exception.class)
public Result doException(Exception ex){
System.out.println("抓到了该异常"+ex);
//出现异常如果想让前端接收,就可以返回一个Result值,这里因为没有定义对应的code值,所以就随便返回了。
return new Result(666,null);
}
}
- @RestControllerAdvice标注,对应的REST风格,如果不是则使用@ControllerAdvice.
- 传递对应形参为所获取的异常类
- 用@ExceptionHandler(Exception.class)标明是要获取哪一个异常类。
- 如果想要返回给前端接收异常信息,这里就是需要返回一个Resulet对象即可.
- 本质使用过aop思想springmvc框架提供的,所以在springmvc扫描时要对该类进行扫描加载。
项目异常处理方案
项目异常分类
- 业务异常(BusinessException)
- 规范的用户行为产生的异常
- 不规范的用户行为产生的异常
- 处理:发送对应消息传递给用户,提醒规范操作
- 系统异常(SystemException)
- 项目运行过程中可预计且无法避免的异常
- 处理:发送固定消息传递给用户,安抚用户
- 发送特定消息给运维人员,提醒维护
- 记录日志
- 处理:发送固定消息传递给用户,安抚用户
- 项目运行过程中可预计且无法避免的异常
- 其他异常(Exception)
- 编程人员未预期的异常
- 处理:发送固定消息传递给用户,安抚用户
- 发送特定消息给编程人员,提醒维护
- 记录日志
- 处理:发送固定消息传递给用户,安抚用户
- 编程人员未预期的异常
具体操作
- 分类,自定义项目的系统和业务异常
//自定义异常
public class SystemException extends RuntimeException{
private Integer code;
public SystemException(String message, Integer code) {
super(message);
this.code = code;
}
public SystemException(String message, Throwable cause, Integer code) {
super(message, cause);
this.code = code;
}
public Integer getCode() {
return code;
}
public void setCode(Integer code) {
this.code = code;
}
}
分别设定对应的code值
public class BusinessException extends RuntimeException{
private Integer code;
public BusinessException(Integer code) {
this.code = code;
}
public BusinessException(String message, Integer code) {
super(message);
this.code = code;
}
public BusinessException(String message, Throwable cause, Integer code) {
super(message, cause);
this.code = code;
}
public Integer getCode() {
return code;
}
public void setCode(Integer code) {
this.code = code;
}
}
对应编码设定
public static final Integer SYSTEM_ERR=50001;
public static final Integer BUSINESS_ERR=50002;
public static final Integer SYSTEM_UNKNOW_ERR=59999;
- 激活自定义异常,将可能出现的异常进行包装
@Override
public Book getById(Integer id) {
if(id!=1){
throw new BusinessException("请不要用你的技术挑战我的耐心",Code.BUSINESS_ERR);
}
//业务层出现异常,将可能出现的异常进行包装,转换为自定义异常
try{
int i=1/0;
}catch (Exception e){
throw new SystemException("服务器访问超市请重试",e,Code.SYSTEM_ERR);
}
return b
}
- 项目异常处理,用springmvc的异常处理器拦截并处理
@RestControllerAdvice
public class ProjectExceptionAdvice {
//处理系统异常
@ExceptionHandler(SystemException.class)
public Result doException(SystemException ex){
System.out.println("抓到了该异常"+ex);
//记录日志
//发送消息给运维
//发送邮件给开发人员
return new Result(ex.getCode(),null,ex.getMessage());
}
//处理业务异常
@ExceptionHandler(BusinessException.class)
public Result doException(BusinessException ex){
System.out.println("抓到了该异常"+ex);
return new Result(ex.getCode(),null,ex.getMessage());
}
//处理Exception异常
@ExceptionHandler(Exception.class)
public Result doException(Exception ex){
System.out.println("抓到了该异常"+ex);
//出现异常如果想让前端接收,就可以返回一个Result值,这里因为没有定义对应的code值,所以就随便返回了。
//记录日志
//发送消息给运维
//发送邮件给开发人员
return new Result(Code.SYSTEM_UNKNOW_ERR,null,"系统繁忙请稍后再试");
}
}
拦截器
访问网站大致过程:
在浏览器访问后,将动态资源首先经过滤器过滤后交给spring的中央控制器,在经过拦截器进行过滤后交给控制层处理,处理后在由拦截器过滤一遍最后交给浏览器。
-
拦截器是一种动态方法调用的机制,在SpringMVC中动态拦截控制器方法的执行。
-
作用:
- 在指定方法调用前后执行预先设定的代码
- 阻止原始放方法的执行
-
拦截器与过滤器的区别:
- 归属不同:Filter属于Servlet技术,Intrerceptor属于SpringMVC技术
- 拦截内容不同:Filter对所有访问进行增强,Interceptor仅针对SpringMVC的访问增强.
具体实现过程:
- 创建拦截器专属的类,因为是拦截控制层的操作,所所以推荐放在控制层.
@Component
public class ProjectInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o) throws Exception {
System.out.println("perHandle");
return true;
}
@Override
public void postHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, ModelAndView modelAndView) throws Exception {
System.out.println("postHandle");
}
@Override
public void afterCompletion(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) throws Exception {
System.out.println("afterCompletion");
}
}
- 实现HandlerInterceptor 接口。重写它的三个方法
- 拦截器注册
@Configuration
//对webapp中的静态资源管理
public class SpringMvcSupport extends WebMvcConfigurationSupport {
@Autowired
private ProjectInterceptor projectInterceptor;
@Override
protected void addResourceHandlers(ResourceHandlerRegistry registry) {
//当访问/pages/??的时候不要走mvc,走/pages目录下的内容
registry.addResourceHandler("/pages/**").addResourceLocations("/pages/");
registry.addResourceHandler("/js/**").addResourceLocations("/js/");
registry.addResourceHandler("/css/**").addResourceLocations("/css/");
registry.addResourceHandler("/plugins/**").addResourceLocations("/plugins/");
}
@Override
protected void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(projectInterceptor).addPathPatterns("/books");
}
}
- addResourceHandlers方法是前面的静态资源注册。
- addInterceptors方法是拦截器的注册。这里需要使用前面实现拦截器的实现类,所以用自动装配该类进行注册。在使用addPathPatterns对要进行拦截的页面拦截操作。
测试结果可以看出,拦截器实现类的方法执行步骤是:
- preHandle方法在拦截的方法前执行
- postHandle方法在拦截的方法后执行
- 最后执行的就是afterCompletion方法
注意:
- public boolean preHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o) throws Exception { retrun true}方法最后返回的是true。
- 如果改为false的话,则不会执行待拦截的方法及其拦截后的拦截方法操作。
即执行结果:
所以返回值未false就可拦截原始方法.
拦截器参数
-
对于httpServletRequest,HttpServletResponse参数可以获取请求和响应的值。
@Override public boolean preHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o) throws Exception { String contentType = httpServletRequest.getHeader("Content-Type"); System.out.println("perHandle.."+contentType); return false; }
-
object对象o。通过o.class查看出该对象的类是HandlerMethod类
可以获取到method类,通过反射获得原始类@Override public boolean preHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o) throws Exception { String contentType = httpServletRequest.getHeader("Content-Type"); System.out.println("perHandle.."+contentType); HandlerMethod handler = (HandlerMethod) o; Method method = handler.getMethod(); System.out.println(method); return false; }
-
ModelAndView。封装springmvc的页面跳转。
-
ex可以拿到原始程序所抛出的异常。
多拦截器
- 当配置多个拦截器时,形成拦截器链
创建:
注册(别忘了自动装配)
@Override
protected void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(projectInterceptor).addPathPatterns("/books");
registry.addInterceptor(projectInterceptor2).addPathPatterns("/books");
}
测试执行:
- 不难看出preHandle()方法是按照在SpringMvcSupport中的注册顺序执行的。
- 而postHandle和afterCompletion的执行顺序则和注册顺序相反。
类似于栈的执行添加和删除顺序 。
注意点:如果在一拦截器中的preHandle方法的返回值时false的话,则不仅会拦截一中的后面拦截方法,同时在一拦截器后面注册的其他拦截器也会被拦截。
具体总结:
Maven进阶
分模块开发意义:
- 将原始模块按照功能拆分成若干个子模块,方便模块间的项目调用,接口共享。
实操:
-
分离pojo模块
-
创建一个新模块,可以看到,新模块中需要添加原pojo所使用的依赖,且原模块分离了pojo,所以无法正常运行。
-
对原模块所需依赖进行添加
-
对原项目添加pojo依赖,使原项目脱离了pojo模块依然能够运行。
添加pojo依赖
-
但是却创建失败。
- 分析原因,找不到该分离模块的依赖。查看maven仓库。
可以开到在pom.xml中的依赖在maven仓库中都有下载。但是却找不到分离模块的依赖下载。<groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>4.3.11.RELEASE</version>
所以需要将分离模块的依赖下载到本地maven仓库中.<artifactId>SpringMVCstudyu2</artifactId> <groupId>com.peng</groupId> <version>1.0-SNAPSHOT</version>
- 分析原因,找不到该分离模块的依赖。查看maven仓库。
-
将分离模块下载到本地maven仓库中
此时在执行原项目就会建立成功. -
注意点,我在5中下载打包pojo项目使用插件中的install时出现了一些问题,解决这个问题我读了该博客:https://blog.csdn.net/gao_zhennan/article/details/89713407
-
依赖管理
现在我在创建一个dao模块
这时原模块中的pom文件中就由pojo和dao模块的依赖,现在我们删掉pojo模块的依赖,构建原模块发现成功建立。
查看ssm2的依赖关系。是因为dao中也依赖了dao的依赖。所以无需再ssm2中添加依赖即可使用dao中的依赖。这种关系我们称为依赖传递。
依赖冲突问题
- 路径优先:当依赖中出现相同的资源时,层级越深,优先级越低
- 声明优先:当资源再相同层级被依赖时,配置顺序靠前的覆盖配置孙旭靠后的。
- 特殊优先:当同级配置了相同资源的不同版本,后配置的覆盖先配置的。
如果依赖发生冲突:
- 可以通过手动添加或修改删除。
- 亦或者再冲突的依赖下(所引用的依赖)使用optional>标签,对该依赖隐藏
例如我现在对dao分离模块的mybatis依赖隐藏
<!-- mybatis所需坐标-->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.11</version>
<optional>true</optional>
</dependency>
则再其他模块使用dao依赖时,是加载不到mybaits依赖的。而失去依赖传递性。
- 如果再你自己的项目中有再使用别人的依赖时别人中的其他依赖对你的项目产生影响,你需要隐藏你的依赖下的其他依赖时。
只能对你该项目的依赖隐藏,而不能对依赖下的其他依赖作用,所以这里要是用 exclusion>标签主动将该依赖下的其他依赖排除
<dependency>
<groupId>com.peng</groupId>
<artifactId>spring_ssm2_dao</artifactId>
<version>1.0-SNAPSHOT</version>
<exclusions>
<exclusion>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
</exclusion>
</exclusions>
</dependency>
这里排除了dao模块下的log4j依赖
- optional>标签是让我们的依赖隐藏,在别人使用时不能用该依赖
- exclusions>标签是主动将我们依赖下的其他被人创建的依赖排除。
多模块开发的继承和聚合
聚合:
- 将多个模块组织成一个整体,同时进行项目构建的过程称为聚合
- 聚合工程:通常是一个不具有业务功能的“空”工程(有且仅有一个pom文件)
- 作用:使用聚合工程可以将多个工程编组,通过对聚合工程进行构建,实现对所含的模块进行同步构建
- 当工程中的某个模块发生更新时,必须保障工程中已更新模块关联的模块同步更新,此时使用聚合工程来解决批量模块同步构建的问题。
聚合项目构建:
- 创建一个新的模块
- 再该模块中的pom.xml中编写新的打包方式
<!-- 聚合工程的打包方式-->
<packaging>pom</packaging>
- 再在pom.xml文件中设置管理模块名称
<!-- 设置管理的模块名称-->
<modules>
<module>../spring_ssm2</module>
<module>../spring_ssm2_pojo</module>
<module>../spring_ssm2_dao</module>
</modules>
- 这里构建聚合项目就完成了,最后测试一下
继承:
- 继承描述的是两个工程间的关系,与java中的继承相似,子工程可以继承父工程中的配置信息,常见于依赖关系的继承。
- 作用:
- 简化配置
- 减少版本冲突
继承操作:
继承和聚合一般是一起用的,所以继承操作我就用上面聚合的项目
- 再要继承该聚合项目的子项目中添加parent>标签
<parent>
<groupId>com.peng</groupId>
<artifactId>maven_01_parent</artifactId>
<version>1.0-SNAPSHOT</version>
<!-- 相对路径-->
<relativePath>../maven_01_parent/pom.xml</relativePath>
</parent>
这样添加了这个的项目就与artifactId中的项目建立了继承关系
2. 将该项目下的子项目中比较通用的依赖全部移到父项目中。
例如,junit就是比较通用的项目就可以移到父项目的依赖中。
而这个spring_ssm_2_dao多模块并不是所有子项目都用得到的就不移动。
添加完后:
父项目的依赖与子项目也可以继承。
这时如果改变父项目中的依赖版本,子项目依赖版本就会被异同操控。
避免了多个项目重复添加重复依赖,且相同依赖但使用版本不同的情况。
- 如果有父项目添加的依赖并不都是所有子项目都用,而是部分子项目用,那么不用的子项目可以通过依赖管理标签选择添加或者不添加依赖
- 再父项目中添加依赖管理:
<!-- 定义依赖管理--> <dependencyManagement> <dependencies> <!--测试所需jar包--> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.11</version> <scope>test</scope> </dependency> </dependencies> </dependencyManagement>
- 需要用的子项目可以手动添加该项目
注意这里并没指定版本,也不能指定版本,版本是由父项目统一管理的。<dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <scope>test</scope> </dependency>
继承与聚合
- 作用:
- 聚合用于快速构建项目
- 继承用于快速配置
- 相同点:
- 聚合与继承的pom.xml文件打包方式相同,可以将两种关系制作到同一pom.xml文件中
- 聚合与继承均属于设计型模块,并无实际的模块内容
- 不同点:
- 聚合是当前模块中配置关系,聚合可以感知到参与聚合的模块有哪些
- 继承是在子模块中配置关系,父模块无法感知哪些子模块继承了自己。
属性
再pom.xml文件中第一个变量表示不同依赖的版本值.
<!-- 定义属性-->
<properties>
<spring.version>4.3.11.RELEASE</spring.version>
<json.version>2.9.0</json.version>
</properties>
将对应依赖的版本改为变量名
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>${spring.version}</version>
</dependency>
<!-- spring容器所需坐标-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${spring.version}</version>
</dependency>
<!-- springmvc所需坐标-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>${spring.version}</version>
</dependency>
这样我们就可以更加简便的更改版本信息。
配置文件加载属性
- 定义属性
<!-- 定义属性-->
<properties>
<spring.version>4.3.11.RELEASE</spring.version>
<json.version>2.9.0</json.version>
<jdbc.url>jdbc:mysql://localhost:3306/ssm_db?useSSL=false</jdbc.url>
</properties>
- 使用变量
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=${jdbc.url}
jdbc.username=root
jdbc.password=1094148867g
- 开启资源文件目录加载属性的过滤器
<build>
<resources>
<resource>
<directory>${project.basedir}/src/main/resources</directory>
<filtering>true</filtering>
</resource>
</resources>
</build>
再directory标签中有一个${project.basedir}。这是maven中的系统内置属性
maven属性:
- 自定义属性(常用)
- 内置属性
- Setting属性
- Java系统属性
- 环境变量属性
版本管理
多环境开发
maven提供了配置多种环境的设定,帮助开发者使用过程中快速切换环境。
做好一个项目后我们需要再不同的环境中运行。
设定不同环境:
<!-- 配置多环境-->
<profiles>
<!-- 开发环境-->
<profile>
<id>env_dep</id>
<!-- 再该环境下使用的属性-->
<properties>
<jdbc.url>jdbc:mysql://127.0.0.1:3306/ssm_db?useSSL=false</jdbc.url>
</properties>
<!-- 设定是否为默认启动的环境-->
<activation>
<activeByDefault>true</activeByDefault>
</activation>
</profile>
<!-- 生产环境-->
<profile>
<id>env_pro</id>
<!-- 再该环境下使用的属性-->
<properties>
<jdbc.url>jdbc:mysql://127.2.2.2:3306/ssm_db?useSSL=false</jdbc.url>
</properties>
</profile>
<!-- 测试环境-->
<profile>
<id>env_test</id>
<!-- 再该环境下使用的属性-->
<properties>
<jdbc.url>jdbc:mysql://127.3.3.3:3306/ssm_db?useSSL=false</jdbc.url>
</properties>
</profile>
</profiles>
可在不同环境中设定该环境中所使用的变量。
mvn -p 环境名 来切换环境运行。
跳过测试
也可以具体设定要跳过的部分,不用全部跳过。
<!-- 跳过测试,设定测试插件,指定跳过部分-->
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.12.4</version>
<!-- 是否跳过测试,如果跳过则全跳过,否则指定需要跳过的Test部分-->
<configuration>
<skipTests>false</skipTests>
<excludes>
<exclude>
**/BookServiceTest.java
</exclude>
</excludes>
</configuration>
</plugin>
私服
私服就是一台独立的服务器,用于解决团队内部的资源共享与资源同步问题。
Nexus私服设置软件
再 E:\SpringMVCstudyu2\nexus-3.20.1-01\bin文件下执行 nexus.exe /run nexus
再浏览器:localhost:8081访问
私服仓库分类:
- 宿主仓库:hosted,保存自主研发+三方资源,上传
- 代理仓库:proxy,代理连接中央仓库,下载
- 仓库组:group,为仓库编组简化下载操作,下载.
- 再本地仓库maven仓库中的setting配置文件下配置私服访问权限.
<server>
<id>私服中的服务器id名称</id>
<username>admin</username>
<password>134</password>
</server>
- 再再镜像标签下配置私服的访问路径
<mirror>
<id>私服id名</id>
<mirorOf>*</mirrorOf>
<url>私服地址</url>
</mirror>
- 再idea中设置将文件打包下载到私服
- 再pom文件下设置
<distributionManagement> <!--临死仓库--> <!-- <snapshotRepository></snapshotRepository>--> <!--正式仓库--> <repository> <id>私服名称</id> <url>私服地址</url> </repository> </distributionManagement>
- diploy 发布到私服
- 再pom文件下设置
SpringBoot
springBoot是由Privotal团队提供的全新框架,其设计目的是用来简化Spring应用的初始化搭建以及开发过程
案例:
- 创建一个新的模块
- 选择当前模块需要的技术集
- 开发控制器类
- 运行自主生成的Application类
前后端分离,后端程序打成jar包可不需要tomcat,idea运行。
- 对springBoot项目打包(执行Maven构建指令package)
- 执行启动指令,再打包的jar包所在目录下的cmd中进行
注意事项:jar支持命令启动需要依赖Maven插件支持,请确认打包时是否具有SpringBoor对应的maven插件java -jar springboot.jar
这样就可脱离idea运行boot程序
springBoot概述
起步依赖
再父项目中的spring-boot-start-parent中配置了各种插件,且又在其父项目依赖spring-boot-start-dependencies中配置类各种版本信息
和各种版本的依赖
所以所具体的依赖就就不写,直接可以写起步依赖即可:
- start :SpringBoot中常见项目名称,定义了当前项目使用的所有项目坐标,以达到减少依赖配置的目的
- parent
- 所有SpringBoot项目要继承的项目,定义了若干个坐标版本号(依赖管理,而给依赖),以达到减少依赖冲突的目的。
- spring-boot-starter-parent(2.5.0)与spring-boot-starter-parent(2.4.6)共计57处坐标版本不同
- 实际开发
- 使用任意坐标时,仅写GAV中的G,A。V由SpringBoot提供
- 如果发生坐标错误,再指定version
辅助功能
starter-web中由starter-tomcat起步依赖,是用来辅助程序启动的
启动方式
再创建springboot项目的时候,会自动创建一个Application类来引导程序启动
- SpringBoot再创建项目时,采用jar的打包方式
- SpringBoot的引导类是项目的入口,运行main方法就可以启动项目
替换boot内置服务器tomcat为jeety
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<!-- 替换boot内置服务器-->
<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>
排除starter-web中的tomcat。添加jetty依赖
基础配置
修改服务器端口,通过配置文件修改
springboot提供了多种属性配置方式。
如果配置文件没有添加到工程下,配置文件就不会有提示效果。需要手动添加到工程下将配置文件。
优先级:properties>yml>yaml
ymal语法规则:
- 大小写敏感
- 属性层级关系使用多行描述,每行结尾使用冒号结束
- 使用缩进表示层机关系,同层级左侧对齐,只允许使用空格(不允许使用Tab键)
- 属性值前面添加空格(属性名与属性值之间使用冒号+空格作为分隔)
-
表示注释
- 数据前面加空格与冒号隔开
数组数据再数据书写位置的下方使用减号作为数据开始的符号,每行书写一个数据,减号与数据间空格分隔。
读取使用yml等配置文件
- @Value直接读取:@Value("${一级属性名.二级属性名...}")
@Value("${lesson}") private String lesson; @Value("${server.port}") private Integer port; @Value("${enterprise.subject[0]}") private String subject;
- 使用Enviroment对象,将数据全部封装到该对象中
@Autowired private Environment environment;
- 定义一个类去读取yml中的信息(重点)
必须使用@Component注解加载到spring容器中,再使用@ConfigurationProperties注解用来读取配置文件,并prefix属性表明要读取的属性。@Data @Component @ConfigurationProperties(prefix = "enterprise") public class Enterprise { private String name; private Integer age; private String tel; private String[] subject; }
@Autowired private Enterprise enterprise;
以上3种方法测试:
@RestController
@RequestMapping("/books")
public class BookCtroller {
@Value("${lesson}")
private String lesson;
@Value("${server.port}")
private Integer port;
@Value("${enterprise.subject[0]}")
private String subject;
@Autowired
private Environment environment;
@Autowired
private Enterprise enterprise;
@GetMapping("{id}")
public String getBook(@PathVariable Integer id){
System.out.println("id==>"+id);
System.out.println(lesson);
System.out.println(port);
System.out.println(subject);
System.out.println(".........................................");
System.out.println(environment.getProperty("lesson"));
System.out.println(environment.getProperty("enterprise.subject[1]"));
System.out.println("**************************************************************");
System.out.println(enterprise);
return "id==>"+id;
}
}
结果
注意:
自定义对象封装数据警告解决方案
<dependency>
<groupId>org.springframework.book</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
多环境开发
- 设置多环境端口 on-profile:设置该端口下的环境名
server:
port: 80
#开发环境
spring:
config:
activate:
on-profile: dev
- 用---来再同一yml文件中配置不同环境
server:
port: 80
#开发环境
spring:
config:
activate:
on-profile: dev
---
server:
port: 81
#生产环境
spring:
config:
activate:
on-profile: pro
---
server:
port: 82
#测试环境
spring:
config:
activate:
on-profile: test
- 启用环境
spring:
profiles:
#选择启用的环境
active: dev
---
server:
port: 80
#开发环境
spring:
config:
activate:
on-profile: dev
---
用properties也可设置多环境
- 启用环境
#设置启用的环境
spring.profiles.active=dev
-
不同环境设置
-
不同环境设置端口
server.port=80
多环境启动命令格式(切换不同环境去执行)
- 带参数启动SpringBoot
java -jar springboot.jar --spring.profiles.active=test
#临时参数修改配置
java -jar springboot.jar --server.port=88
java -jar springboot.jar --server.port=88 --spring.profiles.activee=test
优先级由低到高的配置属性
多环境开发maven与springboot兼容问题
maven中设置多种环境属性
<profiles>
<!-- 开发环境-->
<profile>
<id>dev</id>
<properties>
<profile.active>dev</profile.active>
</properties>
</profile>
<!-- 生产环境-->
<profile>
<id>pro</id>
<activation>
<activeByDefault>true</activeByDefault>
</activation>
<properties>
<profile.active>pro</profile.active>
</properties>
</profile>
<!-- 测试环境-->
<profile>
<id>test</id>
<properties>
<profile.active>test</profile.active>
</properties>
</profile>
</profiles>
SpringBoot中引用Maven属性
spring:
profiles:
#选择启用的环境
active: ${profile.active}
---
server:
port: 80
#开发环境
spring:
config:
activate:
on-profile: dev
---
server:
port: 81
#生产环境
spring:
config:
activate:
on-profile: pro
---
server:
port: 82
#测试环境
spring:
config:
activate:
on-profile: test
---
执行打包操作。
注意:Maven指令执行完毕后,生成了对应的包,其中类参与了编译,但是配置文件并没有编译,而是赋值到包中。
注意要添加插件,对资源文件开启默认占位符解析
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-resources-plugin</artifactId>
<version>3.3.0</version>
<configuration>
<encoding>UTF-8</encoding>
<useDefaultDelimiters>true</useDefaultDelimiters>
</configuration>
</plugin>
配置文件分类
- 一级和二级留做系统打包后设置通用属性
- 三级和四级用于系统开发阶段设置通用属性
整合
Junit
- springBoot整合JUnit
@SpringBootTest(classes = Springboot03Application.class)
class Springboot03ApplicationTests {
@Autowired
private BookService bookService;
@Test
void contextLoads() {
bookService.save();
}
}
@SpringBootTest,测试类注解,测试类定义上方。设置JUnit加载的SpringBoot启动类.
相关属性:
- classes:设置SpringBoot启动类
注意:如果测试类再SpringBoot启动类的包或子包中,可以省略启动类的设置,也就是省略classes的设定
Mybatis
springBoot不需要整合Spring和SpringMvc
复习:Spring整合MyBatis
- SpringConfig
- 导入JdbcConfig
- 导入MyBatisConfig
- JDBCConfig
- 定义数据源(加载properties配置项:driver,url,username,password)
- MyBatisConfig
-
定义SqlSessionFactroyBean
-
定义映射配置
-
springBoot整合MyBatis
- 创建新模块,选择Spring初始化,配置模块相关信息
- 再yml文件中设置数据配置信息及其相关数据源
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/ssm_db
username: root
password: 1094148867g
type: com.alibaba.druid.pool.DruidDataSource
如果没有想用的数据源选项需要手动导入
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.2.16</version>
</dependency>
- 定义数据层接口和映射配置
@Mapper
public interface BookDao {
@Select("select * from book where id=#{id}")
public Book GetById(Integer id);
}
这里需要使用@Mapper注解
观看gethub上的springBoot教程的补充
- SpringBoot配置类的部分解析
@SpringBootApplication
public class SpringBoot01HelloQuickApplication {
public static void main(String[] args) {
SpringApplication.run(SpringBoot01HelloQuickApplication.class, args);
}
}
@SpringBootApplication: 说明这个类是SpringBoot的主配置类,SpringBoot就应该运行这个类的main方法来启动应用.
现在进入该注解中:
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(
excludeFilters = {@Filter(
type = FilterType.CUSTOM,
classes = {TypeExcludeFilter.class}
), @Filter(
type = FilterType.CUSTOM,
classes = {AutoConfigurationExcludeFilter.class}
)}
)
public @interface SpringBootApplication {...}
除开部分元注解,@Target({ElementType.TYPE}),@Retention(RetentionPolicy.RUNTIME),@Documented,@Inherited。外
- @SpringBootConfiguration:SpringBoot的配置类: 标准在某个类上,表示这是一个SpringBoot的配置类
- @EnableAutoConfiguration:开启自动配置功能,springBoot自动配置
- 进入到该注解中:
@AutoConfigurationPackage @Import({AutoConfigurationImportSelector.class}) public @interface EnableAutoConfiguration
- @AutoConfigurationPackage:自动配置包@Import({Registrar.class}):底层注解,给容器导入组件; 将主配置类(@SpringBootApplication标注的类)的所在包及下面所有的子包里面的所有组件扫描到Spring容器。
- @Import({AutoConfigurationImportSelector.class}): 给容器导入组件
- AutoConfigurationImportSelector:导入组件选择器将所有需要导入的组件以及全类名的方式返回;这些组件将以字符串数组 String[] 添加到容器中.
- 进入到该注解中:
配置文件注入
-
ConfigurationProperties
- 创建yml或者properties配置文件
person: age: 18 boss: false birth: 2017/12/12 maps: {k1: v1,k2: 12} lists: - lisi - zhaoliu dog: name: wangwang age: 2 last-name: wanghuahua
- 建立javaBean将配置文件中的变量注入到java中
/** * 将配置文件的配置每个属性的值,映射到组件中 * @ConfigurationProperties:告诉SpringBoot将文本的所有属性和配置文件中的相关配置进行绑定; * prefix = "person" 配置文件爱你的那个属性进行一一映射 * * 只有这个组件是容器中的组件,才能提供到容器中 */ @Component @ConfigurationProperties(prefix = "person") public class Person { private String lastName; private Integer age; private Boolean boss; private Map<String,Object> maps; private List<Object> lists; private Dog dog;
扩:导入配置文件处理器,编写配置就有提示
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring‐boot‐configuration‐processor</artifactId> <optional>true</optional> </dependency>
- 创建yml或者properties配置文件
-
@Value注解
@Component public Class Person{ /** xml配置文件进行bean加载和属性配置 <bean> <property name="lastName" value="字面量/${key}从环境变量/#{spEL}"></property> <bean> **/ @Value("${person.lastName}") private String lastName; @Value("#{11*2}") private Integer age; @Valule("true") private Boolean boss; //不支持复炸类型 <!-- @Value("${person.maps}") private Map<String ,Object> maps; --> }
- @ConfigurationProperties注解只需要使用一次就可以了,@Value注解则需要每个字段都添加
- 松散绑定:yml中对应对象的属性写为:last-name,这个和lastName是一样的,后面跟着的字母默认时大写的,这就是松散绑定
- JSR303数据校验,这个就是我们可以在字段是增加一层过滤器验证,可以保证数据的合法性
- 复杂类型封装,yml可以封装对象,@Value不行。
JSR303数据验证注解: @Vaildated
对某个属性进行校验,以下是邮箱校验,该属性应该为邮箱格式,否则就会报错。
@Email
private String email;
使用Environment对象获取配置类
- 注入该对象
@Autowired
private Environment environment;
- 使用该对象调用配置文件信息
environment.getProperty("person")
总结:
- 我们再某个业务逻辑中获取一下配置文件中的某一项值,使用@Value.
- 如果专门编写了一个javaBean和配置文件进行映射,我们直接使用@ConfigurationProperties
- 其他类似注解
@PropertySource:加载指定的properties文件
@PropertySource(value = {"classpath:person.properties"})
@Component
@ConfigurationProperties(prefix = "person")
public class Person {
private String lastName;
...}
@ImportResource:导入Spring配置文件,并且让这个配置文件生效
以前加载bean的方式就是配置bean.xml配置文件,用该注解让配置文件生效。
-
编写一个Spring配置文件,bean.xml
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <bean id="HelloService" class="com.wdjr.springboot.service.HelloService"></bean> </beans>
-
使用@ImportResource注解
@ImportResource(locations={"classpath:beans.xml"}) @SpringBootApplication public class SpringBoot02ConfigApplication { public static void main(String[] args) { SpringApplication.run(SpringBoot02ConfigApplication.class, args); } }
这样做的缺点时,每次指定xml文件太麻烦。所以推荐使用@Configuration+@Bean的全注解方式。
- 用@Configuration来指明当前类是一个配置类,就是来替代之前的Spring配置文件。
@Configuration
public class MyAppConfig {
//将方法的返回值添加到容器中;容器这个组件id就是方法名
@Bean
public HelloService helloService01(){
System.out.println("配置类给容器添加了HelloService组件");
return new HelloService();
}
}
//测试
@Autowired
ApplicationContext ioc;
@Test
public void testHelloService(){
boolean b = ioc.containsBean("helloService01");
System.out.println(b);
}
配置文件占位符
- 随机数
${random.value} 、${random.int}、${random.long}
${random.int(10)}、${random.int[100,200]}
- 获取配置值
person.age=${random.int}
person.boss=false
person.last-name=张三${random.uuid}
person.maps.k1=v1
person.maps.k2=v2
person.lists=a,b,c
person.dog.name=${person.last-name}'s wanghuahu
person.dog.age=15
如果没有声明person.last-name则会报错,新声明的需要添加默认值。
person.dog.name=${person.last-name:tkyd}'s wanghuahu
则输出tkyd's wanghuahu
自动配置
- 自动配置原理
- SpringBoot启动的时候加载主配置类,开启自动配置功能,@EableAutoConfiguration
- @EnableAutoConfiguration作用:
- 利用AutoConfigurationImportSelector给容器中导入一些组件
- 可以查看selectImports()方法
- 获取候选的配置
List<String> configurations = this.getCandidateConfigurations(annotationMetadata, attributes);
- 扫描类路径下的
SpringFactoriesLoader.loadFactoryNames() 扫描所有jar包类路径下的 MATA-INF/spring.factories 把扫描到的这些文件的内容包装成properties对象 从properties中获取到EnableAutoConfiguration.class类(类名)对应的值,然后把他们添加到容器中
- 每一个自动配置类进行自动配置功能
以HttpEncodingAutoConfiguration为例分析自动配置原理
//表示这是一个配置类,和以前编写的配置文件一样,也可以给容器中添加组件;
@Configuration
//启动指定类的ConfigurationProperties功能;
//进入这个HttpProperties查看,将配置文件中对应的值和HttpProperties绑定起来;
//并把HttpProperties加入到ioc容器中
@EnableConfigurationProperties({HttpProperties.class})
//Spring底层@Conditional注解
//根据不同的条件判断,如果满足指定的条件,整个配置类里面的配置就会生效;
//这里的意思就是判断当前应用是否是web应用,如果是,当前配置类生效
@ConditionalOnWebApplication(
type = Type.SERVLET
)
//判断当前项目有没有这个类CharacterEncodingFilter;SpringMVC中进行乱码解决的过滤器;
@ConditionalOnClass({CharacterEncodingFilter.class})
//判断配置文件中是否存在某个配置:spring.http.encoding.enabled;
//如果不存在,判断也是成立的
//即使我们配置文件中不配置spring.http.encoding.enabled=true,也是默认生效的;
@ConditionalOnProperty(
prefix = "spring.http.encoding",
value = {"enabled"},
matchIfMissing = true
)
public class HttpEncodingAutoConfiguration {
//他已经和SpringBoot的配置文件映射了
private final Encoding properties;
//只有一个有参构造器的情况下,参数的值就会从容器中拿
public HttpEncodingAutoConfiguration(HttpProperties properties) {
this.properties = properties.getEncoding();
}
//给容器中添加一个组件,这个组件的某些值需要从properties中获取
@Bean
@ConditionalOnMissingBean //判断容器没有这个组件?
public CharacterEncodingFilter characterEncodingFilter() {
CharacterEncodingFilter filter = new OrderedCharacterEncodingFilter();
filter.setEncoding(this.properties.getCharset().name());
filter.setForceRequestEncoding(this.properties.shouldForce(org.springframework.boot.autoconfigure.http.HttpProperties.Encoding.Type.REQUEST));
filter.setForceResponseEncoding(this.properties.shouldForce(org.springframework.boot.autoconfigure.http.HttpProperties.Encoding.Type.RESPONSE));
return filter;
}
//。。。。。。。
}
总结:
- 一旦这个配置类生效,这个配置类就会给容器添加各种组件
- 这些组件的属性是从对应的properties类中获取的,这些类里面的每一个属性又是和配置文件绑定的
- 所有再配置文件种能配置的属性都是在XxxProperties类中封装着;
- 配置文件能配置什么就i可以参照某个功能对应这个属性类
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 【译】Visual Studio 中新的强大生产力特性
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义