spring学习笔记
2021-07-06 14:29:31 0 举报
AI智能生成
spring学习笔记
作者其他创作
大纲/内容
架构演变
单一应用架构(数据访问框架ORM是关键)
垂直应用架构(MVC框架是关键)
分布式服务架构(分布式框架RPC是关键)
流动计算架构(用于提高机器利用率的资源调度和治理中心SOA是关键)
框架演变
JSP + Servlet(Serve Applet)+ JavaBean
MVC三层架构
EJB->过多的接口和依赖,侵入性强
Struts1/Struts2 + Spring + Hibernate
Spring + SpringMVC + Mybatis
SpringBoot->约定大于配置
Spring生态
Spring
SpringBoot
SpringCloud
中文文档
github.com->spring-docs
IOC(Inversion of Control)
IOC思想
1.IOC容器控制依赖的对象,对于程序员来说,由之前的主动创建依赖对象到现在的被动接收创建好的依赖对象。
2.IOC是思想,DI是具体实现。
控制反转(IOC):就是应用本身不负责依赖对象的创建和维护,依赖对象的创建及维护是由外部容器负责的,这样控制权就由应用转移到了外部容器,即控制权的转移就是控制反转。
依赖注入(DI):在程序运行期间,由外部容器动态地将依赖对象注入到组件中。主要通过构造函数或者setter来具体实现。
3.IOC容器的解耦作用
IOC容器初始化过程
第一个过程是Resource资源定位。这个Resouce指的是BeanDefinition的资源定位。这个过程就是容器找数据的过程,就像水桶装水需要先找到水一样。
第二个过程是BeanDefinition的载入过程。这个载入过程是把用户定义好的Bean表示成Ioc容器内部的数据结构,而这个容器内部的数据结构就是BeanDefition。
第三个过程是向IOC容器注册这些BeanDefinition的过程,这个过程就是将前面的BeanDefition保存到HashMap中的过程。
在源码中表现为:refresh()方法中的obtainFreshBeanFactory()方法创建BeanFactory以及读取xml文件信息并将每个bean的定义注册为BeanDefinaton后放入一个HashMap中最后赋值给DefaultListableBeanFactory对象
bean的生命周期
IOC容器初始换完成后,当容器启动后就会实例化bean,此时开始了bean的生命周期
注意点
ApplicationContext就是IOC容器的接口,可以通过此对象获取容器中创建的对象
ClassPathXmlApplicationContext
FileSystemXmlApplicationContext
对象在Spring容器中默认是在容器创建完成的时候就已经创建完成,不是需要用的时候才创建,此种情况满足的是单例模式
对象在IOC容器中存储的时候都是单例的,如果需要多例需要修改属性
spring对象的获取(创建方式)
1.通过bean的id获取IOC容器的对象:
例如:
ApplicationContext context = new ClassPathXmlApplicationContext("ioc.xml");
Person person = context.getBean("person", Person.class);
<bean id="person" class="com.fan.bean.Person"></bean>
2.根据bean的类型来获取对象,注意:当通过类型进行获取的时候,如果存在两个相同类型对象,将无法正常获取。
Person person = context.getBean(Person.class);
<bean id="person" class="com.fan.bean.Person"></bean>
3.利用工厂方法创建bean
静态工厂:类名.静态方法()
public class PersonStaticFactory {
public static Person getInstance(String name){
Person person = new Person();
person.setId(1);
person.setName(name);
person.setAge(11);
return person;
}
}
<bean id="person10" class="com.fan.factory.PersonStaticFactory" factory-method="getInstance">
<constructor-arg value="zhangsan"></constructor-arg>
</bean>
实例工厂:先创建工厂实例,然后调用工厂实例的方法
factory-bean:表示具体工厂类的实例
factory-method:表示具体工厂实例的方法
public class PersonInstanceFactory {
public Person getInstance(String name){
Person person = new Person();
person.setId(2);
person.setName(name);
person.setAge(22);
return person;
}
}
<!--先创建工厂实例-->
<bean id="instanceFactory" class="com.fan.factory.PersonInstanceFactory"></bean>
<!--再通过实例调用工厂方法-->
<bean id="person11" class="com.fan.bean.Person" factory-bean="instanceFactory" factory-method="getInstance">
<constructor-arg value="lisi"></constructor-arg>
</bean>
4.通过FactoryBean创建对象
FactoryBean是Spring规定的一个接口,对于FactoryBean接口的实现类,Spring
都会将其作为一个工厂,但是在ioc容器启动的时候不会创建实例,
只有在使用的时候才会创建对象。此方式是spring创建bean方式的一种补充,
用户可以按照需求创建对象,创建的对象交由spring IOC容器来进行管理,无论是否是单例,
都是在用到的时候才会创建该对象,不用该对象不会创建。
使用方法:
1、实现FactoryBean接口
2、将实现类交给IOC容器去管理
public class MyFactoryBean implements FactoryBean<Person> {
/*
* 返回获取的bean
* */
public Person getObject() throws Exception {
Person person = new Person();
person.setId(3);
person.setName("王五");
return person;
}
/*
* 获取返回bean的类型
* */
public Class<?> getObjectType() {
return Person.class;
}
/*
* 判断当前bean是否是单例的
* */
public boolean isSingleton() {
return false;
}
}
<bean id="myFactoryBean" class="com.fan.factory.MyFactoryBean"></bean>
给对象赋值
1.通过property来给属性赋值,name的名称取决于set方法后面的参数首字符小写的名称。然后通过实体类的setter方法实现赋值
配置文件中:
<bean id="person" class="com.fan.bean.Person" >
<property name="id" value="1"></property>
<property name="name" value="zhangsan"></property>
</bean>
实体类中:
public void setId(int id) {
this.id = id;
}
public void setName(String name) {
this.name = name;
}
2.过构造器来给属性赋值,name的名称由构造方法的参数列表来决定
name:表示参数列表的名称
value:表示实际的具体值
type:表示值的类型
index:表示值的下标,从0开始
xml配置文件中:
<bean id="person2" class="com.fan.bean.Person">
<constructor-arg name="id" value="2"></constructor-arg>
<constructor-arg name="name" value="tom"></constructor-arg>
<constructor-arg name="age" value="22"></constructor-arg>
<constructor-arg name="gender" value="男"></constructor-arg>
</bean>
实体类中:
public Person(int id, String name, Integer age, String gender) {
this.id = id;
this.name = name;
this.age = age;
this.gender = gender;
}
当通过构造器方法赋值的时候,name属性可以省略不写,但要注意value的赋值顺序要和构造方法参数列表一致
<constructor-arg value="2"></constructor-arg>
可以加index来保证顺序的正确
<constructor-arg value="2" index="0"></constructor-arg>
当有多个相同参数个数的构造方法存在的时候,默认情况下是覆盖的过程,后面的构造方法会覆盖前面的构造方法,如果非要赋值给另外一个构造方法的话,,可以使用type参数来指定类型
<constructor-arg value="22" type="java.lang.Integer"></constructor-arg>
3.使用p命名空间给属性赋值
xmlns:p="http://www.springframework.org/schema/p"
<bean id="person6" class="com.fan.bean.Person" p:id="5" p:name="wangwu" p:age="25" p:gender="女">
</bean>
xmlns: xml namespace
xsi: xml schema instance
4.给复杂类型赋值
<!--给数组赋值, 使用array标签-->
<!--<property name="hobbies" value="book,sport,movie"></property>-->
<property name="hobbies">
<array>
<value>book</value>
<value>movie</value>
<value>sport</value>
</array>
</property>
<!--给引用类型赋值, 可以使用ref引入外部bean-->
<property name="address" ref="address"></property>
<!--给list赋值-->
<!--<property name="lists" value="1,2,3"></property>-->
<property name="lists">
<list>
<!--使用内部bean,无法从IOC容器中直接获取对象的值-->
<bean class="com.fan.bean.Address">
<property name="province" value="北京"></property>
</bean>
<bean class="com.fan.bean.Address">
<property name="province" value="上海"></property>
</bean>
<!--使用外部bean,可以随意从IOC容器中获取对象的值-->
<ref bean="address"></ref>
</list>
</property>
<!--给set属性赋值-->
<property name="sets">
<set>
<value>zhangsan</value>
<value>zhangsan</value>
<value>wangwu</value>
</set>
</property>
<!--给map赋值-->
<property name="maps">
<map>
<entry key="a" value="aaa"></entry>
<entry key="address" value-ref="address"></entry>
<entry key="address2">
<bean class="com.fan.bean.Address">
<property name="province" value="广东省"></property>
</bean>
</entry>
<entry>
<key>
<value>hehe</value>
</key>
<value>haha</value>
</entry>
<entry key="list">
<list>
<value>11</value>
<value>22</value>
</list>
</entry>
</map>
</property>
<!--给properties赋值-->
<property name="properties">
<props>
<prop key="111">aaa</prop>
<prop key="222">bbb</prop>
</props>
</property>
5.可以通过parent属性来获取父bean中的某些属性值
<!--bean之间的继承关系.
可以使用abstract标签定义抽象bean,为true时无法进行实例化-->
<bean id="parent" class="com.fan.bean.Person" abstract="false">
<property name="id" value="1"></property>
<property name="name" value="zhangsan"></property>
<property name="age" value="20"></property>
<property name="gender" value="男"></property>
</bean>
<!--可以通过parent属性来获取父bean中的某些属性值-->
<bean id="son" class="com.fan.bean.Person" parent="parent">
<property name="name" value="haha"></property>
</bean>
bean的所用域
通过scope属性可以指定当前bean的作用域
singleton:单例模式,从IOC容器中获取的都是同一个对象,默认的作用域
prototype:多例模式,从IOC容器中获取的对象每次都是新创建
注意:
如果是singleton作用域的话,每次在创建IOC容器完成之前此对象已经创建完成
如果是prototype作用域的话,每次是在需要用到此对象的时候才会创建
在spring4.x的版本中还包含另外两种作用域:
request:每次发送请求都会有一个新的对象
session:每一次会话都会有一个新的对象
<bean id="person9" class="com.fan.bean.Person" scope="prototype"></bean>
初始化和销毁方法
spring容器在创建对象的时候可以指定具体的初始化和销毁方法
init-method:在对象创建完成之后会调用初始化方法
destory-method:在容器关闭的时候会调用销毁方法
初始化和销毁的方法跟scope属性也是相关的
如果是singleton的话,初始化和销毁的方法都存在
如果是prototype的话,初始化方法会调用,但是销毁的方法不会调用
//实体类中定义普通的方法
public void init(){
//编写N行逻辑代码完成初始化功能
System.out.println("person对象初始化完成");
}
public void destory(){
System.out.println("person对象被销毁");
}
<bean id="person12" class="com.fan.bean.Person" init-method="init" destroy-method="destory"></bean>
bean对象的增强处理
BeanPostProcessor使用步骤:
1、实现BeanPostProcessor接口并重写其中的方法
2、交给IOC容器管理
public class MyBeanPostProcessor implements BeanPostProcessor {
/**
* 在每一个对象的初始化方法之前执行
* @param bean 表示创建的具体对象
* @param beanName 表示bean的id属性
* @return
* @throws BeansException
*/
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
System.out.println("postProcessBeforeInitialization:" + beanName);
return bean;
}
/**
* 在每一对象的初始化方法后面执行
* @param bean
* @param beanName
* @return
* @throws BeansException
*/
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
System.out.println("postProcessAfterInitialization:" + beanName);
return bean;
}
}
<bean id="myBeanPostProcessor" class="com.fan.bean.MyBeanPostProcessor"></bean>
自动装配
在spring中,可以使用自动装配的功能,spring会把某些bean注入到另外的bean中,
可以使用autowire属性来实现自动装配,有以下几种情况:
default/no: 不装配
byName: 按照bean的id来进行装配(set方法匹配bean的id,如上面的address),根据set方法后面的名称首字母小写决定的,不是参数列表的名称
byType: 按照bean的类型来进行装配,如果有多个相同的类型就会报错,因为IOC容器不知道选择哪一个具体的类型
constructor: 按照构造器进行装配,首先按照类型进行判断,如果有多个类型相同的bean,再按照bean的id去进行判断
<!--以下id为address的bean对象会被自动装配到Person对象中-->
<bean id="address" class="com.fan.bean.Address">
<property name="province" value="河北"></property>
<property name="city" value="邯郸"></property>
<property name="town" value="武安"></property>
</bean>
<bean id="person13" class="com.fan.bean.Person" autowire="byName"></bean>
//测试类
Person person13 = context.getBean("person13", Person.class);
System.out.println(person13);
创建第三方bean对象
首先需要引入第三方相应的ja包,这里以阿里的druid连接池为例:
<!-- https://mvnrepository.com/artifact/com.alibaba/druid -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.23</version>
</dependency>
<!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.47</version>
</dependency>
//ioc容器中创建druid对象
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="username" value="root"></property>
<property name="password" value="123456"></property>
<property name="url" value="jdbc:mysql://localhost:3306/demo"></property>
<property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
</bean>
//测试类
DruidDataSource dataSource = context.getBean("dataSource", DruidDataSource.class);
System.out.println(dataSource); System.out.println(dataSource.getConnection());
引入外部配置文件
需要引入外部的配置文件的时候,需要导入一些context的命名空间:
<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:db.properties"></context:property-placeholder>
<!--在配置文件编写属性的时候需要注意,
spring容器在进行启动的时候,会读取当前系统的某些环境变量的配置,
当前系统的用户名是用username来表示的,所以最好的方式是给配置文件中属性添加前缀来做区分
-->
<bean id="dataSource2" class="com.alibaba.druid.pool.DruidDataSource">
<property name="username" value="${username}"></property>
<property name="password" value="${password}"></property>
<property name="url" value="${url}"></property>
<property name="driverClassName" value="${driverClassName}"></property>
</bean>
</beans>
//位于resources目录下的db.properties
username=root
password=123456
url=jdbc:mysql://localhost:3306/demo
driverClassName=com.mysql.jdbc.Driver
//测试类
DruidDataSource dataSource = context.getBean("dataSource2", DruidDataSource.class);
System.out.println(dataSource); System.out.println(dataSource.getConnection());
SpEL表达式语言的使用
<bean id="address" class="com.fan.bean.Address">
<property name="province" value="河北"></property>
<property name="city" value="邯郸"></property>
<property name="town" value="武安"></property>
</bean>
<bean id="person14" class="com.fan.bean.Person">
<!--可以引入外部bean对象的属性-->
<property name="name" value="#{address.province}"></property>
<!--支持运算符的所有操作-->
<property name="age" value="#{12 * 2}"></property>
<!--可以引入外部bean-->
<property name="address" value="#{address}"></property>
<!--可以调用静态方法-->
<property name="gender" value="#{T(java.util.UUID).randomUUID().toString().substring(0,5)}"></property>
<!--调用非静态方法-->
<property name="hobbies" value="#{address.getCity()}"></property>
</bean>
//测试类
Person person14 = context.getBean("person14", Person.class);
System.out.println(person14);
BeanFactory和FactoryBean的区别
BeanFactory是个Factory,也就是IOC容器或对象工厂,在Spring中,所有的Bean都是由BeanFactory(也就是IOC容器)来进行管理的,提供了实例化对象和拿对象的功能。
使用场景:
- 从Ioc容器中获取Bean(byName or byType)
- 检索Ioc容器中是否包含指定的Bean
- 判断Bean是否为单例
FactoryBean是个Bean,这个Bean不是简单的Bean,而是一个能生产或者修饰对象生成的工厂Bean,它的实现与设计模式中的工厂模式和修饰器模式类似。
使用场景
- ProxyFactoryBean
BeanFactory接口和ApplicationContext接口
相同:
- Spring提供了两种不同的IOC 容器,一个是BeanFactory,另外一个是ApplicationContext,它们都是Java interface,ApplicationContext继承于BeanFactory(ApplicationContext继承ListableBeanFactory。
- 它们都可以用来配置XML属性,也支持属性的自动注入。
- 而ListableBeanFactory继承BeanFactory),BeanFactory 和 ApplicationContext 都提供了一种方式,使用getBean("bean name")获取bean。
不同:
- 当你调用getBean()方法时,BeanFactory仅实例化bean,而ApplicationContext 在启动容器的时候实例化单例bean,不会等待调用getBean()方法时再实例化。
- BeanFactory不支持国际化,即i18n,但ApplicationContext提供了对它的支持。
- BeanFactory与ApplicationContext之间的另一个区别是能够将事件发布到注册为监听器的bean。
- BeanFactory 的一个核心实现是XMLBeanFactory 而ApplicationContext 的一个核心实现是ClassPathXmlApplicationContext,Web容器的环境我们使用WebApplicationContext并且增加了getServletContext 方法。
- 如果使用自动注入并使用BeanFactory,则需要使用API注册AutoWiredBeanPostProcessor,如果使用ApplicationContext,则可以使用XML进行配置。
- 简而言之,BeanFactory提供基本的IOC和DI功能,而ApplicationContext提供高级功能,BeanFactory可用于测试和非生产使用,但ApplicationContext是功能更丰富的容器实现,应该优于BeanFactory
AOP(Aspect-oriented Programming)面向切面编程
AOP定义
面向切面编程(AOP)是对面向对象编程(OOP)的一种补充,AOP提供了一种不一样的编程思想,这个不一样体现在:OOP更多关注的是类,而AOP关注的是切面,并且切面在AOP中是模块化的,如事务管理、日志管理等都可以作为切面来在需要的地方提供相应的服务。同时,AOP也是对IOC的一种增强,但IOC和AOP之间是独立的。其中,AOP最重要的两个思想点是:
1、 提供声明式企业服务。此类服务中最重要的是声明式事务管理。AOP出现之前,对于事务管理我们都是通过编程式的事务管理,比如手动提交、回滚等。而AOP的事务管理是声明式的,可以简单的通过注解来实现,大大简化了对于事务的操作。
2、用户可以自定义切面。这意味着你可以在程序运行期间织入自己想要实现的一些功能。
AOP原理
Spring AOP使用的动态代理,所谓的动态代理就是说AOP框架不会去修改字节码,而是在内存中临时为方法生成一个AOP对象,这个AOP对象包含了目标对象的全部方法,并且在特定的切点做了增强处理,并回调原对象的方法。
Spring AOP中的动态代理主要有两种方式,JDK动态代理和CGLIB动态代理。JDK动态代理通过反射来接收被代理的类,并且要求被代理的类必须实现一个接口。JDK动态代理的核心是InvocationHandler接口和Proxy类。
如果目标类没有实现接口,那么Spring AOP会选择使用CGLIB来动态代理目标类。CGLIB(Code Generation Library),是一个代码生成的类库,可以在运行时动态的生成某个类的子类,注意,CGLIB是通过继承的方式做的动态代理,因此如果某个类被标记为final,那么它是无法使用CGLIB做动态代理的。由于cglib会动态生成某个类的子类,当生成的类过多的时候占用内存过大可能会造成OOM,因为MataSpace默认为21M,所以需要设置其大小,一般设置为230M左右。项目中一般要设置的参数为:线程、堆初始大小以及堆最大大小、元空间初始大小以及元空间最大大小。
Spring AOP中的动态代理主要有两种方式,JDK动态代理和CGLIB动态代理。JDK动态代理通过反射来接收被代理的类,并且要求被代理的类必须实现一个接口。JDK动态代理的核心是InvocationHandler接口和Proxy类。
如果目标类没有实现接口,那么Spring AOP会选择使用CGLIB来动态代理目标类。CGLIB(Code Generation Library),是一个代码生成的类库,可以在运行时动态的生成某个类的子类,注意,CGLIB是通过继承的方式做的动态代理,因此如果某个类被标记为final,那么它是无法使用CGLIB做动态代理的。由于cglib会动态生成某个类的子类,当生成的类过多的时候占用内存过大可能会造成OOM,因为MataSpace默认为21M,所以需要设置其大小,一般设置为230M左右。项目中一般要设置的参数为:线程、堆初始大小以及堆最大大小、元空间初始大小以及元空间最大大小。
AOP术语
AOP中的术语:
Aspect:切面。对关注点的模块化,所谓关注点可以理解为应用程序中公共部分代码逻辑,比如对事物的管理、日志的管理等代码逻辑可以作为一个模块来供其他代码中使用,该模块在AOP中通常称为切面。具体实现方式:xml配置或者注解。xml的方式在随后的代码中讨论,注解的方式通俗的来说就是在普通类上面加一个@Aspect注解。
Join point:连接点。程序执行期间的一个点,如方法执行或异常处理期间的一个点。在Spring AOP中,连接点总是表示方法执行。连接点正是通知方法执行的时机。
advice:通知。用来定义在切面的连接点上采取的操作(某一个方法)。通知类型有五种,以注解的方式来看包括:@Before、@After、@AfterReturning、@AfterThrowing、@Around
Pointcut:切入点。从注解的方式来看,切入点是用来定义连接点的一种表达式,通知就是执行在切入点定义好的连接点之上的。用切入点来定义连接点可以在通知中更简洁的引入,当然也可以不用切点来定义连接点,连接点可以直接在通知中进行定义。
Introduction: 引入。引入其他方法或字段。Spring AOP允许向任何被建议的对象引入新的接口(以及相应的实现)。例如,可以让bean实现一个IsModified接口,以简化缓存。(在AspectJ社区中,引入称为类型间声明。)
Target object:目标对象。被一个或多个切面通知的对象。也称为被通知的对象。因为Spring AOP是通过使用运行时代理实现的,所以这个对象始终是一个代理对象。
AOP proxy:AOP框架为了实现切面契约(通知方法的执行等等)而创建的对象。在Spring框架中,AOP代理是JDK动态代理或CGLIB代理。
Weaving:织入。将切面与其他应用程序类型或对象链接以此来创建一个通知对象。这可以在编译时(例如使用AspectJ编译器)、加载时或运行时完成。与其他纯Java AOP框架一样,Spring AOP在运行时完成织入。
AOP使用
AOP注解的方式:
需要的包可以通过maven导入:spring-context(maven会自动下载该包的相关依赖,比如spring-aop等)、cglib、aspectjrt、aopalliance、spring-aspects。
applicationContext.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
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.fan"></context:component-scan>
<!--开启AOP的注解功能-->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
</beans>
切面类:
package com.fan.util;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.*;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import java.util.Arrays;
@Aspect
@Component //保证能被ioc容器扫描到该类
@Order(200) //决定切面的执行顺序,默认是按照切面类的首字母排序执行
public class LogUtil {
//相同的表达式可以使用切入点抽象成一个方法供其他通知方法引用
@Pointcut("execution(public Integer com.fan.service.MyCalculator.*(..))")
public void myPointCut(){}
@Pointcut("execution(* *(..))")
public void myPointCut1(){}
//@Before(value = "execution(public Integer com.fan.service.MyCalculator.*(..))")
@Before(value = "myPointCut()") //前置通知,在方法执行之前完成
public static void start(JoinPoint joinPoint){
//获取方法签名
Signature signature = joinPoint.getSignature();
//获取方法参数信息
Object[] args = joinPoint.getArgs();
System.out.println("Log---" + signature.getName() + "方法开始执行:参数是" + Arrays.asList(args) );
}
//@AfterReturning(value = "execution(public Integer com.fan.service.MyCalculator.*(..))", returning = "result")
@AfterReturning(value = "myPointCut()", returning = "result") //返回通知,在返回结果之后执行
public static void stop(JoinPoint joinPoint, Object result) {
Signature signature = joinPoint.getSignature();
System.out.println("Log---" + signature.getName() + "方法执行结束:结果是:" + result);
}
//@AfterThrowing(value = "execution(public Integer com.fan.service.MyCalculator.*(..))", throwing = "e")
@AfterThrowing(value = "myPointCut()", throwing = "e") //异常通知,出现异常的时候执行
public static void logException(JoinPoint joinPoint, Exception e){
Signature signature = joinPoint.getSignature();
System.out.println("Log---" + signature.getName() + "抛出异常:" + e.getMessage());
}
//@After("execution(public Integer com.fan.service.MyCalculator.*(..))")
@After("myPointCut()") //后置通知,在方法执行完成之后执行
public static void logFinally(JoinPoint joinPoint){
Signature signature = joinPoint.getSignature();
System.out.println("Log---" + signature.getName() + "方法执行结束...");
}
@Around("myPointCut()") //环绕通知,更细粒度的通知方式
public Object around(ProceedingJoinPoint pjp) throws Throwable {
Signature signature = pjp.getSignature();
Object[] args = pjp.getArgs();
Object result = null;
try {
System.out.println("Log---" + "环绕通知start:" + signature.getName() + "方法开始执行,参数为:" + Arrays.asList(args));
//通过反射的方式调用目标方法(可以自己修改结果值),相当于jdk中代理的method.invoke()
result = pjp.proceed(args);
System.out.println("Log---" + "环绕通知stop:" + signature.getName());
} catch (Throwable throwable) {
System.out.println("Log---" + "环绕通知异常:" + signature.getName());
//出现异常的时候,在环绕通知中解决了,那么普通通知是接收不到的,如果想让普通通知接收到需要进行异常抛出
throw throwable;
}finally {
System.out.println("Log---" + "环绕通知返回:" + signature.getName() + "方法返回结果是:" + result);
}
return result;
}
}
以上切面类中需要注意:
+ 通知的执行顺序:
如果正常执行:@Before-->@After-->@AfterReturning
如果异常执行:@Before-->@After-->@AfterThrowing
+ 切入点表达式:
最精确的匹配方式:
public Integer com.fan.service.MyCalculator.add(Integer, Integer)
在实际生产环境中,更多的时候使用通配符的方式
一、
1、可以用来匹配一个或者多个字符,如public Integer com.fan.service.MyCalculator.*(Integer, Integer),匹配参数满足参数列表的多个方法
2、匹配任意类型的参数,如public Integer com.fan.service.M*Calculator.*(Integer, *)
3、在匹配时一个*只能匹配一层路径,要想匹配多层,如两层路径,可以使用两个*,public Integer com.fan.\*.\*.MyCalculator.add(Integer, Integer)
4、不能匹配访问修饰符,如果不确定访问修饰符是什么,可以直接省略不写,如 Integer com.fan.service.MyCalculator.add(Integer, Integer)
5、返回值可以使用\*来代替,public * com.fan.service.MyCalculator.add(Integer, Integer)
二、
1、可以匹配任意类型的多个参数,如execution(* com.fan.service.MyCalculator.\*(..))
2、可以匹配多层路径,如execution(* com.fan..MyCalculator.\*(..)
最偷懒的方式:
execution(\* \*(..)),第一个*代表返回值类型,第二个*代表所有路径
execution(* com..\*(..))
execution(* \*.\*(..))
如果表达式是以*开头,那么可以代替所有路径
在使用表达式的时候还支持逻辑运算:
&&:多个条件必须同时满足
execution(public Integer com.fan.service.MyCalculator.\*(..)) && execution(\* *(..))
||:多个条件只要满足其中一个即可
execution(public Integer com.fan.service.MyCalculator.\*(..)) || execution(\* *(..))
!:取反,除了该种情况外其他都满足
!execution(public Integer com.fan.service.MyCalculator.add(..))
+ 如果想要获取被代理类方法的参数或者方法名称等信息,必须使用JoinPoint对象,并且该对象作为方法参数必须在参数列表中第一个位置
getSignature()
getArgs()
+ 如果方法中有返回值,那么必须要在注解中添加Returning=“带有返回值的参数”,该参数必须要和参数列表中的参数名称保持一致
如果需要添加异常信息,那么在注解中要添加Throwing=“异常参数”,该参数必须要和参数列表中参数名称保持一致
+ 如果想要添加其他参数,必须要添加args(参数列表),ArgNames(参数列表):
value = "execution(public Integer com.fan.service.MyCalculator.*(..)) && args(joinPoint,i)", argNames = "joinPoint,i"
+ 通知方法在定义的时候对于方法的修饰符、返回值类型都没有明确的要求,
但是要注意,方法的参数不能随便添加
+ 环绕通知:
环绕通知在执行的时候优先于普通通知,
如果是正常结束,那么执行顺序是:
环绕前置通知-->@Before-->环绕后置通知-->环绕返回通知-->@After-->@AfterReturning
如果是异常结束,那么执行顺序是:
环绕前置通知-->@Before-->环绕异常通知-->环绕返回通知-->@After-->@AfterReturning
出现异常的时候,在环绕通知中解决了,那么普通通知是接收不到的,如果想让普通通知接收到需要进行异常抛出 throw throwable,此时执行顺序为:
环绕前置通知--》@Before--》环绕异常通知--》环绕返回通知--》@After--》@AfterThrowing
当应用程序包含多个切面类的时候,按照切面类的名称的首字母进行排序后执行,如果需要人为规定执行顺序,可有在切面类上添加@Order注解,同时可以添加具体的值,值越小,越优先。
需要被以上切面类增强的类:
package com.fan.service;
import org.springframework.stereotype.Service;
@Service
public class MyCalculator /*implements Calculator*/{
public Integer add(Integer i, Integer j) throws NoSuchMethodException {
Integer result = i + j;
return result;
}
public Integer sub(Integer i, Integer j) throws NoSuchMethodException {
Integer result = i - j;
return result;
}
public Integer mul(Integer i, Integer j) throws NoSuchMethodException {
Integer result = i * j;
return result;
}
public Integer div(Integer i, Integer j) throws NoSuchMethodException {
Integer result = i / j;
return result;
}
}
AOP的XML配置方式:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns: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/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
<bean id="logUtil" class="com.fan.util.LogUtil"></bean>
<bean id="myCalculator" class="com.fan.service.MyCalculator"></bean>
<!--配置aop-->
<aop:config>
<!--定义全局的pointcut-->
<!--
<aop:pointcut id="globalPointCut" expression="execution( Integer com.fan.service.MyCalculator.*(..))"/>
-->
<!--声明切面-->
<aop:aspect ref="logUtil">
<!--将通用的表达式抽取出来-->
<aop:pointcut id="myPointCut" expression="execution( Integer com.fan.service.MyCalculator.*(..))"/>
<!--定义切面中的通知方法在切入点所定义的连接点方法上使用-->
<aop:before method="start" pointcut-ref="myPointCut"></aop:before>
<aop:after method="logFinally" pointcut-ref="myPointCut"></aop:after>
<aop:after-returning method="stop" pointcut-ref="myPointCut" returning="result"></aop:after-returning>
<aop:after-throwing method="logException" pointcut-ref="myPointCut" throwing="e"></aop:after-throwing>
<aop:around method="around" pointcut-ref="myPointCut"></aop:around>
</aop:aspect>
<aop:aspect ref="securityUtil">
<aop:before method="start" pointcut-ref="myPointCut"></aop:before>
<aop:after method="logFinally" pointcut-ref="myPointCut"></aop:after>
<aop:after-returning method="stop" pointcut-ref="myPointCut" returning="result"></aop:after-returning>
<aop:after-throwing method="logException" pointcut-ref="myPointCut" throwing="e"></aop:after-throwing>
<aop:around method="around" pointcut-ref="myPointCut"></aop:around>
</aop:aspect>
</aop:config>
</beans>
AOP对事务的管理
AOP基于注解的声明式事务
用到的jar包:spring-context、cglib、aspectjrt、aopalliance、spring-aspects、druid、mysql-connector-java、spring-jdbc
applicationContext.xml:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
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">
<context:component-scan base-package="com.fan"></context:component-scan>
<!--加载数据库配置文件-->
<context:property-placeholder location="classpath:db.properties"></context:property-placeholder>
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="username" value="${jdbc.username}"></property>
<property name="password" value="${jdbc.password}"></property>
<property name="url" value="${jdbc.url}"></property>
<property name="driverClassName" value="${jdbc.driverName}"></property>
</bean>
<!--JdbcTemplate相当于增删改查模板-->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!--配置事务管理器的bean对象-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!--开启基于注解的事务管理器的配置-->
<tx:annotation-driven transaction-manager="transactionManager"></tx:annotation-driven>
</beans>
事务类:
package com.fan.service;
import com.fan.dao.BookDao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import java.io.File;
import java.io.FileNotFoundException;
@Service
public class BookService {
@Autowired
private BookDao bookDao;
//@Transactional(timeout = 3)
//@Transactional(readOnly = true)
//@Transactional(noRollbackFor = {ArithmeticException.class})
//@Transactional(noRollbackForClassName = {"java.lang.ArithmeticException"})
//@Transactional(rollbackFor = {FileNotFoundException.class})
//@Transactional(rollbackForClassName = {"java.io.FileNotFoundException"})
@Transactional(propagation = Propagation.NESTED)
public void buyBook() {
bookDao.getPrice(1);
bookDao.updateBalance("zhangsan", 100);
bookDao.updateStock(1);
int i = 1/0;
//timeout 超时时间测试
//Thread.sleep(3000);
//异常不回滚测试:noRollbackFor = {ArithmeticException.class}
//int i = 1/0;
//1、如果使用try catch把异常int i = 1/0;捕捉处理后,代码仍然可以继续往下执行,此时事务@Transactional
//不再起作用
/*try {
int i = 1/0;
} catch (Exception e) {
e.printStackTrace();
}*/
//2、遇到异常int i = 1/0;直接执行catch处理异常,然后结束,不会执行bookDao.updateStock(1);
//事务@Transactional不会起作用
/*try{
bookDao.getPrice(1);
bookDao.updateBalance("zhangsan", 100);
int i = 1/0;
bookDao.updateStock(1);
}catch(Exception e){
e.printStackTrace();
}*/
//异常回滚测试:rollbackFor = {FileNotFoundException.class}
//new File("a.txt");
}
@Transactional
public void updatePrice(){
bookDao.updatePrice(1);
}
}
以上事务类中需要注意:
* @Transactional(propagation = Propagation.XXX)注解中的值:
propagation:传播特性:表示不同的事务之间执行的关系,调用事务方法的事务方法与当前事务方法在不同的类中时才有事务传播特性,如果是在同一个类中,则被认为是同一个事务,不再具备AOP的事务传播特性。
spring的事务传播行为共有7种:(以下当前方法是指被事务注解Transactional()标记的方法)
1、REQUIRED:如果有事务在运行,当前的方法(被调用的事务方法)就在这个事务内运行,否则就启动一个新的事务,并在自己的事务内运行
如果当前方法有异常,即使在调用当前方法的方法(事务方法)中将当前方法的异常进行try catch,那么调用当前方法的方法中的其他代码仍然会进行回滚。
如果调用当前方法的事务方法中有异常或者调用当前方法的事务方法中调用的其他方法有异常,那么当前方法会进行回滚。
总结:REQUIRED事务方法依赖于外部事务方法,无论是当前事务方法还是调用当前事务方法的事务方法,只要任意一个事务方法中有异常,那么全部事务方法都将进行回滚。
2、REQUIRES_NEW:当前的方法启动新事务(这种事务是独立的,与调用当前方法的方法是否有事务无关),并在它自己的事务内运行,如果有事务正在运行,应该将他挂起(暂停)。
如果当前方法有异常,并且在调用当前方法的方法(事务方法)中将当前方法的异常进行try catch捕获处理,那么调用当前方法的方法中的其他代码不会进行回滚,而只对当前方法的代码进行回滚。也就是说,REQUIRES_NEW启动一个独立的新事务,该事务不会影响其他事务并且也不会受其他事务的影响。
如果调用当前方法的方法中有异常,那么当前方法不会进行回滚,因为当前方法是一个独立的新事务且没有异常,无需进行回滚。
总结:REQUIRES_NEW事务方法不依赖于外部事务方法,只有当前事务方法中有异常才会进行回滚。
3、SUPPORTS:如果有事务在运行,当前的方法就在这个事务内运行,否则它不运行在事务中
4、NOT_SUPPORTED:当前的方法不应该运行在事务中,如果有运行的事务,将他挂起,直到当前方法执行完毕
5、NEVER:当前的方法不应该运行在事务中,如果有运行的事务,就抛出异常(即当前方法不会正常执行)
6、MANDATORY:当前的方法必须运行在事务内,如果没有正在运行的事务,就抛出异常(即当前方法不会正常执行)
7、NESTED:如果有事务在运行,当前的方法就应该在这个事务的嵌套事务(被调用的子事务方法,包括当前方法)内运行,否则,就启动一个新的事务,并在它自己的事务内运行.
如果当前方法有异常,并且在调用当前方法的方法(事务方法)中将当前方法的异常进行try catch捕获处理,那么调用当前方法的方法中的其他事务方法代码不会进行回滚,而只对当前方法的代码进行回滚,这与REQUIRES_NEW相同。
如果调用当前方法的方法中有异常且没有进行try catch异常捕获处理,那么全部事务方法将进行回滚,这与REQUIRES_NEW刚好相反。
也就是说NESTED事务方法除了于自身有关外与调用当前方法的事务方法也是有关系的,只要调用当前方法的事务方法中有异常或者调用当前方法的事务方法中调用的其他方法有异常,那么当前方法就会进行回滚。
总结:NESTED事务方法有异常,那么NESTED事务方法将进行回滚。调用NESTED事务方法的事务方法有异常,那么全部事务方法将进行回滚。
* timeout:超时时间,单位为秒,超过该时间就会进行回滚
* readonly:只读事务:如果配置了只读事务,那么在事务运行期间,不允许对数据进行修改,否则抛出异常
* 设置哪些异常发生时事务不进行回滚:两者除了参数形式不同外,功能相同
noRollBackFor: 如:noRollbackFor = {ArithmeticException.class}
noRollBackForClassName: 如:noRollbackForClassName = {"java.lang.ArithmeticException"}
* 设置哪些异常发生时进行事务回滚:
rollBackFor: 如:rollbackFor = {FileNotFoundException.class}
rollbackForClassName: 如:rollbackForClassName = {"java.io.FileNotFoundException"}
DAO层类使用spring-jdbc提供的JdbcTemplate来操作数据库:
package com.fan.dao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;
@Repository
public class BookDao {
@Autowired
private JdbcTemplate jdbcTemplate;
/**
* 减去某个用户的余额
* @param userName
* @param price
*/
public void updateBalance(String userName, int price){
String sql = "update account set balance=balance-? where username=?";
jdbcTemplate.update(sql, price, userName);
}
/**
* 通过图书id获取图书价格
* @param id
* @return
*/
public int getPrice(int id){
String sql = "select price from book where id=?";
return jdbcTemplate.queryForObject(sql,Integer.class,id);
}
/**
* 更改某本书的库存
* @param id
*/
public void updateStock(int id){
String sql = "update book_stock set stock=stock-1 where id=?";
jdbcTemplate.update(sql, id);
}
/**
* 更新价格
* @param id
*/
public void updatePrice(int id){
String sql = "update book set price=price-1 where id=?";
jdbcTemplate.update(sql, id);
}
}
AOP基于XML配置的声明式事务
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
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
https://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop
https://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd">
<context:component-scan base-package="com.fan"></context:component-scan>
<context:property-placeholder location="classpath:db.properties"></context:property-placeholder>
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="username" value="${jdbc.username}"></property>
<property name="password" value="${jdbc.password}"></property>
<property name="url" value="${jdbc.url}"></property>
<property name="driverClassName" value="${jdbc.driverName}"></property>
</bean>
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!--事务的配置-->
<!--声明一个事务管理器-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
<aop:config>
<aop:pointcut id="exPointcut" expression="execution(* com.fan.service.*.*(..))"/>
<!--事务建议-->
<aop:advisor advice-ref="myAdvice" pointcut-ref="exPointcut"></aop:advisor>
</aop:config>
<tx:advice id="myAdvice" transaction-manager="transactionManager" >
<!--配置事务的属性-->
<tx:attributes>
<!--配置在哪些方法上添加事务以及事务的传播特性-->
<tx:method name="addBook" propagation="REQUIRED" read-only="true"/>
<tx:method name="updatePrice" propagation="REQUIRED"></tx:method>
<!--方法名称支持通配符-->
<!--
<tx:method name="*" propagation="REQUIRED"></tx:method>
-->
</tx:attributes>
</tx:advice>
</beans>
优点
1、Spring通过DI、AOP和消除样板式代码来简化企业级Java开发
2、Spring框架之外还存在一个构建在核心框架之上的庞大生态圈,它将Spring扩展到不同的领域,如Web服务、REST、移动开发以及NoSQL
3、低侵入式设计,代码的污染极低
4、独立于各种应用服务器,基于Spring框架的应用,可以真正实现Write Once,Run Anywhere的承诺
5、Spring的IoC容器降低了业务对象替换的复杂性,提高了组件之间的解耦
6、Spring的AOP支持允许将一些通用任务如安全、事务、日志等进行集中式处理,从而提供了更好的复用
7、Spring的ORM和DAO提供了与第三方持久层框架的的良好整合,并简化了底层的数据库访问
8、Spring的高度开放性,并不强制应用完全依赖于Spring,开发者可自由选用Spring框架的部分或全部
简化开发
基于POJO的轻量级和最小侵入性编程
通过依赖注入和面向接口实现松耦合
基于切面和惯例进行声明式编程
通过切面和模板减少样板式代码
8个模块
Test
Core Container
Beans
Core
Context
SpEL(Spring Expression Lanuage)
AOP
Aspects
Instrumentation
Messaging
Web
WebSocket
Servelt
Web
Portlet
Data Access/Integration
JDBC
ORM(Object Releation Mapping)
OXM(Object XML Mapping)
JMS(Java Message Service)
Transactions
jar包下载
https://repo.spring.io/release/
0 条评论
下一页