3.Spring5
3.1.介绍
- Spring 是分层的 Java SE/EE 应用 full-stack 轻量级开源框架,以 IoC(Inverse Of Control:反转控制)和 AOP(Aspect Oriented Programming:面向切面编程)为内核,提供了展现层 SpringMVC 和持久层 Spring JDBC 以及业务层事务管理等众多的企业级应用技术,还能整合开源世界众多
著名的第三方框架和类库,逐渐成为使用最多的 Java EE 企业应用开源框架。 - 优势:
- 方便解耦,简化开发:通过 Spring 提供的 IoC 容器,可以将对象间的依赖关系交由 Spring 进行控制,避免硬编码所造成的过度程序耦合。用户也不必再为单例模式类、属性文件解析等这些很底层的需求编写代码,可以更专注于上层的应用。
- AOP 编程的支持:通过 Spring 的 AOP 功能,方便进行面向切面的编程,许多不容易用传统 OOP 实现的功能可以通过 AOP 轻松应付。
- 声明式事务的支持:可以将我们从单调烦闷的事务管理代码中解脱出来,通过声明式方式灵活的进行事务的管理,提高开发效率和质量。
- 方便程序的测试:可以用非容器依赖的编程方式进行几乎所有的测试工作,测试不再是昂贵的操作,而是随手可做的事情。
- 方便集成各种优秀框架:Spring 可以降低各种框架的使用难度,提供了对各种优秀框架(Struts、Hibernate、Hessian、Quartz等)的直接支持。
- 降低 JavaEE API 的使用难度:Spring 对 JavaEE API(如 JDBC、JavaMail、远程调用等)进行了薄薄的封装层,使这些 API 的使用难度大为降低。
- Java 源码是经典学习范例:Spring 的源代码设计精妙、结构清晰、匠心独用,处处体现着大师对 Java 设计模式灵活运用以及对 Java 技术的高深造诣。它的源代码无意是 Java 技术的最佳实践的范例。
3.2.入门案例
3.2.1.案例简介
- 导入配置
<!-- maven工程中导入spring依赖 --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>5.2.10.RELEASE</version> </dependency>
- 在maven插件中查看
- org.springframework:spring-context:5.2.10.RELEASE
- org.springframework:spring-aop:5.2.10.RELEASE
- org.springframework:spring-beans:5.2.10.RELEASE
- org.springframework:spring-core:5.2.10.RELEASE
- org.springframework:spring-jcl:5.2.10.RELEASE
- org.springframework:spring-expression:5.2.10.RELEASE
- org.springframework:spring-context:5.2.10.RELEASE
- 创建spring的xml文件,可以看做虚拟容器
- 文件名称:application.xml(或application-xxx.xml),不可修改为其他名称,放在resource文件夹下
<?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"> <!-- bean definitions here --> <!--通过bean标签完成组件的注册--> <!-- id:组件在容器中的唯一标识 name:组件名称,通常省略不写,以id作为name class:接口的实现类的全类名 --> <!-- 通过反射获得实例,因此应该使用实现类的全类名 --> <bean id="userService" class="service.UserServiceImpl"/> </beans>
- 文件名称:application.xml(或application-xxx.xml),不可修改为其他名称,放在resource文件夹下
- 在main下java文件夹里创建一个service文件夹,在文件夹中创建UserService接口及其实现类UserServiceImpl;
public interface UserService { public void sayHello(String username); } public class UserServiceImpl implements UserService{ @Override public void sayHello(String username) { System.out.println("hello " + username); } }
- 在test文件夹的java文件夹内创建TempTest类
@Test public void test(){ //ApplicationContext是抽象容器的具体存在 //0.从容器中取出组件,并测试方法 ApplicationContext applicationContext = new ClassPathXmlApplicationContext("application.xml"); //1.通过xml文件中的id值取得组件 UserService userService1 = (UserService) applicationContext.getBean("userService"); userService1.sayHello("world1"); //2.通过class文件取得组件,容器中(xml文件中)该接口的实现类类型只有一个 //注意:“该类型只有一个”的语句描述,意思是,该接口的实现类,只有一个,以后也是一样的描述意义 UserService userService2 = (UserService) applicationContext.getBean(UserService.class); userService2.sayHello("world2"); //3.既按id,又按类型取得组件 UserService userService3 = (UserService) applicationContext.getBean("userService",UserService.class); userService3.sayHello("world3"); }
注意:
- 在debug中发现,UserService对象都是同一个哈希地址,这是由于spring单例设计模式导致的对同一个组件只会取到同一个对象(不修改设置的前提)
3.2.2.补充案例
-
在上面案例的基础上,再在main—java文件夹下新建一个mapper文件夹,并新建UserMapper接口及其实现类UserMapperImpl
public interface UserMapper { public void sayHello(String username); } public class UserMapperImpl implements UserMapper{ @Override public void sayHello(String username) { System.out.println("hello mapper " + username); } }
-
修改UserService接口的实现类UserServiceImp
public class UserServiceImpl implements UserService{ UserMapper userMapper; //这个set方法名要和容器中相对应 public void setUserMapperzzz(UserMapper userMapper) { this.userMapper = userMapper; } @Override public void sayHello(String username) { userMapper.sayHello(username); } public UserServiceImpl() { System.out.println("userService的无参构造方法"); } }
-
修改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" 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"> <!-- bean definitions here --> <bean id="userService" class="com.cskaoyan.service.UserServiceImpl"> <!--让userService依赖于容器中已经注册的userMapper,建立此依赖关系需要对应变量的set方法--> <!-- name: set方法名,和实现类中的方法名是对应的 ref: 本容器中的组件id --> <property name="userMapperzzz" ref="userMapper"/> </bean> <bean id="userMapper" class="mapper.UserMapperImpl"/> </beans>
-
在test—java文件夹下建立测试类
@Test public void test1(){ ApplicationContext applicationContext = new ClassPathXmlApplicationContext("application.xml"); UserService userService = applicationContext.getBean(UserService.class); userService.sayHello("world4"); UserMapper userMapper = applicationContext.getBean(UserMapper.class); } /* 输出结果: userService的无参构造方法 hello mapper world4 */
注:
- 由此可知,spring实现的方式是通过无参构造方法获得实现类
- 通过debug可以发现,两种方式获取的UserMapper均相同,是同一个实例,同一个组件获取的是同一个实例
3.3.SpringIOC
3.3.1.ApplicationContext的继承关系
- 在IDEA中,选中某个类,按住ctrl+h键,即可查看任意类之间的继承关系
BeanFactory ^ | | | HierachicalBeanFactory<-------------------------| ^ | | | ApplicationEventPublisher<----| | | | | | MessageSource <---------------|---------------------ApplicationContext--------------------->ListableBeanFactory | ^ ResourceLoader | | ^ | | | | | ResourePatternResolver<-------| CongfigurableApplicationContext-------->Lifecycle ^ | | | AbstractApplicationContext ^ | | | AbstractRefreshableApplicationContext ^ | | | AbstractRefreshableConfigApplicationContext ^ | | | AbstractXmlApplicationContext ^ | ------------------------------------------------------------- | | FileSysetemXmlApplicationContext ClassPathXmlApplicationContext
- BeanFactory 才是 Spring 容器中的顶层接口。ApplicationContext 是它的子接口。
- BeanFactory 和 ApplicationContext 的区别:
- 创建对象的时间点不一样。
- ApplicationContext:只要一读取配置文件,默认情况下就会创建对象。
- BeanFactory:什么使用什么时候创建对象。
- 创建对象的时间点不一样。
- ClassPathXmlApplicationContext:从类的根路径下加载配置文件,推荐使用这种
- FileSystemXmlApplicationContext:从磁盘路径上加载配置文件,配置文件可以在磁盘的任意位置。
- AnnotationConfigApplicationContext:当我们使用注解配置容器对象时,需要使用此类来创建 spring 容器。它用来读取注解。
3.3.2.IOC 中 bean 标签和管理对象细节
- bean 标签
- 作用:用于配置对象让 spring 来创建的。默认情况下它调用的是类中的无参构造函数。如果没有无参构造函数则不能创建成功。
- 属性:
- id:给对象在容器中提供一个唯一标识。用于获取对象。
- class:指定类的全限定类名。用于反射创建对象。默认情况下调用无参构造函数。
- scope:指定对象的作用范围。
- singleton:默认值,单例的.
- prototype:多例的.
- request:WEB 项目中,Spring 创建一个 Bean 的对象,将对象存入到 request 域中.
- session:WEB 项目中,Spring 创建一个 Bean 的对象,将对象存入到 session 域中.
- global session:WEB 项目中,应用在 Portlet 环境.如果没有 Portlet 环境那么globalSession 相当于 session.
- init-method:指定类中的初始化方法名称。
- destroy-method:指定类中销毁方法名称。
- Factory-method:工厂创建对象的方法
- Factory-bean:指定工厂的id组件
3.3.3.JavaBean的实例化
-
构造方法:
- 无参构造(最常用)
- 在上述补充案例中,UserMapper添加一个无参构造方法,在函数体内打印一句话,经过测试类测试,可以发现,执行到了无参构造方法
- 同理,如果JavaBean中未提供无参构造方法,而容器中又没有进行相应的配置,那么编译的时候就会报错
- 有参构造
- 在main—java中新建mapper文件夹,UserMapper接口及其实现类UserMapperImpl,OrderMapper接口及其实现类OrderMapperImpl,为空即可
- 在main—java中新建service文件夹,新建UserService接口及其实现类,接口为空即可,实现类内容如下
public class UserServiceImpl implements UserService{ UserMapper userMapper; OrderMapper orderMapper; String parameter; //有参构造方法的形参名可以为任意,但是必须要和容器中的name属性对应 public UserServiceImpl(UserMapper userMapperz, OrderMapper orderMapper,String parameter) { this.userMapper = userMapperz; this.orderMapper = orderMapper; this.parameter = parameter; System.out.println("userServiceImp的有参构造"); } }
- 在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" 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"> <!-- bean definitions here --> <bean id="userService" class="service.UserServiceImpl"> <!-- name:有参构造方法的形参名 ref:引用的容器中其他的组件名,与组件的id对应 value:给对应的参数赋值 --> <constructor-arg name="userMapperz" ref="userMapper"/> <constructor-arg name="orderMapper" ref="orderMapper"/> <constructor-arg name="parameter" value="hello"/> </bean> <bean id="userMapper" class="mapper.UserMapperImpl"/> <bean id="orderMapper" class="mapper.OrderMapperImpl"/> </beans>
- 测试方法:
@Test public void test(){ ApplicationContext applicationContext = new ClassPathXmlApplicationContext("application.xml"); UserService userService = applicationContext.getBean(UserService.class); } /* 输出结果: userServiceImp的有参构造 */
- 无参构造(最常用)
-
工厂
- 静态工厂:工厂中的生产方法是静态方法
- 实例工厂:工厂中的生产方法不为静态方法
- FactoryBean:生产指定的组件,而BeanFactory可以生产全部组件。FactoryBean接口中提供了getObject方法
- 在main—java文件夹下创建一个bean文件夹,并新建一个User类,为空即可
- 在main—java文件夹下创建一个factory文件夹,新建一个StaticFactory类和一个InstanceFactory类
- 在factory文件夹里,新建一个UserFactoryBean类,并实现FactoryBean接口
//静态工厂 public class StaticFactory { public static User getInstance() { return new User(); } } //实例工厂 public class InstanceFactory { public User getInstance() { return new User(); } } //FactoryBean public class UserFactoryBean implements FactoryBean<User> { //这个方法非常重要 @Override public User getObject() throws Exception { System.out.println("调用到factoryBean的getObject方法"); return new User(); } //这个方法可以不需要在意 @Override public Class<?> getObjectType() { return User.class; } }
- 在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" 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"> <!-- bean definitions here --> <!-- 静态工厂 factory-method: 当前class中的方法名(静态的方法) --> <bean id="userFromStaticFactory" class="factory.StaticFactory" factory-method="getInstance"/> <!-- 实例工厂 factory-bean:工厂组件的id factory-method:当前factory-bean中的方法名 组件类型和factory-method对应的方法的返回值类型是相关的 --> <!-- 其实这句可以理解为获取工厂组件,即获取对象 --> <bean id="instanceFactory" class="factory.InstanceFactory"/> <!-- 这句配置可以理解为执行工厂组件获取的对象的factory-method方法 --> <bean id="userFromInstanceFactory" factory-bean="instanceFactory" factory-method="getInstance"/> <!--factoryBean--> <bean id="userFromFactoryBean" class="factory.UserFactoryBean"/> </beans>
- 测试:
@Test public void test1() { //静态工厂 User instance = StaticFactory.getInstance(); //实例工厂 InstanceFactory instanceFactory = new InstanceFactory();//要先去注册工厂组件 User instance1 = instanceFactory.getInstance(); } @Test public void test2(){ ApplicationContext applicationContext = new ClassPathXmlApplicationContext("application.xml"); Object userFromStaticFactory = applicationContext.getBean("userFromStaticFactory"); Object userFromInstanceFactory = applicationContext.getBean("userFromInstanceFactory"); Object userFromFactoryBean = applicationContext.getBean("userFromFactoryBean"); }
注意:
- 这里获取到的User对象并不相同,因为不是同一个组件获取的
3.4.SpringDI
- 依赖注入:Dependency Injection。它是 spring 框架核心 IOC 的具体实现。程序在编写时,通过控制反转,把对象的创建交给了 spring,但是代码中不可能出现没有依赖的情况。IOC 解耦只是降低他们的依赖关系,但不会消除。例如:我们的业务层仍会调用持久层的方法。那这种业务层和持久层的依赖关系,在使用 spring 之后,就让 spring 来维护了。简单的说,就是坐等框架把持久层对象传入业务层,而不用我们自己去获取
- 创建业务层接口及其实现类
public interface IAccountService { void saveAccount(); }
- 创建持久层接口和实现类
public interface IAccountDao { void saveAccount(); }
3.4.1.构造函数注入
- 使用类中的构造函数,给成员变量赋值。注意,赋值的操作不是我们自己做的,而是通过配置的方式,让 spring 框架来为我们注入
public class AccountServiceImpl implements IAccountService { private String name; private Integer age; private Date birthday; public AccountServiceImpl(String name, Integer age, Date birthday) { this.name = name; this.age = age; this.birthday = birthday; } @Override public void saveAccount() { System.out.println(name+","+age+","+birthday); } }
- xml配置文件
<!-- 使用构造函数的方式,给 service 中的属性传值 要求: 类中需要提供一个对应参数列表的构造函数。 涉及的标签: constructor-arg 属性: index:指定参数在构造函数参数列表的索引位置 type:指定参数在构造函数中的数据类型 name:指定参数在构造函数中的名称,用这个找给谁赋值 =======上面三个都是找给谁赋值,下面两个指的是赋什么值的============== value:它能赋的值是基本数据类型和 String 类型 ref:它能赋的值是其他 bean 类型,也就是说,必须得是在配置文件中配置过的 bean --> <bean id="accountService" class="com.itheima.service.impl.AccountServiceImpl"> <constructor-arg name="name" value="张三"></constructor-arg> <constructor-arg name="age" value="18"></constructor-arg> <constructor-arg name="birthday" ref="now"></constructor-arg> </bean> <bean id="now" class="java.util.Date"></bean>
3.4.2.set 方法注入
- 在类中提供需要注入成员的 set 方法
public class AccountServiceImpl implements IAccountService { private String name; private Integer age; private Date birthday; public void setName(String name) { this.name = name; } public void setAge(Integer age) { this.age = age; } public void setBirthday(Date birthday) { this.birthday = birthday; } @Override public void saveAccount() { System.out.println(name+","+age+","+birthday); } }
- xml配置文件
<!-- 通过配置文件给 bean 中的属性传值:使用 set 方法的方式 涉及的标签: property 属性: name:找的是类中 set 方法后面的部分 ref:给属性赋值是其他 bean 类型的 value:给属性赋值是基本数据类型和 string 类型的 实际开发中,此种方式用的较多。 --> <bean id="accountService" class="com.itheima.service.impl.AccountServiceImpl"> <property name="name" value="test"></property> <property name="age" value="21"></property> <property name="birthday" ref="now"></property> </bean> <bean id="now" class="java.util.Date"></bean>
3.4.3.使用 p 名称空间注入数据(本质还是调用 set 方法)
- 通过在 xml 中导入 p 名称空间,使用 p:propertyName 来注入数据,它的本质仍然是调用类中的set 方法实现注入功能
public class AccountServiceImpl4 implements IAccountService { private String name; private Integer age; private Date birthday; public void setName(String name) { this.name = name; } public void setAge(Integer age) { this.age = age; } public void setBirthday(Date birthday) { this.birthday = birthday; } @Override public void saveAccount() { System.out.println(name+","+age+","+birthday); } }
- xml配置文件
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:p="http://www.springframework.org/schema/p" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <bean id="accountService"class="com.itheima.service.impl.AccountServiceImpl4" p:name="test" p:age="21" p:birthday-ref="now"/> </beans>
3.4.4.注入集合属性
- 给类中的集合成员传值,它用的也是 set 方法注入的方式,只不过变量的数据类型都是集合。
public class AccountServiceImpl implements IAccountService { private String[] myStrs; private List<String> myList; private Set<String> mySet; private Map<String,String> myMap; private Properties myProps; public void setMyStrs(String[] myStrs) { this.myStrs = myStrs; } public void setMyList(List<String> myList) { this.myList = myList; } public void setMySet(Set<String> mySet) { this.mySet = mySet; } public void setMyMap(Map<String, String> myMap) { this.myMap = myMap; } public void setMyProps(Properties myProps) { this.myProps = myProps; } @Override public void saveAccount() { System.out.println(Arrays.toString(myStrs)); System.out.println(myList); System.out.println(mySet); System.out.println(myMap); System.out.println(myProps); } }
- xml配置文件
<!-- 注入集合数据 List 结构的:array,list,set Map 结构的:map,entry,props,prop --> <bean id="accountService" class="com.itheima.service.impl.AccountServiceImpl"> <!-- 在注入集合数据时,只要结构相同,标签可以互换 --> <!-- 给数组注入数据 --> <property name="myStrs"> <set> <value>AAA</value> <value>BBB</value> <value>CCC</value> </set> </property> <!-- 注入 list 集合数据 --> <property name="myList"> <array> <value>AAA</value> <value>BBB</value> <value>CCC</value> </array> </property> <!-- 注入 set 集合数据 --> <property name="mySet"> <list> <value>AAA</value> <value>BBB</value> <value>CCC</value> </list> </property> <!-- 注入 Map 数据 --> <property name="myMap"> <props> <prop key="testA">aaa</prop> <prop key="testB">bbb</prop> </props> </property> <!-- 注入 properties 数据 --> <property name="myProps"> <map> <entry key="testA" value="aaa"></entry> <entry key="testB"> <value>bbb</value> </entry> </map> </property> </bean>
3.5.作用域
- singleton:组件在容器中以单例的形式存在,默认是单例形式
- prototype:每一次获取组件时都会获取一个新的组件
- 验证:
- 在main—java中新建三个类,UserService1,UserService2,UserService3,为空即可
- 在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" 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"> <!-- bean definitions here --> <bean id="userService1" class="com.cskaoyan.service.UserService1" scope="singleton"/> <bean id="userService2" class="com.cskaoyan.service.UserService2" scope="prototype"/> <bean id="userService3" class="com.cskaoyan.service.UserService3"/> </beans>
- 在测试类中测试
@Test public void test1() { //singleton ApplicationContext applicationContext = new ClassPathXmlApplicationContext("application.xml"); UserService1 bean1 = applicationContext.getBean(UserService1.class); UserService1 bean2 = applicationContext.getBean(UserService1.class); UserService1 bean3 = applicationContext.getBean(UserService1.class); } @Test public void test2() { //prototype ApplicationContext applicationContext = new ClassPathXmlApplicationContext("application.xml"); UserService2 bean1 = applicationContext.getBean(UserService2.class); UserService2 bean2 = applicationContext.getBean(UserService2.class); UserService2 bean3 = applicationContext.getBean(UserService2.class); } @Test public void mytest3() { //没有写scope ApplicationContext applicationContext = new ClassPathXmlApplicationContext("application.xml"); UserService3 bean1 = applicationContext.getBean(UserService3.class); UserService3 bean2 = applicationContext.getBean(UserService3.class); UserService3 bean3 = applicationContext.getBean(UserService3.class); }
在debug中可发现,singleton配置中,是同一个bean,而prototype不是,同理,没有写范围的也是同一个bean。
3.6.生命周期
- 使用组件经历的过程
1.bean的实例化 | | | 2.参数设置 | | | 3.Aware接口:只有当前组件实现了这个接口,才会执行到对应的方法 | | | ----------------------------------------- | | | | | | | | | | | | BeanNameAware接口 BeanFactoryAware接口 ApplicationAware接口 | | | setBeanName方法 setBeanFactory方法 setApplicationContext方法 | | | | 4.BeanPostProcessor接口:新建一个类实现该接口,所有组件都会执行其中的方法 个人理解:有点类似于Filter,除了其本身,其余所有组件都会执行这两个方法 | | | postProcessBeforeInitialization | | | 5.InitializingBean接口 AfterPropertiesSet方法 | | | 6.自定义的某方法,需要在xml容器中配置 | | | postProcessAfterInitialization 特别注意:scope会影响组件到达可用状态之前的这些生命周期的执行(影响执行开始的时间) 1.对于singleton组件来说,容器初始化的时候(调用getBean方法之间已经执行完了) 同时,singletong组件还有后面的destroy方法要执行:7.DisposableBean的destroy方法,8.自定义的destroy方法(xml文件中要进行相应配置) 2.对于prototype组件来说,当调用getBean(获得组件时),才开始执行生命周期 同时,prototype组件,destroy方法是不执行的
- 演示示例:
- 在main—java下新建一个life文件夹,里边新建三个类,分别是
- 用于演示BeanPostProcessor接口的实现类CustomBeanPostProcessor
- 用于演示生命周期的类LifeCycleBean
- 用于演示prototype组件的类LifeCycleBean2
//作用范围:除了他本身,其他的所有组件 public class CustomBeanPostProcessor implements BeanPostProcessor { //bean:当前正在生命周期的组件 //beanName: 组件的名称 @Override public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { /*if (beanName.contains("datasource")){ DataSource dataSource = (DataSource) bean; String password = dataSource.getPassword();//密文 //解密 String password2 = jiemi(password); dataSource.setPassword(password2); }*/ //传入的bean通过动态代理生成一个代理对象,return代理对象 → 狸猫换太子 System.out.println("beanPostProcessor的before:" + beanName); return bean; } @Override public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { System.out.println("beanPostProcessor的after"); return bean; } } //该类用于演示生命周期的执行 public class LifeCycleBean implements BeanNameAware, BeanFactoryAware, ApplicationContextAware, InitializingBean, DisposableBean { String password; String beanName;//null 👉 通过setBeanName方法完成赋值 BeanFactory beanFactory; ApplicationContext applicationContext; public LifeCycleBean() { System.out.println("1、无参构造方法"); } public void setPassword(String password) { System.out.println("2、设置参数"); this.password = password; } //获得该组件的name 👉 可以利用获得的组件的name给当前组件的成员变量复制 @Override public void setBeanName(String s) { System.out.println("setBeanName:" + s); this.beanName = s; } @Override public void setBeanFactory(BeanFactory beanFactory) throws BeansException { System.out.println("setBeanFactory"); this.beanFactory = beanFactory; } @Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { System.out.println("setApplicationContext"); this.applicationContext = applicationContext; } @Override public void afterPropertiesSet() throws Exception { System.out.println("afterPropertiesSet"); } public void myinit() { System.out.println("自定义的init方法"); } public void mydestroy() { System.out.println("自定义的lifecycleBean的destroy"); } @Override public void destroy() throws Exception { System.out.println("disposableBean的destroy"); } } //该类用于对比singleton组件,展示prototype组件的不同之处 //scope prototype public class LifeCycleBean2 implements DisposableBean { public void mydestroy() { System.out.println("lifecycleBean2的destroy"); } @Override public void destroy() throws Exception { System.out.println("life2 disposableBean的destroy"); } }
- 在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" 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"> <!-- bean definitions here --> <bean id="lifecycleBean" class="life.LifeCycleBean" init-method="myinit" destroy-method="mydestroy"> <property name="password" value="hello"/> </bean> <bean id="lifeCycleBean2" class="life.LifeCycleBean2" scope="prototype" destroy-method="mydestroy"/> <bean class="com.cskaoyan.bean.CustomBeanPostProcessor"/> </beans>
- 在测试类中测试
public class MyTest { @Test public void mytest1(){ ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("application.xml"); //singleton组件,在获得组件之前已经完成了实例化,依旧可以打印 //LifeCycleBean bean = applicationContext.getBean(LifeCycleBean.class); //prototype组件在getBean之后才可以打印 LifeCycleBean2 bean1 = applicationContext.getBean(LifeCycleBean2.class); LifeCycleBean2 bean2 = applicationContext.getBean(LifeCycleBean2.class); LifeCycleBean2 bean3 = applicationContext.getBean(LifeCycleBean2.class); //关闭才可以看到destroy方法的打印输出 applicationContext.close(); } }
- 在main—java下新建一个life文件夹,里边新建三个类,分别是
3.7.注解
- bean标签对应class属性,而通过注解可以免去在xml中配置,无需在容器中注册
- 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" 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"> <!-- bean definitions here --> <!--扫描该包目录以及所有子包:如果发现了组件注册功能的注解--> <context:component-scan base-package="temp"/> </beans>
3.7.1.通用注解
- 通用注解:@Component
- 原组件的id就是Component注解中的value属性值,写在对应的类上
- 如果不写Component注解中的value属性值,那么其默认的值是对应类名首字母小写的形式
@Component是通用注解,可以写在任意类上
3.7.2.其他注解
- @Service:service层的注解
- @Respository:dao层的注解
- @Controller:controller层的注解
可以使用非对应的注解,但是不建议这么做
3.7.3.组件注入
- @Autowired:按类型注入,只有这个类型实现类唯一的时候,才可以使用
- @Autowired+@Qualifier:按类型注入,以value属性值指定组件的id值,用于不唯一的类型
- @Resoure:默认按类型注入,可以使用name属性来指定组件的id值
- @Value:相当于赋值的value属性
组件注入的使用前提是:当前组件的所在类上已经使用注解注入了容器中,内部嵌套的组件才能使用以上注解注入进该组件
- 在mapper包下建立UserMapper接口及其实现类UserMapperImpl1,UserMapperImpl2,OrderMapper接口及其实现类OrderMapperImpl
public interface OrderMapper {
public void hello();
}
//在容器中注册该类,@Component是通用的注解
@Component
public class OrderMapperImpl implements OrderMapper{
@Override
public void hello() {
System.out.println("hello orderMapper2");
}
}
public interface UserMapper {
public void sayHello();
}
//在容器中注册该类,使用dao层专属的注解
@Repository
public class UserMapperImpl1 implements UserMapper{
@Override
public void sayHello() {
System.out.println("hello mapper1");
}
}
//在容器中注册该类
@Repository
public class UserMapperImpl2 implements UserMapper{
@Override
public void sayHello() {
System.out.println("hello mapper2");
}
}
- 在service包下建立UserService接口及其实现类UserServiceImpl
public interface UserService {
public void sayAllHello();
}
//@Component("userService") //组件id为该注解的value属性值
@Component //组件id的默认值为类名的首字母小写,即userServiceImpl
public class UserServiceImpl implements UserService {
//容器中该类型的组件只有一个
@Autowired
OrderMapper orderMapper;
//容器中该类型有两个(两个UserMapper的实现类都注册在了容器里)
@Autowired
@Qualifier("userMapperImpl1")
UserMapper userMapper1;
@Resource(name = "userMapperImpl2") //默认可以按照类型注入,可以使用name属性来指定组件id
UserMapper userMapper2;
/*
重复的类型,不可以使用该注解注册
@Autowired
UserMapper userMapper3;
*/
@Override
public void sayAllHello() {
orderMapper.hello();
userMapper1.sayHello();
userMapper2.sayHello();
}
}
- 测试
@Test
public void test() {
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("application.xml");
UserService bean = applicationContext.getBean(UserService.class);
bean.sayAllHello();
}
3.7.4.生命周期&作用域
- @PostConstruct:自定义的init方法,对应init-method
- @PreDestroy:自定义的destroy方法,对应destroy-method
- @scope:将作用域的值写在注解中,写于类上
- 在上一个案例的基础上,创建一个LifeCycle类,在temp包下
@Component @Scope("singleton") public class LifeCycle { @PostConstruct public void init() { System.out.println("自定义init"); } @PreDestroy public void destroy() { System.out.println("自定义destroy"); } }
3.8.Spring单元测试
- maven工程中导入依赖
<!-- maven工程中导入spring依赖 --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-test</artifactId> <version>5.2.10.RELEASE</version> <scope>test</scope> </dependency>
- 测试容器中组件提供的方法
//给单元测试类构建一个spring的环境,将单元测试类当作是容器中的组件,注入功能的注解 @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration("classpath:application.xml") public class AnnotationTest { @Autowired UserService userService; @Autowired OrderMapper orderMapper; @Autowired ApplicationContext applicationContext; @Test public void test() { userService.sayAllHello(); } }
test方法以及其ContextConfiguration注解会自动获取ApplicationContext,并在结束的时候自动关闭。
3.9.SpringAOP
3.9.1.Spring动态代理
- 字节码随用随创建,随用随加载。它与静态代理的区别也在于此。因静态代理是字节码一上来就创建好,并完成加载。装饰者模式就是静态代理的一种体现。
- 动态代理常用的两种方式
- 基于接口的动态代理
- 提供者:JDK 官方的 Proxy 类。
- 要求:被代理类最少实现一个接口。
- 基于子类的动态代理
- 提供者:第三方的 CGLib,如果报 asmxxxx 异常,需要导入 asm.jar。
- 要求:被代理类不能用 final 修饰的类(最终类)。
在 spring 中,框架会根据目标类是否实现了接口来决定采用哪种动态代理的方式。
- 基于接口的动态代理
3.9.2. AOP 相关术语
- Joinpoint(连接点):所谓连接点是指那些被拦截到的点。在 spring 中,这些点指的是方法,因为 spring 只支持方法类型的连接点。
- Pointcut(切入点):所谓切入点是指我们要对哪些 JoinPoint 进行拦截的定义。
- Advice(通知/增强):所谓通知是指拦截到 Joinpoint 之后所要做的事情就是通知。
- 通知的类型:前置通知,后置通知,异常通知,最终通知,环绕通知。
- Introduction(引介):引介是一种特殊的通知在不修改类代码的前提下, Introduction 可以在运行期为类动态地添加一些方法或 Field。
- Target(目标对象):代理的目标对象。
- Weaving(织入):是指把增强应用到目标对象来创建新的代理对象的过程。spring 采用动态代理织入,而 AspectJ 采用编译期织入和类装载期织入。
- Proxy(代理):一个类被 AOP 织入增强后,就产生一个结果代理类。
- Aspect(切面):是切入点和通知(引介)的结合,等于切入点(Pointcut) + 通知(Advice)
target(被委托类)--------------------weaver(织入)--------------------Proxy(代理类) Pointcut(切入点)---指定增强方法 Advice(通知)---通知切入点范围的方法做出何种增强---相当于对委托类的方法 Joinpoint(连接点)---代理对象正在执行的增强的方法 | | V 获取方法执行过程中的参数 | | V 提供了类似于InvocationHandler的invoke方法的参数
3.9.3.示例
-
创建xml文件的Schema约束
<?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"> </beans>
-
在Service层创建UserService接口及其实现类
public interface UserService { public void sayHello(String name); } @Service public class UserServiceImpl implements UserService { @Override public void sayHello(String name) { System.out.println("hello" + name); } }
-
创建一个Advice通知类组件,需要实现MethodInterceptor接口
@Component public class CustomAdvice implements MethodInterceptor { @Override public Object invoke(MethodInvocation methodInvocation) throws Throwable { System.out.println("hello"); //执行委托类的代码 Object proceed = methodInvocation.proceed(); System.out.println("world"); return proceed; } }
-
在xml中添加bean标签
<!-- 注册一个代理类组件,会自动生成代理类,导致对应接口的实现类不唯一,在对应引用类型上通常要添加两个注解配合使用 --> <bean id="userServiceProxy" class="org.springframework.aop.framework.ProxyFactoryBean"> <!-- 增强委托类中的全部方法 name:固定为target值,表示实现类 ref:容器中委托类的组件id,即目标实现类名称 --> <property name="target" ref="userServiceImpl"/> <!-- 通知类组件 name:固定为interceptorNames value:需要一个字符串的值 --> <property name="interceptorNames" value="customAdvice"/> </bean>
-
在测试类测试
@RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration("classpath:application.xml") public class Temp { //这里,只写一个Autowired会报错,因为容器中既有UserService的实现类UserServiceImpl,还有容器中生成的userServiceProxy代理类 //因此,不仅仅需要加Autowired,还需要加上Qualifier注解,指明组件id @Autowired @Qualifier("userServiceProxy") UserService userService; @Test public void test(){ userService.sayHello("single dog"); } } /* 输出结果: hello hello single dog world */
3.10.AspectJ
- AspectJ实际上是对AOP编程思想的一个实践,当然,除了AspectJ以外,还有很多其它的AOP实现,例如ASMDex,但目前最好、最方便的,依然是AspectJ。
- AspectJ最主要的用途就是切面类的配置,通知组件
3.10.1.AspectJ改进
- 对于3.9.3的案例,可以采用Pointcut组件批量配置的方式,将xml配置更为简洁
- 配置方式:excution方式,或者@annotation注解方式
- excution:自定义的切入点表达式,批量注册
- @annotation:自定义注解方式,增强添加该注解的对应方法
3.10.2.切入点表达式
execution:匹配方法的执行(常用)
execution(表达式),表达式语法:execution([修饰符] 返回值类型 包名.类名.方法名(参数))
注意:这里的参数和返回值类型,如果是复杂类型的,要写全类名
写法说明:
全匹配方式:
public void service.AccountServiceImpl.saveAccount()
访问修饰符可以省略
void service.impl.AccountServiceImpl.saveAccount()
返回值可以使用*号,表示任意返回值,不可以省略
* service.impl.AccountServiceImpl.saveAccount()
包名可以使用*号,表示任意包,但是有几级包,需要写几个*
* *.*.AccountServiceImpl.saveAccount()
使用..来表示当前包,及其子包
* service..AccountServiceImpl.saveAccount()
类名可以使用*号,表示任意类
* service..*.saveAccount()
方法名可以使用*号,表示任意方法
* service..*.*()
参数列表可以使用*,表示参数可以是任意数据类型,但是必须有参数
* service..*.*(*)
参数列表可以使用..表示有无参数均可,有参数可以是任意类型
* service..*.*(..)
全通配方式:
* *..*.*(..)
注:
通常情况下,切入点表达式主要用于对业务层的方法进行增强,所以切入点表达式都是切到业务层实现类。
execution(* com.itheima.service.impl.*.*(..))
总结:
其实切入点表达式类似于mysql中的占位符和通配符的区别,*相当于占位符,..相当于通配符
3.10.3.AOP相关配置
aop:config
作用:用于声明开始 aop 的配置
<aop:config>
<!-- 配置的代码都写在此处 -->
</aop:config>
aop:aspect
作用:用于配置切面。告诉容器哪一个是切面组件
属性:
id:给切面提供一个唯一标识。
ref:引用配置好的通知类 bean 的 id。
<aop:aspect id="txAdvice" ref="txManager">
<!--配置通知的类型要写在此处-->
</aop:aspect>
aop:advisor
作用:用于配置通知组件
属性:
advice-ref:用于指定通知类中的方法
pointcut:用于指定切入点表达式
pointcut-ref::用于指定切入点的表达式的引用
aop:pointcut
作用:用于配置切入点表达式。就是指定对哪些类的哪些方法进行增强。
属性:
expression:用于定义切入点表达式。
id:用于给切入点表达式提供一个唯一标识
<aop:pointcut expression="execution(
public void com.itheima.service.impl.AccountServiceImpl.transfer(
java.lang.String, java.lang.String, java.lang.Float))" id="pt1"/>
aop:before
作用:用于配置前置通知。指定增强的方法在切入点方法之前执行
属性:
method:用于指定通知类中的增强方法名称
ponitcut-ref:用于指定切入点的表达式的引用
poinitcut:用于指定切入点表达式
执行时间点:切入点方法执行之前执行
<aop:before method="beginTransaction" pointcut-ref="pt1"/>
aop:around
作用:用于配置通知过程中的方法增强
属性:
method:用于指定通知类中的增强方法名称
ponitcut-ref:用于指定切入点的表达式的引用
poinitcut:用于指定切入点表达式
执行时间点:切入点方法执行时通过代理增强执行
aop:after
作用:用于配置最终通知
属性:
method:指定通知中方法的名称。
pointct:定义切入点表达式
pointcut-ref:指定切入点表达式的引用
执行时间点:无论切入点方法执行时是否有异常,它都会在其后面执行。
<aop:after method="release" pointcut-ref="pt1"/>
aop:after-returning
作用:用于配置后置通知
属性:
method:指定通知中方法的名称。
pointct:定义切入点表达式
pointcut-ref:指定切入点表达式的引用
returnning: 指定method对应的方法某个参数对应参数来接收方法的执行结果
执行时间点:切入点方法正常执行之后。它和异常通知只能有一个执行
<aop:after-returning method="commit" pointcut-ref="pt1" returnning="result"/>
aop:after-throwing
作用:用于配置异常通知
属性:
method:指定通知中方法的名称。
pointct:定义切入点表达式
pointcut-ref:指定切入点表达式的引用
throwing: 指定method对应方法的哪一个参数来接收委托类方法执行过程中抛出的异常
执行时间点:切入点方法执行产生异常后执行。它和后置通知只能执行一个
<aop:after-throwing method="rollback" pointcut-ref="pt1" throwing="exception"/>
3.10.4.excution改进
-
maven中引入依赖
<dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>1.9.6</version> </dependency>
-
在xml中引入AOP的Schema约束
<?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 https://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd"> <!-- bean definitions here --> <context:component-scan base-package="temp"/> </beans>
-
添加配置
<aop:config> <!-- 创建几个切入点 --> <aop:pointcut id="mypointcut1" expression="excution(public * service..sayHello(*))"/> <aop:pointcut id="mypointcut2" expression="excution(public * service..sayHello())"/> <aop:pointcut id="mypointcut3" expression="excution(public * service..sayHello(..))"/> <!-- 通过此标签选择对应的标签来增强标签里对应匹配的方法 --> <!-- advice-ref:选择通知组件的id pointcut-ref:选择哪一个pointcut的id值,即选择切入点 pointcut:自定义切入点表达式 --> <aop:advisor advice-ref="customAdvice" pointcut-ref="mypointcut3"/> </aop:config>
-
在测试类中测试
@RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration("classpath:application.xml") public class Temp { //不需要指定Qualifier,此时UserService的引用,已经指向了一个新生成的,并且内部方法已经得到了增强的新的实现类的实例化对象 @Autowired UserService userService; @Test public void test(){ userService.sayHello("single dog"); } }
3.10.5.对于aop配置的应用
- 新建一个UserService接口及其实现类
public interface UserService { public void sayHello(); public String sayHello2(String name); } @Service public class UserServiceImpl implements UserService{ @Override public void sayHello() { System.out.println("hello world"); } @Override public String sayHello2(String name) { String result = "hello " + name; System.out.println(result); int i = 1/0; return result; } }
- 创建切面类
@Component public class CustomAspect { public void before(JoinPoint joinPoint){ // before //通过joinPoint拿到正在执行的方法中的对应的一些值 Signature signature = joinPoint.getSignature(); //方法的特征值 String methodName = signature.getName(); //方法名 Object[] args = joinPoint.getArgs(); //方法的携带参数 System.out.println(methodName + "方法携带的参数是:" + Arrays.asList(args)); //if (args.length != 0 && "景甜".equals(args[0])) args[0] = "大甜甜"; Object target = joinPoint.getTarget(); //委托类对象 Object aThis = joinPoint.getThis(); //代理对象 System.out.println(target.getClass()); System.out.println(aThis.getClass()); System.out.println("正道的光"); } public void after(){ //after System.out.println("照在大地上"); } //around通知的返回值为Object,委托类方法的执行结果,类似于invocationHandler的invoke //参数中包含ProceedingJoinPoint还要执行委托类的方法 public Object around(ProceedingJoinPoint joinPoint) throws Throwable { long start = System.currentTimeMillis(); System.out.println("开始时间:" + start); //执行委托类的方法 Object proceed = joinPoint.proceed(); long end = System.currentTimeMillis(); System.out.println("花费时间为:" + (end - start)); return proceed; } public void afterReturning(Object result){ System.out.println("afterReturning:" + result); } //Throwable也可以 public void afterThrowing(Exception exception){//需要接收委托类方法执行过程中抛出的异常 System.out.println("afterThrowing:" + exception.getMessage()); } }
- 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 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"> <!-- bean definitions here --> <context:component-scan base-package="com.cskaoyan"/> <aop:config> <!--service层的任意方法--> <aop:pointcut id="mypointcut" expression="execution(* service..*(..))"/> <aop:aspect ref="customAspect"> <!--pointcut和通知的结合 method属性: 方法名,父标签中的ref属性对应的组件中的方法名 👉 切面组件中的方法名 --> <aop:before method="before" pointcut-ref="mypointcut"/> <aop:after method="after" pointcut-ref="mypointcut"/> <aop:around method="around" pointcut-ref="mypointcut"/> <!--需要通过returning属性指定method对应方法的哪一个参数来接收委托类方法的执行结果--> <aop:after-returning method="afterReturning" pointcut-ref="mypointcut" returning="result"/> <!--需要通过throwing属性指定method对应方法的哪一个参数来接收委托类方法执行过程中抛出的异常--> <aop:after-throwing method="afterThrowing" pointcut-ref="mypointcut" throwing="exception"/> </aop:aspect> </aop:config> </beans>
- 在测试类中测试
@RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration("classpath:application.xml") public class MyTest { @Autowired UserService userService; @Test public void mytest1() { userService.sayHello(); } @Test public void mytest2() { userService.sayHello2("single dog"); } }
3.10.6.自定义注解
- 通过自定义注解实现某些功能
- 自定义一个CountTime注解,位于anno包下
@Target(ElementType.METHOD) //注解位于方法上 @Retention(RetentionPolicy.RUNTIME) //注解在运行时生效 public @interface CountTime { }
- 实现注解的功能,计算方法运行时间
@Component public class CountTimeAdvice implements MethodInterceptor { @Override public Object invoke(MethodInvocation methodInvocation) throws Throwable { long start = System.currentTimeMillis(); Object proceed = methodInvocation.proceed(); long end = System.currentTimeMillis(); System.out.println(methodInvocation.getMethod().getName() + "方法执行时间为:" + (end - start)); return proceed; } }
- 新建一个OrderService接口及其实现类
public interface OrderService { public void method1(); public void method2(); public void method3(); } @Service public class OrderServiceImpl implements OrderService{ @CountTime @Override public void method1() { System.out.println("method1"); } @Override public void method2() { System.out.println("method2"); } @CountTime @Override public void method3() { System.out.println("method3"); try { Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } } }
- 在xml文件中注册
<!-- Scamea约束对标签顺序有所限制,pointcut必须位于adversor标签上边 --> <!-- 指定切入点 --> <aop:pointcut id="countTimePointcut" expression="@annotation(注解的全类名)"/> <!-- 指定组件生效范围 advice-ref:生效组件的id值 pointcut:生效范围 pointcut-ref:选择生效范围的对应标签,可以替换pointcut --> <aop:advisor advice-ref="countTimeAdvice" pointcut="excution(public * service..sayHello(..))"/>
- 在测试类中测试
@RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration("classpath:application.xml") public class MyTest { @Autowired OrderService orderService; @Test public void test(){ orderService.method1(); } @Test public void test2(){ orderService.method2(); } @Test public void test3(){ orderService.method3(); } }
- 自定义一个CountTime注解,位于anno包下
3.11.AspectJ注解形式
- 将3.10.5的程序改为注解形式,一一对应
- 在配置文件中开启注解开关
<?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"> <!-- bean definitions here --> <context:component-scan base-package="com.cskaoyan"/> <!--注解开关--> <aop:aspectj-autoproxy/> </beans>
- UserService接口及其实现类不必变动,修改CustomAspect类
@Aspect //声明该组件是一个通知类组件(Aspect) @Component //注册该组件 public class CustomAspect { /** * 返回值:void * 方法名:任意写 👉 方法名作为id * 参数:不需要 * 方法体 * @Pointcut:value属性值写的是切入点表达式 */ //pointcut以方法的形式存在,声明此方法为Pointcut @Pointcut("execution(* com.cskaoyan.service..*(..))") public void mypointcut() {} //@Before("execution(* com.cskaoyan.service..*(..))")//相当于直接写切入点表达式 @Before("mypointcut()") //可以使用该简便方式 public void before(JoinPoint joinPoint){ // 👉 before //通过joinPoint拿到正在执行的方法中的对应的一些值 Signature signature = joinPoint.getSignature(); String methodName = signature.getName(); Object[] args = joinPoint.getArgs(); System.out.println(methodName + "方法携带的参数是:" + Arrays.asList(args)); if (args.length != 0 && "景甜".equals(args[0])){ args[0] = "大甜甜"; } Object target = joinPoint.getTarget(); //委托类对象 Object aThis = joinPoint.getThis(); //代理对象 System.out.println(target.getClass()); System.out.println(aThis.getClass()); System.out.println("正道的光"); } @After("mypointcut()") public void after(){ //after System.out.println("照在大地上"); } @Around("mypointcut()") //around通知的返回值为Object,委托类方法的执行结果,类似于invocationHandler的invoke //参数中包含ProceedingJoinPoint,还要执行委托类的方法 public Object around(ProceedingJoinPoint joinPoint) throws Throwable { long start = System.currentTimeMillis(); System.out.println("开始时间:" + start); //执行委托类的方法 Object proceed = joinPoint.proceed(); long end = System.currentTimeMillis(); System.out.println("花费时间为:" + (end - start)); return proceed; } @AfterReturning(value = "mypointcut()",returning = "result") public void afterReturning(Object result){ System.out.println("afterReturning:" + result); } @AfterThrowing(value = "mypointcut()",throwing = "exception") //Throwable也可以 public void afterThrowing(Exception exception){//需要接收委托类方法执行过程中抛出的异常 System.out.println("afterThrowing:" + exception.getMessage()); } }
3.12.Spring-Mybatis整合
- 导入依赖文件
<dependencies> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>5.2.10.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-test</artifactId> <version>5.2.10.RELEASE</version> <scope>test</scope> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.13.1</version> <scope>test</scope> </dependency> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>1.9.6</version> </dependency> <!--mybatis--> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>3.5.6</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.47</version> <scope>runtime</scope> </dependency> <!--需要额外引入的依赖--> <!--spring-jdbc\spring-tx transaction--> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-jdbc</artifactId> <version>5.2.10.RELEASE</version> </dependency> <!--mybatis对spring支持的依赖--> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis-spring</artifactId> <version>2.0.6</version> </dependency> <!--datasource--> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>1.2.4</version> </dependency>
- 创建一个UserMapper用于映射Mybatis
public interface UserMapper { String selectUsernameById(@Param("id") Integer id); }
- 创建一个UserService接口及其实现类
public interface UserService { String queryUsernameById(Integer id); } @Service public class UserServiceImpl implements UserService{ @Autowired UserMapper userMapper; //@Autowired //SqlSessionFactory sqlSessionFactory; @Override public String queryUsernameById(Integer id) { //SqlSession sqlSession = sqlSessionFactory.openSession(); //userMapper = sqlSession.getMapper(UserMapper.class); return userMapper.selectUsernameById(id); } }
- spring的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 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"> <!-- bean definitions here --> <context:component-scan base-package="com.cskaoyan"/> <bean id="datasource" class="com.alibaba.druid.pool.DruidDataSource"> <property name="driverClassName" value="com.mysql.jdbc.Driver"/> <property name="url" value="jdbc:mysql://localhost:3306/cskaoyan_db?useUnicode=true&characterEncoding=utf-8"/> <property name="username" value="root"/> <property name="password" value="123456"/> </bean> <!--FactoryBean的getObject方法的返回值是SqlSessionFactory--> <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"> <property name="dataSource" ref="datasource"/> <!--typeAliases--> <property name="typeAliasesPackage" value="com.cskaoyan.bean"/> <!--mybatis.xml--> <property name="configLocation" value="classpath:mybatis.xml"/> </bean> <!--MapperScannerConfigurer,在这里可以加载mybatis的配置,可以不写--> <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer"> <property name="basePackage" value="com.cskaoyan.mapper"/> <!--<property name="sqlSessionFactory" ref="sqlSessionFactory"/>--> <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/> </bean> </beans>
- Mybatis.xml的配置
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd"> <configuration> <settings> <!-- 没有什么特别需要的配置,可以不写此文件 --> <setting name="cacheEnabled" value="true"/> <setting name="lazyLoadingEnabled" value="true"/> </settings> </configuration>
- 编写测试类
@RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration("classpath:application.xml") public class MyTest { @Test public void mytest1() throws IOException { SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder(); InputStream inputStream = Resources.getResourceAsStream("mybatis.xml"); SqlSessionFactory sqlSessionFactory = builder.build(inputStream); SqlSession sqlSession = sqlSessionFactory.openSession(); } @Autowired UserService userService; @Test public void mytest2() { String username = userService.queryUsernameById(2); System.out.println(username); } @Autowired UserMapper userMapper; @Test public void mytest3() { String username = userMapper.selectUsernameById(3); } }
3.13.Spring事务
- PlatformTransactionManager接口提供事务操作的方法,包含有3个具体的操作
- 获取事务状态信息:TransactionStatus getTransaction(TransactionDefinition definition)
- 提交事务:void commit(TransactionStatus status)
- 回滚事务:void rollback(TransactionStatus status)
- 在开发中都是使用它的实现类,真正管理事务的对象:
- 使用 SpringJDBC 或 iBatis 进行持久化数据时使用org.springframework.jdbc.datasource.DataSourceTransactionManager
- 使用Hibernate 版本进行持久化数据时使用org.springframework.orm.hibernate5.HibernateTransactionManager
3.13.1.TransactionDefinition
- 事务的定义信息对象,有如下方法
- 获取事务对象名称:String getName()
- 获取事务隔离级:int getlsolationLevel()
- 获取事务传播行为:int getPropagationBehavior()
- 获取事务超时时间:int getTimeout()
- 默认值是-1,没有超时限制。如果有,以秒为单位进行设置。
- 获取事务是否只读:boolean isReadOnly()
- 建议查询时设置为只读。
- 开启事务的情形:
- 读写型事务:增加、除、修改开启事务。
- 只读型事务:执行查询时,也会开启事务
- 事务的隔离级别:事务隔离级反映事务提交并发访问时的处理态度
- ISOLATION_DEFAULT:默认级别,归属下列某一种
- ISOLATION_READ_UNCOMMITTED:可以读取未提交数据
- ISOLATION_READ_COMMITTED只能读取已提交数据,解决脏读问题(Oracle默认级别)
- ISOLATION_REPEATABLE_READ:是否读取其他事务提交修改后的数据,解决不可重复读问题(MySQL默认级别)
- ISOLATION_SERIALIZABLE:是否读取其他事务提交添加后的数据,解决幻影读问题
- 事务的传播行为
- REQUIRED:如果当前没有事务,就新建一个事务,如果已经存在一个事务中,加入到这个事务中。一般的选择(默认值)
- 要么一起提交,要么一起回滚,任何一个事务发生异常,均回滚
- SUPPORTS:支持当前事务,如果当前没有事务,就以非事务方式执行(没有事务)
- MANDATORY:使用当前的事务,如果当前没有事务,就抛出异常
- REQUERS_NEW:如果没有事务就新建事务,如果当前在事务中,把当前事务挂起,内部事务不受影响(自私型事务)。
- 如果B中调用了A,A发生异常,那么AB均回滚,B发生异常,那么只有B回滚
- NOT_SUPPORTED:以非事务方式执行操作,如果当前存在事务,就把当前事务挂起
- NEVER:以非事务方式运行,如果当前存在事务,抛出异常
- NESTED:如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则执行 REQUIRED 类似的操作(无私型事务)。
- 如果B中调用了A,B发生异常,那么AB均回滚,A发生异常,那么只有A回滚
- REQUIRED:如果当前没有事务,就新建一个事务,如果已经存在一个事务中,加入到这个事务中。一般的选择(默认值)
3.13.2.TransactionStatus
- 此接口提供的是事务具体的运行状态,TransactionStatus接口描述了某个时间点上事务对象的状态信息,包含有6个具体的操作
- 刷新事务:void flush()
- 获取是否存在存储点:boolean hasSavepoint()
- 获取事务是否完成:boolean isCompleted()
- 获取事务是否为新的事务:boolean isNewTransaction()
- 获取事务是否回滚:boolean isRollbackOnly()
- 设置事务回滚:void setRollbackOnly()
3.13.3.基于XML的声明式事务控制
- 搭建一个转账业务,可以实现异常回滚,保证资金安全
-
版本一:TransactionTemplate模板
- 引入依赖
<dependency> <groupId>org.springframework</groupId> <artifactId>spring-jdbc</artifactId> <version>5.2.10.RELEASE</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.47</version> </dependency> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>3.5.6</version> </dependency> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis-spring</artifactId> <version>2.0.6</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>5.2.10.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-test</artifactId> <version>5.2.10.RELEASE</version> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>1.2.4</version> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.13.1</version> <scope>test</scope> </dependency>
- 配置application.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 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"> <!-- bean definitions here --> <context:component-scan base-package="com.cskaoyan"/> <bean id="datasource" class="com.alibaba.druid.pool.DruidDataSource"> <property name="driverClassName" value="com.mysql.jdbc.Driver"/> <property name="url" value="jdbc:mysql://localhost:3306/cskaoyan_db?useUnicode=true&characterEncoding=utf8"/> <property name="username" value="root"/> <property name="password" value="123456"/> </bean> <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"> <property name="dataSource" ref="datasource"/> </bean> <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer"> <property name="basePackage" value="com.cskaoyan.mapper"/> <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/> </bean> <!--TransactionManager--> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="datasource"/> </bean> <!--TransactionTemplate--> <bean class="org.springframework.transaction.support.TransactionTemplate"> <property name="transactionManager" ref="transactionManager"/> </bean> </beans>
- 创建Dao层
public interface AccountMapper { Integer selectMoneyById(@Param("id") Integer id); int updateMoneyById(@Param("money") Integer money, @Param("id") Integer id); }
- 创建映射文件AccountMapper.xml
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="com.cskaoyan.mapper.AccountMapper"> <select id="selectMoneyById" resultType="integer"> select money from cskaoyan_account where id = #{id} </select> <update id="updateMoneyById" > update cskaoyan_account set money = #{money} where id = #{id} </update> </mapper>
- 创建Service层
public interface AccountService { void transfer(Integer fromId, Integer destId, Integer money); } @Service public class AccountServiceImpl implements AccountService{ @Autowired AccountMapper accountMapper; @Autowired TransactionTemplate transactionTemplate; @Override public void transfer(Integer fromId, Integer destId, Integer money) { Integer fromMoney = accountMapper.selectMoneyById(fromId) - money; Integer destMoney = accountMapper.selectMoneyById(destId) + money; //fromMoney -= money; //destMoney += money; //需要返回值的方法,返回值可以自定义 /*Object execute = transactionTemplate.execute(new TransactionCallback<Object>() { //哪一部分需要增加事务,哪部分代码就放入到doInTransaction方法中 //doInTransaction方法的返回值 → 给到TransactionTemplate的execute方法 @Override public Object doInTransaction(TransactionStatus transactionStatus) { accountMapper.updateMoneyById(fromMoney, fromId); //int i = 1 / 0; accountMapper.updateMoneyById(destMoney, destId); return 5; } }); System.out.println(execute);*/ //无需返回值的方法 transactionTemplate.execute(new TransactionCallbackWithoutResult() { @Override protected void doInTransactionWithoutResult(TransactionStatus transactionStatus) { accountMapper.updateMoneyById(fromMoney, fromId); int i = 1/0; accountMapper.updateMoneyById(destMoney, destId); } }); } }
- 创建测试类
@RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration("classpath:application.xml") public class MyTest { @Autowired AccountService accountService; @Test public void test(){ accountService.transfer(1,2,1000); } }
- 引入依赖
-
版本二:TransactionProxy形式
- 创建Dao层
public interface AccountMapper { Integer selectMoneyById(@Param("id") Integer id); int updateMoneyById(@Param("money") Integer money, @Param("id") Integer id); }
- Mybatis映射文件
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="com.cskaoyan.mapper.AccountMapper"> <select id="selectMoneyById" resultType="integer"> select money from cskaoyan_account where id = #{id} </select> <update id="updateMoneyById" > update cskaoyan_account set money = #{money} where id = #{id} </update> </mapper>
- service层
public interface AccountService { void transfer(Integer fromId, Integer destId, Integer money); } @Service public class AccountServiceImpl implements AccountService{ @Autowired AccountMapper accountMapper; @Override public void transfer(Integer fromId, Integer destId, Integer money) { Integer fromMoney = accountMapper.selectMoneyById(fromId) - money; Integer destMoney = accountMapper.selectMoneyById(destId) + money; //fromMoney -= money; //destMoney += money; accountMapper.updateMoneyById(fromMoney, fromId); //int i = 1/0; accountMapper.updateMoneyById(destMoney, destId); } }
- application.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 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"> <!-- bean definitions here --> <context:component-scan base-package="com.cskaoyan"/> <bean id="datasource" class="com.alibaba.druid.pool.DruidDataSource"> <property name="driverClassName" value="com.mysql.jdbc.Driver"/> <property name="url" value="jdbc:mysql://localhost:3306/cskaoyan_db?useUnicode=true&characterEncoding=utf8"/> <property name="username" value="root"/> <property name="password" value="123456"/> </bean> <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"> <property name="dataSource" ref="datasource"/> </bean> <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer"> <property name="basePackage" value="com.cskaoyan.mapper"/> <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/> </bean> <!--TransactionManager--> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="datasource"/> </bean> <!--委托类组件accountServiceImpl--> <!--代理组件--> <bean id="accountServiceProxy" class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean"> <property name="target" ref="accountServiceImpl"/> <property name="transactionManager" ref="transactionManager"/> <!--TransactionAttributes → TransactionDefinition--> <property name="transactionAttributes"> <!--properties类型:既要表达key,又要表达value--> <props> <!-- key的含义:方法名 value的含义:TransactionDefinition PROPAGATION_XXX:传播行为 ISOLATION_XXX:隔离级别 readOnly:只读 timeout_数字:超时时间,单位是秒 -Exception:当发生XXX异常的时候回滚 +Exception:当发生xxx异常是不回滚 --> <prop key="transfer">PROPAGATION_REQUIRED,ISOLATION_DEFAULT,timeout_5</prop> </props> </property> </bean> </beans>
- 测试类测试
@RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration("classpath:application.xml") public class MyTest { @Autowired @Qualifier("accountServiceProxy") AccountService accountService; @Test public void mytest1(){ accountService.transfer(1,2,1000); } }
- 创建Dao层
-
版本三:Transaction-advisor
- 创建Dao层
public interface AccountMapper { Integer selectMoneyById(@Param("id") Integer id); int updateMoneyById(@Param("money") Integer money, @Param("id") Integer id); }
- Mybatis映射文件
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="com.cskaoyan.mapper.AccountMapper"> <select id="selectMoneyById" resultType="integer"> select money from cskaoyan_account where id = #{id} </select> <update id="updateMoneyById" > update cskaoyan_account set money = #{money} where id = #{id} </update> </mapper>
- service层
public interface AccountService { void transfer(Integer fromId, Integer destId, Integer money); } @Service public class AccountServiceImpl implements AccountService{ @Autowired AccountMapper accountMapper; @Override public void transfer(Integer fromId, Integer destId, Integer money) { Integer fromMoney = accountMapper.selectMoneyById(fromId) - money; Integer destMoney = accountMapper.selectMoneyById(destId) + money; //fromMoney -= money; //destMoney += money; accountMapper.updateMoneyById(fromMoney, fromId); int i = 1/0; accountMapper.updateMoneyById(destMoney, destId); } }
- application.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 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/tx https://www.springframework.org/schema/tx/spring-tx.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd"> <!-- bean definitions here --> <context:component-scan base-package="com.cskaoyan"/> <bean id="datasource" class="com.alibaba.druid.pool.DruidDataSource"> <property name="driverClassName" value="com.mysql.jdbc.Driver"/> <property name="url" value="jdbc:mysql://localhost:3306/cskaoyan_db?useUnicode=true&characterEncoding=utf8"/> <property name="username" value="root"/> <property name="password" value="123456"/> </bean> <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"> <property name="dataSource" ref="datasource"/> </bean> <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer"> <property name="basePackage" value="com.cskaoyan.mapper"/> <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/> </bean> <!--TransactionManager--> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="datasource"/> </bean> <aop:config> <aop:pointcut id="txPointcut" expression="execution(* com..service..*(..))"/> <!--事务通知组件--> <aop:advisor advice-ref="transactionAdvice" pointcut-ref="txPointcut"/> </aop:config> <!--tx:advice--> <tx:advice id="transactionAdvice" transaction-manager="transactionManager"> <!--method definition--> <tx:attributes> <tx:method name="transfer*" propagation="REQUIRED" isolation="REPEATABLE_READ" read-only="false" timeout="5"/> </tx:attributes> </tx:advice> </beans>
- 测试类中测试
@RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration("classpath:application.xml") public class MyTest { @Autowired AccountService accountService; @Test public void test1(){ accountService.transfer(1,2,1000); } }
- 创建Dao层
-
版本四:Transactional
- Dao层
public interface AccountMapper { Integer selectMoneyById(@Param("id") Integer id); int updateMoneyById(@Param("money") Integer money, @Param("id") Integer id); }
- Mybatis映射
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="com.cskaoyan.mapper.AccountMapper"> <select id="selectMoneyById" resultType="integer"> select money from cskaoyan_account where id = #{id} </select> <update id="updateMoneyById" > update cskaoyan_account set money = #{money} where id = #{id} </update> </mapper>
- service层
public interface AccountService { void transfer(Integer fromId, Integer destId, Integer money); } @Transactional//ElementType.Type意味着当前组件里的全部方法都增加上事务 @Service public class AccountServiceImpl implements AccountService{ @Autowired AccountMapper accountMapper; @Transactional(isolation = Isolation.REPEATABLE_READ,propagation = Propagation.REQUIRED, readOnly = true,timeout = 5)//ElementType.Method 当前方法增加上事务 @Override public void transfer(Integer fromId, Integer destId, Integer money) { Integer fromMoney = accountMapper.selectMoneyById(fromId) - money; Integer destMoney = accountMapper.selectMoneyById(destId) + money; //fromMoney -= money; //destMoney += money; accountMapper.updateMoneyById(fromMoney, fromId); int i = 1/0; accountMapper.updateMoneyById(destMoney, destId); } }
- application.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 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/tx https://www.springframework.org/schema/tx/spring-tx.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd"> <!-- bean definitions here --> <context:component-scan base-package="com.cskaoyan"/> <bean id="datasource" class="com.alibaba.druid.pool.DruidDataSource"> <property name="driverClassName" value="com.mysql.jdbc.Driver"/> <property name="url" value="jdbc:mysql://localhost:3306/cskaoyan_db?useUnicode=true&characterEncoding=utf8"/> <property name="username" value="root"/> <property name="password" value="123456"/> </bean> <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"> <property name="dataSource" ref="datasource"/> </bean> <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer"> <property name="basePackage" value="com.cskaoyan.mapper"/> <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/> </bean> <!--TransactionManager--> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="datasource"/> </bean> <!-- 打开事务的注解开关 --> <tx:annotation-driven transaction-manager="transactionManager"/> </beans>
- 测试类测试
@RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration("classpath:application.xml") public class MyTest { @Autowired AccountService accountService; @Test public void mytest1(){ accountService.transfer(1,2,1000); } }
- Dao层
-
版本五:Transactional-Config纯注解形式,将版本四改为无application.xml配置文件
- Dao层
public interface AccountMapper { Integer selectMoneyById(@Param("id") Integer id); int updateMoneyById(@Param("money") Integer money, @Param("id") Integer id); }
- Mybatis映射
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="com.cskaoyan.mapper.AccountMapper"> <select id="selectMoneyById" resultType="integer"> select money from cskaoyan_account where id = #{id} </select> <update id="updateMoneyById" > update cskaoyan_account set money = #{money} where id = #{id} </update> </mapper>
- service层
public interface AccountService { void transfer(Integer fromId, Integer destId, Integer money); } @Transactional//ElementType.Type意味着当前组件里的全部方法都增加上事务 @Service public class AccountServiceImpl implements AccountService{ @Autowired AccountMapper accountMapper; @Transactional(isolation = Isolation.REPEATABLE_READ,propagation = Propagation.REQUIRED, readOnly = false,timeout = 5)//ElementType.Method 当前方法增加上事务 @Override public void transfer(Integer fromId, Integer destId, Integer money) { Integer fromMoney = accountMapper.selectMoneyById(fromId) - money; Integer destMoney = accountMapper.selectMoneyById(destId) + money; //fromMoney -= money; //destMoney += money; accountMapper.updateMoneyById(fromMoney, fromId); int i = 1/0; accountMapper.updateMoneyById(destMoney, destId); } }
- 创建一个配置类,用于替代原application.xml的功能
@Configuration //将此类作为配置类 @ComponentScan("com.cskaoyan") //组件扫描功能,value值为包名 @EnableTransactionManagement //打开事务注解的开关 @EnableAspectJAutoProxy // 对应标签aop:aspectj-autoproxy,是aspectj的注解形式的开关,在3.11中用到 public class ApplicationConfig { @Autowired AccountService accountService; /** @Bean注解,相当于原xml配置中的Bean标签 * 返回值:对应bean标签中的class属性值或其接口,即组件的类或接口 * 方法体:按照se的代码风格去写即可 * 组件id:可以使用@Bean的value属性指定;如果不写默认组件id为方法名 */ //对应原xml文件中的数据源配置 //@Bean("datasource") //组件id为datasource @Bean //不写,默认组件id为方法名dataSource public DruidDataSource dataSource(){ DruidDataSource druidDataSource = new DruidDataSource(); druidDataSource.setDriverClassName("com.mysql.jdbc.Driver"); druidDataSource.setUrl("jdbc:mysql://localhost:3306/cskaoyan_db"); druidDataSource.setUsername("root"); druidDataSource.setPassword("123456"); return druidDataSource; } @Bean //组件id为dataSource2 public DruidDataSource dataSource2(){ DruidDataSource druidDataSource = new DruidDataSource(); druidDataSource.setDriverClassName("com.mysql.jdbc.Driver"); druidDataSource.setUrl("jdbc:mysql://localhost:3306/shenguancheng_db"); druidDataSource.setUsername("root"); druidDataSource.setPassword("123456"); return druidDataSource; } //public DataSource /** * 可以通过形参按照类型从容器中取出组件 * 如果容器中该类型组件不止一个,需要使用@Qualifier指定组件id */ @Bean public SqlSessionFactoryBean sqlSessionFactory(@Qualifier("dataSource") DataSource dataSource){ SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean(); sqlSessionFactoryBean.setDataSource(dataSource); return sqlSessionFactoryBean; } @Bean public MapperScannerConfigurer mapperScannerConfigurer(){ MapperScannerConfigurer mapperScannerConfigurer = new MapperScannerConfigurer(); mapperScannerConfigurer.setBasePackage("com.cskaoyan.mapper"); mapperScannerConfigurer.setSqlSessionFactoryBeanName("sqlSessionFactory"); return mapperScannerConfigurer; } @Bean public DataSourceTransactionManager dataSourceTransactionManager(@Qualifier("dataSource") DataSource dataSource){ DataSourceTransactionManager dataSourceTransactionManager = new DataSourceTransactionManager(); dataSourceTransactionManager.setDataSource(dataSource); return dataSourceTransactionManager; } }
- 测试类中测试
@RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(classes = ApplicationConfig.class) // 加载配置类 public class MyTest { @Autowired AccountService accountService; @Test public void mytest1(){ accountService.transfer(1,2,1000); } }
- Dao层
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/181045.html