概述
Spring是轻量级的开源的JavaEE框架
核心部分
一。IOC:控制反转,把创建对象过程交给Spring进行管理
二。AOP:面向切面,不修改源码进行功能增强
特点:
一。方便解耦,简化开发
二。AOP编程支持
三。方便程序测试(JUnit)
四。方便跟其他框架进行整合
五。方便进行事务操作
入门案例:
一。 下载Spring5https://repo.spring.io/ui/native/release/org/springframework/spring/
二。所在文件目录G:\CodeWorkSpace\spring-5.3\spring-framework-5.3.20
三。Spring5相关jar包
四。Spring核心容器jar包
spring-beans-5.3.20.jar
spring-context-5.3.20.jar
spring-core-5.3.20.jar
spring-expression-5.3.20.jar
-
创建类
public class User { public void add(){ System.out.println("add...."); } }
-
创建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="user" class="com.mercurius.spring5.User"> </bean> </beans>
通过测试类验证bean创建过程
public class TestDemo { @Test public void testAddOfUser(){ //加载配置文件(以classpath作为相对路径) ApplicationContext context=new ClassPathXmlApplicationContext("bean.xml"); //获取配置创建的对象,转化为相应的类型 User user = context.getBean("user", User.class); System.out.println(user); user.add(); /** * 结果: * com.mercurius.spring5.User@7403c468 * add.... */ } }
IOC容器
一。IOC底层原理
- IOC:控制反转——面向对象编程的一种设计原则,降低计算机代码之间的耦合度(常用方式:依赖注入DI)
- 对象创建和对象之间的调用过程,均交由Spring管理
- 底层逻辑:XML解析、工厂模式、反射
- 完整过程:
- 使用XML配置文件,配置创建的对象
<bean id="user" class="com.mercurius.spring5.User"></bean>
- 创建一个工厂类,并使用反射的方式生成对象
public class UserFactory { public static User getUser() throws ClassNotFoundException, InstantiationException, IllegalAccessException { //通过反射创建对象 Class clazz=Class.forName("com.mercurius.spring5.User"); User user= (User) clazz.newInstance(); return user; } }
- 使用XML配置文件,配置创建的对象
二。IOC接口【IOC容器的实现方式】
-
BeanFactory
IOC容器的基本实现,是Spring内部的使用接口
特点:加载配置文件的时候不会创建对象,在获取对象的时候才会创建对象
@Test public void TestCreateUserWithBeanFactory(){ //加载配置文件 BeanFactory beanFactory=new ClassPathXmlApplicationContext("bean.xml"); //获取配置的对象(此时才创建对象) User user = beanFactory.getBean("user", User.class); System.out.println(user); user.add(); /** * 结果: * com.mercurius.spring5.User@7403c468 * add.... */ }
-
ApplicationContenxt【一般使用】
BeanFactory接口的子接口,提供更多更强大的功能
特点:加载配置文件的同时已经完成对象的创建【把耗时耗资源的过程放在启动时候】
实现类(idea快捷键:光标停在对象,ctrl+h)
FileSystemXmlApplicationContext
:配置文件所处磁盘的绝对路径ClassPathXmlApplicationContext
:配置文件所处的类路径
三。IOC操作Bean管理
-
组成部分
- Spring对象创建
- Spring对象属性注入
-
操作方式
-
基于XML配置文件实现
①对象的创建(Bean标签)
Bean标签
<!-- 配置对象创建--> <bean id="user" class="com.mercurius.spring5.User"></bean>
Bean属性
id属性:唯一标识 class:类全路径
创建时默认执行无参的构造方法:没有无参构造器会报出以下错误
Caused by: org.springframework.beans.BeanInstantiationException: Failed to instantiate [com.mercurius.spring5.User]: No default constructor found; nested exception is java.lang.NoSuchMethodException
②基于XML方式注入属性
1)DI:依赖注入(属性注入)
/** * 第一种注入方式:使用set方法注入 */ public class Book { private String bookName; private String bookAuthor; public void setBookName(String bookName) { this.bookName = bookName; } public void setBookAuthor(String bookAuthor) { this.bookAuthor = bookAuthor; } } /******************************************/ //配置文件 <bean id="book" class="com.mercurius.spring5.Book"> <!-- 使用property标签 name:属性名称 value:属性值 --> <property name="bookName" value="易筋经"></property> <property name="bookAuthor" value="天王老子"></property> </bean> //测试方式 @Test public void testCreateBook(){ ApplicationContext context=new ClassPathXmlApplicationContext("bean.xml"); Book book= (Book) context.getBean("book"); System.out.println(book.getBookName()+":"+book.getBookAuthor()); /** * 结果: * 易筋经:天王老子 */ } /* *第二种注入方式:使用有参构造器注入 */ public class Orders { private String oname; private String address; public Orders(String oname, String address) { this.oname = oname; this.address = address; } public void printValue(){ System.out.println("oname:"+oname+"\n"+"address"+address); } } /*******************************************/ //配置文件 <bean id="order" class="com.mercurius.spring5.domain.Orders"> <constructor-arg name="oname" value="订单名称1"></constructor-arg> <constructor-arg name="address" value="订单地址1"></constructor-arg> </bean> //测试 @Test public void testCreateBookWithConstructor(){ ApplicationContext context=new ClassPathXmlApplicationContext("bean.xml"); Orders order= (Orders) context.getBean("order"); order.printValue(); }
2)XML注入其他类型属性
-
字面量:
(1)null值: <property name="address"> <null/> </property> (2)属性值包含特殊符号: <property name="address"> <value><![CDATA[<<南京>>]]</value> </property>
-
外部bean:
<bean id="userService" class="com.mercurius.spring5.service.UserService"> <!-- 注入userDao name表示为属性名称 ref表示创建的bean的id值 --> <property name="userDao" ref="userDao"></property> </bean> <bean id="userDao" class="com.mercurius.spring5.dao.impl.UserDaoImpl"></bean> /************具体类*************/ public class UserService { private UserDao userDao; public void setUserDao(UserDao userDao){ this.userDao=userDao; } public void add(){ System.out.println("service add........"); userDao.update(); } }
-
注入属性-内部bean
<bean id="emp" class="com.mercurius.spring5.domain.Emp"> <property name="name" value="Aries"></property> <property name="gender" value="Male"></property> <property name="dept"> <!-- 内部bean--> <bean id="dept" class="com.mercurius.spring5.domain.Dept"> <property name="dname" value="人事部"></property> </bean> </property> </bean> /**************************类*************/ public class Emp { private String name; private String gender; private Dept dept; public String getName() { return name; } public void setName(String name) { this.name = name; } public String getGender() { return gender; } public void setGender(String gender) { this.gender = gender; } public Dept getDept() { return dept; } public void setDept(Dept dept) { this.dept = dept; } public void printf(){ System.out.println(name+"\n"+gender+"\n"+dept.getDname()); } }
-
注入集合属性
<!-- 集合类型属性注入--> <bean id="std" class="com.mercurius.spring5.domain.Eu"> <property name="courses"> <array> <value>java课程</value> <value>python课程</value> </array> </property> <property name="list"> <list> <value>耳机</value> <value>电脑</value> <value>音频</value> </list> </property> <!-- 对象列表--> <property name="courselist"> <list> <ref bean="course1"></ref> <ref bean="course2"></ref> </list> </property> <property name="map"> <map> <entry key="k1" value="老虎"></entry> <entry key="k2" value="狮子"></entry> </map> </property> <property name="sets"> <set> <value>mysql</value> <value>oracle</value> </set> </property> </bean> <!-- 对象列表的元素--> <bean id="course1" class="com.mercurius.spring5.domain.Course"> <property name="courseName" value="英语"></property> </bean> <bean id="course2" class="com.mercurius.spring5.domain.Course"> <property name="courseName" value="语文"></property> </bean> /***********************************/ public class Eu { private String[] courses; private List<String> list; //对象列表 private List<Course> courselist; public List<Course> getCourselist() { return courselist; } public void setCourselist(List<Course> courselist) { this.courselist = courselist; } private Map<String,Object> map; private Set<String> sets; public Set<String> getSets() { return sets; } public void setSets(Set<String> sets) { this.sets = sets; } public String[] getCourses() { return courses; } public void setCourses(String[] courses) { this.courses = courses; } public List<String> getList() { return list; } public void setList(List<String> list) { this.list = list; } public Map<String, Object> getMap() { return map; } public void setMap(Map<String, Object> map) { this.map = map; } }
3)基于XML的自动装配
<bean id="emp" class="com.mercurius.spring5.beanchapter.domain.Emp" autowire="byName"> </bean> <bean id="dept" class="com.mercurius.spring5.beanchapter.domain.Dept"> <property name="dname" value="设么东西"></property> </bean> /*********************************/ public class Emp { private String name; private String gender; private Dept dept; public String getName() { return name; } public void setName(String name) { this.name = name; } public String getGender() { return gender; } public void setGender(String gender) { this.gender = gender; } public Dept getDept() { return dept; } public void setDept(Dept dept) { this.dept = dept; } public void printf(){ System.out.println(name+"\n"+gender+"\n"+dept.getDname()); } }
4)外部属性文件(数据库配置文件)
jdbc.properties
prop.driverClass=com.mysql.jdbc.Driver prop.url=jdbc:mysql://localhost:3306/userDb prop.userName=root prop.password=root
在xml文件中引入context空间【可以使用context标签】
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns=“http://www.springframework.org/schema/beans”
xmlns:xsi=“http://www.w3.org/2001/XMLSchema-instance”*** xmlns:context=“http://www.springframework.org/schema/context***”
xsi:schemaLocation=“http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd”>
-
-
引入外部文件并引用属性
<!-- 引入外部属性文件-->
<context:property-placeholder location="classpath:jdbc.properties"/>
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="${prop.driverClass}"></property>
<property name="url" value="${prop.url}"></property>
<property name="username" value="${prop.userName}"></property>
<property name="password" value="${prop.password}"></property>
</bean>
-
基于注解方式实现
1)注解作用范围:类、方法上、属性上
2)注解目的:简化XML配置
3)Spring针对Bean管理中创建对象提供的注解
@Component、@Service、@Controller、@Repository;四个注解功能是一样,都可以用来创建bean实例
4)基于注解方式实现对象创建
①注解所使用的依赖
spring-aop-5.3.20.jar
②开启组件扫描:告诉spring容器需要在哪些类上启用注解
<!-- 开启组件扫描-->
<context:component-scan base-package="com.mercurius.spring5.beanchapter.beanAutowired"/>
<!--
组件扫描的拓展用法:
use-default-filters="false"
不使用默认的filter使用自己配置的filter
include-filter:设置扫描哪些内容
-->
<context:component-scan base-package="com.mercurius.spring5.beanchapter.beanAutowired" use-default-filters="false">
<!-- 扫描controller注解-->
<context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
<!-- 不扫描Service注解-->
<context:exclude-filter type="annotation" expression="org.springframework.stereotype.Service"/>
</context:component-scan>
③使用注解创建bean并验证
@Component(value = "templateService")
//默认值为首字母小写
//等同于<bean id="templateService" class="com.mercurius.spring5.beanchapter.beanAutowired.AutowiredTemplateService">
public class AutowiredTemplateService {
public void add(){
System.out.println("add().....");
}
}
/******************************************/
@Test
public void testAutowiredTemplate(){
ApplicationContext context=new ClassPathXmlApplicationContext("beanDruid.xml");
AutowiredTemplateService templateService = context.getBean("templateService", AutowiredTemplateService.class);
System.out.println(templateService);
templateService.add();
}
④使用注解完成属性注入
a.@Autowired:根据属性类型进行自动装配
1.使用创建对象注解完成对象创建
2.在创建的对象中使用Autowired完成对对象的自动装配[自动实现了setter方法]
@Component(value = "templateService")
//默认值为首字母小写
//等同于<bean id="templateService" class="com.mercurius.spring5.beanchapter.beanAutowired.AutowiredTemplateService">
public class AutowiredTemplateService {
@Autowired
private TemplateDao templateDao;
public void add(){
System.out.println("add().....");
}
public void templateDaoAdd(){
templateDao.add();
}
}
/***************************************/
@Repository
public class TemplateDaoImpl implements TemplateDao {
@Override
public void add() {
System.out.println("dao add....");
}
}
b.@Qualifier:根据属性名称进行注入;需要和@Autowired一起使用
@Repository(value="templateDao1")
public class TemplateDaoImpl implements TemplateDao {
@Override
public void add() {
System.out.println("dao add....");
}
}
@Autowired
@Qualifier(value = "templateDao1")//当有多个相同类型对象时,可以根据名称注入指定对象
private TemplateDao templateDao;
c.@Resource:可以根据类型注入,可以根据名称注入
@Resource(name = "templateDao1")//默认下根据类型注入,指定name则根据名称注入
private TemplateDao templateDao1;
d.@Value:注入普通类型属性
@Value(value="abc")
private String value;
e.完全注解开发
@Configuration//相当于bean配置xml文件
@ComponentScan(basePackages = "com.mercurius")
public class SpringConfig {
}
测试类
@Test
public void configTest(){
//加载配置类
ApplicationContext context=new AnnotationConfigApplicationContext(SpringConfig.class);
AutowiredTemplateService templateService = context.getBean("templateService", AutowiredTemplateService.class);
System.out.println(templateService);
templateService.add();
/**
* 结果:
* com.mercurius.spring5.beanchapter.beanAutowired.AutowiredTemplateService@636be97c
* add().....
* dao add....
*/
}
- SpringBean类型
1)普通Bean:在配置文件中定义的bean类型就是返回类型
<bean id="user" class="com.mercurius.spring5.domain.User"></bean>
//加载配置文件
BeanFactory beanFactory=new ClassPathXmlApplicationContext("bean.xml");
//获取配置的对象(此时才创建对象)
User user = beanFactory.getBean("user", User.class);
2)FactoryBean:实现BeanFactory接口,使之变成一个生成Bean的工厂类
public class MyFactoryBean implements FactoryBean<Course> {
/**
* 定义返回Bean类型
* @return
* @throws Exception
*/
@Override
public Course getObject() throws Exception {
Course course=new Course();
return course;
}
@Override
public Class<?> getObjectType() {
return null;
}
@Override
public boolean isSingleton() {
return FactoryBean.super.isSingleton();
}
}
/******************************************/
<bean id="myBean" class="com.mercurius.spring5.factory.MyFactoryBean"></bean>
@Test
public void testFactoryBean(){
ApplicationContext context=new ClassPathXmlApplicationContext("bean4.xml");
Course myBean= context.getBean("myBean",Course.class);
System.out.println(myBean);
}
-
Bean作用域
<!-- 1.在Spring中可以设置创建bean实例是单实例还是多实例 (1)在spring配置文件bean标签里面有属性(scope)用于设置单实例还是多实例 (2)scope属性值: singleton(默认,单实例对象) prototype(多实例对象) request(创建的bean会放入request对象中) session(创建的bean放入session对象中) --> <bean id="myBean" class="com.mercurius.spring5.factory.MyFactoryBean" scope="prototype"></bean> 2.在Spring里面,默认情况下bean是单实例对象
-
Bean生命周期
1.生命周期:对象创建到对象销毁的过程
2.具体阶段:创建—>属性设置—>后置处理器before方法—>bean初始化—>后置处理器after方法—>bean的获取—>bean的销毁
(1)通过构造器创建bean实例(无参数构造)
(2)为bean的属性设置值和对其他bean引用(调用set方法)
(3)把bean的实例传递给bean后置处理器的方法【Before方法】(实现接口BeanPostProcessor来创建后置处理器)
(4)调用bean初始化方法(需要进行配置)
(5)bean实例传递给bean后置处理器的方法【after方法】
(6)bean可以使用(对象能够获取到)
(7)当容器关闭时候,调用bean的销毁方法(需要进行配置销毁的方法)
3.演示:见demo1
demo1:
<bean id="initalBean" class="com.mercurius.spring5.beanCreate.InitalBean" init-method="initMethod" destroy-method="destroyMethod">
<property name="name" value="bean名称"></property>
</bean>
<!-- 配置后置处理器-->
<bean id="beanPost" class="com.mercurius.spring5.beanCreate.BeanPost"></bean>
/**************************************************/
public class InitalBean {
private String name;
public InitalBean(){
System.out.println("第一步:无参构造函数执行....并创建实例");
}
public void setName(String name) {
this.name = name;
System.out.println("第二步:set方法执行....设置值");
}
public void initMethod(){
System.out.println("第三步:执行初始化方法....需配置");
}
public void destroyMethod(){
System.out.println("第五步:执行销毁方法....");
}
}
/******************bean的后置处理器**********************/
public class BeanPost implements BeanPostProcessor {
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
System.out.println("初始化之前执行的方法.....需创建bean");
return bean;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
System.out.println("初始化之后执行的方法.....需创建bean");
return bean;
}
}
/****************************************/
@Test
public void initTest1(){
ApplicationContext context=new ClassPathXmlApplicationContext("beanInital.xml");
InitalBean initalBean = context.getBean("initalBean", InitalBean.class);
System.out.println("第四步:获取创建的bean实例对象....");
System.out.println(initalBean);
//手动销毁bean
((ClassPathXmlApplicationContext) context).close();
/**
* 结果:
* 第一步:无参构造函数执行....并创建实例
* 第二步:set方法执行....设置值
* 初始化之前执行的方法.....需创建bean
* 第三步:执行初始化方法....需配置
* 初始化之后执行的方法.....需创建bean
* 第四步:获取创建的bean实例对象....
* com.mercurius.spring5.beanCreate.InitalBean@31f924f5
* 第五步:执行销毁方法....
*/
}
四。Spring中的AOP
1.概念:面向切面编程,利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率
2.底层原理:
1)使用动态代理
a.有2种情况动态代理
第一种 有接口情况,使用JDK动态代理
基本原理:生成代理类来调用目标类实现的接口方法并进行增强
结构组成:
1)目标接口:定义规范
2)具体实现类:方法的具体实现(被代理对象)
3)InvocationHandler接口的实现类:传入目标类、调用目标方法并增加其他功能代码
public MySellWineInvocationHandler(Object target) {
//传入目标类
this.target = target;
}
————————————————————————————————————————————————————————————————————
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//使用反射机制,调用目标方法
Object feeObj = method.invoke(target,args);
//对目标方法的增强....
//一般返回的是方法处理后的结果值,因此proxy这个代理对象可以不需要使用
return totalFee;
————————————————————————————————————————————————————————————————————
【每个代理类的实例都关联到了实现该接口的动态代理类调用处理程序中,当我们通过动态代理对象调用一个方法时候,这个方法的调用就会被转发到实现InvocationHandler接口类的invoke方法来调用】
4)Proxy类:创建一个代理对象的类,最常用的是newProxyInstance
newProxyInstance(ClassLoader loader,Class<?>[] interfaces, InvocationHandler h)
————————————————
loader:定义了由哪个classloader对象对生成的代理类进行加载
interfaces:声明了代理类实现的接口,并提供代理类调用
h:动态代理对象调用方法时将会关联到哪个InvocationHandler上
第二种 没有接口情况,使用CGLIB动态代理
基本原理:对指定目标类生成一个子类(代理类),覆盖其中的方法并实现增强
结构组成:
1)目标类(无实现接口)
2)拦截器MethodInterceptor实现类:调用代理类实例上的proxy方法的父类方法并实现增强
public Object intercept(Object obj, Method method, Object[] arg, MethodProxy proxy) throws Throwable {
System.out.println("Before:"+method);
Object object=proxy.invokeSuper(obj, arg);
System.out.println("After:"+method);
return object;
}
3)动态代理类Enhancer:设置父类、回调的拦截器以及创建代理类。
Enhancer enhancer=new Enhancer();
enhancer.setSuperclass(ConcreteClassNoInterface.class);
enhancer.setCallback(new ConcreteClassInterceptor());
ConcreteClassNoInterface ccni=(ConcreteClassNoInterface)enhancer.create();
//代理类调用代理方法...
3.相关术语以及操作
(5)AspectJ:非Spring组成部分,是独立的AOP框架,与Spring框架结合一起使用【基于注解方式实现】(底层使用了cglib和jdk2种实现方式,取决于目标对象是否使用接口实现)
①相关依赖
spring-aop-5.3.20.jar
spring-aspects-5.3.20.jar
②切入点表达式:
a.概念:知道对哪个类里面的哪个方法进行增强
b.语法结构:execution(【权限修饰符】【返回类型】【类全路径】【方法名称】(【参数列表】))
例子:execution(* com.mercurius.dao.MercuriusDao.add(..)
权限修饰符可以不填,返回类型用*表示所有类型、参数列表使用..、方法使用*表示所有方法、类使用*表示包下所有类
③JoinPoint与ProceedingJoinPoint比较
1.ProceedingJoinPoint只适用于环绕通知,因为只有环绕通知,才能控制目标方法的运行.
2.JoinPoint 适用于其它的四大通知类型,可以用来记录运行的数据.
3. ProceedingJoinPoint 中有特殊的方法proceed();
4. 如果使用"JoinPoint" 则必须位于参数的第一位
④AOP操作:
a.组成部分:增强类、被增强的类
@Component
public class User {
public void add(){
System.out.println("add....");
}
}
b.在spring配置文件中,开启注解扫描,开启生成代理对象
@Configuration
@ComponentScan(basePackages = "com.mercurius.spring5.aopchapter")
@EnableAspectJAutoProxy//开启生成代理对象
public class AopConfig {
}
c.在增强类上面添加注解@Aspect,配置不同类型的通知
@Aspect//从spring容器中找到并生成对应代理类
@Component//在spring容器中创建对象
public class UserProxy {
@Before(value = "execution(* com.mercurius.spring5.aopchapter.User.add(..))")
public void before(){
System.out.println("before....");
}
@After(value = "execution(* com.mercurius.spring5.aopchapter.User.add(..))")
public void after(){
System.out.println("after....");
}
@Around(value = "execution(* com.mercurius.spring5.aopchapter.User.add(..))")
public void around(ProceedingJoinPoint joinPoint) throws Throwable{
System.out.println("around之前....");
joinPoint.proceed();
System.out.println("around之后....");
}
@AfterReturning(value = "execution(* com.mercurius.spring5.aopchapter.User.add(..))")
public void afterReturn(){
System.out.println("AfterReturning.....");
}
@AfterThrowing(value = "execution(* com.mercurius.spring5.aopchapter.User.add(..))")
public void ffterThrowing(){
System.out.println("ffterThrowing.....");
}
}
d.测试
@Test
public void testAop(){
// ApplicationContext context=new ClassPathXmlApplicationContext("aop.xml");
ApplicationContext context=new AnnotationConfigApplicationContext(AopConfig.class);
User user = context.getBean("user", User.class);
user.add();
/**
* 结果:
* around之前....
* before....
* add....
* AfterReturning.....
* after....
* around之后....
*/
}
(1)连接点:类中能被增强的方法
(2)切入点:实际被增强的方法(既可以为表达式,又可以为注解)
(3)通知(增强):具体增强的逻辑部分
①前置通知:方法前的通知
@Before(value = "execution(* com.mercurius.spring5.aopchapter.User.add(..))")
public void before(){
System.out.println("before....");
}
@Before(value = "@annotation(controllerLog)")//可以对注解标注的地方实现增强
public void before(JoinPoint joinPoint, Log controllerLog){
System.out.println("before....");
}
②后置通知:方法后的通知(正常返回后执行)
@AfterReturning(value = "execution(* com.mercurius.spring5.aopchapter.User.add(..))")
public void afterReturn(){
System.out.println("AfterReturning.....");
}
③环绕通知:方法前+方法后的通知
@Around(value = "execution(* com.mercurius.spring5.aopchapter.User.add(..))")
public void around(ProceedingJoinPoint joinPoint) throws Throwable{
System.out.println("around之前....");
joinPoint.proceed();
System.out.println("around之后....");
}
④异常通知:出现异常后的通知
@AfterThrowing(value = "execution(* com.mercurius.spring5.aopchapter.User.add(..))")
public void ffterThrowing(){
System.out.println("ffterThrowing.....");
}
⑤最终通知:类似于finally(不管出现异常与否都执行)
@After(value = "execution(* com.mercurius.spring5.aopchapter.User.add(..))")
public void after(){
System.out.println("after....");
}
各个通知的执行顺序(spring5.3.2)
环绕前置=>前置通知=>后置通知=>最终通知=>环绕后置
(4)切面:把【通知】应用到【切入点】的过程
(5)切入点的公共抽取:
@Pointcut(value = "execution(* com.mercurius.spring5.aopchapter.User.add(..))")
public void publicPointCut(){
}
@Before(value = "publicPointCut()")
public void beforeWithPublicPointcut(){
System.out.println("beforeWithPublicPointcut");
}
(6)有多个增强类对同一个方法进行增强,设置增强类优先级
a。在增强类上面添加注解@Order(数字类型值),值越小优先级越高(最小值为0)
五.事务操作
1.事务概念:数据库的基本单元,要么都成功,要么都失败
2.事务四个特性(ACID):
(1)原子性:逻辑上的一组操作框定为基本单元,要么都成功,一个失败则全部失败
(2)一致性:数据操作前跟操作后总量维持不变(交易100,一方多100的情况下另一方必定少100)
(3)隔离性:多事务操作彼此不会造成影响
(4)持久性:事务提交后对数据库影响是永久的
3.事务管理类型:
(1)编程式事务管理
(2)声明式事务管理【底层使用的是AOP原理】
a.基于注解方式(推荐)
b.基于XML配置文件
4.Spring事务管理
(1)提供一个接口,代表事务管理器,这个接口针对不同的框架提供不同的实现类
//总接口
PlatformTransactionManager
//mybatis具体事务管理器
DataSourceTransactionManager
//Hibernate具体事务管理器
HibernateTransactionManager
(2)事务操作(注解声明式事务管理)
a.在spring配置文件配置事务管理器
<!--创建事务管理器-->
<!--
public DataSourceTransactionManager(DataSource dataSource) {
this();
this.setDataSource(dataSource);
this.afterPropertiesSet();
}
-->
<!-- 引入外部属性文件-->
<context:property-placeholder location="classpath:jdbc.properties"/>
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="${prop.driverClass}"></property>
<property name="url" value="${prop.url}"></property>
<property name="username" value="${prop.userName}"></property>
<property name="password" value="${prop.password}"></property>
</bean>
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
b.在spring配置文件,开启事务注解
在spring配置文件中引入名称空间
<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="transactionManager"></tx:annotation-driven>
c.添加事务注解(类或者方法)
①注解在类上,表示类上所有方法均受控在事务管理下;注解在方法上,则只对该方法进行事务管理
@Service
public class PersonServiceImpl implements PersonService{
@Override
@Transactional
public void test() {
System.out.println("test.......");
}
}
d.完全注解方式
@Configuration//配置类
@ComponentScan(basePackages = "com.mercurius.spring5")//开启包扫描
@EnableTransactionManagement//开启事务
public class transactionConfig {
//创建数据库连接池
@Bean
public DruidDataSource getDruidDataSource(){
DruidDataSource dataSource=new DruidDataSource();
dataSource.setDriverClassName("com.mysql.jdbc.Driver");
dataSource.setUrl("jdbc:mysql://localhost:3306/userDb");
dataSource.setUsername("root");
dataSource.setPassword("root");
return dataSource;
}
/**
*创建事务管理器
* @param dataSource SpringIOC容器会根据参数类型找到创建的Bean,并完成自动注入的过程
* @return
*/
@Bean
public DataSourceTransactionManager getDataSourceTransactionManager(DruidDataSource dataSource){
DataSourceTransactionManager transactionManager = new DataSourceTransactionManager();
transactionManager.setDataSource(dataSource);
return transactionManager;
}
}
5.事务参数(传播行为)
1)propagation:事务传播行为
事务方法套事务方法这个过程中事务的管理操作
【假设事务A方法调用事务B方法,对于事务B方法而言】
REQUIRED:向上继承事务或者自己创建一个事务
REQUIRED_NEW:始终创建一个事务,并将当前【父级】执行的事务挂起;父级异常不回滚
NESTED:始终创建一个事务,有当前事务则需等待父级事务结束才提交;父级异常回滚
SUPPORTS:如果当前存在事务,则加入事务;如果当前不存在事务,则以非事务方式运行,这个和不写没区别
NOT_SUPPORTS:当前方法不能运行在事务中,若有则将事务挂起
MANDATORY:向上继承事务或者抛出异常(保证父级方法必须有事务)
NEVER:不允许使用事务,否则抛出异常
2)isolation:事务隔离级别
多事务操作时对隔离产生影响的程度
读问题:脏读【读到没提交事务的数据】、不可重复度【读到已提交事务的数据】、幻读【读到已提交事务的添加数据】
脏读 不可重复读 幻读
读未提交: √ √ √
读已提交:× √ √ (oracle)
可重复读:× × √ (mysql)
串行化: × × ×
3)timeout:超时时间
事务需要在一定时间内进行提交,不提交则进行回滚,默认-1不超时,单位为s
4)readOnly:是否只读
默认值为false,表示可以查询,也可以添加修改删除操作
设置为true,只能查询
5)rollbackFor:对什么回滚
设置哪些异常需要进行事务回滚
6)noRollbackFor:对什么不回滚
设置哪些异常不进行回滚
五.Spring5新功能
1.整个Spring5基于Java8
2.自带通用的日志封装
(1)已经移除Log4jConfigListener,官方建议使用Log4j2
(2)Spring5中整合Log4j2(log4j2.xml)
<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="info">
<!-- 先定义所有的appenders-->
<Appenders>
<!-- 输出到控制台-->
<Console name="Console" target="SYSTEM_OUT">
<!-- 输出格式-->
<PatternLayout pattern="[%d{yyyy-MM-dd;HH:mm:ss.SSS Z}] [%-5p] [%t] [%c] %m%n"></PatternLayout>
</Console>
</Appenders>
<!-- 定义logger,只有定义了logger并引入的appender,appender才能生效-->
<!-- root用于指定项目的根日志,如果没有单独指定logger,则会使用root作为默认的日志输出-->
<Loggers>
<root level="info">
<appender-ref ref="Console"/>
</root>
</Loggers>
</Configuration>
private static final Logger log=LoggerFactory.getLogger(UserLog.class);
3.支持@Nullable注解
可以使用在方法上、属性上、参数上,表示方法返回可以为空,属性值可以为空,参数值可以为空。
4.支持lambda表达式
5.支持整合Junit5
(1)整合Junit4
@RunWith(SpringJUnit4ClassRunner.class)//指定单元测试框架
@ContextConfiguration("classpath:aop.xml")//加载配置文件
public class AopTest {
@Autowired
private User user;
@Test
public void testAop2(){
user.add();
}
}
(2)整合Junit5
//@ExtendWith(SpringExtension.class)
//@ContextConfiguration("classpath:aop.xml")
@SpringJUnitConfig(locations = "classpath:aop.xml")//或者使用复合注解
public class AopTest {
@Autowired
private User user;
@Test
public void testAop2(){
user.add();
}
6.webFlux
1)前置知识:
①函数式编程与命名式编程
命名式编程:传统编程方式,需要自己制定实现逻辑【how to do】
函数式编程:忽略实现逻辑,调API知结果【what to do】
**数组取最小值:
命名式编程:遍历数组,俩俩比较,取最小值的那个,最终得出结果
函数式编程:IntStream.of(arrs).min().getAsInt();
lambda表达式:返回了实现接口的对象实例,且该接口只有一个实现方法【变量=方法体】,见demo1
**@FunctionInterface注解:校验接口是否只有一个实现方法(不包括默认方法)
**默认方法 default int add(int x,int y){…}
**java8常用函数接口:
Function<Integer,String>:输入为Integer类型的参数,输出为String类型的结果,见demo2
Predicate:输入一个参数,返回boolean,断言函数接口
Consumer:输入一个参数,不返回,消费者函数接口
Supplier:提供一个数据,生产者函数接口
**方法引用:
见demo3
**变量引用:lambda表达式中需要引用的变量需在定义时候声明为常量 (final),jdk8中默认对lambda表达式引用的变量添加了final
**级联表达式:(多个箭头组成的lambda表达式)
Function<Integer,Function<Integer,Integer>> fun=x -> y -> x+y;
System.out.println(fun.apply(2).apply(3));//结果为5
**柯里化:把多个参数的函数转换为只有一个参数的函数
目的:简化参数个数
Function<Integer,Function<Integer,Function<Integer,Integer>>>fun2=x ->y -> x;
System.out.println(fun2.apply(2).apply(3).apply(4));//结果为9
demo1:
/**
* 没有返回值的写法
*
* */
Runnable ok = new Runnable() {
@Override
public void run() {
System.out.println("ok");
}
};
new Thread(ok).start();
//jdk8 lambda
Runnable runnable=()-> System.out.println("ok");
new Thread(runnable).start();
/**
* 有返回值的写法
*
* */
interface Inte1{
int getI(int i);
default int add(int x,int y){
return x+y;
}
}
Inte1 i1=(i) -> i*2;
Inte1 i2=i->i*2;
Inte1 i3=(i) -> {
return i*2;
};
demo2:
public class MoneyDemo {
private int money;
MoneyDemo(int money){
this.money=money;
}
/**
* 参数表示接口方法实现的对象实例
* @param function
*/
public void printMoney(Function<Integer,String> function){
System.out.println(function.apply(this.money));
}
public static void main(String[] args) {
int money=9999;
MoneyDemo demo=new MoneyDemo(money);
demo.printMoney(i -> i+"元");
/**
* 结果:
* 9999元
*/
}
}
demo3:
class Dog{
private String name="哮天犬";
private int food=10;
public Dog(){
}
public Dog(String name){
this.name=name;
}
public static void bark(Dog dog){
System.out.println(dog+"叫了");
}
public int eat(Integer val){
System.out.println("吃了"+val);
return food-val;
}
@Override
public String toString() {
return name;
}
}
public class MethodRef {
public static void main(String[] args) {
//静态方法引用:类名::方法名
Consumer<Dog> consumer=Dog::bark;
Dog dog=new Dog();
consumer.accept(dog);
//非静态方法,使用对象实例的方法引用
Function<Integer,Integer> function=dog::eat;
System.out.println(function.apply(3));
//构造函数的方法引用
//无参构造函数
Supplier<Dog> supplier=Dog::new;
Dog dog1 = supplier.get();
System.out.println(dog1);
//有参构造函数
Function<String,Dog> function1=Dog::new;
Dog dogWC = function1.apply("旺财");
System.out.println(dogWC);
}
}
②Stream流编程:
概念:流水线处理:输入数据,经过一系列ApI操作后最终得出结果
**创建操作:创建一个流
**中间操作:返回stream的操作
**终止操作:得到最终结果值
**惰性操作:终止没有调用的情况下,中间操作不会执行
创建流的方法
集合:Collection.stream/parallelStream
数组:Arrays.stream
数字Stream:
IntStream/LongStream.range/rangeClosed
Random.ints/longs/doubles
自己创建:Stream.generate/iterate
中间操作:
**无状态操作:(前后无依赖关系)
map/mapToXXX:A对象转化为B对象、类型转化
flatMap/flatMapToXXX:应用于对象属性中具有list等集合类型
filter:过滤条件
peek:类似于foreach但是中间操作
unordered:并行流常用
**有状态操作:(前后存在依赖关系)
distinct:去重
sorted:排序
limit/skip:限制
见demo4
**终止操作:
****非短路操作:需要等待所有数据完成才返回
forEach/forEachOrdered:多线程时保证输入时顺序
collect/toArray:转化为数组/集合
reduce: 做添加、计算操作
min/max/count:取最值、数量
****短路操作:得到一个数据就可返回
findFirst/findAny
allMatch/anyMatch/noneMatch
**并行流
****调用parallel产生并行流
****调用sequential产生串行流
****并行流使用的线程池:ForkJoinPool.commonPool,默认线程数为当前机器的cpu核心线程个数
****自定义线程池:
ForkJoinPool pool=new ForkJoinPool(20);
pool.submit(具体操作);
pool.shutdown();//不再接收新任务
**收集器
Collectors.toList()//收集为一个list
Collectors.toSet()//收集为一个set
Collectors.toCollection();//收集为hashmap
Collectors.toCollection(TreeSet::new)//收集为TreeSet
Collectors.summarizingInt(Student::getAge);//汇总学生年龄(总数、总和、最小值、最大值、平均值)
Collectors.partitionBy(s->s.getGennder()==Gender.MALE);//分块(key:判断条件,value:判断结果列表)
Collectors.groupingBy(Student::getGrade);//分组(key:分组条件,value:分组结果列表)
**Stream的执行机制
****所有操作时链式执行,一个元素只迭代一次
****每一个中间操作都会返回一个新的流,流里面有一个属性sourceStage执行同一个地方,就是head
【sourceStage:head节点;nextStage:下一个流(下个为终止操作时为null)】
****有状态操作会把无状态操作截断
【链式操作流程:无状态操作1—>无状态操作2】
item1->无状态1->无状态2 item2->无状态1->无状态2】
【链式操作流程:无状态操作1—>有状态—>无状态操作2 】
item1->无状态1 item2->无状态1 ||(截断)item1->有状态 item2->有状态 || item1->无状态2 item->无状态2
****并行环境下,有状态的中间操作不一定能并行操作;parallel/sequential并不创建流,只修改Head的并行标志
demo4
List<String> list=new ArrayList<>();
//从集合创建
list.stream();
list.parallelStream();
//从数组创建
Arrays.stream(new int[]{2,3,4});
//数字流
IntStream.of(1,2,3);
//带边界值的数字流
IntStream.rangeClosed(1,10);
//随机流(不指定数量会无限制创建)
new Random().ints().limit(10);
//自定义流
Random random = new Random();
Stream.generate(()->random.nextInt()).limit(10);
//把长度大于2的每个单词的长度输出出来
String str="my name is 007";
Stream.of(str.split(" ")).filter(t->t.length()>2).map(e->e.length()).forEach(System.out::println);
//intStream/longStream并不是Stream的子类,所以要进行装箱boxed
//打印单词中各字符的assci数值
Stream.of(str.split(" ")).flatMap(s->s.chars().boxed()).forEach(System.out::println);
//使用reduce拼接字符串(参数为二元函数,表示2个同类型参数)
Optional<String> reduce = Stream.of(str.split(" ")).reduce((x, y) -> x + "|" + y);
//对空指针返回空串
System.out.println(reduce.orElse(""));
//或者 带初始值
String reduce1 = Stream.of(str.split(" ")).reduce("", (x, y) -> x + "|" + y);
System.out.println(reduce1);
2)SpringWebFlux
**概念:非阻塞开发模型,运行在netty和servlet3.1容器上,支持高并发量
与SpringMVC关系:
①webflux是非阻塞的、SpringMVC是阻塞的【一个请求对应一个线程】
②webflux基于ReactiveStream,MVC基于Servlet API
③webFlux不使用关系型数据库(关系型不支持webflux)
**异步Servlet
同步servlet运行机制:请求发送到tomcat容器,tomcat容器分配线程并调用servlet执行,业务逻辑执行的时间=线程执行的时间
异步servlet运行机制:
开启异步上下文(AsyncContext);把业务逻辑放到异步任务中(CompletableFuture);执行后通知上下文结束执行(AsyncContext.complete());【目的:耗时操作转移给其他线程,而调用servlet的线程不受耗时操作限制,可以处理其他事情,因此提高了吞吐量,达到高并发作用】(但对于前台而言,响应时间为最大耗时操作)
5)webflux开发
①基于Reactor(jdk8 stream[流操作]+ jdk9 reactive stream[发布者-订阅者模式,且订阅者可以自行选择消费数量])
示例:
String[] strs={“1”,“2”,“3”};
Flux.fromArray(strs).map(s->Integer.parseInt(s)).subscribe(subscriber);
②异步示例:见demo11
③完整webflux示例,见demo12
注意:webflux开发所有的操作都是基于流进行,且流的终止操作必须交由springboot来进行消费
demo11
@RequestMapping("/test1")
public String test1(){
long l = System.currentTimeMillis();
String str = createStr();
//线程阻塞
long l1 = System.currentTimeMillis();
System.out.println(l1-l);
/**
* 耗时:5004
*/
return "test1";
}
@RequestMapping("/test2")
public Mono<String> test2(){
long l = System.currentTimeMillis();
//没有使用subscribe完成终止操作,即所取的是中间操作,因此不造成线程阻塞
Mono<String> mono=Mono.fromSupplier(()->createStr());
long l1 = System.currentTimeMillis();
System.out.println(l1-l);
/**
* 耗时:5
*/
return mono;
}
private String createStr(){
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "createStr";
}
/**
*
* @return
*/
@RequestMapping(value="/test3")
public Flux<String> test3(){
Flux<String> result=Flux.fromStream(IntStream.range(1,5).mapToObj(i->{
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "flux data--"+i;
}));
return result;
}
demo12
@RestController
@RequestMapping("/user")
public class UserController {
private final IUserRepository repository;
public UserController(IUserRepository repository) {
this.repository = repository;
}
@GetMapping("/")
public Flux<User> getAll(){
return repository.findAll();
}
@GetMapping(value="/stream/all",produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public Flux<User> streamgetAll(){
return repository.findAll();
}
/**
*
* @param user
* @return
*/
@PostMapping("/add")
public Mono<User> createUser(@RequestBody User user){
user.setId(null);
return this.repository.save(user);
}
@DeleteMapping("/{id}")
public Mono<ResponseEntity<Void>> deleteUser(@PathVariable("id")String id){
return this.repository.findById(id)
//要操作数据,返回一个mono这个时候用flatMap
//如果不是操作数据,而是转换数据用map
.flatMap(user -> this.repository.delete(user).then(Mono.just(new ResponseEntity<Void>(HttpStatus.OK))))
.defaultIfEmpty(new ResponseEntity<>(HttpStatus.NOT_FOUND));
}
@PutMapping("/{id}")
public Mono<ResponseEntity<User>> updateUser(@PathVariable("id") String id,
@RequestBody User user){
return this.repository.findById(id)
.flatMap(u->{
u.setAge(user.getAge());
u.setName(user.getName());
return this.repository.save(u);
})
.map(u-> new ResponseEntity<User>(u,HttpStatus.OK))
.defaultIfEmpty(new ResponseEntity<>(HttpStatus.NOT_FOUND));
}
}
④RouterFunction模式
a.webflux与mvc的类关系:ServerRequest= =HttpServletRequest ServerResponse= =HttpServletResponse
b.相关组件:
UserHandler
RouterFunction<ServerResponse>
/**
* @ClassName UserHandler 相当于controller
*
* @Description
* 格式:
* 请求参数:ServerRequest request
* 返回数据:Mono<ServerResponse>
*
*
* @Author 梁伟标
* @Date 2022/6/15 10:20
* @Version 1.0
**/
@Component
public class UserHandler {
private final IUserRepository repository;
public UserHandler(IUserRepository repository) {
this.repository = repository;
}
/**
* 查询
* @param request
* @return
*/
public Mono<ServerResponse> getAllUser(ServerRequest request){
return ServerResponse.ok().contentType(MediaType.APPLICATION_JSON)
.body(this.repository.findAll(), User.class);
}
/**
* 创建
* @param request
* @return
*/
public Mono<ServerResponse> createUser(ServerRequest request){
Mono<User> user=request.bodyToMono(User.class);
return user.flatMap(u->{
//校验代码在这里执行
if(u.getName()==null){
throw new RuntimeException();
}
return ServerResponse.ok().contentType(MediaType.APPLICATION_JSON)
.body(this.repository.saveAll(user),User.class);
});
}
public Mono<ServerResponse> deleteUser(ServerRequest request){
String id = request.pathVariable("id");
return this.repository.findById(id).flatMap(user->this.repository.delete(user).then(ServerResponse.ok().build()))
.switchIfEmpty(ServerResponse.notFound().build());
}
}
/**
* @ClassName UserRoute 制定路由规则
* @Description TODO
* @Date 2022/6/15 10:35
* @Version 1.0
**/
@Configuration
public class AllRouters {
@Bean
RouterFunction<ServerResponse> userRouter(UserHandler handler){
return RouterFunctions.nest(
RequestPredicates.path("/user"),//相当于类上@RequestMapping("/user")
RouterFunctions.route(RequestPredicates.GET("/"),handler::getAllUser))//相当于方法上@GetMapping("/")
.andRoute(RequestPredicates.POST("/").and(RequestPredicates.accept(MediaType.APPLICATION_JSON)),handler::createUser)
.andRoute(RequestPredicates.DELETE("/{id}"),handler::deleteUser);
}
}