目录
spring框架核心容器支持函数式风格GenericApplicationContext,lambda表达式
spring创建对象(小例)
通过.xml文件
<bean id="user" class="com.ms.spring5.User"/>
如何获取对象呢
//1.加载配置文件
ApplicationContext context = new ClassPathXmlApplicationContext("bean1.xml");
//2.获取配置创建的对象
User user = context.getBean("user",User.class);
System.out.println(user);
user.add();
IOC容器
概念
- 控制反转,把对象创建和对象之间的调用过程,交给 Spring 进行管理
- 使用 IOC 目的:为了耦合度降低
- 通过.xml得到对象案例就是 IOC 实现
原理
- xml解析、工厂模式,反射
工厂模式的小例
IOC的原理
IOC接口
IOC本质上就是一个工厂
sprig提供了两个IOC容器的实现(两个接口)
- BeanFactory——是spring内部使用的接口,不提供给开发人员使用,加载配置文件时候,不会创建对象,只有获取使用对象才会创建
- ApplicationContext——BeanFactory接口的子接口,更丰富,提供给开发人员使用,加载配置文件时候,就会把配置文件中的对象都创建
这种耗时的对象创建工作,服务器启动就应该被创建,所以建议使用ApplicationContext
ApplicationContext的两个实现类
这两个实现的主要区别就是在获取的时候所填的.xml文件名不同,前者要求全路径,后者要求相对路径(项目路径)
Bean管理操作
- spring创建对象
- spring注入属性
操作的方式主要有两种
- 基于xml文件实现
- 基于注解的方式实现
基于xml文件实现
bean标签的一些属性
id | 唯一标识 |
class | 类的全路径 |
name | 和id差不多 |
默认使用无参构造创建对象
注入属性
- DI:依赖注入,就是注入属性 1.使用set注入 2.使用有参构造注入
使用set注入
<bean id="user" class="com.ms.spring5.User">
<property name="userName" value="张三"/>
<property name="age" value="20"/>
</bean>
使用有参构造注入(默认使用无参构造创建对象)
构造函数有几个参数,那么就要写几个与之对应constructor-arg标签
<bean id="user" class="com.ms.spring5.User">
<constructor-arg name="userName" value="张三"/>
<constructor-arg index="1" value="20"/><!--也可以用index-->
</bean>
(1)字面量
1、null值
<bean id="user" class="com.ms.spring5.User">
<property name="userName">
<null/>
</property>
</bean>
2、属性值包含特殊符号
<bean id="user" class="com.ms.spring5.User">
<property name="userName">
<value>
<![CDATA[<<张三>>]]]><!--输入的内容为<<张三>>-->
</value>
</property>
</bean>
(2)外部bean
<bean id="userService" class="com.ms.service.UserService">
<property name="userDao" ref="userDao"/>
</bean>
<bean id="userDao" class="com.ms.dao.UserDaoImpl"/>
ApplicationContext context = new ClassPathXmlApplicationContext("bean1.xml");
UserService userService = context.getBean("userService", UserService.class);
userService.add();
UserService中要有UserDao的set方法
(3)内部bean
<bean id="user" class="com.ms.spring5.User">
<property name="userName" value="张三"/>
<property name="age" value="20"/>
<property name="info"><!--也可以通过ref外部bean的方式注入-->
<bean class="com.ms.spring5.Info"><!--可以不写id-->
<property name="news" value="你好"/>
<property name="number">
<null/>
</property>
</bean>
</property>
</bean>
(4)级联赋值
<bean id="user" class="com.ms.spring5.User">
<property name="userName" value="张三"/>
<property name="age" value="20"/>
<property name="info.news" value="你好"/><!--news要有get方法-->
</bean>
(5)集合属性
<bean id="user" class="com.ms.spring5.User">
<property name="userName" value="张三"/>
<property name="strings"><!--数组-->
<array>
<value>java</value>
<value>python</value>
</array>
</property>
<!--数组和list一样-->
<property name="list"><!--list-->
<list>
<value>java</value>
<value>python</value>
</list>
</property>
<property name="map"><!--map-->
<map>
<entry key="姓名" value="张三"/>
<entry key="性别" value="男"/>
</map>
</property>
<property name="set"><!--set-->
<set>
<value>mysql</value>
<value>redis</value>
</set>
</property>
</bean>
(6)集合中是对象
<bean id="user" class="com.ms.spring5.User">
<property name="userName" value="张三"/>
<property name="list">
<list>
<ref bean="info1"/>
<ref bean="info2"/>
</list>
</property>
</bean>
<bean id="info1" class="com.ms.spring5.Info">
<property name="news" value="你好"/>
<property name="number" value="20"/>
</bean>
<bean id="info2" class="com.ms.spring5.Info">
<property name="news" value="hello"/>
<property name="number" value="20"/>
</bean>
自动装配
概念:根据指定装配规则(属性名称或者属性类型),Spring 自动将匹配的属性值进行注入
- 根据属性名称
<!--实现自动装配 bean 标签属性 autowire,配置自动装配 autowire 属性常用两个值: byName 根据属性名称注入 ,注入值 bean 的 id 值和类属性名称一样 byType 根据属性类型注入 --> <bean id="emp" class="com.ms.autowire.Emp" autowire="byName"> <!--手动装配:<property name="dept" ref="dept"/>--> </bean> <bean id="dept" class="com.ms.autowire.Dept"/>
-
根据属性类型
<bean id="emp" class="com.ms.autowire.Emp" autowire="byType"/>
外部属性文件
以配置数据库为例
- 新建jdbc.properties文件
- 在配置文件中引入context名称空间
xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
- 配置数据库信息
<!--直接配置连接池--> <!--<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"> <property name="driverClassName" value="com.mysql.jdbc.Driver"/> <property name="url" value="jdbc:mysql://localhost:3306/userDb"/> <property name="username" value="root"/> <property name="password" value="root"/> </bean>--> <!--引入外部属性文件--> <context:property-placeholder location="classpath:jdbc.properties"/> <!--配置连接池--> <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"> <property name="driverClassName" value="${prop.driverClass}"/> <property name="url" value="${prop.url}"/> <property name="username" value="${prop.userName}"/> <property name="password" value="${prop.password}"/> </bean>
基于注解实现
bean管理
spring提供管理bean的注解,都可以用来创建bean的实例,混用也是可以的
- @Component
- @Service
- @Controller
- @Repository
基于注解实现对象的创建
- 开启扫描配置
1.引入context名称空间
xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
2.配置
<!--开启组件扫描配置--> <context:component-scan base-package="com.ms.service,com.ms.dao"/><!--,隔开--> <context:component-scan base-package="com.ms"/>
- 添加注解
package com.ms.service; import org.springframework.stereotype.Component; //value可以不写,默认是类名首字母小写 @Component(value = "userService")//<bean id="userService" /> public class UserService { public void add(){ System.out.println("service add()"); } }
- 测试
开启组件扫描的细节
<!--
use-default-filters:默认是true,使用默认的filter知道所有类扫描
false:不使用默认的,自己配置
-->
<context:component-scan base-package="com.ms" use-default-filters="false">
<context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/><!--只扫描带controller注解的类-->
</context:component-scan>
<context:component-scan base-package="com.ms">
<context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/><!--不扫描带controller注解的类-->
</context:component-scan>
基于注解实现属性注入
spring中提供的注解
- @Autowired 根据属性类型
- @Qualifier 根据属性名称
- @Resource 既可以根据类型,也可以根据名称 本身是javax中的
- @Value 普通类型
完全注解开发
创建配置类代替xml文件
1.配置类
package com.ms.config;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
@Configuration
@ComponentScan(basePackages = "com.ms")//配置作用的范围
public class SpringConfig {
}
2.测试
@Test
public void testService2(){
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(SpringConfig.class);
UserService userService = context.getBean("userService", UserService.class);
userService.add();
}
spring中的Bean
1、普通Bean 2、工厂Bean(FactoryBean)
-
普通 bean:在配置文件中定义 bean 类型就是返回类型
-
在配置文件定义 bean 类型可以和返回类型不一样
-
创建类,让这个类作为工厂 bean,实现接口 FactoryBean
-
实现接口里面的方法,在实现的方法中定义返回的 bean 类型
public class MyBean implements FactoryBean { //定义返回的对象 @Override public Object getObject() throws Exception { Info info = new Info(); info.setNews("你好啊啊"); info.setNumber(20); return info; } @Override public Class<?> getObjectType() { return null; } @Override public boolean isSingleton() { return FactoryBean.super.isSingleton(); } }
@Test public void myBean(){ ApplicationContext context = new ClassPathXmlApplicationContext("bean1.xml"); Info myBean = context.getBean("myBean", Info.class);//注意此时获取的bean.class System.out.println(myBean); }
-
bean的作用域
如何设置
bean标签的属性scope
prototype(多实例):调用getBean才会创建
singleton(单实例):spring加载配置文件,就会创建单实例对象
<bean id="user" class="com.ms.spring5.User" scope="prototype">
<property name="userName" value="张三"/>
<property name="age" value="20"/>
</bean>
bean的其他属性
- request:一次请求,每次创建对象放到request中
- session:一次会话,每次创建对象放到session中
bean的生命周期
- 通过构造器创建实例(也可以通过其他创建),无参构造
- 为bean的属性设置值和对其他bean的引用(set方法)
- before init:把bean的实例传递给bean后置处理器
- 调用bean的初始化方法(需要进行配置)
- after init:把bean的实例传递给bean后置处理器
- bean可以用了
- 容器关闭时,调用bean的销毁方法
Bean对象的代码
package com.ms.bean;
public class Orders {
private String name;
public Orders() {
System.out.println("第一步:调用bean的无参构造创建了实例");
}
public String getName() {
return name;
}
public void setName(String name) {
System.out.println("第二步:调用set方法设置属性的值");
this.name = name;
}
//创建执行初始化的方法
public void initMethod(){
System.out.println("第三步:执行初始化的方法");
}
//创建销毁时候的方法
public void destroyMethod(){
System.out.println("第五步:执行了销毁的方法");
}
}
后置处理器代码
package com.ms.bean;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.lang.Nullable;
public class MyBeanPost implements BeanPostProcessor {
@Nullable
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
System.out.println("初始化之前");
return bean;
}
@Nullable
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
System.out.println("初始化之后");
return bean;
}
}
xml配置
init-method:初始化执行的方法
destroy-method:销毁执行的方法
<bean id="orders" class="com.ms.bean.Orders" init-method="initMethod" destroy-method="destroyMethod">
<property name="name" value="手机"/>
</bean>
<!--配置后置处理器-->
<bean id="myBeanPost" class="com.ms.bean.MyBeanPost"/>
结果
AOP
概念
- 面向切面编程(方面),利用 AOP 可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。
- 通俗描述:不通过修改源代码方式,在主干功能里面添加新功能
底层与原理
使用动态代理
有接口的情况
使用JDK动态代理
无接口的情况
![](https://i-blog.csdnimg.cn/blog_migrate/52dbda5b9a8938df5bf8f049c81bff7f.png)
JDK动态代理简单实现
参数:
- 类加载器
- 增强方法所在的类所实现的接口,可以是多个
- 实现InvocationHandler,创建代理对象,用于增强
1、创建接口并实现
package com.ms;
public interface UserDao {
public int add(int a,int b);
public String update(String id);
}
package com.ms;
public class UserDaoImpl implements UserDao {
@Override
public int add(int a, int b) {
return a + b;
}
@Override
public String update(String id) {
return id;
}
}
2、使用proxy创建接口代理对象
package com.ms;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Arrays;
public class JDKProxy {
public static void main(String[] args) {
Class[] interfaces = {UserDao.class};
UserDaoImpl userDao = new UserDaoImpl();
UserDao dao = (UserDao) Proxy.newProxyInstance(JDKProxy.class.getClassLoader(), interfaces, new UserDaoProxy(userDao));
System.out.println("res:" + dao.add(1, 2));
}
}
class UserDaoProxy implements InvocationHandler {
//创建谁的代理对象,就把谁传递过来
//通过有参构造
private Object obj;
public UserDaoProxy(Object obj) {
this.obj = obj;
}
//增强的逻辑
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//方法之前
System.out.println("方法之前执行...,方法名:" + method.getName() + ",传递的参数:" + Arrays.toString(args));
//增强执行的方法
Object res = method.invoke(obj, args);
System.out.println("方法之后执行..." + obj);
//方法之后
return res;
}
}
AOP操作中的一些术语
1、连接点
一个类中哪些方法可以被增强,这些方法称为连接点
2、切入点
实际被真正增强的方法
3、通知(增强)
实际增强的部分逻辑称为通知
- 前置通知——方法之前执行
- 后置通知——方法之后执行
- 环绕通知——方法前后都执行
- 异常通知——出现异常执行
- 最终通知——finally
4、切面
把通知应用到切入点的过程
5、切入点表达式
作用:知道对哪个类的哪个方法进行增强
语法结构
execution([权限修饰符][返回类型][方法名称][参数列表])
例:execution(*com.ms.dao.UserDao.add(..))//对add方法增强
execution(*com.ms.dao.UserDao.*(..))//对所有方法增强
execution(*com.ms.dao.*.*(..))//对dao包中所有类的所有方法增强
AOP操作
spring框架一般都是基于AspectJ实现AOP操作
AspectJ不是spring的部分,独立的AOP框架,可以单独使用
如何实现
准备两个类User(需要被增强的类),UserProxy(增强类)
1、基于xml配置文件实现
Book类
package com.ms.aopxml;
public class Book {
public void buy() {
System.out.println("buy()");
}
}
BookProxy类
package com.ms.aopxml;
public class BookProxy {
public void before(){
System.out.println("before()");
}
}
结果
2、基于注解实现
①配置文件中开启注解扫描(也可以通过配置类)
②使用注解创建User和UserProxy对象
③在增强类上添加注解@Aspect
④在spring配置文件中开启生成代理对象
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">
<!--注解扫描-->
<context:component-scan base-package="com.ms.aop"/>
<!--开启Aspect生成代理对象-->
<aop:aspectj-autoproxy/>
</beans>
⑤配置不同类型的通知
在增强类中,在作为通知方法上面添加通知类型注解,使用切入点表达式配置
代码即结果
package com.ms.aop;
import org.springframework.stereotype.Component;
@Component
public class User {
public void add(){
System.out.println("add()");
}
}
package com.ms.aop;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
@Component
@Aspect//生成代理对象
public class UserProxy{
//前置通知
@Before(value = "execution(* com.ms.aop.User.add())")
public void before(){
System.out.println("before()");
}
//方法执行之后
@After(value = "execution(* com.ms.aop.User.add())")
public void after(){
System.out.println("after()");
}
//方法给出返回值之后,无论有无异常都执行
@AfterReturning(value = "execution(* com.ms.aop.User.add())")
public void afterReturn(){
System.out.println("afterReturn()");
}
//有异常执行
@AfterThrowing(value = "execution(* com.ms.aop.User.add())")
public void afterThrow(){
System.out.println("afterThrow()");
}
@Around(value = "execution(* com.ms.aop.User.add())")
public void around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
System.out.println("环绕之前()");
proceedingJoinPoint.proceed();//执行被增强的方法
System.out.println("环绕之后()");
}
}
相同切入点的抽取
//相同切入点
@Pointcut(value = "execution(* com.ms.aop.User.add())")
public void pointDemo(){
}
//前置通知
@Before(value = "pointDemo()")
public void before(){
System.out.println("before()");
}
多个增强类对同一个方法增强
@Order(1)//数字越小优先级越高
@Before(value = "execution(* com.ms.aop.User.add())")
public void before(){
System.out.println("person.proxy.before()");
}
//前置通知
@Order(2)
@Before(value = "execution(* com.ms.aop.User.add())")
public void before(){
System.out.println("user.proxy.before()");
}
完全注解开发中如何配置
新建配置类
package com.ms.config;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
@Configuration
@ComponentScan(basePackages = {"com.ms"})
@EnableAspectJAutoProxy(proxyTargetClass = true)
public class ConfigAop {
}
JdbcTemplate
概念
spring框架对JDBC的封装,使之方便的操作数据库
怎么用
1.配置数据源
上面有说过
2、在配置文件中创建jdbcTemplate对象
<!--jdbcTemplate对象-->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<!--注入数据源DataSource-->
<property name="dataSource" ref="dataSource"/>
</bean>
3、在dao类中,注入jdbcTemplate,操作数据库
package com.ms.dao;
import com.ms.entity.Book;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;
import java.util.ArrayList;
import java.util.List;
@Repository
public class BookDaoImpl implements BookDao {
@Autowired
private JdbcTemplate jdbcTemplate;
@Override
public void add(Book book) {
String sql = "insert into Book values(?,?,?)";
jdbcTemplate.update(sql, book.getId(), book.getName(), book.getStatus());
}
@Override
public int updateBook(Book book) {
String sql = "update Book set name=?,status=? where id=?";
return jdbcTemplate.update(sql, book.getName(), book.getStatus(), book.getId());
}
@Override
public int deleteBook(String id) {
String sql = "delete from Book where id=?";
return jdbcTemplate.update(sql, id);
}
@Override
public int count() {
String sql = "select count(*) from Book";
return jdbcTemplate.queryForObject(sql, Integer.class);
}
@Override
public Book getById(String id) {
String sql = "select * from Book where id=?";
return jdbcTemplate.queryForObject(sql, new BeanPropertyRowMapper<>(Book.class), id);
}
@Override
public List<Book> findAll() {
String sql="select * from Book";
return jdbcTemplate.query(sql, new BeanPropertyRowMapper<>(Book.class));
}
//批量添加
@Override
public int addBatch(List<Book> books) {
String sql="insert into Book values(?,?,?)";
List<Object[]> batchArgs=new ArrayList<>();
for (Book book : books) {
Object[] o={book.getId(),book.getName(),book.getStatus()};
batchArgs.add(o);
}
int[] update = jdbcTemplate.batchUpdate(sql, batchArgs);
return update.length;
}
@Override
public int updateBatch(List<Book> books) {
String sql="update Book set name=?,status=? where id=?";
List<Object[]> batchArgs=new ArrayList<>();
for (Book book : books) {
Object[] o={book.getName(),book.getStatus(),book.getId()};//这个顺序要和问号的顺序相同
batchArgs.add(o);
}
return jdbcTemplate.batchUpdate(sql, batchArgs).length;
}
@Override
public int deleteBatch(List<Book> books) {
String sql="delete from Book where id=?";
List<Object[]> batchArgs=new ArrayList<>();
for (Book book : books) {
Object[] o={book.getId()};//这个顺序要和问号的顺序相同
batchArgs.add(o);
}
return jdbcTemplate.batchUpdate(sql, batchArgs).length;
}
}
事务操作
事务的四大特性
- 原子性——要么都成功,要么都失败
- 一致性——操作之前,操作之后数据总量是不变的
- 隔离性——多事务之间不会影响
- 持久性——事务一旦提交,数据就会变化
dao层一般不包含事务,只操作数据库
service层(业务逻辑层)调用dao层,可能有事务
spring中的事务
编程式事务
@Autowired
private DataSource dataSource;
@Override
public void accountMoney(String moneyFrom, String moneyTo, Integer money) {
PlatformTransactionManager manager=null;
TransactionStatus ts=null;
try {
//1、开启事务
manager=new DataSourceTransactionManager(dataSource);
ts=manager.getTransaction(new DefaultTransactionDefinition());
//2、业务逻辑
userDao.addMoney(moneyTo,money);
userDao.reduceMoney(moneyFrom,money);
//3、无异常,事务提交
manager.commit(ts);
}catch (Exception e){
e.printStackTrace();
//4、事务回滚
assert manager != null;
assert ts != null;
manager.rollback(ts);
}
}
声明式事务
底层就是使用aop
注解方式
第一步:配置事务管理器
<bean id="dataSourceTransactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>>
第二步:开启事务注解
- 引入tx名称空间
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xmlns:tx="http://www.springframework.org/schema/tx" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd">
- 开启事务注解
<!--开启事务注解--> <tx:annotation-driven transaction-manager="dataSourceTransactionManager"/>
第三步:在service类上,或其方法上添加注解@Transactional
在类上则所有方法都有事务
在方法上则该方法有事务
xml方式
第一步:配置事务管理器(和注解方式一样)
第二步:配置通知(增强的部分)
<!--配置通知-->
<tx:advice id="advice">
<!--配置事务参数-->
<tx:attributes>
<!--指定事务添加到哪个方法-->
<tx:method name="accountMoney" propagation="REQUIRED"/><!--添加事务到accountMoney()方法上-->
<tx:method name="account*"/><!--添加事务到account开头的方法上-->
</tx:attributes>
</tx:advice>
第三步:配置切入点和切面
<!--配置切入点和切面-->
<aop:config>
<!--切入点-->
<aop:pointcut id="pt" expression="execution(* com.ms.service.impl.UserServiceImpl.*(..))"/>
<!--切面-->
<aop:advisor advice-ref="advice" pointcut-ref="pt"/>
</aop:config>
完全注解发开中的声明式事务
package com.ms.config;
import com.alibaba.druid.pool.DruidDataSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.support.PropertiesLoaderUtils;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import javax.sql.DataSource;
import java.io.IOException;
import java.util.Properties;
@Configuration
@ComponentScan(basePackages = "com.ms")
@EnableTransactionManagement//开启事务注解
public class TxConfig {
//创建数据库的连接池
@Bean
public DruidDataSource addDataSource() throws IOException {
DruidDataSource dataSource = new DruidDataSource();
ClassPathResource pathResource = new ClassPathResource("jdbc.properties");
Properties properties = PropertiesLoaderUtils.loadProperties(pathResource);
dataSource.setDriverClassName(properties.getProperty("prop.driverClass"));
dataSource.setUrl(properties.getProperty("prop.url"));
dataSource.setUsername(properties.getProperty("prop.userName"));
dataSource.setPassword(properties.getProperty("prop.password"));
return dataSource;
}
//创建jdbcTemplate
@Bean
public JdbcTemplate addJdbcTemplate(DataSource dataSource){//这样会到ioc容器中按类型注入
JdbcTemplate jdbcTemplate = new JdbcTemplate();
//注入dataSource
jdbcTemplate.setDataSource(dataSource);
return jdbcTemplate;
}
//创建事务管理器
@Bean
public DataSourceTransactionManager addDataSourceTransactionManager(DataSource dataSource){
return new DataSourceTransactionManager(dataSource);
}
}
spring事务中的API
对应不同操作数据库方法所对应的实现类,jdbcTemplate,mybatis操作数据库使用DataSourceTransactionManager
这些属性在注解中配置和在配置文件中配置都是一样的
事务的传播行为
@Transactional(propagation = Propagation.REQUIRED)
<!--配置通知-->
<tx:advice id="advice">
<!--配置事务参数-->
<tx:attributes>
<!--指定事务添加到哪个方法-->
<tx:method name="accountMoney" propagation="REQUIRED"/><!--添加事务到accountMoney()方法上-->
<tx:method name="account*"/><!--添加事务到account开头的方法上-->
</tx:attributes>
</tx:advice>
REQUIRED:单独执行方法1,开启事务A,如果方法2调用了方法1,事务A会加入到方法2开启的事务B中
REQUIRED_NEW:方法2如果失败,方法1依然能提交
SUPPORTS:如果方法单独执行,可以不开启事务,如果它调用了一个有事务的方法,那么她它就加入到所调用方法的事务中
事务的隔离级别
@Transactional(propagation = Propagation.REQUIRED,isolation = Isolation.READ_COMMITTED)
若不考虑事务的隔离性面临的三个问题
- 脏读——一个事务读取到了另一个事务未提交的数据
- 不可重复读——一个事务当读到了另一个事务修改的数据,可能下次读取的值就变化了
- 幻(虚)读——一个未提交的事务读到了另一个事务新添加的数据
timeOut:超时时间
事务需在一定时间内提交,不提交就回滚
默认值:-1,不设置,秒为单位
@Transactional(timeout = 10)
readOnly:是否只读
true:只能查询
false:默认值,可查可改
@Transactional(readOnly = true)
rollbackFor:回滚
设置出现哪些异常要回滚
@Transactional(rollbackFor = IndexOutOfBoundsException.class)
noRollbackFor:不回滚
设置出现哪些异常不回滚
spring整合其他框架
整合log4j2
1、所需要的jar包,也可以通过maven引入
2、创建log4j2.xml文件,名字固定
<?xml version="1.0" encoding="UTF-8"?>
<!--日志级别以及优先级排序(小———>大): OFF > FATAL > ERROR > WARN > INFO > DEBUG > TRACE > ALL -->
<!--Configuration后面的status用于设置log4j2自身内部的信息输出,可以不设置,当设置成trace时,可以看到log4j2内部各种详细输出-->
<configuration status="INFO">
<!--先定义所有的appender-->
<appenders>
<!--输出日志信息到控制台-->
<console name="Console" target="SYSTEM_OUT">
<!--控制日志输出的格式-->
<PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"/>
</console>
</appenders>
<!--然后定义logger,只有定义了logger并引入的appender,appender才会生效-->
<!--root:用于指定项目的根日志,如果没有单独指定Logger,则会使用root作为默认的日志输出-->
<loggers>
<root level="info">
<appender-ref ref="Console"/>
</root>
</loggers>
</configuration>
spring框架核心容器支持@Nullable注解
该注解可以用在方法上,属性上,参数上,表示方法返回可以为空,属性值可以为空,参数值可以为空
spring框架核心容器支持函数式风格GenericApplicationContext,lambda表达式
@Test
public void testLambda(){
GenericApplicationContext context = new GenericApplicationContext();
//把book对象注册
context.refresh();//内容清空
//指定bean的名字注册
context.registerBean("book",Book.class,()->new Book("1","西游记","3"));
System.out.println(context.getBean("book", Book.class));
//不指定bean的名字注册
context.registerBean(Book.class,()->new Book("2","水浒传","2"));
System.out.println(context.getBean("com.ms.entity.Book"));
}
sping整合junit4测试框架
相关依赖
package com.ms.test;
import com.ms.service.UserService;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
@RunWith(SpringJUnit4ClassRunner.class)//指定单元测试框架
@ContextConfiguration("classpath:bean1.xml")//加载配置文件
public class SpringTest {
@Autowired
private UserService userService;
@Test
public void testSpringTest(){
userService.accountMoney("1","2",100);
}
}
sping整合junit5测试框架
@ExtendWith(SpringExtension.class)
@ContextConfiguration("classpath:bean1.xml")//加载配置文件
public class SpringTest {
@Autowired
private UserService userService;
@Test
public void testSpringTest(){
userService.accountMoney("1","2",100);
}
}
也可以使用一个复合注解
@SpringJUnitConfig(locations = "classpath:bean1.xml")
SpringWebflux
介绍
spring5新添加的模块,用于web开发,功能和springmvc类似,webflux使用响应式编程
springmvc基于servlet容器,webflux是一种异步非阻塞框架,在servlet3.1之后才支持,核心是Reactor的相关API实现的
异步非阻塞
异步:调用者发送请求,无需等服务器回应再做其他操作
同步:调用者发送请求,需等服务器回应再做其他操作
阻塞:被调用者收到请求不立即给出反馈
非阻塞:被调用者收到请求立即给出反馈
webflux特点
1、非阻塞式,可以在不扩充硬件的基础上,提高系统吞吐量和伸缩性
2、函数式编程
3、与springmvc的比较
二者都可以使用注解方式,都运行在tomcat服务器中
springmvc采用命名式编程,webflux采用异步响应式编程
webflux可以用在微服务网关中
响应式编程
java8及其以前版本
package com.ms.demo.demorector8.rector8;
import java.util.Observable;
public class ObserverDemo extends Observable {
public static void main(String[] args) {
ObserverDemo observer = new ObserverDemo();
//添加观察者
observer.addObserver((var1,var2)->{
System.out.println("发生变化");
});
observer.addObserver((var1,var2)->{
System.out.println("手动被观察者通知,准备改变");
});
observer.setChanged();//数据变化
observer.notifyObservers();//通知
}
}
java8以后版本就过时了
Reactor的实现响应式编程
通俗点讲,这就是一种规范
Reactor有两个核心的类,Mono,Flux,都实现了Publisher,提供了丰富的操作符。
Flux对象实现发布者,返回n个元素;Mono也实现发布者,返回0或1个元素
Mono,Flux都是数据流的发布者,可以发出三种数据信号:元素值,错误信号,完成信号(后两者都是终止信号,告诉订阅者数据流结束)
- 错误信号和完成信号都是终止信号,不能共存的
- 如果没有发送任何元素值,而是直接发送错误或者完成信号,表示是空数据流
- 如果没有错误信号,没有完成信号,表示是无限数据流
代码演示
引入依赖
<!--rector依赖-->
<dependency>
<groupId>io.projectreactor</groupId>
<artifactId>reactor-core</artifactId>
<version>3.1.5.RELEASE</version>
</dependency>
package com.ms.demo.demorector8.recator8;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Stream;
public class TestReactor {
public static void main(String[] args) {
//just直接申明
Flux.just(1,2,3,4);//多个
Mono.just(1);//1或0个
//其他
Integer[] arr={1,2,3,4};
Flux.fromArray(arr);
List<Integer> list= Arrays.asList(arr);
Flux.fromIterable(list);
Stream<Integer> stream=list.stream();
Flux.fromStream(stream);
Flux.error(new Exception("错误"));
}
}
调用 just 或者其他方法只是声明数据流,数据流并没有发出,只有进行订阅之后才会触发数据流,不订阅什么都不会发生的
操作符
对数据流进行一道道操作,成为操作符,比如工厂流水线
- map——元素映射为新元素
- flatMap——元素映射为流
把每个元素转换流,把转换之后多个流合并大的流
springwebflux执行流程和核心API
SpringWebflux 基于 Reactor,默认使用容器是 Netty,Netty 是高性能的 NIO 框架,异步非阻塞的框架
Netty
BIO,阻塞方式
NIO,非阻塞方式
SpringWebflux的执行过程
SpringWebflux 核心控制器 DispatchHandler,实现接口 WebHandler,负责请求处理
其他的控制器
- HandlerMapping——请求需要的处理方法
- HandlerAdapter——真正负责请求处理(业务逻辑)
- HandlerResultHandler——响应结果处理
依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
public interface WebHandler {
/**
* Handle the web server exchange.
* @param exchange the current server exchange
* @return {@code Mono<Void>} to indicate when request handling is complete
*/
Mono<Void> handle(ServerWebExchange exchange);
}
API
两个接口
- RouterFunction(路由处理)
- HandlerFunction(处理函数)
注解编程模型方式
和springmvc使用相似,只需要把相关依赖配置到项目中(spring-boot-starter-webflux),SpringBoot 自动配置相关运行容器,默认情况下使用 Netty 服务器
说明 :
SpringMVC 方式实现,同步阻塞的方式,基于 SpringMVC+Servlet+Tomcat
SpringWebflux 方式实现,异步非阻塞 方式,基于 SpringWebflux+Reactor+Netty
第一步:配置文件中指定端口号
第二步:创建先关包和类
和springmvc一样的
第三步:代码
UserService
package com.ms.demowebflux.service;
import com.ms.demowebflux.entity.User;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
public interface UserService {
//和springmvc有所区别
Mono<User> getUserById(Integer id);//一个元素
Flux<User> getAllUser();//多个元素
Mono<Void> saveUser(Mono<User> userMono);//无返回值
}
UserServiceImpl
package com.ms.demowebflux.service.impl;
import com.ms.demowebflux.entity.User;
import com.ms.demowebflux.service.UserService;
import org.springframework.stereotype.Service;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import java.util.HashMap;
import java.util.Map;
@Service
public class UserServiceImpl implements UserService {
//数据
private final Map<Integer,User> users=new HashMap<>();
public UserServiceImpl() {
this.users.put(1,new User("张三","男",20));
this.users.put(2,new User("李四","女",34));
this.users.put(3,new User("王五","男",12));
}
@Override
public Mono<User> getUserById(Integer id) {
return Mono.justOrEmpty(this.users.get(id));//查询不到返回空
}
@Override
public Flux<User> getAllUser() {
return Flux.fromIterable(this.users.values());
}
@Override
public Mono<Void> saveUser(Mono<User> userMono) {
return userMono.doOnNext(u -> {
int id = users.size() + 1;
this.users.put(id, u);
}).thenEmpty(Mono.empty());//值清空表示终止
}
}
UserController
package com.ms.demowebflux.controller;
import com.ms.demowebflux.entity.User;
import com.ms.demowebflux.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
@RestController
@RequestMapping("/user")
public class UserController {
@Autowired
private UserService userService;
@GetMapping("getUserById/{id}")
public Mono<User> getUserById(@PathVariable Integer id){
return userService.getUserById(id);
}
@GetMapping("getAllUser")
public Flux<User> getAllUser(){
return userService.getAllUser();
}
@PostMapping("saveUser")
public Mono<Void> saveUser(@RequestBody User user){
return userService.saveUser(Mono.just(user));
}
}
第四步:测试
函数式编程模型
需要自己初始化服务器
基于函数式编程模型时候,有两个核心接口:RouterFunction(实现路由功能,请求转发给对应的 handler)和 HandlerFunction(处理请求生成响应的函数)。核心任务定义两个函数式接口的实现并且启动需要的服务器。
SpringWebflux 请 求 和 响 应 不 再 是 ServletRequest 和 ServletResponse ,而是ServerRequest 和 ServerResponse
第一步:不在需要控制层controller
第二步:创建 Handler(具体实现方法)
package com.ms.demowebflux2.handler;
import com.ms.demowebflux2.entity.User;
import com.ms.demowebflux2.service.UserService;
import org.springframework.http.MediaType;
import org.springframework.web.reactive.function.server.ServerRequest;
import org.springframework.web.reactive.function.server.ServerResponse;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
public class UserHandler {
private final UserService userService;
public UserHandler(UserService userService) {
this.userService = userService;
}
public Mono<ServerResponse> getUserById(ServerRequest request) {
Integer id = Integer.valueOf(request.pathVariable("id"));
//控制处理
Mono<ServerResponse> notFound = ServerResponse.notFound().build();
Mono<User> userMono = userService.getUserById(id);
//userMono转换返回
//使用reactor操作符flatMap
return ServerResponse.ok().contentType(MediaType.APPLICATION_JSON).body(userMono, User.class).switchIfEmpty(notFound);
}
public Mono<ServerResponse> getAllUser(ServerRequest request) {
Flux<User> users = userService.getAllUser();
return ServerResponse.ok().contentType(MediaType.APPLICATION_JSON).body(users, User.class);
}
public Mono<ServerResponse> saveUser(ServerRequest request) {
Mono<User> userMono = request.bodyToMono(User.class);
return ServerResponse.ok().build(userService.saveUser(userMono));
}
}
第三步:初始化服务器,编写Router
package com.ms.demowebflux2;
import com.ms.demowebflux2.handler.UserHandler;
import com.ms.demowebflux2.service.UserService;
import com.ms.demowebflux2.service.impl.UserServiceImpl;
import org.springframework.http.server.reactive.HttpHandler;
import org.springframework.http.server.reactive.ReactorHttpHandlerAdapter;
import org.springframework.web.reactive.function.server.RouterFunction;
import org.springframework.web.reactive.function.server.RouterFunctions;
import org.springframework.web.reactive.function.server.ServerResponse;
import reactor.netty.http.server.HttpServer;
import java.io.IOException;
import static org.springframework.http.MediaType.APPLICATION_JSON;
import static org.springframework.web.reactive.function.server.RequestPredicates.*;
import static org.springframework.web.reactive.function.server.RouterFunctions.toHttpHandler;
public class Server {
//1、创建路由
public RouterFunction<ServerResponse> routerFunction() {
UserService userService = new UserServiceImpl();
UserHandler handler = new UserHandler(userService);
return RouterFunctions.route(GET("/user/getUserById/{id}").and(accept(APPLICATION_JSON)), handler::getUserById)
.andRoute(GET("/user/getAllUser").and(accept(APPLICATION_JSON)), handler::getAllUser)
.andRoute(POST("/user/saveUser").and(accept(APPLICATION_JSON)), handler::saveUser);
}
//2、创建服务完成适配
public void createReactorServer(){
//路由
RouterFunction<ServerResponse> routerFunction = routerFunction();
//handler
HttpHandler httpHandler=toHttpHandler(routerFunction);
//适配
ReactorHttpHandlerAdapter adapter = new ReactorHttpHandlerAdapter(httpHandler);
//服务器
HttpServer httpServer=HttpServer.create().port(8002);//指定端口,否则随机生成
httpServer.handle(adapter).bindNow();
}
public static void main(String[] args) throws IOException {
Server server = new Server();
server.createReactorServer();
System.out.println("enter to exit");
System.in.read();
}
}
第四步:测试
WebClient调用
不使用浏览器调用
新建Client类
package com.ms.demowebflux2;
import com.ms.demowebflux2.entity.User;
import org.springframework.http.MediaType;
import org.springframework.web.reactive.function.client.WebClient;
import reactor.core.publisher.Flux;
public class Client {
public static void main(String[] args) {
WebClient webClient = WebClient.create("http://localhost:8002");
Integer id = 1;
User user = webClient
.get()
.uri("user/getUserById/{id}", id)
.accept(MediaType.APPLICATION_JSON)
.retrieve().bodyToMono(User.class)
.block();
System.out.println("============================"+user+"============================");
Flux<User> userFlux = webClient
.get().uri("user/getAllUser")
.accept(MediaType.APPLICATION_JSON)
.retrieve().bodyToFlux(User.class);
userFlux.map(User::toString).buffer().doOnNext(System.out::print).blockFirst();
}
}
测试