Spring Core
Spring Ioc容器
容器配置文件
maven管理的Spring项目中,spring配置文件一半放在/src/main/java/resources目录下,比如
/src/main/java/resources/beans.xml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 <?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 https://www.springframework.org/schema/beans/spring-beans.xsd" > <bean id ="dustball" class ="top.dustball.pojo.User" > <property name ="name" value ="dustball" /> <property name ="pwd" value ="sjh123456" /> <property name ="id" value ="114514" /> </bean > </beans >
配置Spring容器对象
总共有五种标签
标签
意义
属性
元素
beans
bean组
bean
一个pojo对象
alias
bean的别名
description
描述,相当于注释
import
导入其他xml配置
为什么要控制反转?
参考为什么要用IOC:inversion
of controll反转控制(把创建对象的权利交给框架) - 周文豪 - 博客园
(cnblogs.com)
之前在学javaweb时,每次高层访问底层,比如Service访问Dao层,高层上都要持有底层对象的句柄,并且掌握合适释放底层对象
实际上很麻烦,也没必要
这就好比去饭店吃饭,客人非要管着厨师怎么做饭.而实际上只需要管好点菜就行了
于是考虑使用设计模式中的单例模式,而引入设计模式又会造成代码的复杂性
反正不就是要一个对象,并且好管理吗?让Spring框架干这个事情
这就是Spring容器干的事情
将所有beans扔进Spring容器,让他管理,对于程序来说,Spring容器在全局位置,程序员可以自由调用
然后用BeanFactory等等工厂,对程序员提供接口,这个BeanFactory干了个啥?意思意思:
img
类域里面有一个静态代码块,其用意是,当BeanFactory对象创建时仅执行一次,也就是单例模式.
静态代码块中读取了bean.properties,根据xml文件的配置,实例化bean放到Map<String,Object>
beans容器中
这个容器实际上是一个Map字典,例子中使用HashMap实现之
此后要使用容器中的对象时,只需要调用工厂的getBean方法,传入的对象id作为键去查beans哈希表,查到就返回对象引用
所谓"依赖注入",就是指把java bean放到Spring容器中去
害tm注入,牛逼哄哄的,害什么控制反转,纯纯吓唬人
container magic
bean在何时创建
Spring Bean
从配置文件实例化Spring容器时创建
也就是说
1 ApplicationContext context=new ClassPathXmlApplicationContext ("beans.xml" );
这玩意执行之后,beans.xml中的所有Bean都会创建,并放置在Bean缓存池中
这一点可以下断点观察
但是,如果beans.xml中,一个bean如果带上了懒加载的属性,则啥时候用这个bean时,它才不得不加载
比如
1 2 3 4 5 <bean id ="dustball" class ="top.dustball.pojo.User" lazy-init ="true" > <property name ="name" value ="dustball" /> <property name ="pwd" value ="sjh123456" /> <property name ="id" value ="114514" /> </bean >
这个dustball就不会跟随context的实例化而同时创建,如果从来不调用dustball,它就不会被创建
bean属性
id&class
最基本的属性就是id和class
id是这个bean在容器中的键,值就是bean的对象引用
class是这个bean的类
1 2 3 4 5 <bean id ="dustball" class ="top.dustball.pojo.User" > <property name ="name" value ="dustball" /> <property name ="pwd" value ="sjh123456" /> <property name ="id" value ="114514" /> </bean >
scope
规定bean的作用域,
singleton,单例模式,每次getBean调用,返回同一个实例.默认的作用域.
如果没有懒加载属性,则该bean会随容器一起创建,随容器一起销毁
prototype,原型模式,每次getBean调用,都会返回一个新的实例.
啥时候getBean,啥时候才会创建
init-method
不建议使用
不建议使用
不建议使用
初始化回调函数
创建bean使用无参构造函数,可以在其中进行初始化
也可以另外指定初始化函数,该函数必须无参数无返回值
1 2 3 4 5 <bean id ="dustball" class ="top.dustball.pojo.User" scope ="prototype" init-method ="init" destroy-method ="destroy" > <property name ="name" value ="dustball" /> <property name ="pwd" value ="sjh123456" /> <property name ="id" value ="114514" /> </bean >
指定User类的init函数为初始化函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 package top.dustball.pojo;import lombok.*;@Data @ToString public class User { private int id; private String name; private String pwd; public User () { System.out.println("User ctor called" ); } public User (int id, String name, String pwd) { System.out.println("User 3arg ctor called" ); this .id = id; this .name = name; this .pwd = pwd; } public void init () { System.out.println("init method called" ); } public void destroy () { System.out.println("destroy method called" ); } }
Spring依赖注入
基于ctor的依赖注入
通过自定义的有参构造函数进行实例化就是基于ctor的依赖注入
1 2 3 4 5 <bean id ="dustball" class ="top.dustball.pojo.User" scope ="prototype" > <constructor-arg name ="id" value ="1" type ="int" /> <constructor-arg name ="name" value ="dustball" type ="java.lang.String" /> <constructor-arg name ="pwd" value ="sjh" type ="java.lang.String" /> </bean >
这里使用键决定参数对应关系,也可以使用index下标对应ctor的参数
这个配置届时会调用User的三参数ctor
1 2 3 4 5 6 public User (int id, String name, String pwd) { System.out.println("User 3arg ctor called" ); this .id = id; this .name = name; this .pwd = pwd; }
基于setter的依赖注入
之前用property元素配置bean时,之所以能够成功,是因为lombok的@Data注解,自动帮我们加上了setter方法
1 2 3 4 5 <bean id ="deutschball" class ="top.dustball.pojo.User" scope ="prototype" > <property name ="id" value ="2" /> <property name ="name" value ="deutschball" /> <property name ="pwd" value ="sjh" /> </bean >
<property name="id" value="2"/>
这句要求User类要有setID方法,对其传递参数2
注入成员对象
之前的User类有三个成员变量,一个基本数据类型int,两个String类型
现在UserProxy{User user;String
seviceID};类型包括了一个User成员对象和一个String成员变量
如何注入这么一个UserProxy类型的bean呢?
1 2 3 4 5 6 7 8 9 <bean id ="vader" class ="top.dustball.pojo.User" > <property name ="id" value ="11" /> <property name ="name" value ="vader" /> <property name ="pwd" value ="sjh" /> </bean > <bean id ="vader_proxy" class ="top.dustball.pojo.UserProxy" > <property name ="user" ref ="vader" /> <property name ="serviceID" value ="0x00001" /> </bean >
<property name="user" ref="vader"/>
这里的ref应为一个bean的id标识
注入内部bean
bean里面可以包含一个bean作为成员对象
1 2 3 4 5 6 7 8 9 10 11 12 <bean id ="proxy" class ="top.dustball.pojo.UserProxy" > <property name ="user" > <bean class ="top.dustball.pojo.User" id ="proxyUser" > <property name ="id" value ="3" /> <property name ="name" value ="puppet" /> <property name ="pwd" value ="sjh" /> </bean > </property > <property value ="0x00001" name ="serviceID" /> </bean >
其中UserProxy类长这样:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 package top.dustball.pojo;import lombok.AllArgsConstructor;import lombok.Data;import lombok.NoArgsConstructor;import lombok.ToString;@Data @AllArgsConstructor @NoArgsConstructor @ToString public class UserProxy { User user; String serviceID; }
注入集合
啥时候用到啥时候回来学
自动装配
自动装配是为了简化注入成员对象的情形
不使用自动装配:
1 2 3 4 5 6 7 8 9 <bean id ="vader" class ="top.dustball.pojo.User" > <property name ="id" value ="11" /> <property name ="name" value ="vader" /> <property name ="pwd" value ="sjh" /> </bean > <bean id ="vader_proxy" class ="top.dustball.pojo.UserProxy" > <property name ="user" ref ="vader" /> <property name ="serviceID" value ="0x00001" /> </bean >
关键在于<property name="user" ref="vader"/>
这一句,表明了本bean引用了哪一个bean
使用自动装配之后可以省去这句,由Spring(具体是谁我也不知道)自动决定引用哪一个bean
默认情况下是不会自动装配的,需要手动设置装配哪一个bean.
感觉上显示写明装配哪一个bean,代码更加清晰,并且也不会多复杂.自动装配反而降低可读性
byName
1 2 3 4 5 6 7 8 9 <bean id ="user" class ="top.dustball.pojo.User" > <property name ="id" value ="11" /> <property name ="name" value ="vader" /> <property name ="pwd" value ="sjh" /> </bean > <bean id ="vader_proxy" class ="top.dustball.pojo.UserProxy" autowire ="byName" > <property name ="serviceID" value ="0x00001" /> </bean >
UserProxy中不需要显式指定user成员引用哪一个bean,因为其bean属性中有autowire="byName"
,根据名称自动装配
啥叫"根据名称自动装配?"
1 2 3 4 public class UserProxy { User user; String serviceID; }
这里成员对象user,其键名就是"user",因此Spring会根据这个"user"去找id="user"的bean,也就是
1 <bean id ="user" class ="top.dustball.pojo.User" >
如果找到了,则使用该bean,否则使用null值
byType
1 2 3 4 5 6 7 8 9 <bean id ="user" class ="top.dustball.pojo.User" > <property name ="id" value ="11" /> <property name ="name" value ="vader" /> <property name ="pwd" value ="sjh" /> </bean > <bean id ="vader_proxy" class ="top.dustball.pojo.UserProxy" autowire ="byType" > <property name ="serviceID" value ="0x00001" /> </bean >
byType,根据类型自动专配,user成员是一个User类型,beans.xml中的user类型的bean自动作为成员进行装配
如果有两个以上的user
bean,则会报错expected single matching bean but found 2: dustball,user
constructor
构造函数参数匹配
比较类似于byType,
区别就是constructor-arg和property的区别
1 2 3 4 5 6 7 8 9 10 <bean id ="user" class ="top.dustball.pojo.User" > <property name ="id" value ="11" /> <property name ="name" value ="vader" /> <property name ="pwd" value ="sjh" /> </bean > <bean id ="vader_proxy" class ="top.dustball.pojo.UserProxy" autowire ="constructor" > <constructor-arg name ="serviceID" value ="0x0001" /> </bean >
使用注解开发
在beans.xml中加上注解配置和自动扫描
1 2 3 4 5 6 7 8 9 10 11 12 <?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 https://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd" > <context:annotation-config/> <context:component-scan base-package ="top.dustball.pojo" /> </beans>
注意此时自动扫描主机范围是top.dustball.pojo
这个包下,可以扩大范围
1 <context:component-scan base-package ="top.dustball" />
@Component
作用于类上
自动给类创建一个pojo
比如top.dustball.pojo.User类这样写
1 2 3 4 5 6 7 8 @Data @ToString @Component("dustball") public class User { public int id; private String name; private String pwd; }
@Component("dustball")
将会自动创建一个bean,其id就是dustball
在测试类中就可以通过context.getBean("dustball")
调用之
Component注解只能创建对象,但是无法设置对象的属性值,可以通过@Value注解设置属性值
如果只写@Component
,不显示指定id,则默认bean使用类名的首字母小写作为id,即User类对应user
bean
衍生注解
衍生注解
作用于
@Respository
UserDao
@Service
UserService
@Controller
UserController
需要注意的是,beans.xml中指定要扫描的位置,
原来的位置是pojo包下的所有Dao层的类
1 <context:component-scan base-package ="top.dustball.pojo" />
现在要拓展范围,扫描所有Dao,Service,Controller层的类
1 <context:component-scan base-package ="top.dustball" />
@Value
作用于属性或者setter上,配合@Component使用,初始化bean的各个属性
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 package top.dustball.pojo;import org.springframework.beans.factory.annotation.*;import lombok.*;import org.springframework.beans.factory.annotation.Value;import org.springframework.stereotype.Component;@Data @ToString @Component("dustball") public class User { @Value("1") public int id; @Value("dustball") private String name; @Value("sjh") private String pwd; }
此时Spring容器中自动创建的id=dustball的bean,三个属性均已初始化
@Autowired
作用于成员对象或者其setter方法上
自动装配成员对象
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 package top.dustball.pojo;import lombok.AllArgsConstructor;import lombok.Data;import lombok.NoArgsConstructor;import lombok.ToString;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.beans.factory.annotation.Value;import org.springframework.stereotype.Repository;@AllArgsConstructor @NoArgsConstructor @Data @ToString @Repository public class UserProxy { @Autowired User user; @Value("0x10000") String serviceID; }
实际上是byType 实现的
因此beans.xml中应该有一个id为user的bean,或者User类上带有Component注解或者其衍生注解
Autowired和Required的关系:
1 2 3 public @interface Autowired { boolean required () default true ; }
默认情况下required=true,这意味着user这个成员对象必须被一个bean装配,如果SpringIoc容器中找不到合适的bean则报错
1 No qualifying bean of type 'top.dustball.pojo.User' available
如果required=false,则该成员对象可以为null,SpringIoc容器中找不到合适的bean就直接摆烂不找了,赋值为null
@Qualifier
配合@Autowired一起使用
1 2 3 4 5 6 7 8 public class UserProxy { @Autowired(required = true) @Qualifier(value = "dustball") User user; @Value("0x10000") String serviceID; }
本来只用@Autowired相当于byType自动装配bean,现在希望装配一个指定id的bean
1 @Qualifier(value = "dustball")
这就限定了必须使用id=dustball的这个bean
1 2 3 4 5 <bean id ="dustball" class ="top.dustball.pojo.User" > <property name ="id" value ="11" /> <property name ="name" value ="vader" /> <property name ="pwd" value ="sjh" /> </bean >
@Scope
作用于类上,配合@Component以及其衍生注解一起使用
用于指定该bean的作用域,要么是singleton要么是prototype
1 2 @Scope("singleton") @Scope("prototype")
1 2 3 4 5 6 7 8 9 10 11 ... @Repository @Scope("prototype") public class UserProxy { @Autowired(required = true) @Qualifier(value = "dustball") User user; @Value("0x10000") String serviceID; }
xml给👴爬
之前不管是纯xml配置还是使用注解简化的xml配置,都离不开xml
即使是注解开发,也需要一个这种的xml文件:
1 2 3 4 5 6 7 8 9 10 <?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 https://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd" > <context:annotation-config /> <context:component-scan base-package ="top.dustball" /> </beans >
实际上注解贡献的bean和xml中注册的bean地位是完全相同的
"基于java的配置",就是指,使用一个java类作为bean的注册来源,而不再使用任何xml文件
比如top.dustball.pojo.UserConfig这个类,作为bean的来源,可以这样写
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 package top.dustball.pojo;import org.junit.jupiter.api.Test;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import org.springframework.context.annotation.Scope;@Configuration public class UserConfig { @Bean(initMethod = "init") @Scope("singleton") public User deutschball () { return new User (100 ,"deutschball" ,"sjh" ); } @Bean public UserProxy deutschproxy () { return new UserProxy (deutschball(),"0x1000" ); } }
@Configuration注解作用于类上 ,表明本类作为bean的注册来源
被@Bean注解修饰的成员函数将会生成一个id为函数名的bean
可以结合@Scope修饰该bean的作用域
如果有成员对象依赖,可以使用其他函数的返回值,只要将对应函数修饰为单例模式,就可以保证每次返回同一个对象
需要注意的是,基于java类的配置,会与直接在User,UserProxy类上写的@Component注解打架 ,并且有可能创建两个bean
因此要么 使用java类的配置,要么使用xml+注解,不要混用
基于java类的配置
基于xml文件的配置
方法名
bean id
方法返回值
bean class
@Bean修饰方法
注册一个bean
还要注意的是,实例化容器时有变动
从xml构建容器要这样写:
1 ApplicationContext applicationContext = new ApplicationContext ("beans.xml" );
从java类构建容器要这样写:
1 ApplicationContext context=new AnnotationConfigApplicationContext (UserConfig.class);
代理模式
为啥要学代理模式呢?
因为Spring AOP由动态代理实现,学动态代理是为了知道其原理
为啥要先学静态代理呢?因为动态代理和静态代理的目的相同
在不改变已有代码的基础上,增加新功能
至于为啥不能改变原有代码,是主人的变态任务罢了
比如第三方jar包提供的类或者函数,要增强其功能,就可以通过代理模式实现
十五斤,三十块,满意了吧?
静态代理
静态代理通过组合实现
也就是被代理对象作为代理类的成员对象.
类图表示为:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 classDiagram class NotePad{ +void print(); +void insert(row,col,text) +void delete(row,col,length) } class RealNotePad{ -filename:string +void print(); +void insert(int index,string text) +void delete(int begin,int end) +void open(string filename) } class ProxyNotePad{ -realnotepad:RealNotePad -filename:string -buffer:string +void print(); +void insert(row,col,text) +void delete(row,col,length) } NotePad<|..RealNotePad NotePad<|..ProxyNotePad class Main{ void main(); } ProxyNotePad<..Main RealNotePad <..ProxyNotePad ProxyNotePad o..RealNotePad
动态代理
动态体现在:没有实际存在的代理类,在运行时用反射创建临时代理类,实例化代理对象之后返回这个代理对象,交由被代理的接口管理句柄
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 package top.dustball.dao;import lombok.AllArgsConstructor;import lombok.Data;import java.lang.reflect.InvocationHandler;import java.lang.reflect.Method;import java.lang.reflect.Proxy;@Data @AllArgsConstructor public class ProxyInvocationHandler implements InvocationHandler { private Object target; public Object getProxy () { return Proxy.newProxyInstance( target.getClass().getClassLoader(), target.getClass().getInterfaces(), this ); } @Override public Object invoke (Object proxy, Method method, Object[] args) throws Throwable { System.out.println(method.getName()+" method called" ); return method.invoke(target,args); } }
使用代理:
1 2 3 4 UserDaoImpl userDao=new UserDaoImpl (); ProxyInvocationHandler handler = new ProxyInvocationHandler (userDao);UserDao proxy=(UserDao)handler.getProxy(); proxy.delete(10 );
1.UserDaoImpl userDao=new UserDaoImpl();
实例化了一个普通的UserDao实现类对象userDao,这跟之前没有用代理的情形没有区别
2.ProxyInvocationHandler handler = new ProxyInvocationHandler(userDao);
userDao作为被代理对象,其引用传递给handler.target保管,便于handler成员方法调用
ProxyInvocationHandler类中实现了两个方法,getProxy和invoke,(忽略lombok自动实现的方法),
其中InvocationHandler接口实际上只要求必须实现invoke方法
1 public Object invoke (Object proxy, Method method, Object[] args) throws Throwable
其中proxy是被代理的对象,method是proxy对象要被增强的方法,args是本来应该传递给该方法的参数
getProxy纯粹是我们为了省事才写到ProxyInvocationHandler类中的方法
到此为止只调用过ProxyInvocationHandler的全参数构造函数(lombok注解@AllArgsConstructor),也就是说,只做了target引用赋值这么一件事
大的要来了
3.UserDao proxy=(UserDao)handler.getProxy();
获取了一个代理对象,怎么说获取就获取了?为什么从handler那里获取?关键在于handler.getProxy函数
这个函数干了啥?
1 2 3 4 5 6 7 public Object getProxy () { return Proxy.newProxyInstance( target.getClass().getClassLoader(), target.getClass().getInterfaces(), this ); }
java.lang.reflect.Proxy
这个类用于动态生成代理类,只需传入目标接口、目标接口的类加载器以及InvocationHandler便可为目标接口生成代理类及代理对象。具体函数干了啥,需要反射的知识,现在不会
三个参数,
第一个是被代理类的类加载器,也就是target.getClass().getClassLoader()
,也就是UserDaoImpl类的类加载器,
然而视频教程里这里写的是this.getClass().getClassLoader()
,也就是ProxyInvocationHandler类的类加载器
弹幕的说法是,这两个类都是我们自己写的自定义类,所以类加载器是一个
打印观察发现确实如此
1 2 3 4 5 UserDaoImpl userDao=new UserDaoImpl (); System.out.println(userDao.getClass().getClassLoader()); ProxyInvocationHandler handler = new ProxyInvocationHandler (userDao);System.out.println(handler.getClass().getClassLoader());
执行结果
1 2 jdk.internal.loader.ClassLoaders$AppClassLoader @78308db1 jdk.internal.loader.ClassLoaders$AppClassLoader @78308db1
都是使用应用类加载器(实际上一共就三个类加载器)
在这里插入图片描述
也就是说,这三个参数传递给Proxy.newProxyInstance
之后,该函数并不知道要代理哪一个对象,只知道需要代理哪些接口,增强方法在this.invoke,那么代理对象执行的业务,是如何作用到原对象上的呢?
通过invoke函数作用到原对象userDao上
第二个是需要代理的接口,因为被代理类可以implements多个接口,因此这里可以有选择地代理其接口
第三个是本对象(一个实现InvocationHanler接口的对象,目的是绑定本对象的invoke函数,增强代理接口的功能)
三个参数传入Proxy.newProxyInstance
之后,返回一个代理对象,交给左值UserDao proxy
保管
这个代理对象具有哪些函数呢?这个由第二个参数决定,传入的接口中有啥函数,这个代理对象就有啥函数
并且代理对象每次调用这些函数,(不管哪一个)都会首先调用invoke方法,可以在invoke函数中增强函数功能
4.proxy.delete(10);
代理对象调用接口中的delete函数,然而是否真的能够调用到UserDaoImpl.delete()函数,得看ProxyInvocationHandler.invoke函数的脸色
1 2 3 4 5 @Override public Object invoke (Object proxy, Method method, Object[] args) throws Throwable { System.out.println(method.getName()+" method called" ); return method.invoke(target,args); }
invoke拦截proxy.delete这次调用,然后打印delete method called
然后才会将调用转发给target.delete(args)
具体底层怎么实现,学了反射再说
Spring AOP
之前学了动态代理,并不是让我们直接用动态代理写代码,SpringAOP已经帮我们实现了,我们只需要调用其接口
官方の废话
概念
意义
横切关注点
跨越应用程序多个模块的方法或者功能,与业务逻辑无关但是需要关注,比如日志,安全,缓存,事务
切面
横切关注点被模块化的特殊对象,切面是一个类
通知
切面要完成的工作,通知是切面类的一个方法
目标
被通知的对象(目标对象)
代理
代理对象
切入点
可以被通知的函数,每个成员函数都可以作为切入点
连接点
实际被通知的函数,只有感兴趣的切入点才会被作为连接点
AOP/过滤器/钩子
比较类似的几个东西
AOP用于拦截函数调用
过滤器用于拦截用户请求
钩子是回调性质的,用钩子也可以改变程序控制流
AOP和钩子的区别在于,钩子必须给每个函数分别设置,但是AOP可以直接给多个函数上钩子
Spring AOP
Spring AOP是AOP的实现
Spring
AOP可以劫持一个方法,在方法执行之前/之后,或者抛出异常时添加额外功能
image-20230129091802544
当代理对象执行一个业务,比如add时:
1 2 3 4 5 6 代理对象.add(){ 验证参数(); 前置日志(); 目标对象.add(); 后置日志(); }
这样看来目标对象的所有业务逻辑都被劫持了,并且在代理对象中加上了相同的前置和后置业务
能否有选择的劫持,不同的函数调用劫持后不同对待呢?
如果是我们自己实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 package top.dustball.dao;import lombok.AllArgsConstructor;import lombok.Data;import java.lang.reflect.InvocationHandler;import java.lang.reflect.Method;import java.lang.reflect.Proxy;@Data @AllArgsConstructor public class ProxyInvocationHandler implements InvocationHandler { private Object target; public Object getProxy () { return Proxy.newProxyInstance( this .getClass().getClassLoader(), target.getClass().getInterfaces(), this ); } @Override public Object invoke (Object proxy, Method method, Object[] args) throws Throwable { if (method.getName().equals("add" )){ System.out.println("add hijacked" ); return 0 ; } else if (method.getName().equals("select" )){ return method.invoke(target,args); } else { System.out.println(method.getName()+" method called" ); return method.invoke(target,args); } } }
代理对象的所有方法调用都会首先被劫持到invoke方法,invoke方法决定下一步如何
在SpringAOP中,不需要自己写动态代理的逻辑,只需要写好配置文件
方法1:XML配置
首先在Service层有一个UserServiceImpl下面用SpringAOP代理之
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 package top.dustball.service.User;public class UserServiceImpl implements UserService { @Override public void add () { System.out.println("UserService add calleda" ); } @Override public void select () { } @Override public void update () { } @Override public void delete () { } }
在log包下有一个日志类LogBefore实现了MethodBeforeAdvice接口,这个类将会被作为切面,其before函数将会作为通知作用于UserServiceImpl的成员函数调用之前
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 package top.dustball.log;import org.springframework.aop.MethodBeforeAdvice;import java.lang.reflect.Method;public class LogBefore implements MethodBeforeAdvice { @Override public void before (Method method, Object[] args, Object target) throws Throwable { System.out.println("log before " +method.getName()+" is called" ); method.invoke(target,args); } }
下面就是xml文件的配置了
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 <?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 https://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" > <context:annotation-config /> <context:component-scan base-package ="top.dustball" /> <bean id ="userService" class ="top.dustball.service.User.UserServiceImpl" /> <bean id ="logBefore" class ="top.dustball.log.LogBefore" /> <bean id ="logAfter" class ="top.dustball.log.LogAfter" /> <aop:config > <aop:pointcut id ="cutBefore" expression ="execution(* top.dustball.service.User.UserServiceImpl.* (..))" /> <aop:pointcut id ="cutAfterAdd" expression ="execution(* top.dustball.service.User.UserServiceImpl.add())" /> <aop:advisor advice-ref ="logBefore" pointcut-ref ="cutBefore" /> <aop:advisor advice-ref ="logAfter" pointcut-ref ="cutAfterAdd" /> </aop:config > </beans >
首先注册三个bean,其中userService是目标对象
另外两个bean都是日志类,作为切面
然后是aop配置,其中expression表达式是关键,它决定本切入点作用于被代理对象的哪一个函数
1 <aop:pointcut id ="cutBefore" expression ="execution(* top.dustball.service.User.UserServiceImpl.* (..))" />
这句话干了个什么事呢?给UserServieImpl类的任何函数都带上cutBefore切入点
1 2 3 4 5 6 7 execution( modifiers-pattern ret-type-pattern declaring-type-pattern name-pattern(param-pattern) throws-pattern )
returning type pattern,name pattern, and parameters
pattern是必须的.
ret-type-pattern:可以为*表示任何返回值,全路径的类名等.
name-pattern:指定方法名, 代表所有
set代表以set开头的所有方法.
parameters pattern:指定方法参数(声明的类型),
(..)代表所有参数,(*
)代表一个参数
(*
,String)代表第一个参数为任何值,第二个为String类型.
到现在位置是只定义了切入点,并没有往里切入东西,此时调用userService对象,看不出被代理的作用
下面给切入点插入通知就体现代理的作用了
1 <aop:advisor advice-ref ="logBefore" pointcut-ref ="cutBefore" />
给cutBefore切入点加上logBefore通知
测试类这样写:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 package top.dustball;import org.springframework.context.ApplicationContext;import org.springframework.context.support.ClassPathXmlApplicationContext;import top.dustball.service.User.UserService;public class TestUser { public static void main (String[] args) { ApplicationContext context = new ClassPathXmlApplicationContext ("beans.xml" ); UserService userService = (UserService) context.getBean("userService" ); userService.add(); } }
运行结果:
1 2 3 4 log before add is calledUserService add called UserService add called log after add is called with return value = null
表明userService已经被代理了
然而奇怪的是,我们并没有显式调用代理对象,而是从容器context中拿出目标对象userService直接使用
程序逻辑却没有根据userService.add本来的样子走,而是执行了logBefore.before切面通知之后,然后才执行userService.add
也就是说,在Test类看来,他不知道userService.add到底怎么实现的,他也看不到存在AOP,他只管调用就可以了
奇怪的是,测试类中只调用了一次userService.add,结果中却打印了两次UserService add called
这是因为在LogBefore.before切面通知函数中,我们画蛇添足了
1 2 3 4 5 6 7 8 9 public class LogBefore implements MethodBeforeAdvice { @Override public void before (Method method, Object[] args, Object target) throws Throwable { System.out.println("log before " +method.getName()+" is called" ); method.invoke(target,args); } }
method.invoke(target,args);
这句会在before函数执行完毕之后,被自动执行,不需要我们手动调用
手动调用了就会再执行一次,去掉即可
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 package top.dustball.log;import org.springframework.aop.MethodBeforeAdvice;import java.lang.reflect.Method;public class LogBefore implements MethodBeforeAdvice { @Override public void before (Method method, Object[] args, Object target) throws Throwable { System.out.println("log before " +method.getName()+" is called" ); } }
遗留问题
既然Test中调用的仍然是userService这个bean,那么把他作为UserServiceImpl可以不
也就是说
1 2 UserServiceImpl userServiceimp = (UserServiceImpl) context.getBean("userService" );UserService userService = (UserService) context.getBean("userService" );
这两种写法哪个对?
结果证明UserService userService = (UserService) context.getBean("userService");
这个是对的
一定要注意左值是UserService接口类型,不是UserServiceImpl类型
那么为啥userService这个bean一开始是一个UserServiceImpl的对象,后来就不是了?
如果不加aop:config这一段,两种写法是都可以的
1 2 3 4 5 6 7 8 9 10 <aop:config > <aop:pointcut id ="cutBefore" expression ="execution(* top.dustball.service.User.UserServiceImpl.* (..))" /> <aop:pointcut id ="cutAfterAdd" expression ="execution(* top.dustball.service.User.UserServiceImpl.add())" /> <aop:advisor advice-ref ="logBefore" pointcut-ref ="cutBefore" /> <aop:advisor advice-ref ="logAfter" pointcut-ref ="cutAfterAdd" /> </aop:config >
加上aop:config之前,userService.getClass()="class top.dustball.service.User.UserServiceImpl"
加上aop:config之后,userService.getClass()="class jdk.proxy2.$Proxy7"
,已经是代理对象了
至于到底发生了什么让userService被狸猫换太子,现在不想管
方法2:使用自定义类实现AOP
基本都使用方法1,啥时候用到这个法再来学吧
方法3:使用注解开发
首先要有一个切面类
@Aspect注解作用到的类就作为切面类 ,
其方法中被@Before或者@After或者 @Around注解的 ,成为通知
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 package top.dustball.AOP;import org.aspectj.lang.ProceedingJoinPoint;import org.aspectj.lang.Signature;import org.aspectj.lang.annotation.After;import org.aspectj.lang.annotation.Around;import org.aspectj.lang.annotation.Aspect;import org.aspectj.lang.annotation.Before;@Aspect public class AnnotationPointCut { @Around("execution(* top.dustball.service.User.UserServiceImpl.* (..))") public void around (ProceedingJoinPoint proceedingJoinPoint) throws Throwable { System.out.println("before" ); Signature signature = proceedingJoinPoint.getSignature(); System.out.println(signature); proceedingJoinPoint.proceed(); System.out.println("after" ); } }
然后将该切面类注册到Spring容器中
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 <?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 https://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" > <context:annotation-config /> <context:component-scan base-package ="top.dustball" /> <bean id ="annotationPointCut" class ="top.dustball.AOP.AnnotationPointCut" /> <aop:aspectj-autoproxy /> </beans >
实现的功能和方法1相同
测试类:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 package top.dustball;import org.springframework.context.ApplicationContext;import org.springframework.context.support.ClassPathXmlApplicationContext;import top.dustball.service.User.UserService;import top.dustball.service.User.UserServiceImpl;public class TestUser { public static void main (String[] args) { ApplicationContext context = new ClassPathXmlApplicationContext ("beans.xml" ); UserService userService = (UserService) context.getBean("userService" ); userService.add(); } }
执行结果
1 2 3 4 before void top.dustball.service.User.UserService.add() UserService add called after
Spring+MyBatis
maven依赖
一定要注意spring的各个组件版本号一致
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 <dependency > <groupId > org.springframework</groupId > <artifactId > spring-core</artifactId > <version > 6.0.4</version > </dependency > <dependency > <groupId > org.springframework</groupId > <artifactId > spring-beans</artifactId > <version > 6.0.4</version > </dependency > <dependency > <groupId > org.projectlombok</groupId > <artifactId > lombok</artifactId > <version > 1.18.24</version > <scope > provided</scope > </dependency > <dependency > <groupId > org.springframework</groupId > <artifactId > spring-context</artifactId > <version > 6.0.4</version > </dependency > <dependency > <groupId > org.aspectj</groupId > <artifactId > aspectjweaver</artifactId > <version > 1.9.19</version > </dependency > <dependency > <groupId > org.aspectj</groupId > <artifactId > aspectjweaver</artifactId > <version > 1.9.2</version > <scope > runtime</scope > </dependency > <dependency > <groupId > org.springframework</groupId > <artifactId > spring-jdbc</artifactId > <version > 6.0.4</version > </dependency > <dependency > <groupId > org.mybatis</groupId > <artifactId > mybatis-spring</artifactId > <version > 3.0.1</version > </dependency > <dependency > <groupId > org.mybatis.spring.boot</groupId > <artifactId > mybatis-spring-boot-starter</artifactId > <version > 3.0.1</version > </dependency > <dependency > <groupId > com.mysql</groupId > <artifactId > mysql-connector-j</artifactId > <version > 8.0.32</version > </dependency > <dependency > <groupId > org.springframework</groupId > <artifactId > spring-webmvc</artifactId > <version > 6.0.4</version > </dependency > <dependency > <groupId > org.slf4j</groupId > <artifactId > slf4j-api</artifactId > <version > 2.0.6</version > </dependency >
回顾javaweb中使用mybatis
之前javaweb中如何使用mybatis的?
mybatis-config.xml配置
在resources目录下有一个mybatis-config.xml文件,其作用主要是配置数据源和映射mapper映射关系
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 <?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "https://mybatis.org/dtd/mybatis-3-config.dtd" > <configuration > <typeAliases > <package name ="top.dustball.pojo" /> </typeAliases > <environments default ="development" > <environment id ="development" > <transactionManager type ="JDBC" /> <dataSource type ="POOLED" > <property name ="url" value ="jdbc:mysql://localhost:3306/mybatis" /> <property name ="driver" value ="com.mysql.cj.jdbc.Driver" /> <property name ="username" value ="root" /> <property name ="password" value ="sjh123456" /> </dataSource > </environment > </environments > <mappers > <mapper resource ="top/dustball/mapper/UserMapper/UserMapper.xml" /> </mappers > </configuration >
top.dustball.mapper.UserMapper包
一个UserMapper接口,一个UserMapper.xml映射配置文件
其中UserMapper.xml必须绑定UserMapper接口,
1 2 3 4 5 6 7 <?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "https://mybatis.org/dtd/mybatis-3-mapper.dtd" > <mapper namespace ="top.dustball.mapper.UserMapper.UserMapper" > </mapper >
然后可以在UserMapper.xml中写CRUD标签,比如<select>
也可以在UserMapper接口中直接使用注解开发
1 2 3 4 5 6 7 8 9 10 11 package top.dustball.mapper.UserMapper;import org.apache.ibatis.annotations.Select;import top.dustball.pojo.User;public interface UserMapper { @Select("SELECT * FROM user WHERE id= #{userID}") public User getUser (int userID) ; }
top.dustball.utils下创建MyBatisUtil工具类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 package top.dustball.utils;import org.apache.ibatis.io.Resources;import org.apache.ibatis.session.SqlSession;import org.apache.ibatis.session.SqlSessionFactory;import org.apache.ibatis.session.SqlSessionFactoryBuilder;import java.io.InputStream;public class MyBatisUtil { private static SqlSessionFactory sqlSessionFactory; static { try { String resource = "mybatis-config.xml" ; InputStream inputStream = Resources.getResourceAsStream(resource); sqlSessionFactory = new SqlSessionFactoryBuilder ().build(inputStream); } catch (Exception e) { e.printStackTrace(); } } public static SqlSession getSqlSession () { return sqlSessionFactory.openSession(); } }
测试
此后就可以在测试类中MyBatisUtil.getSqlSession了
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 package top.dustball;import org.apache.ibatis.io.Resources;import org.apache.ibatis.session.SqlSession;import org.apache.ibatis.session.SqlSessionFactory;import org.apache.ibatis.session.SqlSessionFactoryBuilder;import org.springframework.context.ApplicationContext;import org.springframework.context.support.ClassPathXmlApplicationContext;import org.springframework.core.io.InputStreamResource;import top.dustball.mapper.UserMapper.UserMapper;import top.dustball.pojo.User;import top.dustball.utils.MyBatisUtil;import java.io.IOException;import java.io.InputStream;public class TestUser { public static void main (String[] args) throws IOException { SqlSession sqlSession = MyBatisUtil.getSqlSession(); UserMapper mapper = sqlSession.getMapper(UserMapper.class); User user = mapper.getUser(2 ); System.out.println(user); } }
Spring中使用mybatis
配置spring-dao.xml
(mybatis-config.xml可以保留也可以直接扬了,spring完全可以覆盖mybatis的配置,这里选择保留)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 <?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:util ="http://www.springframework.org/schema/util" xsi:schemaLocation ="http://www.springframework.org/schema/beans https://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/util https://www.springframework.org/schema/util/spring-util.xsd" > <bean id ="datasource" class ="org.springframework.jdbc.datasource.DriverManagerDataSource" > <property name ="driverClassName" value ="com.mysql.jdbc.Driver" /> <property name ="url" value ="jdbc:mysql://localhost:3306/mybatis" /> <property name ="username" value ="root" /> <property name ="password" value ="sjh123456" /> </bean > <bean id ="sqlSessionFactory" class ="org.mybatis.spring.SqlSessionFactoryBean" > <property name ="dataSource" ref ="datasource" /> <property name ="configLocation" value ="classpath:mybatis-config.xml" /> </bean > <bean id ="sqlSession" class ="org.mybatis.spring.SqlSessionTemplate" > <constructor-arg name ="sqlSessionFactory" ref ="sqlSessionFactory" /> </bean > <bean id ="userMapper" class ="top.dustball.mapper.UserMapper.UserMapperImpl" > <property name ="sqlSession" ref ="sqlSession" /> </bean > </beans >
一是注意sqlSession的类型是SqlSessionTemplate,不再是SelSession(实际上是相同的作用)
二是注意最后创建的映射器实例,是UserMapperImpl类的实例,(显然UserMapper接口不能实例化)
这是区别于javaweb中使用mybatis的地方,之前只需要UserMapper接口和UserMapper.xml即可
现在还需要加一个UserMapperImpl,因为Spring
bean是实例,只有类才可以实例化
总结一下就是
实例化数据源
实例化工厂
实例化会话
实例化映射器
top.dustball.mapper.UserMapper包下再创建UserMapperImpl类
现在UserMapper包下有三个文件
1 2 3 4 5 6 7 8 9 PS C:\Users\86135\Desktop\sprint-01\Demo2\src\main\java\top\dustball\mapper\UserMapper> ls Directory: C:\Users\86135\Desktop\sprint-01\Demo2\src\main\java\top\dustball\mapper\UserMapper Mode LastWriteTime Length Name ---- ------------- ------ ---- -a--- 2023/1/30 11:22 352 UserMapper.java -a--- 2023/1/30 10:47 316 UserMapper.xml -a--- 2023/1/30 11:24 668 UserMapperImpl.java
其中UserMapperImpl是新增的实现类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 package top.dustball.mapper.UserMapper;import org.mybatis.spring.SqlSessionTemplate;import top.dustball.pojo.User;public class UserMapperImpl implements UserMapper { private SqlSessionTemplate sqlSession; public void setSqlSession (SqlSessionTemplate sqlSession) { this .sqlSession = sqlSession; } @Override public User getUser (int userID) { UserMapper mapper = sqlSession.getMapper(UserMapper.class); return mapper.getUser(userID); } }
原本获取mapper映射器是用户(程序员)的任务,需要在测试类中完成
现在mapper直接被封装到UserMapperImpl中
程序员只需要在测试类中调用userMapper.getUser(2);
测试
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 package top.dustball;import org.springframework.context.support.ClassPathXmlApplicationContext;import top.dustball.mapper.UserMapper.UserMapper;import top.dustball.pojo.User;import top.dustball.utils.MyBatisUtil;import java.io.IOException;public class TestUser { public static void main (String[] args) throws IOException { ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext ("spring-dao.xml" ); UserMapper userMapper = (UserMapper) context.getBean("userMapper" ); User user = userMapper.getUser(2 ); System.out.println(user); } }
spring+mybatis简化用法
之前在UserMapperImpl中我们需要维护一个成员对象sqlSession,如果让UserMapperImpl继承SqlSessionDaoSupport类,则不再需要
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 package top.dustball.mapper.UserMapper;import org.mybatis.spring.SqlSessionTemplate;import org.mybatis.spring.support.SqlSessionDaoSupport;import top.dustball.pojo.User;public class UserMapperImpl extends SqlSessionDaoSupport implements UserMapper { @Override public User getUser (int userID) { return getSqlSession().getMapper(UserMapper.class).getUser(userID); } }
这个类已经帮我们实现了getSqlSession方法
需要注意的是,在spring-dao.xml中稍有变化
1 2 3 4 5 6 7 8 9 之前: <bean id="userMapper" class="top.dustball.mapper.UserMapper.UserMapperImpl" > <property name="sqlSession" ref="sqlSession" /> </bean> 之后: <bean id="userMapper" class="top.dustball.mapper.UserMapper.UserMapperImpl" > <property name="sqlSessionTemplate" ref="sqlSession" /> </bean>
之前我们手动维护的成员对象叫做sqlSession,而之后继承自SqlSessionDaoSupport的是sqlSessionTemplate对象,实际上两个作用相同,就是property中的键名要改一下而已
使用事务
AOP实现事务织入
只需要在spring-dao.xml中增加配置,不需要修改任何源代码
spring-dao.xml这样写:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 <?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:util ="http://www.springframework.org/schema/util" xmlns:tx ="http://www.springframework.org/schema/tx" xsi:schemaLocation ="http://www.springframework.org/schema/beans https://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/util https://www.springframework.org/schema/util/spring-util.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd" > <bean id ="datasource" class ="org.springframework.jdbc.datasource.DriverManagerDataSource" > <property name ="driverClassName" value ="com.mysql.jdbc.Driver" /> <property name ="url" value ="jdbc:mysql://localhost:3306/mybatis" /> <property name ="username" value ="root" /> <property name ="password" value ="sjh123456" /> </bean > <bean id ="sqlSessionFactory" class ="org.mybatis.spring.SqlSessionFactoryBean" > <property name ="dataSource" ref ="datasource" /> <property name ="configLocation" value ="classpath:mybatis-config.xml" /> </bean > <bean id ="sqlSession" class ="org.mybatis.spring.SqlSessionTemplate" > <constructor-arg name ="sqlSessionFactory" ref ="sqlSessionFactory" /> </bean > <bean id ="userMapper" class ="top.dustball.mapper.UserMapper.UserMapperImpl" > <property name ="sqlSessionTemplate" ref ="sqlSession" /> </bean > <bean id ="transactionManager" class ="org.springframework.jdbc.datasource.DataSourceTransactionManager" > <constructor-arg ref ="datasource" /> </bean > <tx:advice id ="txAdvice" transaction-manager ="transactionManager" > <tx:attributes > <tx:method name ="add*" propagation ="REQUIRED" /> <tx:method name ="delete*" propagation ="REQUIRED" /> <tx:method name ="update*" /> <tx:method name ="select" read-only ="true" /> <tx:method name ="*" propagation ="REQUIRED" /> </tx:attributes > </tx:advice > <aop:config > <aop:pointcut id ="pointcut" expression ="execution(* top.dustball.mapper.*.*.* (..) )" /> <aop:advisor advice-ref ="txAdvice" pointcut-ref ="pointcut" /> </aop:config > </beans >
增加了一个transactionManager,一个txAdvice,一个aop
其中事务管理器可以有多种,JDBC,Druid等等
关键在于<tx:advice>
标签,即事务通知,该标签用于说明给哪些方法上事务,只是针对方法名,此时还不会针对类名
1 <tx:method name ="add*" propagation ="REQUIRED" />
这里propagation属性用于设置事务的传播属性,一般都是用REQUIRED,其他的有需要再查
事务的七种传播特性
默认值是required
默认值是required
默认值是required
传播特性
意义
required
如果存在当前事务,那么加入该事务, 如果不存在事务,就创建一个事务。这是propagation
的默认值
supports
如果当前已经存在事务,那么加入该事务, 否则创建一个所谓的空事务。
mandatory
当前必须存在一个事务,否则抛出异常
requires-new
如果当前存在事务,先把当前事务相关内容封装到一个实体, 然后重新创建一个新事务,并接受这个实体作为参数,用于事务恢复。
not-supported
如果当前存在事务,挂起当前事务,然后新的方法在没有事务的环境中执行。 没有spring事务的环境下,sql的提交完全依赖于defaultAutoCommit
属性值
never
如果当前存在事务,则抛出异常。 否则在无事务的环境上执行代码
nested
如果当前存在事务,则使用savepoint
技术将当前事务状态进行保存,<br
/然后底层公用一个链接, 当nested内部出现错误的时候,自行回滚到save point
的状态。 只要外部捕获到了异常,就可以继续进行外部事务的提交,而不会受到内嵌事务的干扰。 但是,如果外部事物抛出了异常,整个大事务都会回滚。
最后aop:config标签的作用是,将tx:advice通知作用与mapper包下的所有类的所有方法
之后所有的Dao层操作都是事务操作.
此前和此后在测试类中调用没有区别.