学习Spring----AOP
动态代理–精致小案列
需求说明:
1, 有一个交通接口Vehicle, 有一个run(), 下面有两个实现类Car 和 Ship
2. 当运行Car对象的run() 和 Ship对象的run()时,输出内容如下:
交通工具开始运行了...
大轮船在水上 running...
交通工具停止运行了...
============================
交通工具开始运行了...
小汽车在公路 running...
交通工具停止运行了...
原始解决方案
public interface Vehicle {
public void run();
}
public class Car implements Vehicle{
@Override
public void run() {
System.out.println("交通工具开始运行了...");
System.out.println("小汽车在公路 running...");
System.out.println("交通工具停止运行了...");
}
}
public class Ship implements Vehicle{
@Override
public void run() {
System.out.println("交通工具开始运行了...");
System.out.println("大轮船在水上 running...");
System.out.println("交通工具停止运行了...");
}
}
@Test
public void testProexy() {
Vehicle vehicle = new Ship();
vehicle.run();
}
动态代理(利用反射)解决方案:
public interface Vehicle {
public void run();
public void fly(int height);
}
public class Car implements Vehicle{
@Override
public void run() {
//System.out.println("交通工具开始运行了...");
System.out.println("小汽车在公路 running...");
//System.out.println("交通工具停止运行了...");
}
@Override
public void fly(int height) {
System.out.println("飞机飞行的高度为 " + height);
}
}
public class Ship implements Vehicle{
@Override
public void run() {
//System.out.println("交通工具开始运行了...");
System.out.println("大轮船在水上 running...");
//System.out.println("交通工具停止运行了...");
}
@Override
public void fly(int height) {
System.out.println("飞机飞行的高度为 " + height);
}
}
package com.zzti.spring.proxy_;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
/**
* 该类可以返回一个代理对象
*/
public class VehicleProxyProvider {
//定义一个属性,target_vehicle表示真正要执行的对象
private Vehicle target_vehicle;
//构造器
public VehicleProxyProvider(Vehicle target_vehicle){
this.target_vehicle = target_vehicle;
}
//编写一个方法,可以返回一个代理对象, 该代理对象可以通过反射机制调用到被代理对象的方法
public Vehicle getProxy() {
//得到类加载器
ClassLoader classLoader = target_vehicle.getClass().getClassLoader();
//得到要代理的对象/被执行对象 的接口信息. 底层是通过接口来完成调用
Class<?>[] interfaces = target_vehicle.getClass().getInterfaces();
//创建InvocationHandler对象
//因为InvocationHandler是接口, 所以我们可以通过匿名对象的方式来创建该对象
/**
* public interface InvocationHandler {
* public Object invoke (Object proxy, Method method, Object[]args)
* throws Throwable;
*}
* invoke()是将来执行我们的target_vehicle的方法时,会调用到
*/
InvocationHandler invocationHandler = new InvocationHandler() {
/**
* invoke()是将来执行我们的target_vehicle的方法时,会调用到
* @param proxy 表示代理对象
* @param method 通过代理对象调用方法时的哪个方法 代理对象.run()
* @param args 表示调用 代理对象.run(xx) 传入的参数
* @return 表示 代理对象.run(xx) 执行后的结果
* @throws Throwable
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("交通工具开始运行了...");
/**
* method是? ===>
* target_vehicle是? ===> Car对象/Ship对象
* args 是 null
*/
Object result = method.invoke(target_vehicle, args);
System.out.println("交通工具停止运行了...");
return result;
}
};
/**
* public static Object newProxyInstance(ClassLoader loader,
* Class<?>[] interfaces,
* InvocationHandler h)
* Proxy.newProxyInstance可以返回一个代理对象
* ClassLoader loader 类的加载器
* Class<?>[] interfaces 就是奖励啊要代理的对象的接口信息
* InvocationHandler h 调用处理器/对象 有一个非常重要的方法invoke
*/
Vehicle proxy = (Vehicle)Proxy.newProxyInstance(classLoader, interfaces, invocationHandler);
return proxy;
}
}
@Test
public void testProexyRun() {
Vehicle vehicle = new Car();
//创建VehicleProxyProvider对象, 并且传入要代理的对象
VehicleProxyProvider vehicleProxyProvider = new VehicleProxyProvider(vehicle);
//获取代理对象, 该对象可以代理执行方法
Vehicle proxy = vehicleProxyProvider.getProxy();
System.out.println("proxy的编译类型是 Vehicle");
//proxy的运行类型是= class com.sun.proxy.$Proxy2
System.out.println("proxy的运行类型是= " + proxy.getClass());
proxy.run();
//proxy.fly(200);
}
=============================================
proxy的编译类型是 Vehicle
proxy的运行类型是= class com.sun.proxy.$Proxy2
交通工具开始运行了...
小汽车在公路 running...
交通工具停止运行了...
============================================
proxy的编译类型是 Vehicle
proxy的运行类型是= class com.sun.proxy.$Proxy2
交通工具开始运行了...
飞机飞行的高度为 200
交通工具停止运行了...
动态代理的 动态是怎么体现的?
- proxy的运行类型是 class com.sun.proxy.$Proxy2 该类型被转成Vehicle,因此可以调用Vehicle的接口方法
- 当执行run()时会调用,根据java的动态代理绑定机制,这时直接调用Car的run()是 通过proxy对象的invocationHandler的invoke(),invoke()使用反射机制来调用run()[这个run() 也可以是Vehicle接口下面的其他方法],这时可以在调用run()前,进行前置和后置处理
- 也就是说proxy的target_vehicle运行类型只要是实现了Vehicle接口,就可以调用不同的方法,是动态的,变化的。底层就是反射完成的。
动态代理深入(处理日志)
土方法;
public interface SmartAnimal {
public float getSum(float i,float j);
public float getSub(float i,float j);
}
public class SmartDog implements SmartAnimal {
@Override
public float getSum(float i, float j) {
System.out.println("日志-方法名-getSum()开始-参数: " + i + "," + j);
float result = i + j;
System.out.println("方法内部打印: result= " + result);
System.out.println("日志-方法名-getSum()结束-结果: result= " + result);
return result;
}
@Override
public float getSub(float i, float j) {
System.out.println("日志-方法名-getSub()开始-参数: " + i + "," + j);
float result = i - j;
System.out.println("方法内部打印: result= " + result);
System.out.println("日志-方法名-getSub()结束-结果: result= " + result);
return result;
}
}
@Test
public void smartAnimalTest() {
SmartDog smartDog = new SmartDog();
smartDog.getSum(10.50f,20.50f);
smartDog.getSub(10,8);
}
使用动态代理
//在调用目标方法之前打印日志
public void before(Object proxy, Method method, Object[] args){
System.out.println("before~日志-方法名:" + method.getName() +"--方法开始--参数: " + Arrays.asList(args));
}
//在调用目标方法之后打印日志
public void after(Method method, Object result){
System.out.println("after~日志-方法名:" + method.getName() +"--方法正常结束--result= " + result);
}
public SmartAnimal getProxy() {
//得到类加载器
ClassLoader classLoader = target_smartAnimal.getClass().getClassLoader();
//得到要代理的对象/被执行对象 的接口信息. 底层是通过接口来完成调用
Class<?>[] interfaces = target_smartAnimal.getClass().getInterfaces();
InvocationHandler invocationHandler = new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object result = null;
try {
before(proxy, method, args);//切入到目标方法前
result = method.invoke(target_smartAnimal, args);
after(method, result);//切入到目标方法后
}catch (Exception e){
e.printStackTrace();
System.out.println("日志-方法名:" + method.getName() +"--方法抛出异常--异常类型: " + e.getClass().getName());
}finally {
System.out.println("日志-方法名:" + method.getName() +"--方法最终结束");
}
return result;
}
};
//将上面的classLoader,interfaces,invocationHandler构建成一个Vehicle的代理对象
SmartAnimal proxy = (SmartAnimal)Proxy.newProxyInstance(classLoader, interfaces, invocationHandler);
return proxy;
}
@Test
public void smartAnimalTestByProexy() {
SmartAnimal smartAnimal = new SmartDog();
MyProxyProvider myProxyProvider = new MyProxyProvider(smartAnimal);
SmartAnimal proxy = myProxyProvider.getProxy();
proxy.getSum(10.50f,20.50f);
proxy.getSub(1,0.50f);
}
这种方法耦合度太高,开发简易的AOP切入类
public class AOP {
//在调用目标方法之前打印日志
public static void before(Object proxy, Method method, Object[] args){
System.out.println("AOP~日志-方法名:" + method.getName() +"--方法开始--参数: " + Arrays.asList(args));
}
//在调用目标方法之后打印日志
public static void after(Method method, Object result){
System.out.println("AOP~日志-方法名:" + method.getName() +"--方法正常结束--result= " + result);
}
}
public SmartAnimal getProxy() {
//得到类加载器
ClassLoader classLoader = target_smartAnimal.getClass().getClassLoader();
//得到要代理的对象/被执行对象 的接口信息. 底层是通过接口来完成调用
Class<?>[] interfaces = target_smartAnimal.getClass().getInterfaces();
InvocationHandler invocationHandler = new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object result = null;
try {
AOP.before(proxy, method, args);//切入到目标方法前
result = method.invoke(target_smartAnimal, args);
AOP.after(method, result);//切入到目标方法后
}catch (Exception e){
e.printStackTrace();
System.out.println("日志-方法名:" + method.getName() +"--方法抛出异常--异常类型: " + e.getClass().getName());
}finally {
System.out.println("日志-方法名:" + method.getName() +"--方法最终结束");
}
return result;
}
};
//将上面的classLoader,interfaces,invocationHandler构建成一个Vehicle的代理对象
SmartAnimal proxy = (SmartAnimal)Proxy.newProxyInstance(classLoader, interfaces, invocationHandler);
return proxy;
}
引出AOP
- 土方法不够灵活,复用性差还是一种硬编码(没有注解和反射支撑)。
- 从而引出AOP,底层是ASPECTJ
- AOP的全称(aspect oriented programming),面向切面编程
两张图说明AOP的相关概念
AOP的实现方式
- 基于动态代理的方式[内置aop实现]
- 使用框架aspectj来实现
快速入门:
需要引入核心的aspect包
在切面类中声明通知方法
- 前置通知: @Before
- 返回通知: @AfterReturning
- 异常通知: @AfterThrowing
- 后置通知: @After
- 环绕通知: @Around
try {
//前置通知
System.out.println("日志-方法名:" + method.getName() +"--方法开始--参数: " + Arrays.asList(args));
result = method.invoke(target_smartAnimal, args);
//返回通知
System.out.println("日志-方法名:" + method.getName() +"--方法正常结束--result= " + result);
}catch (Exception e){
e.printStackTrace();
//异常通知
System.out.println("日志-方法名:" + method.getName() +"--方法抛出异常--异常类型: " + e.getClass().getName());
}finally {
//后置通知
System.out.println("日志-方法名:" + method.getName() +"--方法最终结束");
}
//环绕通知可以将四个通知合并管理
测试几种通知的案列:
public interface SmartAnimal {
public float getSum(float i,float j);
public float getSub(float i,float j);
}
@Component
public class SmartDog implements SmartAnimal {
@Override
public float getSum(float i, float j) {
float result = i + j;
//int res = 10 / 0;//测试异常通知
System.out.println("方法内部打印: result= " + result);
return result;
}
@Override
public float getSub(float i, float j) {
float result = i - j;
System.out.println("方法内部打印: result= " + result);
return result;
}
}
package com.zzti.spring.aop.aspectj;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
import java.util.Arrays;
/**
* 该类就是管理切面编程
*/
@Aspect //表示该类是一个切面类(底层切面编程的支撑[动态代理+反射+动态绑定])
@Component //表示需要加入到ioc容器
public class SmartAnimalAspect {
/**
* 1. @Before 表示在我们的目标对象执行方法前执行
* 2. value = "execution(public float com.zzti.spring.aop.aspectj.SmartDog.getSum(float,float))"
* 表示指定切入到哪个类的哪个方法 形式是: 访问修饰符 返回类型 全类名.方法名(形参列表)
* 3. showBeginLog方法 可以理解成: 一个切入方法,这个方法名可以随意指定
* 4. JoinPoint joinPoint在底层执行时,由Aspectj切面框架,会给切入方法传入joinPoint对象,通过该方法, 可以获取到相关信息
* 5. @Before(value = "execution(public float com.zzti.spring.aop.aspectj.SmartDog.*(float,float))") 这样写的话,SmartDog()下面所有的方法,都会切入到目标方法中
* @param joinPoint
*/
@Before(value = "execution(public float com.zzti.spring.aop.aspectj.SmartDog.*(float,float))")
public void showBeginLog(JoinPoint joinPoint){
System.out.println("======前置通知======");
Signature signature = joinPoint.getSignature();
System.out.println("前置通知~日志-方法名:" + signature.getName() +"--方法开始--参数: " + Arrays.asList(joinPoint.getArgs()));
}
/**
* AfterReturning 返回通知, 把showSuccessEndLog方法切入到目标对象方法正常执行完毕后的地方
* 如果我们希望把目标方法执行的结果,返回给切入方法,需要在加上returning属性, 同时切入方法增加Object res
* 要注意returning = "res" 和 Object res 的 res名字要一致
* @param joinPoint
* @param res
*/
@AfterReturning(value = "execution(public float com.zzti.spring.aop.aspectj.SmartDog.getSum(float,float))",returning = "res")
public void showSuccessEndLog(JoinPoint joinPoint, Object res){
System.out.println("======返回通知======");
Signature signature = joinPoint.getSignature();
System.out.println("返回通知~日志-方法名:" + signature.getName() +"--方法正常结束--返回的结果是: " + res);
}
/**
* AfterThrowing 异常通知, 把showExceptionLog方法切入到目标对象方法发生异常的catch{}
* @param joinPoint
* @param throwable
*/
@AfterThrowing(value = "execution(public float com.zzti.spring.aop.aspectj.SmartDog.getSum(float,float))",throwing = "throwable")
public void showExceptionLog(JoinPoint joinPoint, Throwable throwable){
System.out.println("======异常通知======");
Signature signature = joinPoint.getSignature();
System.out.println("异常通知~日志-方法名:" + signature.getName() +"--方法异常信息--返回的结果是: " + throwable);
}
/**
* After 后置通知, 把showFinallyEndLog方法切入到目标对象方法执行后(不管是否发生异常,都要执行finally{})
* @param joinPoint
*/
@After(value = "execution(public float com.zzti.spring.aop.aspectj.SmartDog.getSum(float,float))")
public void showFinallyEndLog(JoinPoint joinPoint){
System.out.println("======后置通知======");
Signature signature = joinPoint.getSignature();
System.out.println("后置通知~日志-方法名:" + signature.getName());
}
}
@Test
public void smartDogTestByProxy(){
ApplicationContext ioc = new ClassPathXmlApplicationContext("beans06.xml");
//需要通过接口类型来获取到注入的SmartDog对象--就是代理对象
SmartAnimal smartAnimal = ioc.getBean(SmartAnimal.class);
smartAnimal.getSum(10,2);
System.out.println("===================");
smartAnimal.getSub(10,5);
}
==================测试结果========================
======前置通知======
前置通知~日志-方法名:getSum--方法开始--参数: [10.0, 2.0]
方法内部打印: result= 12.0
======返回通知======
返回通知~日志-方法名:getSum--方法正常结束--返回的结果是: 12.0
======后置通知======
后置通知~日志-方法名:getSum
===================
======前置通知======
前置通知~日志-方法名:getSub--方法开始--参数: [10.0, 5.0]
方法内部打印: result= 5.0
细节说明:
- 关于切面类方法命名虽然可以随意命名,但是自己可以规范一下, 比如: showBeginLog()、showSuccessEndLog()、showExceptionLog()、showFinallyEndLog()
- 切入表达式的更多配置,比如使用模糊配置: @Before(value = “execution(public float com.zzti.spring.aop.aspectj.SmartDog.*(float,float))”)
- 表示所有访问权限,所有包的下所有类的所有方法,都会被执行该前置通知方法@Before(value = “execution(* .(…))”)
- 当spring容器开启了
<aop:aspectj-autoproxy />,我们获取注入的对象,需要以接口的类型获取,因为你注入的对象.getClass(),已经是代理类型了。同时也可以通过id获取,但是也要转成接口类型。
课后作业:
public interface UsbInterface {
public void work();
}
@Component
public class Phone implements UsbInterface{
@Override
public void work() {
System.out.println("Phone work() 执行了~~~");
}
}
@Component
public class Camera implements UsbInterface{
@Override
public void work() {
System.out.println("Camera work() 执行了~~~");
}
}
package com.zzti.spring.aop.aspectj.homework;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;
@Aspect
@Component
public class UsbAspect {
@Before(value = "execution(public void com.zzti.spring.aop.aspectj.homework.Phone.work()) || execution(public void com.zzti.spring.aop.aspectj.homework.Camera.work())")
public void showBeginLog(JoinPoint joinPoint){
System.out.println("======前置通知======");
Signature signature = joinPoint.getSignature();
System.out.println("前置通知~日志-方法名:" + signature.getName());
}
//切入表达式也可以指向接口的方法, 这时切入表达式会对实现接口的类/对象生效
//比如下面的对UsbInterface切入, 那么对实现类Phone 和 Camera 对象都作用了
@After(value = "execution(public void com.zzti.spring.aop.aspectj.homework.UsbInterface.work())")
public void showFinallyEndLog(JoinPoint joinPoint){
System.out.println("======后置通知======");
Signature signature = joinPoint.getSignature();
System.out.println("后置通知~日志-方法名:" + signature.getName());
}
}
package com.zzti.spring.aop.aspectj.homework;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class UsbAspectTest {
@Test
public void testUsbAspect(){
ApplicationContext ioc = new ClassPathXmlApplicationContext("beans06.xml");
//需要通过接口类型来获取到注入的UsbInterface对象--就是代理对象
//这样会报错,因为UsbInterface下面有两个bean(phone,camera), 不知道使用哪个
//UsbInterface usbInterface = ioc.getBean(UsbInterface.class);
//方式1
//UsbInterface usbInterface = ioc.getBean("phone",UsbInterface.class);
//UsbInterface usbInterface2 = ioc.getBean("camera",UsbInterface.class);
//usbInterface.work();
//usbInterface2.work();
//方式2
UsbInterface phone = (UsbInterface) ioc.getBean("phone");
UsbInterface camera = (UsbInterface) ioc.getBean("camera");
phone.work();
System.out.println("=========分隔符==========");
camera.work();
}
}
===============测试结果================
======前置通知======
前置通知~日志-方法名:work
Phone work() 执行了~~~
======后置通知======
后置通知~日志-方法名:work
=========分隔符==========
======前置通知======
前置通知~日志-方法名:work
Camera work() 执行了~~~
======后置通知======
后置通知~日志-方法名:work
切入表达式
-
切入点表达式作用:
通过表达式的方式
定位一个或多个
具体的连接点 -
语法细节
a. 切入点表达式的语法格式execution([权限修饰符] [返回值类型] [全类名/简单类名] [方法名] ([参数列表]))
举例说明: execution(* com.zzti.spring…aop.aspectj.SmartDog.*(…))
第一个 * 代表 任意修饰符及任意返回值
第二个 * 代表 任一方法
. . 匹配任意数量、任意类型的参数
若目标类、接口与切面类在同一个包中可以省略。b. 列举几个小列子
1.execution(
public
* com.zzti.spring…aop.aspectj.SmartDog.(…)) ==> SmartDog中的所有公共方法
2.execution( publicdouble
com.zzti.spring…aop.aspectj.SmartDog.(…)) ==> SmartDog中返回double类型数值的方法
3.execution( public double com.zzti.spring…aop.aspectj.SmartDog.*(double
, . .)) ==> SmartDog中第一个参数为double类型的方法,后面的. .匹配任意数量、任意类型的参数c. 切入点表达式重用(为了统一管理切入点表达式)
@Aspect //表示该类是一个切面类(底层切面编程的支撑[动态代理+反射+动态绑定])
@Component //表示需要加入到ioc容器
public class SmartAnimalAspect {
//定义一个切入点, 在后面使用时可以直接使用,提高了复用性
@Pointcut(value = "execution(public float com.zzti.spring.aop.aspectj.SmartDog.*(float,float))")
public void myPointcut(){
}
//使用定义好的切入点
@Before(value = "myPointcut()")
//@Before(value = "execution(public float com.zzti.spring.aop.aspectj.SmartDog.*(float,float))")
public void showBeginLog(JoinPoint joinPoint){
System.out.println("======前置通知======");
Signature signature = joinPoint.getSignature();
System.out.println("前置通知~日志-方法名:" + signature.getName() +"--方法开始--参数: " + Arrays.asList(joinPoint.getArgs()));
}
@AfterReturning(value = "myPointcut()",returning ="res")
//@AfterReturning(value = "execution(public float com.zzti.spring.aop.aspectj.SmartDog.getSum(float,float))",returning = "res")
public void showSuccessEndLog(JoinPoint joinPoint, Object res){
System.out.println("======返回通知======");
Signature signature = joinPoint.getSignature();
System.out.println("返回通知~日志-方法名:" + signature.getName() +"--方法正常结束--返回的结果是: " + res);
}
@AfterThrowing(value ="myPointcut()",throwing = "throwable")
//@AfterThrowing(value = "execution(public float com.zzti.spring.aop.aspectj.SmartDog.getSum(float,float))",throwing = "throwable")
public void showExceptionLog(JoinPoint joinPoint, Throwable throwable){
System.out.println("======异常通知======");
Signature signature = joinPoint.getSignature();
System.out.println("异常通知~日志-方法名:" + signature.getName() +"--方法异常信息--返回的结果是: " + throwable);
}
@After(value = "myPointcut()")
//@After(value = "execution(public float com.zzti.spring.aop.aspectj.SmartDog.getSum(float,float))")
public void showFinallyEndLog(JoinPoint joinPoint){
System.out.println("======后置通知======");
Signature signature = joinPoint.getSignature();
System.out.println("后置通知~日志-方法名:" + signature.getName());
}
}
-
注意事项与细节
-
切入表达式也可以指向类的方法,这时切入表达式会对该类/对象生效;
@Before(value = "execution(public void com.zzti.spring.aop.aspectj.homework.Phone.work()) || execution(public void com.zzti.spring.aop.aspectj.homework.Camera.work())")
public void showBeginLog(JoinPoint joinPoint){
System.out.println("======前置通知======");
Signature signature = joinPoint.getSignature();
System.out.println("前置通知~日志-方法名:" + signature.getName());
}
- 切入表达式也可以指向接口的方法,这时切入表达式会对实现了接口的类/对象生效;
//切入表达式也可以指向接口的方法, 这时切入表达式会对实现接口的类/对象生效
//比如下面的对UsbInterface切入, 那么对实现类Phone 和 Camera 对象都作用了
@After(value = "execution(public void com.zzti.spring.aop.aspectj.homework.UsbInterface.work())")
public void showFinallyEndLog(JoinPoint joinPoint){
System.out.println("======后置通知======");
Signature signature = joinPoint.getSignature();
System.out.println("后置通知~日志-方法名:" + signature.getName());
}
- 切入表达式也可以对没有实现接口的类,进行切入.(这种方式是通过CGlib动态代理的)
/切入表达式也可以对没有实现接口的类, 进行切入
//给Car设置一个后置通知
@After(value = "execution(public void Car.run())")
public void ok1(JoinPoint joinPoint){
System.out.println("======Car后置通知======");
Signature signature = joinPoint.getSignature();
System.out.println("Car后置通知~日志-方法名:" + signature.getName());
}
@Test
public void testCar() {
ApplicationContext ioc = new ClassPathXmlApplicationContext("beans06.xml");
Car car = ioc.getBean(Car.class);
//car对象仍然是代理对象
//car的类型是= class com.zzti.spring.aop.aspectj.homework.Car$$EnhancerBySpringCGLIB$$1fd8defb
//这种没有实现接口来代理,是基于Spring的CGlib; 之前通过实现接口是通过动态代理jdk的proxy
System.out.println("car的类型是= " + car.getClass());
car.run();
}
两个动态代理的区别
-
JDK动态代理是面向接口的,只能增强实现类中接口中存在的方法。CGlib是面向父类的,可以增强父类的所有方法
-
JDK得到的对象是JDK代理对象实例,而CGlib得到的对象是被代理对象的子类
参考如下链接:
https://www.cnblogs.com/threeAgePie/p/15832586.html
AOP—JoinPoint
通过JoinPoint可以获取到调用方法的签名
joinPoint.getSignature().getName();//获取目标方法名
joinPoint.getSignature().getDeclaringType().getSimpleName();//获取目标方法所属类的简单名
joinPoint.getSignature().getDeclaringTypeName();//获取目标方法所属类的类名
joinPoint.getSignature().getModifiers();//获取目标方法声明类型(public,private,protected)
AOP—环绕通知[了解]
环绕通知可以完成其他四个通知要做的事情
package com.zzti.spring.aop.aspectj;
import com.zzti.spring.aop.proxy3.AOP;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
import java.util.Arrays;
import java.util.List;
/**
* 该类就是管理切面编程
*/
@Aspect //表示该类是一个切面类(底层切面编程的支撑[动态代理+反射+动态绑定])
@Component //表示需要加入到ioc容器
public class SmartAnimalAspect2 {
//环绕通知
@Around(value = "execution(public float com.zzti.spring.aop.aspectj.SmartDog.*(float,float))")
public Object doAround(ProceedingJoinPoint joinPoint){
Object result = null;
String methodName = joinPoint.getSignature().getName();
try {
//1.相当于完成前置通知完成的事情
Object[] args = joinPoint.getArgs();
List<Object> objectList = Arrays.asList(args);
System.out.println("环绕通知==" + methodName + "方法开始了====参数有: " + objectList);
//在环绕通知中一定要调用joinPoint.proceed()来执行目标方法
result = joinPoint.proceed();
//2.相当于返回通知完成的事情
System.out.println("环绕通知==" + methodName + "方法结束了====结果是: " + result);
} catch (Throwable throwable) {
//3.相当于异常通知完成的事情
System.out.println("环绕通知==" + methodName + "方法抛出异常了====异常对象是: " + throwable);
} finally {
//4.相当于后置通知完成的事情
System.out.println("环绕通知==" + methodName + "方法抛最终结束了");
}
return result;
}
}
@Test
public void smartDogTestByAround(){
ApplicationContext ioc = new ClassPathXmlApplicationContext("beans06.xml");
//需要通过接口类型来获取到注入的SmartDog对象--就是代理对象
SmartAnimal smartAnimal = ioc.getBean(SmartAnimal.class);
smartAnimal.getSum(10,2);
System.out.println("===================");
smartAnimal.getSub(10,5);
}
=======================测试结果============================
环绕通知==getSum方法开始了====参数有: [10.0, 2.0]
方法内部打印: result= 12.0
环绕通知==getSum方法结束了====结果是: 12.0
环绕通知==getSum方法抛最终结束了
===================
环绕通知==getSub方法开始了====参数有: [10.0, 5.0]
方法内部打印: result= 5.0
环绕通知==getSub方法结束了====结果是: 5.0
环绕通知==getSub方法抛最终结束了
AOP–切面优先级问题
如果同一个方法,有多个切面在同一个切入点切入,那么执行的优先级如何控制,
通过@Order(value=n)来控制, n值越小, 优先级越高。
@Order(value = 1)
@Aspect //表示该类是一个切面类(底层切面编程的支撑[动态代理+反射+动态绑定])
@Component //表示需要加入到ioc容器
public class SmartAnimalAspect {
.......
}
@Order(value = 2)
@Aspect //表示该类是一个切面类(底层切面编程的支撑[动态代理+反射+动态绑定])
@Component //表示需要加入到ioc容器
public class SmartAnimalAspect3 {
.......
}
@Test
public void smartDogTestByProxy(){
ApplicationContext ioc = new ClassPathXmlApplicationContext("beans06.xml");
//需要通过接口类型来获取到注入的SmartDog对象--就是代理对象
SmartAnimal smartAnimal = ioc.getBean(SmartAnimal.class);
smartAnimal.getSum(10,2);
System.out.println("===================");
smartAnimal.getSub(10,5);
}
==================测试结果===================
======前置通知======
前置通知~日志-方法名:getSum--方法开始--参数: [10.0, 2.0]
======前置通知======
SmartAnimalAspect3-前置通知~日志-方法名:getSum--方法开始--参数: [10.0, 2.0]
方法内部打印: result= 12.0
======返回通知======
SmartAnimalAspect3-返回通知~日志-方法名:getSum--方法正常结束--返回的结果是: 12.0
======后置通知======
SmartAnimalAspect3-后置通知~日志-方法名:getSum
======返回通知======
返回通知~日志-方法名:getSum--方法正常结束--返回的结果是: 12.0
======后置通知======
后置通知~日志-方法名:getSum
===================
======前置通知======
前置通知~日志-方法名:getSub--方法开始--参数: [10.0, 5.0]
======前置通知======
SmartAnimalAspect3-前置通知~日志-方法名:getSub--方法开始--参数: [10.0, 5.0]
方法内部打印: result= 5.0
======返回通知======
返回通知~日志-方法名:getSub--方法正常结束--返回的结果是: 5.0
======后置通知======
后置通知~日志-方法名:getSub
注意事项:
不能理解成,优先级越高的每个消息通知都先执行
,这个和方法调用机制(Filter过滤器链式调用类似)
AOP–基于XML配置AOP
SmartAnimal 和 SmartDog(@Component注解去掉)不变
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
<!--配置SmartAnimalAspect beans-->
<bean id="smartAnimalAspect" class="com.zzti.spring.aop.xml.SmartAnimalAspect"/>
<!--配置SmartDog-->
<bean id="smartDog" class="com.zzti.spring.aop.xml.SmartDog"/>
<aop:config>
<!--配置切入点-->
<aop:pointcut id="myPointCut" expression="execution(public float com.zzti.spring.aop.xml.SmartDog.*(float,float))"/>
<aop:aspect ref="smartAnimalAspect" order="1">
<!--配置各个通知对应的切入点-->
<aop:before method="showBeginLog" pointcut-ref="myPointCut"/>
<aop:after-returning method="showSuccessEndLog" pointcut-ref="myPointCut" returning="res"/>
<aop:after-throwing method="showExceptionLog" pointcut-ref="myPointCut" throwing="throwable"/>
<aop:after method="showFinallyEndLog" pointcut-ref="myPointCut"/>
<!--还可以配置环绕通知-->
<!--<aop:around method=""/>-->
</aop:aspect>
</aop:config>
</beans>
package com.zzti.spring.aop.xml;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.Signature;
import java.util.Arrays;
public class SmartAnimalAspect {
public void showBeginLog(JoinPoint joinPoint){
System.out.println("======前置通知======");
Signature signature = joinPoint.getSignature();
System.out.println("XML-前置通知~日志-方法名:" + signature.getName() +"--方法开始--参数: " + Arrays.asList(joinPoint.getArgs()));
}
public void showSuccessEndLog(JoinPoint joinPoint, Object res){
System.out.println("======返回通知======");
Signature signature = joinPoint.getSignature();
System.out.println("XML-返回通知~日志-方法名:" + signature.getName() +"--方法正常结束--返回的结果是: " + res);
}
public void showExceptionLog(JoinPoint joinPoint, Throwable throwable){
System.out.println("======异常通知======");
Signature signature = joinPoint.getSignature();
System.out.println("XML-异常通知~日志-方法名:" + signature.getName() +"--方法异常信息--返回的结果是: " + throwable);
}
public void showFinallyEndLog(JoinPoint joinPoint){
System.out.println("======后置通知======");
Signature signature = joinPoint.getSignature();
System.out.println("XML-后置通知~日志-方法名:" + signature.getName());
}
}
@Test
public void smartDogTestByXml(){
ApplicationContext ioc = new ClassPathXmlApplicationContext("beans07.xml");
//需要通过接口类型来获取到注入的SmartDog对象--就是代理对象
SmartAnimal smartAnimal = ioc.getBean(SmartAnimal.class);
smartAnimal.getSum(10,6);
System.out.println("===================");
smartAnimal.getSub(20,5);
}
=============测试结果======================
======前置通知======
XML-前置通知~日志-方法名:getSum--方法开始--参数: [10.0, 6.0]
方法内部打印: result= 16.0
======返回通知======
XML-返回通知~日志-方法名:getSum--方法正常结束--返回的结果是: 16.0
======后置通知======
XML-后置通知~日志-方法名:getSum
===================
======前置通知======
XML-前置通知~日志-方法名:getSub--方法开始--参数: [20.0, 5.0]
方法内部打印: result= 15.0
======返回通知======
XML-返回通知~日志-方法名:getSub--方法正常结束--返回的结果是: 15.0
======后置通知======
XML-后置通知~日志-方法名:getSub
课后作业:
<context:component-scan base-package="com.zzti.spring.aop.homework" />
<!--开启基于注解的aop功能-->
<aop:aspectj-autoproxy />
public interface Cal {
public void cal1(int n);
public void cal2(int n);
}
package com.zzti.spring.aop.homework;
import org.springframework.stereotype.Component;
@Component
public class MyCal implements Cal{
@Override
public void cal1(int n) {
int res = 0;
for (int i = 0; i <= n; i++) {
res += i;
}
System.out.println("cal1 res= " + res);
}
@Override
public void cal2(int n) {
int res = 1;
for (int i = 1; i <= n; i++) {
res *= i;
}
System.out.println("cal2 res= " + res);
}
}
package com.zzti.spring.aop.homework;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
@Aspect
@Component
public class CalAspect {
@Pointcut(value = "execution(public void com.zzti.spring.aop.homework.MyCal.*(int))")
public void myPointcut(){
}
@Before(value="myPointcut()")
public void starCal(){
System.out.println("开始执行计算== " + System.currentTimeMillis());
}
@AfterReturning(value = "myPointcut()")
public void endCal(){
System.out.println("结束执行计算== " + System.currentTimeMillis());
}
}
@Test
public void testCalAspect(){
ApplicationContext ioc = new ClassPathXmlApplicationContext("beans06.xml");
//需要通过接口类型来获取到注入的MyCal对象--就是代理对象
Cal cal = ioc.getBean(Cal.class);
cal.cal1(5);
System.out.println("=========分界线==========");
cal.cal2(5);
}
==================测试结果======================
开始执行计算== 1657541597514
cal1 res= 15
结束执行计算== 1657541597514
=========分界线==========
开始执行计算== 1657541597514
cal2 res= 120
结束执行计算== 1657541597515
先看个案列,引出对Spring底层实现再思考
前面已经实现了,Spring XML注入bean,Spring注解方式注入bean,Spring AOP动态代理实现。
思考1: 原生Spring如何实现依赖注入和singleton、prototype
Spring如何实现ioc容器创建和初始化
Spring如何实现getBean,根据singleton 和prototype来返回bean实例?
答: 之前是用的@Service、@Repository、@Controller都是单例的,若在UserController类上加上@Scope(value=“prototype”),就是多例的,创建同一个userController就是两个对象。
思考2. 原生Spring如何实现 BeanPostProcessor(bean后置处理器)
<context:component-scan base-package="com.zzti.spring.processor"/>
<!--bean的后置处理器-->
<bean id="myBeanPostProcessor" class="com.zzti.spring.processor.MyBeanPostProcessor"/>
package com.zzti.spring.processor;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
public class MyBeanPostProcessor implements BeanPostProcessor {
/**
* 在bean初始化之前完成某些任务
* @param bean 就是ioc容器返回的对象,如果这里被替换会修改,则返回的bean对象也会被修改
* @param beanName 就是ioc容器配置的bean的名称
* @return Object 就是返回的bean对象
* @throws BeansException
*/
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
System.out.println("postProcessBeforeInitialization被调用 " + beanName + ",bean= "+ bean);
return bean;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
System.out.println("postProcessAfterInitialization被调用 " + beanName + ",bean= "+ bean.getClass());
return bean;
}
}
@Component
public class UserDao {
public void hi(){
System.out.println("UserDao hi()执行了~~");
}
}
@Component
public class UserService {
@Autowired
private UserDao userDao;
public void m1(){
userDao.hi();
}
//通过注解指定在构造器完成后执行的方法, 即完成初始化任务
@PostConstruct
public void init(){
System.out.println("初始化业务~~~");
}
}
@Component
public class UserController {
}
@Test
public void testBeanPostProcessor(){
ApplicationContext ioc = new ClassPathXmlApplicationContext("beans08.xml");
UserController userController1 = (UserController)ioc.getBean("userController");
UserController userController2 = (UserController)ioc.getBean("userController");
System.out.println("userController1= " + userController1);
System.out.println("userController2= " + userController2);
UserService userService = (UserService)ioc.getBean("userService");
System.out.println("userService= " + userService);
userService.m1();
UserDao userDao = (UserDao)ioc.getBean("userDao");
System.out.println("userDao= " + userDao);
}
=================测试结果================================
postProcessBeforeInitialization被调用 userController,bean= com.zzti.spring.processor.UserController@14a50707
postProcessAfterInitialization被调用 userController,bean= class com.zzti.spring.processor.UserController
postProcessBeforeInitialization被调用 userDao,bean= com.zzti.spring.processor.UserDao@4d518b32
postProcessAfterInitialization被调用 userDao,bean= class com.zzti.spring.processor.UserDao
postProcessBeforeInitialization被调用 userService,bean= com.zzti.spring.processor.UserService@3ddc6915
初始化业务~~~
postProcessAfterInitialization被调用 userService,bean= class com.zzti.spring.processor.UserService
userController1= com.zzti.spring.processor.UserController@14a50707
userController2= com.zzti.spring.processor.UserController@14a50707
userService= com.zzti.spring.processor.UserService@3ddc6915
UserDao hi()执行了~~
userDao= com.zzti.spring.processor.UserDao@4d518b32
思考3. 原生Spring如何实现AOP?
思考4. 分析一下,AOP和BeanPostProcessor之间的关系
- AOP实现Spring可以通过给一个类,加入注解 @EnableAspectJAutoProxy来指定
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import({AspectJAutoProxyRegistrar.class})
public @interface EnableAspectJAutoProxy {
boolean proxyTargetClass() default false;
boolean exposeProxy() default false;
}
解读:
- AOP底层是基于BeanPostProcessor机制的
- 即在Bean创建好后,根据是否需要Aop处理,决定返回代理对象,还是原生bean
- 在返回代理对象时,就可以根据要代理的类和方法来返回
- 其实这个机制并不难,本质就是在BeanPostProcessor机制 + 动态代理技术
Spring整体架构分析
手动实现Spring底层机制[初始化ioc容器+依赖注入+BeanPostProcessor机制+AOP]
1.实现任务阶段1----编写自己Spring容器,实现扫描包,得到bean的class对象
知识扩展: 类加载器
java的类加载器分为3种
Bootstrap类加载器 ===> 对应路径jre/lib
Ext类加载器 ===> 对应路径jre/lib/ext
App类加载器 ===> 对应路径classpath
"D:\Program Files\Java\jdk1.8.0_301\bin\java.exe" -ea -Didea.test.cyclic.buffer.size=1048576 "-javaagent:F:\idea\IntelliJ IDEA Community Edition 2021.2.2\lib\idea_rt.jar=51353:F:\idea\IntelliJ IDEA Community Edition 2021.2.2\bin" -Dfile.encoding=UTF-8 -classpath "F:\idea\IntelliJ IDEA Community Edition 2021.2.2\lib\idea_rt.jar;F:\idea\IntelliJ IDEA Community Edition 2021.2.2\plugins\junit\lib\junit5-rt.jar;F:\idea\IntelliJ IDEA Community Edition 2021.2.2\plugins\junit\lib\junit-rt.jar;D:\Program Files\Java\jdk1.8.0_301\jre\lib\charsets.jar;D:\Program Files\Java\jdk1.8.0_301\jre\lib\deploy.jar;D:\Program Files\Java\jdk1.8.0_301\jre\lib\ext\access-bridge-64.jar;D:\Program Files\Java\jdk1.8.0_301\jre\lib\ext\cldrdata.jar;D:\Program Files\Java\jdk1.8.0_301\jre\lib\ext\dnsns.jar;D:\Program Files\Java\jdk1.8.0_301\jre\lib\ext\jaccess.jar;D:\Program Files\Java\jdk1.8.0_301\jre\lib\ext\jfxrt.jar;D:\Program Files\Java\jdk1.8.0_301\jre\lib\ext\localedata.jar;D:\Program Files\Java\jdk1.8.0_301\jre\lib\ext\nashorn.jar;D:\Program Files\Java\jdk1.8.0_301\jre\lib\ext\sunec.jar;D:\Program Files\Java\jdk1.8.0_301\jre\lib\ext\sunjce_provider.jar;D:\Program Files\Java\jdk1.8.0_301\jre\lib\ext\sunmscapi.jar;D:\Program Files\Java\jdk1.8.0_301\jre\lib\ext\sunpkcs11.jar;D:\Program Files\Java\jdk1.8.0_301\jre\lib\ext\zipfs.jar;D:\Program Files\Java\jdk1.8.0_301\jre\lib\javaws.jar;D:\Program Files\Java\jdk1.8.0_301\jre\lib\jce.jar;D:\Program Files\Java\jdk1.8.0_301\jre\lib\jfr.jar;D:\Program Files\Java\jdk1.8.0_301\jre\lib\jfxswt.jar;D:\Program Files\Java\jdk1.8.0_301\jre\lib\jsse.jar;D:\Program Files\Java\jdk1.8.0_301\jre\lib\management-agent.jar;D:\Program Files\Java\jdk1.8.0_301\jre\lib\plugin.jar;D:\Program Files\Java\jdk1.8.0_301\jre\lib\resources.jar;D:\Program Files\Java\jdk1.8.0_301\jre\lib\rt.jar;D:\Exercise_items\hsp_spring\target\classes;D:\maven-repository\junit\junit\4.11\junit-4.11.jar;D:\maven-repository\org\hamcrest\hamcrest-core\1.3\hamcrest-core-1.3.jar;D:\Exercise_items\hsp_spring\src\main\webapp\lib\junit-4.12.jar;D:\Exercise_items\hsp_spring\src\main\webapp\lib\dom4j-1.6.1.jar;D:\Exercise_items\hsp_spring\src\main\webapp\lib\spring-aop-5.3.8.jar;D:\Exercise_items\hsp_spring\src\main\webapp\lib\hamcrest-core-1.3.jar;D:\Exercise_items\hsp_spring\src\main\webapp\lib\spring-core-5.3.8.jar;D:\Exercise_items\hsp_spring\src\main\webapp\lib\spring-beans-5.3.8.jar;D:\Exercise_items\hsp_spring\src\main\webapp\lib\hamcrest-library-1.3.jar;D:\Exercise_items\hsp_spring\src\main\webapp\lib\spring-context-5.3.8.jar;D:\Exercise_items\hsp_spring\src\main\webapp\lib\commons-logging-1.1.3.jar;D:\Exercise_items\hsp_spring\src\main\webapp\lib\spring-expression-5.3.8.jar" com.intellij.rt.junit.JUnitStarter -ideVersion5 -junit4 com.zzti.spring.aop.proxy_.TestProxy,smartAnimalTest
扫描包,得到bean的class对象,排除包下面不是bean的对象
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Component {
//@Component可以传入属性值,默认为""
String value() default "";
}
package com.zzti.myspring.component;
import com.zzti.myspring.annotation.Component;
/**
* 1. 使用 @Component("monsterService")修饰
* 2. 给该MonsterService注入到容器,设置beanName为monsterService
* 3. 若没有设置, 默认为 类名首字母小写
*/
@Component("monsterService")
public class MonsterService {
}
package com.zzti.myspring.component;
import com.zzti.myspring.annotation.Component;
@Component("monsterDao")
public class MonsterDao {
}
package com.zzti.myspring.ioc;
import com.zzti.myspring.annotation.ComponentScan;
/**
* 这是一个配置类,作用类似我们原生spring的beans.xml 容器配置文件
*/
@ComponentScan(value = "com.zzti.myspring.component")
public class HspMySpringConfig {
}
package com.zzti.myspring.ioc;
import com.zzti.myspring.annotation.Component;
import com.zzti.myspring.annotation.ComponentScan;
import java.io.File;
import java.net.URL;
/**
* 这个类的作用类似, spring原生的ioc容器
* 1. 搭建基本结构并获取的扫描包
* 2. 获取扫描包下所有的.class文件
* 3. 获取全类名, 反射对象, 放入容器
*/
public class HspMySpringApplicationContext {
private Class configClass;
public HspMySpringApplicationContext(Class configClass){
this.configClass = configClass;
//1. 解析配置类
//2. 获取到配置类的 @ComponentScan(value = "com.zzti.myspring.component")
ComponentScan componentScan = (ComponentScan)this.configClass.getDeclaredAnnotation(ComponentScan.class);
String path = componentScan.value();
//扫描路径path= com.zzti.myspring.component
System.out.println("扫描路径path= " + path);
//3. 获取扫描路径下所有的类文件
//1) 先得到类加载器
ClassLoader classLoader = HspMySpringApplicationContext.class.getClassLoader();
//在获取某个包对应的url时, 要求是com/zzti/myspring/component
//URL resource = classLoader.getResource("com/zzti/spring/component");
//System.out.println(resource);
//2) 将path转换成com/zzti/myspring/component形式
path = path.replace(".", "/");
//3) 获取到我们要加载的类路径(工作路径: file:/D:/Exercise_items/hsp_spring/target/classes/com/zzti/spring/component )
//解决文件路径空格转变成了转义字符%20的问题
URL resource = classLoader.getResource(path);
System.out.println("resource= " + resource);
File file = new File(resource.getFile());
if (file.isDirectory()) {
File[] files = file.listFiles();
for (File f : files) {
String fileAbsolutePath = f.getAbsolutePath();
System.out.println("===============================");
System.out.println("文件绝对路径= " + fileAbsolutePath);
if (fileAbsolutePath.endsWith(".class")) {
//先得到类的完整类路径, 形式为: com.zzti,myspring.component.MonsterService
String className = fileAbsolutePath.substring(fileAbsolutePath.lastIndexOf("\\") + 1, fileAbsolutePath.indexOf(".class"));
String classFullPath = path.replace("/",".") + "." + className;
System.out.println("类名= " + className);
System.out.println("类的全路径= " + classFullPath);
//这里只处理.class文件
try {
Class<?> aClass = classLoader.loadClass(classFullPath);
if (aClass.isAnnotationPresent(Component.class)) {
//如果这个类有@Component注解,说明是一个bean
System.out.println("是一个bean= " + aClass);
}else {
//如果这个类没有@Component注解,说明不是一个bean
System.out.println("不是一个bean= " + aClass);
}
} catch (Exception e) {
e.printStackTrace();
}
System.out.println("===================================");
}
}
}
}
public Object getBean(String name){
return null;
}
}
package com.zzti.myspring;
import com.zzti.myspring.ioc.HspMySpringApplicationContext;
import com.zzti.myspring.ioc.HspMySpringConfig;
public class AppMain {
public static void main(String[] args) {
HspMySpringApplicationContext hspMySpringApplicationContext = new HspMySpringApplicationContext(HspMySpringConfig.class);
}
}
==============测试结果============
扫描路径path= com.zzti.myspring.component
resource= file:/D:/Exercise_items/hsp_spring/hsp_myspring/target/classes/com/zzti/myspring/component
===============================
文件绝对路径= D:\Exercise_items\hsp_spring\hsp_myspring\target\classes\com\zzti\myspring\component\MonsterDao.class
类名= MonsterDao
类的全路径= com.zzti.myspring.component.MonsterDao
是一个bean= class com.zzti.myspring.component.MonsterDao
===================================
===============================
文件绝对路径= D:\Exercise_items\hsp_spring\hsp_myspring\target\classes\com\zzti\myspring\component\MonsterService.class
类名= MonsterService
类的全路径= com.zzti.myspring.component.MonsterService
是一个bean= class com.zzti.myspring.component.MonsterService
===================================
2. 实现任务阶段2----扫描bean信息封装到BeanDefinition对象,并放入到Map
因为bean的作用域可能是singleton,也可能是prototype;所以Spring容器将扫描到的bean信息,保存到集合,这样当getBean() 根据实际情况来处理
//定义我们的Scope注解
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Scope {
//不需要给默认值
String value();
}
package com.zzti.myspring.ioc;
/**
* 在扫描时,将bean信息, 封装到BeanDefinition对象中
* 然后将BeanDefinition对象,放入到spring容器集合中
*/
public class BeanDefinition {
//bean对应的Class对象
private Class clazz;
//bean的作用域(scope/prototype)
private String scope;
public Class getClazz() {
return clazz;
}
public void setClazz(Class clazz) {
this.clazz = clazz;
}
public String getScope() {
return scope;
}
public void setScope(String scope) {
this.scope = scope;
}
@Override
public String toString() {
return "BeanDefinition{" +
"clazz=" + clazz +
", scope='" + scope + '\'' +
'}';
}
}
@Component("monsterService")
@Scope("prototype")
public class MonsterService {
}
package com.zzti.myspring.ioc;
import com.zzti.myspring.annotation.Component;
import com.zzti.myspring.annotation.ComponentScan;
import com.zzti.myspring.annotation.Scope;
import java.io.File;
import java.net.URL;
import java.util.concurrent.ConcurrentHashMap;
/**
* 这个类的作用类似, spring原生的ioc容器
* 1. 搭建基本结构并获取的扫描包
* 2. 获取扫描包下所有的.class文件
* 3. 获取全类名, 反射对象, 放入容器
*/
public class HspMySpringApplicationContext {
private Class configClass;
//如果这个bean是单例的,就直接放入这个单例的bean对象池
private ConcurrentHashMap<String,Object> singletonObjects = new ConcurrentHashMap<>();
//将bean的定义,放在这个集合
private ConcurrentHashMap<String,BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<>();
public HspMySpringApplicationContext(Class configClass){
//通过扫描,得到beanDefinition的map
beanDefinitionByscan(configClass);
System.out.println(beanDefinitionMap);
}
private void beanDefinitionByscan(Class configClass){
this.configClass = configClass;
//1. 解析配置类
//2. 获取到配置类的 @ComponentScan(value = "com.zzti.myspring.component")
ComponentScan componentScan = (ComponentScan)this.configClass.getDeclaredAnnotation(ComponentScan.class);
String path = componentScan.value();
//扫描路径path= com.zzti.myspring.component
System.out.println("扫描路径path= " + path);
//3. 获取扫描路径下所有的类文件
//1) 先得到类加载器
ClassLoader classLoader = HspMySpringApplicationContext.class.getClassLoader();
//在获取某个包对应的url时, 要求是com/zzti/myspring/component
//URL resource = classLoader.getResource("com/zzti/spring/component");
//System.out.println(resource);
//2) 将path转换成com/zzti/myspring/component形式
path = path.replace(".", "/");
//3) 获取到我们要加载的类路径(工作路径: file:/D:/Exercise_items/hsp_spring/target/classes/com/zzti/spring/component )
//解决文件路径空格转变成了转义字符%20的问题
URL resource = classLoader.getResource(path);
System.out.println("resource= " + resource);
File file = new File(resource.getFile());
if (file.isDirectory()) {
File[] files = file.listFiles();
for (File f : files) {
String fileAbsolutePath = f.getAbsolutePath();
System.out.println("===============================");
System.out.println("文件绝对路径= " + fileAbsolutePath);
if (fileAbsolutePath.endsWith(".class")) {
//先得到类的完整类路径, 形式为: com.zzti,myspring.component.MonsterService
String className = fileAbsolutePath.substring(fileAbsolutePath.lastIndexOf("\\") + 1, fileAbsolutePath.indexOf(".class"));
String classFullPath = path.replace("/",".") + "." + className;
System.out.println("类名= " + className);
System.out.println("类的全路径= " + classFullPath);
//这里只处理.class文件
try {
Class<?> aClass = classLoader.loadClass(classFullPath);
if (aClass.isAnnotationPresent(Component.class)) {
//如果这个类有@Component注解,说明是一个bean
System.out.println("是一个bean= " + aClass);
//这里不能直接将bean实例放入singletonObjects,
//原因是若bean是prototype,则需要每次创建新的bean对象;
// 所以Spring底层是将bean信息封装到BeanDefinition对象中,便于getBean的操作
BeanDefinition beanDefinition = new BeanDefinition();
beanDefinition.setClazz(aClass);
//获取bean的name
Component componentAnnotation = aClass.getDeclaredAnnotation(Component.class);
String beanName = componentAnnotation.value();
//获取bean的scope
if (aClass.isAnnotationPresent(Scope.class)) {
//如果bean类上面有@Scope注解
Scope scopeAnnotation = aClass.getDeclaredAnnotation(Scope.class);
beanDefinition.setScope(scopeAnnotation.value());
}else {
//如果bean类上面没有@Scope注解,默认是singleton
beanDefinition.setScope("singleton");
}
//放入到beanDefinitionMap中
beanDefinitionMap.put(beanName,beanDefinition);
}else {
//如果这个类没有@Component注解,说明不是一个bean
System.out.println("不是一个bean= " + aClass);
}
} catch (Exception e) {
e.printStackTrace();
}
System.out.println("===================================");
}
}
}
}
public Object getBean(String name){
return null;
}
}
测试结果如下:
扫描路径path= com.zzti.myspring.component
resource= file:/D:/Exercise_items/hsp_spring/hsp_myspring/target/classes/com/zzti/myspring/component
===============================
文件绝对路径= D:\Exercise_items\hsp_spring\hsp_myspring\target\classes\com\zzti\myspring\component\Car.class
类名= Car
类的全路径= com.zzti.myspring.component.Car
不是一个bean= class com.zzti.myspring.component.Car
===================================
===============================
文件绝对路径= D:\Exercise_items\hsp_spring\hsp_myspring\target\classes\com\zzti\myspring\component\MonsterDao.class
类名= MonsterDao
类的全路径= com.zzti.myspring.component.MonsterDao
是一个bean= class com.zzti.myspring.component.MonsterDao
===================================
===============================
文件绝对路径= D:\Exercise_items\hsp_spring\hsp_myspring\target\classes\com\zzti\myspring\component\MonsterService.class
类名= MonsterService
类的全路径= com.zzti.myspring.component.MonsterService
是一个bean= class com.zzti.myspring.component.MonsterService
===================================
{monsterService=BeanDefinition{clazz=class com.zzti.myspring.component.MonsterService, scope='prototype'}, monsterDao=BeanDefinition{clazz=class com.zzti.myspring.component.MonsterDao, scope='singleton'}}
3. 实现任务阶段3----初始化bean单例池,并完成getBean方法,createBean方法
初始化单例池,也就是若bean是单例的就实例化,并放入到单例池Map中。
单例池bean map集合是如何保存的 (key[beanName]–value[单例bean对象])
public HspMySpringApplicationContext(Class configClass){
//通过扫描,得到beanDefinition的map
beanDefinitionByscan(configClass);
System.out.println(beanDefinitionMap);
//通过beanDefinitionMap,初始化singletonObjects单例池
Enumeration<String> keys = beanDefinitionMap.keys();
while (keys.hasMoreElements()) {
//得到beanName
String beanName = keys.nextElement();
//通过beanName得到beanDefinition
BeanDefinition beanDefinition = beanDefinitionMap.get(beanName);
if ("singleton".equalsIgnoreCase(beanDefinition.getScope())) {
//将该bean实例放入singletonObjects
Object bean = createBean(beanDefinition);
singletonObjects.put(beanName,bean);
}
}
System.out.println("singletonObjects 单例池= " + singletonObjects);
}
//创建bean
public Object createBean(BeanDefinition beanDefinition){
//得到bean的类型
Class clazz = beanDefinition.getClazz();
Object instance = null;
try {
//使用反射得到实例
instance = clazz.getDeclaredConstructor().newInstance();
return instance;
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
}
//若没有创建成功,返回null
return null;
}
//得到bean
public Object getBean(String name){
if (beanDefinitionMap.containsKey(name)) {
BeanDefinition beanDefinition = beanDefinitionMap.get(name);
//得到bean的scope,分别处理
if ("singleton".equalsIgnoreCase(beanDefinition.getScope())) {
//单例,直接从bean单例池中获取
return singletonObjects.get(name);
}else {
//不是单例, 则没有返回新的实例
return createBean(beanDefinition);
}
}else {
throw new NullPointerException("没有该bean");
}
}
public class AppMain {
public static void main(String[] args) {
HspMySpringApplicationContext hspMySpringApplicationContext = new HspMySpringApplicationContext(HspMySpringConfig.class);
//通过spring容器对象,获取bean对象
MonsterService monsterService1 = (MonsterService)hspMySpringApplicationContext.getBean("monsterService");
MonsterService monsterService2 = (MonsterService)hspMySpringApplicationContext.getBean("monsterService");
//MonsterService monsterService4 = (MonsterService)hspMySpringApplicationContext.getBean("xxxService");
System.out.println("monsterService1= " + monsterService1);
System.out.println("monsterService2= " + monsterService2);
//System.out.println("monsterService4= " + monsterService4);//java.lang.NullPointerException: 没有该bean
MonsterDao monsterDao = (MonsterDao)hspMySpringApplicationContext.getBean("monsterDao");
MonsterDao monsterDao1 = (MonsterDao)hspMySpringApplicationContext.getBean("monsterDao");
System.out.println("monsterDao= " + monsterDao);
System.out.println("monsterDao1= " + monsterDao1);
}
}
===============测试结果=================
{monsterService=BeanDefinition{clazz=class com.zzti.myspring.component.MonsterService, scope='prototype'}, monsterDao=BeanDefinition{clazz=class com.zzti.myspring.component.MonsterDao, scope='singleton'}}
singletonObjects 单例池= {monsterDao=com.zzti.myspring.component.MonsterDao@6f79caec}
monsterService1= com.zzti.myspring.component.MonsterService@5d3411d
monsterService2= com.zzti.myspring.component.MonsterService@2471cca7
monsterDao= com.zzti.myspring.component.MonsterDao@6f79caec
monsterDao1= com.zzti.myspring.component.MonsterDao@6f79caec
4. 实现任务阶段4----完成依赖注入
@Target({ElementType.METHOD,ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface Autowired {
//String required() default "true";
}
@Component("monsterDao")
public class MonsterDao {
public void hi(){
System.out.println("hi 我是monsterDao, select * from table....");
}
}
@Component("monsterService")
@Scope("prototype")
public class MonsterService {
@Autowired
private MonsterDao monsterDao;
public void m1(){
monsterDao.hi();
}
}
//创建bean
public Object createBean(BeanDefinition beanDefinition){
//得到bean的类型
Class clazz = beanDefinition.getClazz();
Object instance = null;
try {
//使用反射得到实例
instance = clazz.getDeclaredConstructor().newInstance();
//完成依赖注入
for (Field declaredField : clazz.getDeclaredFields()) {
if (declaredField.isAnnotationPresent(Autowired.class)) {
//如果该属性有@Autowired,就进行组装
Object bean = getBean(declaredField.getName());
declaredField.setAccessible(true);//因为属性是private,需要爆破
declaredField.set(instance,bean);
}
}
return instance;
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
}
//若没有创建成功,返回null
return null;
}
//测试一个依赖注入
MonsterService monsterService = (MonsterService)hspMySpringApplicationContext.getBean("monsterService");
monsterService.m1();
===================测试结果=========================
singletonObjects 单例池= {monsterDao=com.zzti.myspring.component.MonsterDao@6f79caec}
hi 我是monsterDao, select * from table....
5. 实现任务阶段5----bean后置处理器实现
/**
* 1. 根据原生Spring定义了一个InitializingBean接口
* 2. 该InitializingBean接口有一个方法 void afterPropertiesSet() throws Exception
* 3. 当一个bean实现这个接口后, 就实现afterPropertiesSet(),这个方法就是初始化方法
*/
public interface InitializingBean {
void afterPropertiesSet() throws Exception;
}
@Component("monsterService")
@Scope("prototype")
public class MonsterService implements InitializingBean{
@Autowired
private MonsterDao monsterDao;
public void m1(){
monsterDao.hi();
}
/**
* afterPropertiesSet(),就是在bean的setter()执行完之后,被spring容器调用
* @throws Exception
*/
@Override
public void afterPropertiesSet() throws Exception {
System.out.println("MonsterService 初始化方法被调用 程序员在这里加入初始化业务逻辑");
}
}
//创建bean
public Object createBean(BeanDefinition beanDefinition){
//得到bean的类型
Class clazz = beanDefinition.getClazz();
Object instance = null;
try {
//使用反射得到实例
instance = clazz.getDeclaredConstructor().newInstance();
//完成依赖注入
for (Field declaredField : clazz.getDeclaredFields()) {
if (declaredField.isAnnotationPresent(Autowired.class)) {
//如果该属性有@Autowired,就进行组装
Object bean = getBean(declaredField.getName());
declaredField.setAccessible(true);//因为属性是private,需要爆破
declaredField.set(instance,bean);
}
}
System.out.println("==========创建好了==========" + instance);
//这里调用初始化,如果bean实现了InitializingBean
if (instance instanceof InitializingBean) {
try {
((InitializingBean)instance).afterPropertiesSet();
} catch (Exception e) {
e.printStackTrace();
}
}
return instance;
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
}
//若没有创建成功,返回null
return null;
}
============测试结果(若bean实现了InitializingBean,就会在创建好后,调用它的初始化方法)==============
singletonObjects 单例池= {=com.zzti.myspring.component.HspBeanPostProcessor@78308db1, monsterDao=com.zzti.myspring.component.MonsterDao@5451c3a8}
==========创建好了==========com.zzti.myspring.component.MonsterService@2c7b84de
MonsterService 初始化方法被调用 程序员在这里加入初始化业务逻辑
hi 我是monsterDao, select * from table....
/**
* 参考原生Spring容器定义一个接口BeanPostProcessor
* 这个接口有两个方法
* 这两个方法,会对Spring容器的所有Bean生效,已经是切面编程的概念
*/
public interface BeanPostProcessor {
/**
* 1. postProcessBeforeInitialization在bean的初始化方法前调用
* @param bean
* @param beanName
* @return
*/
default Object postProcessBeforeInitialization(Object bean,String beanName){
return bean;
}
/**
* 1. postProcessAfterInitialization在bean的初始化方法后调用
* @param bean
* @param beanName
* @return
*/
default Object postProcessAfterInitialization(Object bean,String beanName){
return bean;
}
}
package com.zzti.myspring.component;
import com.zzti.myspring.annotation.Component;
import com.zzti.myspring.processor.BeanPostProcessor;
/**
* 1. 这是我们自己的一个后置处理器
* 2. 实现了BeanPostProcessor,我们可以重写before和after方法
* 3. 在spring容器中,仍然把HspBeanPostProcessor当做一个bean对象, 要注入到容器中,
* 所以要用@Component标识一下
* 4. 我们要让HspBeanPostProcessor成为真正的后置处理器,
* 需要在容器中加入业务代码,还要考虑多个后置处理器注入到容器的问题
*/
@Component
public class HspBeanPostProcessor implements BeanPostProcessor {
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) {
System.out.println("后置处理器HspBeanPostProcessor Before被调用, bean类型= "
+ bean.getClass() + ",bean的名字= " + beanName);
return bean;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) {
System.out.println("后置处理器HspBeanPostProcessor After被调用, bean类型= "
+ bean.getClass() + ",bean的名字= " + beanName);
return bean;
}
}
package com.zzti.myspring.ioc;
public class HspMySpringApplicationContext {
//存放后置处理器,放在List中
private List<BeanPostProcessor> beanPostProcessorList = new ArrayList<>();
public HspMySpringApplicationContext(Class configClass){
//通过扫描,得到beanDefinition的map
beanDefinitionByscan(configClass);
System.out.println(beanDefinitionMap);
//通过beanDefinitionMap,初始化singletonObjects单例池
Enumeration<String> keys = beanDefinitionMap.keys();
while (keys.hasMoreElements()) {
//得到beanName
String beanName = keys.nextElement();
//通过beanName得到beanDefinition
BeanDefinition beanDefinition = beanDefinitionMap.get(beanName);
if ("singleton".equalsIgnoreCase(beanDefinition.getScope())) {
//将该bean实例放入singletonObjects
Object bean = createBean(beanName,beanDefinition);
singletonObjects.put(beanName,bean);
}
}
System.out.println("singletonObjects 单例池= " + singletonObjects);
}
private void beanDefinitionByscan(Class configClass){
this.configClass = configClass;
//1. 解析配置类
//2. 获取到配置类的 @ComponentScan(value = "com.zzti.myspring.component")
ComponentScan componentScan = (ComponentScan)this.configClass.getDeclaredAnnotation(ComponentScan.class);
String path = componentScan.value();
//扫描路径path= com.zzti.myspring.component
System.out.println("扫描路径path= " + path);
//3. 获取扫描路径下所有的类文件
//1) 先得到类加载器
ClassLoader classLoader = HspMySpringApplicationContext.class.getClassLoader();
//在获取某个包对应的url时, 要求是com/zzti/myspring/component
//URL resource = classLoader.getResource("com/zzti/spring/component");
//System.out.println(resource);
//2) 将path转换成com/zzti/myspring/component形式
path = path.replace(".", "/");
//3) 获取到我们要加载的类路径(工作路径: file:/D:/Exercise_items/hsp_spring/target/classes/com/zzti/spring/component )
//解决文件路径空格转变成了转义字符%20的问题
URL resource = classLoader.getResource(path);
System.out.println("resource= " + resource);
File file = new File(resource.getFile());
if (file.isDirectory()) {
File[] files = file.listFiles();
for (File f : files) {
String fileAbsolutePath = f.getAbsolutePath();
System.out.println("===============================");
System.out.println("文件绝对路径= " + fileAbsolutePath);
if (fileAbsolutePath.endsWith(".class")) {
//先得到类的完整类路径, 形式为: com.zzti,myspring.component.MonsterService
String className = fileAbsolutePath.substring(fileAbsolutePath.lastIndexOf("\\") + 1, fileAbsolutePath.indexOf(".class"));
String classFullPath = path.replace("/",".") + "." + className;
System.out.println("类名= " + className);
System.out.println("类的全路径= " + classFullPath);
//这里只处理.class文件
try {
Class<?> aClass = classLoader.loadClass(classFullPath);
if (aClass.isAnnotationPresent(Component.class)) {
//如果这个类有@Component注解,说明是一个bean
System.out.println("是一个bean= " + aClass);
//增加一个逻辑,如果这个clazz类型是实现了BeanPostProcessor接口,说明是一个bean处理器,特殊处理
//因为aClass并不是一个对象实例,不能用instanceof来判断aClass是不是BeanPostProcessor
if (BeanPostProcessor.class.isAssignableFrom(aClass)) {
//创建一个实例对象
BeanPostProcessor beanPostProcessor = (BeanPostProcessor)aClass.newInstance();
//放入到beanPostProcessorList
beanPostProcessorList.add(beanPostProcessor);
continue;
}
//这里不能直接将bean实例放入singletonObjects,
//原因是若bean是prototype,则需要每次创建新的bean对象;
// 所以Spring底层是将bean信息封装到BeanDefinition对象中,便于getBean的操作
BeanDefinition beanDefinition = new BeanDefinition();
beanDefinition.setClazz(aClass);
//获取bean的name
Component componentAnnotation = aClass.getDeclaredAnnotation(Component.class);
String beanName = componentAnnotation.value();
//获取bean的scope
if (aClass.isAnnotationPresent(Scope.class)) {
//如果bean类上面有@Scope注解
Scope scopeAnnotation = aClass.getDeclaredAnnotation(Scope.class);
beanDefinition.setScope(scopeAnnotation.value());
}else {
//如果bean类上面没有@Scope注解,默认是singleton
beanDefinition.setScope("singleton");
}
//放入到beanDefinitionMap中
beanDefinitionMap.put(beanName,beanDefinition);
}else {
//如果这个类没有@Component注解,说明不是一个bean
System.out.println("不是一个bean= " + aClass);
}
} catch (Exception e) {
e.printStackTrace();
}
System.out.println("===================================");
}
}
}
}
//创建bean
public Object createBean(String beanName,BeanDefinition beanDefinition){
//得到bean的类型
Class clazz = beanDefinition.getClazz();
Object instance = null;
try {
//使用反射得到实例
instance = clazz.getDeclaredConstructor().newInstance();
//完成依赖注入
for (Field declaredField : clazz.getDeclaredFields()) {
if (declaredField.isAnnotationPresent(Autowired.class)) {
//如果该属性有@Autowired,就进行组装
Object bean = getBean(declaredField.getName());
declaredField.setAccessible(true);//因为属性是private,需要爆破
declaredField.set(instance,bean);
}
}
System.out.println("==========创建好了==========" + instance);
//这里调用初始化,如果bean实现了InitializingBean
if (instance instanceof InitializingBean) {
try {
((InitializingBean)instance).afterPropertiesSet();
} catch (Exception e) {
e.printStackTrace();
}
}
//在bean初始化后调用所有bean处理器的postProcessAfterInitialization,在调用时,不能保证顺序
//如果希望指定对象短些bean进行初始化后处理,可以在处理器的postProcessAfterInitialization加入相关的业务处理
for (BeanPostProcessor beanPostProcessor : beanPostProcessorList) {
instance = beanPostProcessor.postProcessAfterInitialization(instance, beanName);
}
return instance;
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
}
//若没有创建成功,返回null
return null;
}
//得到bean
public Object getBean(String name){
if (beanDefinitionMap.containsKey(name)) {
BeanDefinition beanDefinition = beanDefinitionMap.get(name);
//得到bean的scope,分别处理
if ("singleton".equalsIgnoreCase(beanDefinition.getScope())) {
//单例,直接从bean单例池中获取
return singletonObjects.get(name);
}else {
//不是单例, 则没有返回新的实例
return createBean(name,beanDefinition);
}
}else {
throw new NullPointerException("没有该bean");
}
}
}
=============测试结果============================
==========创建好了==========com.zzti.myspring.component.MonsterDao@78308db1
后置处理器HspBeanPostProcessor After被调用, bean类型= class com.zzti.myspring.component.MonsterDao,bean的名字= monsterDao
singletonObjects 单例池= {monsterDao=com.zzti.myspring.component.MonsterDao@78308db1}
==========创建好了==========com.zzti.myspring.component.MonsterService@5a07e868
MonsterService 初始化方法被调用 程序员在这里加入初始化业务逻辑
后置处理器HspBeanPostProcessor After被调用, bean类型= class com.zzti.myspring.component.MonsterService,bean的名字= monsterService
6. 实现任务阶段6----AOP机制实现
以下是没有使用注解的方式实现的,也可以使用注解,更简洁点。
public interface SmartAnimal {
public float getSum(float i,float j);
public float getSub(float i,float j);
}
@Component("smartDog")
public class SmartDog implements SmartAnimal {
@Override
public float getSum(float i, float j) {
//System.out.println("日志-方法名-getSum()开始-参数: " + i + "," + j);
float result = i + j;
//System.out.println("方法内部打印: result= " + result);
//System.out.println("日志-方法名-getSum()结束-结果: result= " + result);
return result;
}
@Override
public float getSub(float i, float j) {
//System.out.println("日志-方法名-getSub()开始-参数: " + i + "," + j);
float result = i - j;
//System.out.println("方法内部打印: result= " + result);
//System.out.println("日志-方法名-getSub()结束-结果: result= " + result);
return result;
}
}
public class SmartAnimalAspect {
public static void showBeginLog(){
System.out.println("======前置通知======");
}
public static void showSuccessEndLog(){
System.out.println("======返回通知======");
}
}
@Component
public class HspBeanPostProcessor implements BeanPostProcessor {
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) {
System.out.println("后置处理器HspBeanPostProcessor Before被调用, bean类型= "
+ bean.getClass() + ",bean的名字= " + beanName);
return bean;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) {
System.out.println("后置处理器HspBeanPostProcessor After被调用, bean类型= "
+ bean.getClass() + ",bean的名字= " + beanName);
//根据情况返回代理对象,还是bean原生对象
//实现AOP,返回代理对象,即对bean对象进行包装,不在返回原生的bean
if ("smartDog".equals(beanName)) {
//使用动态代理,代理bean,返回的代理对象
Object proxyInstance = Proxy.newProxyInstance(HspBeanPostProcessor.class.getClassLoader(), bean.getClass().getInterfaces(), new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("method= " + method.getName());
Object result = null;
if ("getSum".equals(method.getName())) {
SmartAnimalAspect.showBeginLog();
result = method.invoke(bean,args);
SmartAnimalAspect.showSuccessEndLog();
}else {
result = method.invoke(bean,args);
}
return result;
}
});
return proxyInstance;
}
//不需要实现AOP,就返回原生的bean
return bean;
}
}
//测试AOP机制
MonsterService monsterService = (MonsterService)hspMySpringApplicationContext.getBean("monsterService");
SmartAnimal smartDog = (SmartAnimal)hspMySpringApplicationContext.getBean("smartDog");
smartDog.getSum(10,20);
System.out.println("-----------分割getSum与getSub--------------");
smartDog.getSub(10,20);
================测试结果===================
method= getSum
======前置通知======
======返回通知======
-----------分割getSum与getSub--------------
method= getSub
JdbcTemplate
Spring提供了一个操作数据库(表)功能强大的类JdbcTemplate。我们可以用ioc容器来配置一个jdbcTemplate对象,使用它来完成对数据库表的各种操作。
jdbc.properties
jdbc.user=root
jdbc.password=root
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/spring?useUnicode=true&characterEncoding=utf-8
jdbc_ioc.xml
<!--引入外部的jdbc.properties-->
<context:property-placeholder location="classpath:jdbc.properties"/>
<!--配置数据源对象-DataSource-->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<!--给数据源对象配置属性值-->
<property name="user" value="${jdbc.user}"/>
<property name="password" value="${jdbc.password}"/>
<property name="driverClass" value="${jdbc.driver}"/>
<property name="jdbcUrl" value="${jdbc.url}"/>
</bean>
<!--配置JdbcTemplate对象-->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<!--给jdbcTemplate对象配置dataSource-->
<property name="dataSource" ref="dataSource"/>
</bean>
@Test
public void testDataSourceByJdbcTemplate(){
ApplicationContext ioc = new ClassPathXmlApplicationContext("jdbc_ioc.xml");
DataSource dataSource = ioc.getBean(DataSource.class);
Connection connection = null;
try {
connection = dataSource.getConnection();
} catch (SQLException e) {
e.printStackTrace();
}
//获取到connection= com.mchange.v2.c3p0.impl.NewProxyConnection@63a65a25
System.out.println("获取到connection= " + connection);
System.out.println("ok~~");
}
添加一个monster
@Test
public void addDataByJdbcTemplate() {
ApplicationContext ioc = new ClassPathXmlApplicationContext("jdbc_ioc.xml");
JdbcTemplate jdbcTemplate = ioc.getBean(JdbcTemplate.class);
//添加一个monster----添加方式1
//String sql = "insert into monster values(400,'红孩儿','三味真火')";
//jdbcTemplate.execute(sql);
//System.out.println("插入成功~~");
//添加一个monster----添加方式2--推荐使用
String sql = "insert into monster values(?,?,?)";
int affected = jdbcTemplate.update(sql, 600, "红孩儿2", "三味真火2");
System.out.println("add ok~~ affected= " + affected);//affected表示执行后表受影响的记录数
//更新一个monster的skill
String sql2 = "update monster set skill=? where id=?";
int affected2 = jdbcTemplate.update(sql2, "美人计", 300);
System.out.println("update ok affected= " + affected2);
}
批量添加
//批量添加
@Test
public void addBatchDataByJdbcTemplate() {
ApplicationContext ioc = new ClassPathXmlApplicationContext("jdbc_ioc.xml");
JdbcTemplate jdbcTemplate = ioc.getBean(JdbcTemplate.class);
String sql = "insert into monster values(?,?,?)";
List<Object[]> param_list = new ArrayList<Object[]>();
param_list.add(new Object[]{400,"白蛇精","吃人"});
param_list.add(new Object[]{500,"青蛇精","吃小孩"});
jdbcTemplate.batchUpdate(sql,param_list);
System.out.println("bathData add ok~~~");
}
查询id=100的monster,并封装到Monster
//查询id=100的monster,并封装到Monster
@Test
public void selectDataByJdbcTemplate() {
ApplicationContext ioc = new ClassPathXmlApplicationContext("jdbc_ioc.xml");
JdbcTemplate jdbcTemplate = ioc.getBean(JdbcTemplate.class);
//String sql = "select id,name,skill from monster where id=?";
String sql = "select id as monsterId,name as monsterName,skill from monster where id=?";
//RowMapper是一个接口,可以将查询的结果,封装到指定的Monster对象中
RowMapper<Monster> rowMapper = new BeanPropertyRowMapper<>(Monster.class);
Monster monster = jdbcTemplate.queryForObject(sql, rowMapper, 100);
//monster= Monster{monsterId=null, monsterName='null', skill='吐火'}
/**
* 为什么查询出来的monsterId=null, monsterName='null'?
* 因为sql中的字段要和实体类中自己写的字段要对应上
*/
System.out.println("monster= " + monster);
}
查询id>=100的monster,并封装到Monster
//查询id>=100的monster,并封装到Monster
@Test
public void selectMulDataByJdbcTemplate() {
ApplicationContext ioc = new ClassPathXmlApplicationContext("jdbc_ioc.xml");
JdbcTemplate jdbcTemplate = ioc.getBean(JdbcTemplate.class);
String sql = "select id as monsterId,name as monsterName,skill from monster where id>=?";
RowMapper<Monster> rowMapper = new BeanPropertyRowMapper<>(Monster.class);
List<Monster> monsterList = jdbcTemplate.query(sql, rowMapper, 200);
for (Monster monster : monsterList) {
System.out.println("id>=?的monster= " + monster);
}
}
查询返回结果只有一行一列的值
//查询返回结果只有一行一列的值
@Test
public void selectScalarDataByJdbcTemplate() {
ApplicationContext ioc = new ClassPathXmlApplicationContext("jdbc_ioc.xml");
JdbcTemplate jdbcTemplate = ioc.getBean(JdbcTemplate.class);
String sql = "select name as monsterName from monster where id=500";
String monsterName = jdbcTemplate.queryForObject(sql, String.class);
System.out.println("monsterName= " + monsterName);
}
使用map传入具名参数完成操作
<!--配置JdbcTemplate对象-->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<!--给jdbcTemplate对象配置dataSource-->
<property name="dataSource" ref="dataSource"/>
</bean>
//使用map传入具名参数完成操作
@Test
public void testDataByNamedParameterJdbcTemplate() {
ApplicationContext ioc = new ClassPathXmlApplicationContext("jdbc_ioc.xml");
NamedParameterJdbcTemplate namedParameterJdbcTemplate = ioc.getBean(NamedParameterJdbcTemplate.class);
String sql = "insert into monster values(:id,:name,:skill)";
Map<String, Object> map = new HashMap<>();
map.put("id",600);
map.put("name","螃蟹精");
map.put("skill","钳子大法无敌");
namedParameterJdbcTemplate.update(sql,map);
System.out.println("namedParameterJdbcTemplate add ok~~");
}
使用SqlParameterSource来封装具名参数
//使用SqlParameterSource来封装具名参数,
@Test
public void testDataBySqlParameterSource() {
ApplicationContext ioc = new ClassPathXmlApplicationContext("jdbc_ioc.xml");
NamedParameterJdbcTemplate namedParameterJdbcTemplate = ioc.getBean(NamedParameterJdbcTemplate.class);
String sql = "insert into monster values(:monsterId,:monsterName,:skill)";
Monster monster = new Monster(900, "狐狸精", "狐媚之术");
BeanPropertySqlParameterSource source = new BeanPropertySqlParameterSource(monster);
namedParameterJdbcTemplate.update(sql,source);
System.out.println("SqlParameterSource add ok~~");
}
Dao对象中使用jdbcTemplate,完成对数据的操作
<!--配置自动扫描包-->
<context:component-scan base-package="com.zzti.spring.jdbc.dao"/>
@Repository
public class MonsterDao {
@Autowired
private JdbcTemplate jdbcTemplate;
public void save(Monster monster){
String sql = "insert into monster values(?,?,?)";
jdbcTemplate.update(sql,monster.getMonsterId(),monster.getMonsterName(),monster.getSkill());
}
}
//Dao对象中使用jdbcTemplate,完成对数据的操作
@Test
public void testDataByDao() {
ApplicationContext ioc = new ClassPathXmlApplicationContext("jdbc_ioc.xml");
MonsterDao monsterDao = ioc.getBean(MonsterDao.class);
Monster monster = new Monster(800, "大虾精", "吃虾米");
monsterDao.save(monster);
System.out.println("testDataByDao add ok~~");
}
四. 声明式事务
- 传统方式(容易理解,但代码冗余,效率低,不利于扩展)
Connection connection = JdbcUtils.getConnection();
try{
// 1.先设置事务不要自动提交
connection.setAutoCommint(false);
//2.进行各种crud
//多个表的修改, 添加, 删除
//3. 提交
connection.commit();
}catch(Exception e){
//4. 回滚
connection rollback();
}
- 声明式事务
需求说明–用户购买商品
需求分析:
- 通过商品id获取价格 购买商品(某人购买商品,
- 修改用户的余额)
- 修改库存存量
- 事务处理
Connection connection = JdbcUtils.getConnection();
try{
// 1.先设置事务不要自动提交
connection.setAutoCommint(false);
//2.进行各种crud
//多个表的修改, 添加, 删除
select from 商品表 ===> 获取价格
修改用户余额
修改库存量
//3. 提交
connection.commit();
}catch(Exception e){
//4. 回滚
connection rollback();
}
Spring的声明式事务处理,可以将上面三个步骤分别写成一个方法,然后统一管理[底层使用AOP(动态代理+动态绑定+反射+注解)]
1. 先完成对GoodsDao
创建三张表account(id,name,money)、goods(id,name,price)、amount(id,num)
tx_ioc.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd">
<!--配置自动扫描包-->
<context:component-scan base-package="com.zzti.spring.tx.dao"/>
<!--开启基于注解的声明式事务功能-->
<tx:annotation-driven transaction-manager="dataSourceTransactionManager"/>
<!--引入外部的jdbc.properties-->
<context:property-placeholder location="classpath:jdbc.properties"/>
<!--配置数据源对象-DataSource-->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<!--给数据源对象配置属性值-->
<property name="user" value="${jdbc.user}"/>
<property name="password" value="${jdbc.password}"/>
<property name="driverClass" value="${jdbc.driver}"/>
<property name="jdbcUrl" value="${jdbc.url}"/>
</bean>
<!--配置JdbcTemplate对象-->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<!--给jdbcTemplate对象配置dataSource-->
<property name="dataSource" ref="dataSource"/>
</bean>
<!--配置事务管理器-->
<bean id="dataSourceTransactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
</beans>
GoodsDao.java
package com.zzti.spring.tx.dao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;
@Repository
public class GoodsDao {
@Autowired
private JdbcTemplate jdbcTemplate;
//根据id查询价格
/**
*
* @param id 商品id
* @return
*/
public Float queryPriceById(Integer id){
String sql = "select price from goods where id=?";
Float price = jdbcTemplate.queryForObject(sql, Float.class, id);
return price;
}
//修改用户余额
/**
*
* @param id 用户id
* @param money 用户余额
*/
public void updateBalance(Integer id,Float money){
String sql = "update account set money=money-? where id=?";
jdbcTemplate.update(sql,money,id);
}
//修改库存量
/**
*
* @param id 商品id
* @param amount 商品库存
*/
public void updateAmount(Integer id, int amount){
String sql = "update amount set num=num-? where id=?";
jdbcTemplate.update(sql,amount,id);
}
}
//完成对GoodsDao的测试
//1. 查询商品价格
@Test
public void queryPriceByIdTest(){
ApplicationContext ioc = new ClassPathXmlApplicationContext("tx_ioc.xml");
GoodsDao bean = ioc.getBean(GoodsDao.class);
System.out.println("=======查询价格======");
Float priceById = bean.queryPriceById(1);
System.out.println("商品的价格是= " + priceById);
}
//2. 修改用户余额(购买商品后)
@Test
public void updateBalanceTest(){
ApplicationContext ioc = new ClassPathXmlApplicationContext("tx_ioc.xml");
GoodsDao bean = ioc.getBean(GoodsDao.class);
System.out.println("=======修改用户余额(购买商品后)======");
bean.updateBalance(1,100f);
System.out.println("修改余额成功~~");
}
//3. 修改商品的库存量
@Test
public void updateAmountTest(){
ApplicationContext ioc = new ClassPathXmlApplicationContext("tx_ioc.xml");
GoodsDao bean = ioc.getBean(GoodsDao.class);
System.out.println("=======修改商品的库存量======");
bean.updateAmount(1,30);
System.out.println("修改商品的库存量 成功~~");
}
2. 验证不使用事务就会出现数据不一致现象
正常情况下: 购买完商品,余额和库存量都会减少
GoodsService.java
/**
* 验证不使用事务就会出现数据不一致现象
*/
@Service
public class GoodsService {
@Autowired
private GoodsDao goodsDao;
public void buyGoods(int user_id,int goods_id,int num){
//得到商品价格
Float goods_price = goodsDao.queryPriceById(goods_id);
//减去价格,得到余额
goodsDao.updateBalance(user_id,goods_price * num);
//模拟一个异常,就会发现数据不一致问题
//int i = 10/0;
//更新库存
goodsDao.updateAmount(goods_id,num);
}
}
@Test
public void buyGoodsTest(){
ApplicationContext ioc = new ClassPathXmlApplicationContext("tx_ioc.xml");
GoodsService bean = ioc.getBean(GoodsService.class);
bean.buyGoods(1,1,1);
System.out.println("购买商品成功,没有加入事务~~");
}
非正常情况下:
- 故意将updateAmount中的SQL语句写错,会出现余额会继续减少,但是库存量不会减少的情况。
public void updateAmount(Integer goods_id, int goods_num){
String sql = "updatex goods_amount set goods_num=goods_num-? where goods_id=?";
jdbcTemplate.update(sql,goods_num,goods_id);
}
- 为了解决这种情况,我们使用声明式事务
<!--配置事务管理器(一定要配置数据源属性,指定对那个数据源进行事务管理控制)-->
<bean id="dataSourceTransactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<!--开启基于注解的声明式事务功能-->
<tx:annotation-driven transaction-manager="dataSourceTransactionManager"/>
在GoodsService.java中增加一个方法
/**
* 加入声明式事务注解, 默认是传播属性: REQUIRED
* 1. 标识方法中的, 对数据库的操作作为一个事务管理,
* 也是AOP机制,底层是动态代理来调用buyGoodsByTx
* 2.在执行buyGoodsByTx()前, 先调用事务管理器的doBegin(),再调用buyGoodsByTx()
* 若没有发生异常,则调用事务管理器的doCommit()
* 若发生异常,就调用管理管理器的doRollback()
* @param user_id
* @param goods_id
* @param num
*/
@Transactional
public void buyGoodsByTx(int user_id,int goods_id,int num){
Float goods_price = goodsDao.queryPriceById(goods_id);
goodsDao.updateBalance(user_id,goods_price * num);
goodsDao.updateAmount(goods_id,num);
}
//加入声明式事务
@Test
public void buyGoodsTestByTx(){
ApplicationContext ioc = new ClassPathXmlApplicationContext("tx_ioc.xml");
GoodsService bean = ioc.getBean(GoodsService.class);
bean.buyGoodsByTx(1,2,1);
System.out.println("~~购买商品成功~~");
}
这样就会包异常说SQL语句写错,但是余额和库存量都不会减少了。
五. 事务的传播机制
- 当有几个事务处理并存时,如何控制?
- 比如用户去购买两次商品(使用不同的方法), 每个方法都是一个事务,该如何控制?
主要分析REQUIRED
和REQUIRED_NEW
这两种事务,处理事务的策略如下:
@Transactional(本身就是一个事务)
public void multiTxTest(){
ApplicationContext ioc = new ClassPathXmlApplicationContext("tx_ioc.xml");
GoodsService goodsService = ioc.getBean(GoodsService.class);
goodsService.buyGoods(1,1,1);(一个事务)
goodsService.buyGoods2(2,2,2);(又一个事务)
}
如果设置为REQUIRED_NEW
,buyGoods2()错误,不会影响到 buyGoods(),反之亦然。即它们的事务是独立的。
如果设置为REQUIRED,buyGoods2() 和 buyGoods() 是一个整体,只要有方法的事务错误,那么两个方法都不会执行。
应用实例:
在GoodsDao.java中加入下面的方法,其中updateBalance2的SQL语句故意写错
public Float queryPriceById2(Integer goods_id){
String sql = "select price from goods where goods_id=?";
Float price = jdbcTemplate.queryForObject(sql, Float.class, goods_id);
return price;
}
public void updateBalance2(Integer account_id,Float money){
String sql = "updatex account set money=money-? where account_id=?";
jdbcTemplate.update(sql,money,account_id);
}
public void updateAmount2(Integer goods_id, int goods_num){
String sql = "update goods_amount set goods_num=goods_num-? where goods_id=?";
jdbcTemplate.update(sql,goods_num,goods_id);
}
GoodsService.java
@Transactional
public void buyGoodsByTx(int user_id,int goods_id,int num){
Float goods_price = goodsDao.queryPriceById(goods_id);
goodsDao.updateBalance(user_id,goods_price * num);
goodsDao.updateAmount(goods_id,num);
}
//加入声明式事务注解, 默认是传播属性: REQUIRED
@Transactional
public void buyGoodsByTx02(int user_id,int goods_id,int num){
Float goods_price = goodsDao.queryPriceById2(goods_id);
goodsDao.updateBalance2(user_id,goods_price * num);
goodsDao.updateAmount2(goods_id,num);
}
MultiplyTxService.java
@Service
public class MultiplyTxService {
@Autowired
private GoodsService goodsService;
@Transactional
public void multiTxTest(){
goodsService.buyGoodsByTx(2,2,2);
goodsService.buyGoodsByTx02(1,1,1);
}
}
//测试事务的传播机制
@Test
public void BuyGoodsByMulTxTest(){
ApplicationContext ioc = new ClassPathXmlApplicationContext("tx_ioc.xml");
MultiplyTxService bean = ioc.getBean(MultiplyTxService.class);
bean.multiTxTest();
System.out.println("-------ok------");
}
因为@Transactional 默认是传播属性: REQUIRED,所以余额和库存量,都不会变化。
将传播机制修改为REQUIRED_NEW
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void buyGoodsByTx(int user_id,int goods_id,int num){
Float goods_price = goodsDao.queryPriceById(goods_id);
goodsDao.updateBalance(user_id,goods_price * num);
goodsDao.updateAmount(goods_id,num);
}
//加入声明式事务注解, 默认是传播属性: REQUIRED
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void buyGoodsByTx02(int user_id,int goods_id,int num){
Float goods_price = goodsDao.queryPriceById2(goods_id);
goodsDao.updateBalance2(user_id,goods_price * num);
goodsDao.updateAmount2(goods_id,num);
}
因为REQUIRED_NEW的事务机制是相互独立,所以buyGoodsByTx()中的事务会生效,buyGoodsByTx2()中的事务不会生效。
六. 事务的隔离级别
隔离级别说明
- 默认的隔离级别,就是mysql数据库默认的隔离级别,一般为Repeatable Read(可重读)
- 看源码可知 Isolation.DEFAULT是: use the default isolation level of the underying datastore
- 查看数据库默认的隔离级别 select @@global.tx_isolation
隔离级别设置和测试
- Repeatable Read(可重读)
//隔离级别,一般为Repeatable Read(可重读)
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void buyGoodsByTxISOLATION(int user_id,int goods_id,int num){
Float goods_price = goodsDao.queryPriceById(goods_id);
System.out.println("第一次读取的价格= " + goods_price);
//在此处下一个断点,执行到这里时,在SQLyog中修改对应商品的价格,然后看下第二次读取到的价格情况
//update spring.goods set price=100 where goods_id=1;在SQLyog中,默认是自动提交
goods_price = goodsDao.queryPriceById(goods_id);
System.out.println("第二次读取的价格= " + goods_price);
}
两次读取到的价格不会受到SQLyog修改影响
- Read Committed(读取提交内容)
@Transactional(propagation = Propagation.REQUIRES_NEW,isolation = Isolation.READ_COMMITTED)
public void buyGoodsByTxISOLATION(int user_id,int goods_id,int num){
Float goods_price = goodsDao.queryPriceById(goods_id);
System.out.println("第一次读取的价格= " + goods_price);
//在此处下一个断点,执行到这里时,在SQLyog中修改对应商品的价格,然后看下第二次读取到的价格情况
//update spring.goods set price=200 where goods_id=1;在SQLyog中,默认是自动提交
goods_price = goodsDao.queryPriceById(goods_id);
System.out.println("第二次读取的价格= " + goods_price);
}
两次读取到的价格会受到SQLyog修改影响
七. 事务的超时回滚
- 如果一个事务执行的时间超过某个时间限制,就让该事务回滚
- 可以通过设置事务超时回滚实现
@Transactional(propagation = Propagation.REQUIRES_NEW,isolation = Isolation.READ_COMMITTED,timeout = 2)
//超时回滚
@Transactional(timeout = 2)
public void buyGoodsByTxTimeout(int user_id,int goods_id,int num){
Float goods_price = goodsDao.queryPriceById(goods_id);
goodsDao.updateBalance(user_id,goods_price * num);
System.out.println("========超时 start=======");
try {
Thread.sleep(4000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("========超时 end=======");
goodsDao.updateAmount(goods_id,num);
}
会抛出异常,事务回滚,原来的操作将会撤销,Transaction timed out: deadline was Sat Jul 16 15:44:41 CST 2022
//测试超时回滚
@Test
public void BuyGoodsByTxTimeoutTest(){
ApplicationContext ioc = new ClassPathXmlApplicationContext("tx_ioc.xml");
GoodsService bean = ioc.getBean(GoodsService.class);
bean.buyGoodsByTxTimeout(1,1,1);
System.out.println("-------ok------");
}