spring基础
spring 基础知识整理
ioc
什么是ioc:
–> 控制反转(Inversion of Control):我们通常使用一个对象的时候,需要显式的将这个对象new出来,而在ioc的思想下,spring容器会帮我们实例化对象并且管理它,当我们需要使用它的时候,告诉spring容器,spring容器会将实例对象送上门来。
ioc和di
–>ioc和di做的事都一样,只是描述的角度不同,ioc说的是讲类实例化和管理的权利交给了ioc容器,di说的是如果某个类A需要容器里面另外一个类B,那么容器就会自动把这个依赖的类送上门来(依赖注入)
控制反转是目的,依赖依赖反转是手段
aop
在不改变原有代码的情况下对原有逻辑进行增强
解决类与类之间耦合度高,没有事务控制的问题
横切逻辑代码往往是权限校验代码、日志代码、事务控制代码、性能监控代码。
动态代理的两种实现方式
1.jdk动态代理
2.cglib动态代理
aspectj和spring aop: AspectJ使用的是编译期前或者后和类加载时进行织入,Spring AOP利用的是运行时织入。即在使用目标对象的代理执行应用程序时,编译这些切面(使用JDK动态代理或者CGLIB代理)
手写ioc和aop事务控制
先画个类图
UserMoney是普通的DTO
按照类图从食物链最低端开始创建
创建获取连接工具类ConnectUtil
public class ConnectUtil {
ThreadLocal<Connection> connectionThreadLocal = new ThreadLocal<>();
//c3p0数据源
ComboPooledDataSource comboPooledDataSource;
public ComboPooledDataSource getComboPooledDataSource() {
return comboPooledDataSource;
}
public void setComboPooledDataSource(ComboPooledDataSource comboPooledDataSource) {
this.comboPooledDataSource = comboPooledDataSource;
}
public Connection getConnetion() throws Exception {
//从当前线程获得连接,使得多次update使用同一个connection
Connection connection = connectionThreadLocal.get();
if (null == connection) {
connection = comboPooledDataSource.getConnection();
connectionThreadLocal.set(connection);
}
return connection;
}
}
创建事务控制工具类TransactionUtil,依赖ConnectUtil
public class TransactionUtil {
ConnectUtil connectUtil;
public ConnectUtil getConnectUtil() {
return connectUtil;
}
public void setConnectUtil(ConnectUtil connectUtil) {
this.connectUtil = connectUtil;
}
void commmit() throws Exception {
Connection connetion = connectUtil.getConnetion();
connetion.commit();
}
//设置事务不自动提交
void openTransaction() throws Exception {
Connection connetion = connectUtil.getConnetion();
connetion.setAutoCommit(false);
}
void rollBack() throws Exception {
Connection connetion = connectUtil.getConnetion();
connetion.rollback();
}
}
创建aop代理工具类AopProxy,依赖事务控制工具类TransactionUtil
public class AopProxy {
TransactionUtil transactionUtil;
public TransactionUtil getTransactionUtil() {
return transactionUtil;
}
public void setTransactionUtil(TransactionUtil transactionUtil) {
this.transactionUtil = transactionUtil;
}
public Object getProxy(Object target){
Object o = Proxy.newProxyInstance(this.getClass().getClassLoader(), target.getClass().getInterfaces(), new InvocationHandler() {
@Override
public Object invoke(Object o, Method method, Object[] args) throws Throwable {
//关闭事务自动提交
transactionUtil.openTransaction();
try {
//service方法执行
Object invoke = method.invoke(target, args);
//提交事务
transactionUtil.commmit();
return invoke;
}catch (Exception e){
//事务回滚
transactionUtil.rollBack();
throw e;
}
}
});
return o;
}
}
配置其他需要用到的 Dao,Service,DTO
public interface Dao {
int updateUser(UserMoney userMoney) throws Exception;
}
public class DaoImpl implements Dao {
ConnectUtil connectUtil;
public ConnectUtil getConnectUtil() {
return connectUtil;
}
public void setConnectUtil(ConnectUtil connectUtil) {
this.connectUtil = connectUtil;
}
//user_money表只有两个字段 user和money
@Override
public int updateUser(UserMoney userMoney) throws Exception {
Connection con = connectUtil.getConnetion();
String sql = "update user_money set money=? where user=?";
PreparedStatement preparedStatement = con.prepareStatement(sql);
preparedStatement.setInt(1,userMoney.getMoney());
preparedStatement.setString(2,userMoney.getUser());
int i = preparedStatement.executeUpdate();
preparedStatement.close();
return i;
}
}
public interface Service {
int updateUser() throws Exception;
}
public class ServiceImpl implements Service {
public Dao dao;
public Dao getDao() {
return dao;
}
public void setDao(Dao dao) {
this.dao = dao;
}
@Override
public int updateUser() throws Exception {
UserMoney userMoney = new UserMoney();
userMoney.setUser("tom");
userMoney.setMoney(0);
dao.updateUser(userMoney);
//让它抛异常出来,事务管理器好回滚
int i = 1 / 0;
userMoney.setUser("jerry");
userMoney.setMoney(0);
dao.updateUser(userMoney);
return 1;
}
}
public class UserMoney {
String user;
Integer money;
public String getUser() {
return user;
}
public void setUser(String user) {
this.user = user;
}
public Integer getMoney() {
return money;
}
public void setMoney(Integer money) {
this.money = money;
}
}
在resource下创建配置文件xxxx.xml,并配置好上面的bean
我的是mysql8,如果是mysql5.7的driverClass要使用com.mysql.jdbc.Driver
创建Factorybean 在静态代码块中加载上面的xml配置文件
public class SpringBeanFactory {
public static Map<String, Object> beanFactory = new HashMap<>();
static {
//读取配置文件
InputStream inputStream = SpringBeanFactory.class.getClassLoader().getResourceAsStream("springConfig.xml");
try {
//解析配置文件成Document
Document document = new SAXReader().read(inputStream);
Element rootElement = document.getRootElement();
//获得文件中所有的bean节点
List<Element> list = rootElement.selectNodes("//bean");
for (int i = 0; i < list.size(); i++) {
Element element = list.get(i);
String claz = element.attributeValue("class");
String id = element.attributeValue("id");
//反射实例化bean
Object o = Class.forName(claz).newInstance();
//放到beanFactory中去
beanFactory.put(id, o);
}
//获得文件中所有的property节点
List<Element> propertyList = rootElement.selectNodes("//property");
for (int i = 0; i < propertyList.size(); i++) {
Element element = propertyList.get(i);
String name = element.attributeValue("name");
String value = element.attributeValue("value");
String ref = element.attributeValue("ref");
//获得property节点的父节点的属性
String claz = element.getParent().attributeValue("class");
String parentId = element.getParent().attributeValue("id");
//获得父节点的类型
Class<?> parentClass = Class.forName(claz);
//获得前面实例化的放到beanfactory中的bean
Object parentObject = beanFactory.get(parentId);
//这里使用内省的方式把property设置到bean中,内省需要提供get,set方法
//设置<property name="user" value="root"></property>中的value
if(StringUtils.isNotBlank(value)){
PropertyDescriptor propertyDescriptor = new PropertyDescriptor(name,parentClass);
Method writeMethod = propertyDescriptor.getWriteMethod();
writeMethod.invoke(parentObject, value);
}
//设置<property name="comboPooledDataSource" ref="comboPooledDataSource" ></property>中的ref
if(StringUtils.isNotBlank(ref)){
Object refObject = beanFactory.get(ref);
PropertyDescriptor propertyDescriptor = new PropertyDescriptor(ref,parentClass);
Method writeMethod = propertyDescriptor.getWriteMethod();
writeMethod.invoke(parentObject, refObject);
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
测试一下
从容器中获取代理对象,把new(当前对象)改为获取代理对象执行逻辑
ioc注入的几种方式
1.xml
xml创建bean
方式一:使用无参构造函数
在默认情况下,它会通过反射调用无参构造函数来创建对象。如果类中没有无参构造函数,将创建 失败。
<!--配置service对象-->
<bean id="userService" class="com.test.service.impl.TransferServiceImpl"> </bean>
方式二:使用静态方法创建
在实际开发中,我们使用的对象有些时候并不是直接通过构造函数就可以创建出来的,它可能在创 建的过程 中会做很多额外的操作。
public class InstanceFactory {
public static TransferService getTransferService(){
return new TransferServiceImpl();
}
}
<!--使用静态方法创建对象的配置方式-->
<bean id="userService" class="com.test.factory.InstanceFactory" factory-method="getTransferService"></bean>
方式三:使用实例化方法创建
此种方式和上面静态方法创建其实类似,区别是用于获取对象的方法不再是static修饰的了,而是 类中的一 个普通方法。此种方式比静态方法创建的使用几率要高一些。
在早期开发的项目中,工厂类中的方法有可能是静态的,也有可能是非静态方法,当是非静态方法 时,即可采用下面的配置方式:
<!--使用实例方法创建对象的配置方式-->
<bean id="beanFactory" class="com.test.factory.instancemethod.InstanceFactory"></bean>
<bean id="transferService" factory-bean="beanFactory" factory- method="getTransferService"></bean>
作用范围的改变
在spring框架管理Bean对象的创建时,Bean对象默认都是单例的,但是它支持配置的方式改变作用范围
我们实际开发中用到最多的作用范围就是singleton(单例模式)和 prototype(原型模式,也叫多例模式)
<!--配置service对象-->
<bean id="transferService" class="com.test.service.impl.TransferServiceImpl" scope="singleton"></bean>
不同作用范围的生命周期
单例模式:singleton
对象出生:当创建容器时,对象就被创建了。
对象活着:只要容器在,对象一直活着。
对象死亡:当销毁容器时,对象就被销毁了。
一句话总结:单例模式的bean对象生命周期与容器相同。
多例模式:prototype
对象出生:当使用对象时,创建新的对象实例。
对象活着:只要对象在使用中,就一直活着。
对象死亡:当对象⻓时间不用时,被java的垃圾回收器回收了。
一句话总结:多例模式的bean对象,spring框架只负责创建,不负责销毁。
Bean标签属性
在基于xml的IoC配置中,bean标签是最基础的标签。它表示了IoC容器中的一个对象。换句话 说,如果一个对象想让spring管理,在XML的配置中都需要使用此标签配置,Bean标签的属性如 下:
id属性: 用于给bean提供一个唯一标识。在一个标签内部,标识必须唯一。
class属性:用于指定创建Bean对象的全限定类名。
name属性:用于给bean提供一个或多个名称。多个名称用空格分隔。
factory-bean属性:用于指定创建当前bean对象的工厂bean的唯一标识。当指定了此属性之后, class属性失效。
factory-method属性:用于指定创建当前bean对象的工厂方法,如配合factory-bean属性使用, 则class属性失效。如配合class属性使用,则方法必须是static的。
scope属性:用于指定bean对象的作用范围。通常情况下就是singleton。当要用到多例模式时, 可以配置为prototype。
init-method属性:用于指定bean对象的初始化方法,此方法会在bean对象装配后调用。必须是 一个无参方法。
destory-method属性:用于指定bean对象的销毁方法,此方法会在bean对象销毁前执行。它只 能为scope是singleton时起作用。
xml注入bean
set方法:在xml中配置property,会调用该属性的set方法
<bean id="student" class="com.test.pojo.Student">
<property name="name" value="chris"></property>
<property name="age" value="18"></property>
</bean>
在使用set方法注入时,需要使用 property 标签,该标签属性如下:
name:指定注入时调用的set方法名称。(注:不包含set这三个字母,druid连接池指定属性名称)
value:指定注入的数据。它支持基本类型和String类型。
ref:指定注入的数据。它支持其他bean类型。写的是其他bean的唯一标识。
构造器注入
<bean id="student" class="com.test.pojo.Student">
<constructor-arg value="tom" index="0"></constructor-arg>
<constructor-arg value="19" index="1"></constructor-arg>
</bean>
在使用构造函数注入时,涉及的标签是 constructor-arg ,该标签有如下属性:
name:用于给构造函数中指定名称的参数赋值。
index:用于给构造函数中指定索引位置的参数赋值。
value:用于指定基本类型或者String类型的数据。
ref:用于指定其他Bean类型的数据。写的是其他bean的唯一标识。
复杂数据类型注入
<property name="myArray">
<array>
<value>array1</value>
<value>array2</value>
<value>array3</value>
</array>
</property>
<property name="myMap">
<map>
<entry key="key1" value ="value1"/>
<entry key="key2" value ="value2"/>
</ map>
</property>
<property name="mySet">
<set>
<value>set1</value>
<va lue>set2</value>
/set>
</property>
<property name ="myProperties">
<props>
<prop key="prop1">value1</prop>
<prop key="prop2">value2</prop>
</props>
</property>
在List结构的集合数据注入时, array , list , set 这三个标签通用,另外注值的 value 标签内部 可以直接写值,也可以使用 bean 标签配置一个对象,或者用 ref 标签引用一个已经配合的bean 的唯一标识。
在Map结构的集合数据注入时, map 标签使用 entry 子标签实现数据注入, entry 标签可以使 用key和value属性指定存入map中的数据。使用value-ref属性指定已经配置好的bean的引用。 同时 entry 标签中也可以使用 ref 标签,但是不能使用 bean 标签。而 property 标签中不能使 用 ref 或者 bean 标签引用对象
2.注解
注解创建bean
在@config类上加上@Configuration,配置好生成bean的方法并return该bean,然后再该方法上加上注解@Bean
@Configuration
public class SpringBeanConfiguration {
@Bean
public FirstBean firstBean() {
return new FirstBean();
}
}
@Configuration 注解,表名当前类是一个配置类
@ComponentScan 注解,替代 context:component-scan
@PropertySource,引入外部属性配置文件
@Import 引入其他配置类
@Value 对变量赋值,可以直接赋值,也可以使用 ${} 读取资源配置文件中的信息 @Bean 将方法返回对象加入 SpringIOC 容器
lazy-Init 延迟加载
ApplicationContext 容器的默认行为是在启动服务器时将所有 singleton bean 提前进行实例化。提前 实例化意味着作为初始化过程的一部分,ApplicationContext 实例会创建并配置所有的singleton bean。
<bean id="testBean" class="cn.test.LazyBean" />
该bean默认的设置为:
<bean id="testBean" calss="cn.test.LazyBean" lazy-init="false" />
lazy-init=“false”,立即加载,表示在spring启动时,立刻进行实例化。
如果不想让一个singleton bean 在 ApplicationContext实现初始化时被提前实例化,那么可以将bean
设置为延迟实例化。
<bean id="testBean" calss="cn.test.LazyBean" lazy-init="true" />
注解注入bean
使用@Autowired,当一个类型有多个bean值的时候,会造成无法选择具体注入哪一个的情况, 这个时候我们需要配合着@Qualifier使用。
public class TransferServiceImpl {
@Autowired
@Qualifier(name="jdbcAccountDaoImpl")
private AccountDao accountDao;
}
设置 lazy-init 为 true 的 bean 将不会在 ApplicationContext 启动时提前被实例化,而是第一次向容器
通过 getBean 索取 bean 时实例化的。
如果一个设置了立即加载的 bean1,引用了一个延迟加载的 bean2 ,那么 bean1在容器启动时被实例化,而 bean2 由于被 bean1 引用,所以也被实例化,这种情况也符合延时加载的 bean 在第一次调用 时才被实例化的规则。
也可以在容器层次中通过在 元素上使用 “default-lazy-init” 属性来控制延时初始化
<beans default-lazy-init="true">
//...
</beans>
如果一个 bean 的 scope 属性为 scope=“pototype” 时,即使设置了 lazy-init=“false”,容器启动时也不会实例化bean,而是调用 getBean 方法实例化的。
延迟加载应用场景
(1)开启延迟加载一定程度提高容器启动和运转性能
(2)对于不常使用的 Bean 设置延迟加载,这样偶尔使用的时候再加载,不必要从一开始该 Bean就占用资源
factoryBean和beanFactory
beanFactory:beanFactory是容器的顶级接口,负责生产和管理容器中的bean,具体的规则由它的子类及其实现类来完成。
factoryBean:spring中有两种bean,不同bean和factoryBean,factorybean定义了获取bean的方法(自定义bean的创建过程,完成复杂bean的创建)
BeanFactory与ApplicationContext区别
BeanFactory是Spring框架中IoC容器的顶层接口,它只是用来定义一些基础功能,定义一些基础规范,而 ApplicationContext是它的一个子接口,所以ApplicationContext是具备BeanFactory提供的全部功能 的。
通常,我们称BeanFactory为SpringIOC的基础容器,ApplicationContext是容器的高级接口,比 BeanFactory要拥有更多的功能,比如说国际化支持和资源访问(xml,java配置类)
定义一个factoryBean:
public class CompanyFactoryBean implements FactoryBean<Company> {
private String companyInfo; // 公司名称,地址,规模
public void setCompanyInfo(String companyInfo) {
this.companyInfo = companyInfo;
}
@Override
public Company getObject() throws Exception {
// 模拟创建复杂对象Company
Company company = new Company();
String[] strings = companyInfo.split(",");
company.setName(strings[0]);
company.setAddress(strings[1]);
company.setScale(Integer.parseInt(strings[2]));
return company;
}
@Override
public Class<?> getObjectType() {
return Company.class;
}
@Override
public boolean isSingleton() {
return true;
}
}
使用上面的工厂
<bean id="companyBean" class="com.test.factory.CompanyFactoryBean">
<property name="companyInfo" value="公司名,地址,500"/>
</bean>
获取FactoryBean产生的对象
Object companyBean = applicationContext.getBean("companyBean");
System.out.println("bean:" + companyBean);
// 结果如下
bean:Company{name='公司名', address='地址', scale=500}
获取FactoryBean,需要在id之前添加“&”
Object companyBean = applicationContext.getBean("&companyBean");
System.out.println("bean:" + companyBean);
// 结果如下
bean:com.test.factory.CompanyFactoryBean@54f7ed09
启动 IoC 容器的方式
Java环境下启动IoC容器
ClassPathXmlApplicationContext:从类的根路径下加载配置文件(推荐使用)
FileSystemXmlApplicationContext:从磁盘路径上加载配置文件
AnnotationConfigApplicationContext:纯注解模式下启动Spring容器
Web环境下启动IoC容器
从xml启动容器
<!DOCTYPE web-app PUBLIC
"-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN" "http://java.sun.com/dtd/web-app_2_3.dtd" >
<web-app>
<display-name>Archetype Created Web Application</display-name>
<!--配置Spring ioc容器的配置文件-->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:applicationContext.xml</param-value>
</context-param>
<!--使用监听器启动Spring的IOC容器-->
<listener>
<listener- class>org.springframework.web.context.ContextLoaderListener</listener- class>
</listener>
</web-app>
从配置类启动容器
<!DOCTYPE web-app PUBLIC
"-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN" "http://java.sun.com/dtd/web-app_2_3.dtd" >
<web-app>
<display-name>Archetype Created Web Application</display-name>
<!--告诉ContextloaderListener知道我们使用注解的方式启动ioc容器-->
<context-param>
<param-name>contextClass</param-name>
<param-value>org.springframework.web.context.support.AnnotationConfigWebApplicationContext</param-value>
</context-param>
<!--配置启动类的全限定类名-->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>com.test.SpringConfig</param-value>
</context-param>
<!--使用监听器启动Spring的IOC容器-->
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
</web-app>
BeanPostProcessor 和 BeanFactoryPostProcessor
beanfactory实例化后可以使用BeanFactoryPostProcessor处理一些事
bean实例化后可以使用BeanPostProcessor
public interface BeanPostProcessor {
Object postProcessBeforeInitialization(Object var1, String var2) throws BeansException;
Object postProcessAfterInitialization(Object var1, String var2) throws BeansException;
}
该接口提供了两个方法,分别在Bean的初始化方法前和初始化方法后执行,具体这个初始化方法指的是 什么方法,类似我们在定义bean时,定义了init-method所指定的方法
定义一个类实现了BeanPostProcessor,默认是会对整个Spring容器中所有的bean进行处理。如果要对 具体的某个bean处理,可以通过方法参数判断,两个类型参数分别为Object和String,第一个参数是每 个bean的实例,第二个参数是每个bean的name或者id属性的值。所以我们可以通过第二个参数,来判 断我们将要处理的具体的bean。
public interface BeanFactoryPostProcessor {
void postProcessBeanFactory(ConfigurableListableBeanFactory var1) throws BeansException;
}
BeanFactory级别的处理,是针对整个Bean的工厂进行处理
可以通过ConfigurableListableBeanFactory参数获得BeanDefinition,然后修改BeanDefinition的属性
spring中使用AOP
名词 | 解释 |
---|---|
Joinpoint(连接点) | 它指的是那些可以用于把增强代码加入到业务主线中的点,那么由上图中我们可 以看出,这些点指的就是方法。在方法执行的前后通过动态代理技术加入增强的 代码。在Spring框架AOP思想的技术实现中,也只支持方法类型的连接点。 |
Pointcut(切入点) | 它指的是那些已经把增强代码加入到业务主线进来之后的连接点。由上图中,我 们看出表现层 transfer 方法就只是连接点,因为判断访问权限的功能并没有对 其增强。 |
Advice(通 知/增强) | 它指的是切面类中用于提供增强功能的方法。并且不同的方法增强的时机是不一 样的。比如,开启事务肯定要在业务方法执行之前执行;提交事务要在业务方法 正常执行之后执行,而回滚事务要在业务方法执行产生异常之后执行等等。那么 这些就是通知的类型。其分类有:前置通知 后置通知 异常通知 最终通知 环绕通知。 |
Target(目标对象) | 它指的是代理的目标对象。即被代理对象。 |
Proxy(代理) | 它指的是一个类被AOP织入增强后,产生的代理类。即代理对象。 |
Weaving(织入) | 它指的是把增强应用到目标对象来创建新的代理对象的过程。spring采用动态代 理织入,而AspectJ采用编译期织入和类装载期织入。 |
Aspect(切面) | 它指定是增强的代码所关注的方面,把这些相关的增强代码定义到一个类中,这 个类就是切面类。例如,事务切面,它里面定义的方法就是和事务相关的,像开 启事务,提交事务,回滚事务等等,不会定义其他与事务无关的方法。 |
默认情况下,Spring会根据被代理对象是否实现接口来选择使用JDK还是CGLIB。当被代理对象没有实现 任何接口时,Spring会选择CGLIB。当被代理对象实现了接口,Spring会选择JDK官方的代理技术,不过 我们可以通过配置的方式,让Spring强制使用CGLIB。
aop实现
XML 模式
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>5.1.12.RELEASE</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.4</version>
</dependency>
xml配置:
第一步:把通知Bean交给Spring管理
第二步:使用aop:config开始aop的配置
第三步:使用aop:aspect配置切面
第四步:使用对应的标签配置通知的类型
<!--把通知bean交给spring来管理-->
<bean id="logUtil" class="com.test.utils.LogUtil"></bean>
<!--开始aop的配置-->
<aop:config>
<!--配置切面-->
<aop:aspect id="logAdvice" ref="logUtil">
<!--配置前置通知-->
<!--访问修饰符 返回值 包名.包名.包名.类名.方法名(参数列表)-->
<!-- 返回值可以使用*,表示任意返回值 -->
<aop:before method="printLog" pointcut="execution(public * com.test.service.impl.TransferServiceImpl.updateAccountByCardNo(com.test.pojo.Account))"></aop:before>
</aop:aspect>
</aop:config>
XML+注解模式
xml中加入
<!--开启spring对注解aop的支持-->
<aop:aspectj-autoproxy/>
@Component
@Aspect
public class LogUtil {
/*
* 第一步:编写一个方法
* 第二步:在方法使用@Pointcut注解
* 第三步:给注解的value属性提供切入点表达式
* 细节:
* 1.在引用切入点表达式时,必须是方法名+(),例如"pointcut()"。
* 2.在当前切面中使用,可以直接写方法名。在其他切面中使用必须是全限定方法名。 */
@Pointcut("execution(* com.test.service.impl.*.*(..))")
public void pointcut(){}
@Before("pointcut()")
public void beforePrintLog(JoinPoint jp){
Object[] args = jp.getArgs();
System.out.println("前置通知:beforePrintLog,参数是:"+ Arrays.toString(args));
}
@AfterReturning(value = "pointcut()",returning = "rtValue") public void afterReturningPrintLog(Object rtValue){
System.out.println("后置通知:afterReturningPrintLog,返回值 是:"+rtValue);
}
@AfterThrowing(value = "pointcut()",throwing = "e") public void afterThrowingPrintLog(Throwable e){
System.out.println("异常通知:afterThrowingPrintLog,异常是:"+e);
}
/**
* 环绕通知
* @param pjp * @return
*/
@Around("pointcut()")
public Object aroundPrintLog(ProceedingJoinPoint pjp){
//定义返回值
Object rtValue = null;
try{
//前置通知 System.out.println("前置通知");
//1.获取参数
Object[] args = pjp.getArgs();
//2.执行切入点方法
rtValue = pjp.proceed(args);
//后置通知
System.out.println("后置通知");
}catch (Throwable t){
//异常通知 System.out.println("异常通知");
t.printStackTrace();
}finally { //最终通知
System.out.println("最终通知"); }
return rtValue;
}
}
注解模式
用配置类
@Configuration
@ComponentScan("com.test") @EnableAspectJAutoProxy //开启spring对注解AOP的支持 public class SpringConfiguration {
}
替换掉xml
<aop:aspectj-autoproxy/>
spring声明式事务的支持
编程式事务:在业务代码中添加事务控制代码,这样的事务控制机制就叫做编程式事务
声明式事务:通过xml或者注解配置的方式达到事务控制的目的,叫做声明式事务
事务的四大特性
原子性(Atomicity) 原子性是指事务是一个不可分割的工作单位,事务中的操作要么都发生,要么都
不发生。
从操作的⻆度来描述,事务中的各个操作要么都成功要么都失败
一致性(Consistency) 事务必须使数据库从一个一致性状态变换到另外一个一致性状态。
例如转账前A有1000,B有1000。转账后A+B也得是2000。
一致性是从数据的⻆度来说的,(1000,1000) (900,1100),不应该出现(900,1000)
隔离性(Isolation) 事务的隔离性是多个用户并发访问数据库时,数据库为每一个用户开启的事务,
每个事务不能被其他事务的操作数据所干扰,多个并发事务之间要相互隔离。
比如:事务1给员工涨工资2000,但是事务1尚未被提交,员工发起事务2查询工资,发现工资涨了2000 块钱,读到了事务1尚未提交的数据(脏读)
持久性(Durability) 持久性是指一个事务一旦被提交,它对数据库中数据的改变就是永久性的,接下来即使数据库发生故障
也不应该对其有任何影响。
脏读:一个线程中的事务读到了另外一个线程中未提交的数据。
不可重复读:一个线程中的事务读到了另外一个线程中已经提交的update的数据(前后内容不一样)
虚读(幻读):一个线程中的事务读到了另外一个线程中已经提交的insert或者delete的数据(前后条 数不一样)
数据库共定义了四种隔离级别:
Serializable(串行化):可避免脏读、不可重复读、虚读情况的发生。(串行化) 最高
Repeatable read(可重复读):可避免脏读、不可重复读情况的发生。(幻读有可能发生) 第二 会对要update的行进行加锁
Read committed(读已提交):可避免脏读情况发生。不可重复读和幻读一定会发生。 第三
Read uncommitted(读未提交):最低级别,以上情况均无法保证。(读未提交) 最低
事务的传播行为
事务往往在service层进行控制,如果出现service层方法A调用了另外一个service层方法B,A和B方法本 身都已经被添加了事务控制,那么A调用B的时候,就需要进行事务的一些协商,这就叫做事务的传播行为。
事务 | 解释 |
---|---|
PROPAGATION_REQUIRED | 如果当前没有事务,就新建一个事务,如果已经存在一个事务中, 加入到这个事务中。这是最常⻅的选择。 |
PROPAGATION_SUPPORTS | 支持当前事务,如果当前没有事务,就以非事务方式执行。 |
PROPAGATION_MANDATORY | 使用当前的事务,如果当前没有事务,就抛出异常。 |
PROPAGATION_REQUIRES_NEW | 新建事务,如果当前存在事务,把当前事务挂起。 |
PROPAGATION_NOT_SUPPORTED | 以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。 |
PROPAGATION_NEVER | 以非事务方式执行,如果当前存在事务,则抛出异常。 |
PROPAGATION_NESTED | 如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则 执行与PROPAGATION_REQUIRED类似的操作。 |
Spring中事务的API
public interface PlatformTransactionManager {
/**
* 获取事务状态信息*/
TransactionStatus getTransaction(@Nullable TransactionDefinition definition)
throws TransactionException;
/**
* 提交事务 */
void commit(TransactionStatus status) throws TransactionException;
/**
* 回滚事务 */
void rollback(TransactionStatus status) throws TransactionException;
}
Spring 声明式事务配置
导入jar
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.1.12.RELEASE</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.4</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.1.12.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>5.1.12.RELEASE</version>
</dependency>
使用xml方式
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<!--定制事务细节,传播行为、隔离级别等-->
<tx:attributes>
<!--一般性配置-->
<tx:method name="*" read-only="false" propagation="REQUIRED" isolation="DEFAULT" timeout="-1"/>
<!--针对查询的覆盖性配置-->
<tx:method name="query*" read-only="true" propagation="SUPPORTS"/>
</tx:attributes>
</tx:advice>
<aop:config>
<!--advice-ref指向增强=横切逻辑+方位-->
<aop:advisor advice-ref="txAdvice" pointcut="execution(* com.lagou.edu.service.impl.TransferServiceImpl.*(..))"/>
</aop:config>
xml+注解的方式
<!--配置事务管理器-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!--在spring中先配置好dataSource-->
<property name="dataSource" ref="dataSource"></property>
</bean>
<!--开启spring对注解事务的支持-->
<tx:annotation-driven transaction-manager="transactionManager"/>
在接口、类或者方法上添加@Transactional注解
@Transactional(readOnly = true,propagation = Propagation.SUPPORTS)
注解方式
在 Spring 的配置类上添加 @EnableTransactionManagement
在spring中,spring在对象调用初始化方法后会调用BeanPostProcessor后置处理器对bean进行增强
其中有一个AbstractAutoProxyCreator这个BeanPostProcessor会把对象的aop增强和通用拦截进行合并并创建代理对象