代理模式、AOP、事务

一、代理模式

1:什么是代理模式
代理模式就是控制对象的访问,由代理类来实现接口去调用接口的实现类,就类似与 中介的模式
2:代理模式的分类
静态代理:
就是在程序运行前定义的方法就已经存在代理类里面,代理类和委托类的关系在运行前就已经确定了 ,从如下代码中我们就可以看出代理类也是在实现了IUseDao接口但是在这个代理里面实现的方法中添加了一层事务的处理。所以我们可以看出在IUserDaoImpl这个实现类之前接口就可实现类已经确定关系,只不过当这个实现类再次去调用接口中的方法,代理类里面会对调用之前和之后进行一些方法上的处理,但是在这个静态代理中我们发现这边生成了目标对象就是UserDaoProxy这个类,所以我们不建议使用静态代理,耦合比较严重。

public interface IUserDao {
	void save();
}
public class UserDao implements IUserDao {
	public void save() {
		System.out.println("已经保存数据...");
	}
}
// 代理类
public class UserDaoProxy implements IUserDao {
	private IUserDao target;

	public UserDaoProxy(IUserDao iuserDao) {
		this.target = iuserDao;
	}

	public void save() {
		System.out.println("开启事物...");
		target.save();
		System.out.println("关闭事物...");
	}

}

动态代理:
1:代理对象,不需要实现接口
2:代理对象的生成,是利用JDK的API,动态的在内存中构建代理对象(需要我们指定创建代理对象/目标对象实现的接口的类型)
一、JDK动态代理
原理,是根据类加载器和接口创建代理类
实现方式就是去实现InvocationHandler接口,使用InvocationHandler接口中调用处理器invoke()方法
在通过Proxy类根据业务对象的类文件(每个对象都有自己的类文件)和对象的实例创建动态代理类
获取业务对象的类文件是通过反射机制获取动态构造函数,通过构造函数获取代理类实例,如下代码:

// 每次生成动态代理类对象时,实现了InvocationHandler接口的调用处理器对象 
public class InvocationHandlerImpl implements InvocationHandler {
	private Object target;// 这其实业务实现类对象,用来调用具体的业务方法
	// 通过构造函数传入目标对象
	public InvocationHandlerImpl(Object target) {
		this.target = target;
	}

	public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
		Object result = null;
		System.out.println("调用开始处理");
		result = method.invoke(target, args);
		System.out.println("调用结束处理");
		return result;
	}

	public static void main(String[] args) throws NoSuchMethodException, SecurityException, InstantiationException,
			IllegalAccessException, IllegalArgumentException, InvocationTargetException {
		// 被代理对象
		IUserDao userDao = new UserDao();
		InvocationHandlerImpl invocationHandlerImpl = new InvocationHandlerImpl(userDao);
		// 获取类文件
		ClassLoader loader = userDao.getClass().getClassLoader();
		// 获取对象实例
		Class<?>[] interfaces = userDao.getClass().getInterfaces();
		// 主要装载器、一组接口及调用处理动态代理实例
		IUserDao newProxyInstance = (IUserDao) Proxy.newProxyInstance(loader, interfaces, invocationHandlerImpl);
		newProxyInstance.save();
	}
}

JDK动态代理是通过反射机制获取都目标对象的类文件和实例,通过Proxy.newProxyInstance创建动态代理对象
其中的缺点必须是面向接口,目标业务类必须实现InvocationHandler接口

二、CGLIB 动态代理
原理:利用asm开源包,对代理对象的class文件加载进来,通过修改其字节码文件生成子类来处理
CGLIB代理要实现MethodInterceptor接口,实现getInstance方法,要在方法内创建Enhancer对象来创建动态代理对象然后setSuperclass(代理的那个类,那个对象),在设置setCallback(目标对象类也就是你创建的实现MethodInterceptor这个要生成代理对象的类),然后用Enhancer。create进行创建。
如下代码

public class CglibProxy implements MethodInterceptor {
	private Object targetObject;
	// 这里的目标类型为Object,则可以接受任意一种参数作为被代理类,实现了动态代理
	public Object getInstance(Object target) {
		// 设置需要创建子类的类
		this.targetObject = target;
		Enhancer enhancer = new Enhancer();
		// 设置要代理的目标类
		enhancer.setSuperclass(target.getClass());
		// 设置目标对象要生成代理对象的类
		enhancer.setCallback(this);
		return enhancer.create();
	}

	public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
		System.out.println("开启事物");
		Object result = proxy.invoke(targetObject, args);
		System.out.println("关闭事物");
		// 返回代理对象
		return result;
	}
	public static void main(String[] args) {
		CglibProxy cglibProxy = new CglibProxy();
		// 直接创建目标生成代理对象的类调用使用Enhancer创建的代理对象即可。这样就创建生成代理对象
		UserDao userDao = (UserDao) cglibProxy.getInstance(new UserDao());
		userDao.save();
	}
}

两个动态代理的区别:
JDK动态代理是利用反射机制生成一个实现代理接口的匿名类方法要实现InvokerHandler接口来实现
CGLIB动态代理是利用asm开源包,通过修改其字节码文件生成子类来处理。其中执行的效率更高
如果目标对象实现了接口,默认情况下会采用JDK的动态代理实现AOP
如果目标对象实现了接口,可以强制使用CGLIB实现AOP
如果目标对象没有实现了接口,必须采用CGLIB库,spring会自动在JDK动态代理和CGLIB之间转换
JDK动态代理只能对实现了接口的类生成代理,而不能针对类 。
CGLIB是针对类实现代理,主要是对指定的类生成一个子类,覆盖其中的方法 。
因为是继承,所以该类或方法最好不要声明成final ,final可以阻止继承和多态

在这里插入图片描述
通过上述对静态代理和动态代理的描述上我们理解到,代理模式实际上就是类似于我们现实生活中的中介,我们买房子或者买产品不是直接去找厂家去拿货而是通过中间人去拿货,这样中间人可以为我们包装好或者处理一下问题。所以使用代理可以让我们在代理类上做一些逻辑上的操作,这个就是代理模式所以这个就映射出我们即将要学习的AOP编程

AOP编程

AOP不是一种技术而是一种思想,它是面向切面编程,它的应用场景主要在于,日志记录、性能统计、安全控制、事务处理、异常处理等,AOP的主要意图就是能将公共逻辑代码和业务代码解耦分离,,可以通过预编译的方式和运行期间动态代理实现不修改源代码的情况下给程序添加功能的一种技术。
我们再从另一种方式来去理解aop:
关注点:重复的代码就叫做关注点
切面:关注点形成的类,就叫做切面,
切入点:执行目标对象方法,可以通过切入点表达式,指定拦截哪些类的哪些方法;给指定的类在运行的时候植入切面类的代码

从概念上的理解上就是在AOP底层实际上采用的就是代理模式,就是在要执行的程序之前或者之后添加了一些公共的逻辑,这就是AOP的思想。

我们在程序中书写切面程序的时候我们现在常采用的方式就是注解的方式去实现AOP主要的注解包含以下:

@Aspect 这个注解就是声明当前类是一个切面
@Pointcut 这个注解就是声明切点,在这个注解中可以指定切入点的表达式
@Before("pointCut_()") 前置通知:目标方法之前去执行
@After后置通知目标方法之后去执行
@AfterReturning  返回后通知  执行方法结束前执行(如果执行方法有异常则不执行)
@AfterThrowing 异常通知:出现异常时候执行
@Around 环绕通知 环绕目标方法执行


@Component
@Aspect
public class Aop {

	@Pointcut("这里面可以采用写入切点表达式,根据那个类根据注解作为切缺点,也就是拦截哪些类的那些方法,给指定的类运行时植入切面类的代码")
	public void login(){
	}

	@Before("login()")
	public void begin() {
		System.out.println("前置通知");
	}

	@After("execution(* com.itmayiedu.service.UserService.add(..))")
	public void commit() {
		System.out.println("后置通知");
	}

	@AfterReturning("execution(* com.itmayiedu.service.UserService.add(..))")
	public void afterReturning() {
		System.out.println("运行通知");
	}

	@AfterThrowing("execution(* com.itmayiedu.service.UserService.add(..))")
	public void afterThrowing() {
		System.out.println("异常通知");
	}

	@Around("execution(* com.itmayiedu.service.UserService.add(..))")
	public void around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
       System.out.println("我是环绕通知-前");
       proceedingJoinPoint.proceed();
       System.out.println("我是环绕通知-后");
	}

}

// 通过切面控制redis的开启和关闭

@Aspect //确定该类是切面
@Component
public class RedisAspect {
    private Logger logger = LoggerFactory.getLogger(getClass());
    /**
     * 是否开启redis缓存  true开启   false关闭
     */
    @Value("${renren.redis.open: false}")
    private boolean open;
	// 在执行RedisUtils的类的之前之后去验证是否开启redis
    @Around("execution(* com.esua.esd.common.redis.RedisUtils.*(..))")
    public Object around(ProceedingJoinPoint point) throws Throwable {
        Object result = null;
        if(open){
            try{
                result = point.proceed();
            }catch (Exception e){
                logger.error("redis error", e);
                throw new RenException(ErrorCode.REDIS_ERROR);
            }
        }
        return result;
    }
}
// 通过切面自动收集日志
@Aspect
@Component  // 表示实例化到容器中
public class LogOperationAspect {
    @Autowired
    private SysLogOperationService sysLogOperationService;
	// 这个代表就是创建自定义的注解 就是在方法上标注自定义的注解,这个时候切面就会在方法执行的时候凡是方法有这个注解的都会被切面切入到
	// 并且可以通过 ProceedingJoinPoint  获取方法的参数等
    @Pointcut("@annotation(com.esua.esd.common.annotation.LogOperation)")
    public void logPointCut() {

    }

    @Around("logPointCut()")
    public Object around(ProceedingJoinPoint point) throws Throwable {
        long beginTime = System.currentTimeMillis();
        try {
            //执行方法
            Object result = point.proceed();

            //执行时长(毫秒)
            long time = System.currentTimeMillis() - beginTime;
            //保存日志
            saveLog(point, time, OperationStatusEnum.SUCCESS.value());

            return result;
        }catch(Exception e) {
            //执行时长(毫秒)
            long time = System.currentTimeMillis() - beginTime;
            //保存日志
            saveLog(point, time, OperationStatusEnum.FAIL.value());

            throw e;
        }
    }

不管采用注解还是直接使用切面类的方式,当我们知道在不同方法中要写同样的逻辑的时候或者我们要处理某一些逻辑的时候我们可以去利用aop的思想

AOP原理实现

复述: AOP原理是基于静态代理和动态代理实现的,静态代理需要生产目标代理对象,动态代理不需要生成目标代理对象。动态代理主要分为jdk动态代理,CGLIB
jdk需要借口,基于反射机制,CGLIB基于ASM字节码包装的类库, ASM字节码文件的运行效率大于反射机制

AOP的核心逻辑是在AnnotationAwareAspectJAutoProxyCreator类里面实现的
流程说明,在spring容器启动,每个bean的实例化之前都会先经过AbstractAutoProxyCreator类的postProcessAfterInitialization的这个方法,然后接下来是再去调用wrapIfNecessary方法

public Object postProcessBeforeInstantiation(Class<?> beanClass, String beanName) {
        Object cacheKey = this.getCacheKey(beanClass, beanName);
        if (!StringUtils.hasLength(beanName) || !this.targetSourcedBeans.contains(beanName)) {
            if (this.advisedBeans.containsKey(cacheKey)) {
                return null;
            }

            if (this.isInfrastructureClass(beanClass) || this.shouldSkip(beanClass, beanName)) {
                this.advisedBeans.put(cacheKey, Boolean.FALSE);
                return null;
            }
        }

        TargetSource targetSource = this.getCustomTargetSource(beanClass, beanName);
        if (targetSource != null) {
            if (StringUtils.hasLength(beanName)) {
                this.targetSourcedBeans.add(beanName);
            }

            Object[] specificInterceptors = this.getAdvicesAndAdvisorsForBean(beanClass, beanName, targetSource);
            Object proxy = **this.createProxy(beanClass, beanName, specificInterceptors, targetSource);**
            this.proxyTypes.put(cacheKey, proxy.getClass());
            return proxy;
        } else {
            return null;
        }
    }

从这个postProcessAfterInitialization 方法中我们看到this.createProxy 这个方法,然后我们在跟着点击进去

protected Object createProxy(Class<?> beanClass, @Nullable String beanName, @Nullable Object[] specificInterceptors, TargetSource targetSource) {
     .......
        return **proxyFactory.**getProxy****(this.getProxyClassLoader());
    }

我们再次看到 proxyFactory.getProxy 代理工厂获取代理 继续点击进去我们再次发现

public Object getProxy(@Nullable ClassLoader classLoader) {
        return **this.createAopProxy()**.getProxy(classLoader);
    }

我们再次点击this.createAopProxy() 创建代理的方法我们再次进入到了新的接口上

在这里插入图片描述
我们就会看到如图,当我们看到图中的内容的时候我们就会发现这不是我们熟悉的动态代理的模式吗,jdk和cglib的动态代理模式吗?

所以在AOP源码中核心的类就是AbstractAutoProxyCreator从这个类我们就会慢慢的知道AOP的最终还是利用动态代理去实现的不仅用了JDK动态代理而且还用了CGLIB的代理模式;

Spring事务管理

一、事务的基本特性
1:原子性 : 是指事务所包含的操作要么全部成功,要么就全部失败;
2:一致性:一致性就是事务必须使数据库从一个一致性状态变换到另一个一致性状态,事务之前什么样事务时候还是什么样,AB之前转账,不管她俩如何转账总额不会变化
3:隔离性:当多个用户并发访问数据库的时候,在操作同一张表的时候每一个用户开启的事务,不能被其他事务的操作所干扰,多个并发事务之间是相互隔离的。事务与事务之间互相不干扰
4:持久性,就是一旦事务提交了,那么对数据库数据的改变就是永久性的,即使在数据库系统故障的情况下也不会丢失提交事务的操作

二、Spring事务的控制分类
1:编程式事务控制 --手动控制事务,就叫做编程式事务控制
比如:JDBC中我们知道的connection.setAutoCommite(false) //手动控制事务
在Hibernate中 Session.beginTransaction() // 开启一个事务
手动开启事务的好处就是可以指定的方法,指定方法那几行提交事务,属于细粒度事务控制比较灵活但是开发较为繁琐。
2:声明式事务
Spring提供了对事务的控制这个就是声明式事务,是基于AOP实现的。其中的原理是使用编程事务+反射机制进行包装的
我们通过代码来理解Spring事务的框架:如下代码

@Component
public class TransactionUtils {

	@Autowired
	private DataSourceTransactionManager dataSourceTransactionManager;

	// 开启事务
	public TransactionStatus begin() {
		TransactionStatus transaction = dataSourceTransactionManager.getTransaction(new DefaultTransactionAttribute());
		return transaction;
	}

	// 提交事务
	public void commit(TransactionStatus transactionStatus) {
		dataSourceTransactionManager.commit(transactionStatus);
	}

	// 回滚事务
	public void rollback(TransactionStatus transactionStatus) {
		dataSourceTransactionManager.rollback(transactionStatus);
	}
}

@Service
public class UserService {
	@Autowired
	private UserDao userDao;
	@Autowired
	private TransactionUtils transactionUtils;

	public void add() {
		TransactionStatus transactionStatus = null;
		try {
			transactionStatus = transactionUtils.begin();
			userDao.add("wangmazi", 27);
			int i = 1 / 0;
			System.out.println("我是add方法");
			userDao.add("zhangsan", 16);
			transactionUtils.commit(transactionStatus);
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			if (transactionStatus != null) {
				transactionStatus.rollbackToSavepoint(transactionStatus);
			}
		}

	}

}

我们从上面代码可以看出,TransationUtils有关事务的工具类是利用编程事务实现的手动事务,既类似于JDBC编程实现事务管理,管理使用TransactionTemplate或者直接使用底层的PlatformTransactionManager。对于编程式事务管理,spring推荐使用TransactionTemplate。

然后根据编程式事务我们使用AOP技术来封装手动事务,如下代码

@Component
@Aspect
public class AopTransaction {
	@Autowired
	private TransactionUtils transactionUtils;

	// // 异常通知
	@AfterThrowing("execution(* com.itmayiedu.service.UserService.add(..))")
	public void afterThrowing() {
		System.out.println("程序已经回滚");
		// 获取程序当前事务 进行回滚
		TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
	}

	// 环绕通知
	@Around("execution(* com.itmayiedu.service.UserService.add(..))")
	public void around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
		System.out.println("开启事务");
		TransactionStatus begin = transactionUtils.begin();
		proceedingJoinPoint.proceed();
		transactionUtils.commit(begin);
		System.out.println("提交事务");
	}

}

上述代码就是利用切面编程对编程式事务进行封装,比如Around通知里面我们我们利用表达式找到和数据库交互的一层,把有关事务的逻辑进行统一提取,这样就可以使用该切面类统一管理事务。所以,在spring中事务的管理的思想就是如此的。
声明式事务基础就是建立在AOP思想之上,其本质就如同上文所说的对有关方法进行前后的拦截,然后在目标方法开始创建之前创建或者加入一个事务,在目标方法执行完之后根据执行情况提交或者回滚事务,声明式事务最大的优点就是不需要通过编程方式去管理事务,也就不需要在业务逻辑代码中去掺杂事务管理的有关代码。

使用事务的注意事项:在事务程序运行过程中如果没有错误,就会自动的提交事务,如果程序运行发生异常,则会自动回滚,如果使用了try捕获异常的时候,一定要在catch里面手动回滚代码:TransactionAspectSupport.currentTransactionStatus().setRollbackOnly()
上面这句代码就是如果我们想在事务中try代码,我们要进行手动回滚,通常在AOP底层会有封装,但是我们在处理有关问题也不能遗漏该问题的存在

Spring事务的传递性

在srping中事务是有关许多定义的,
Propagation(key属性确定代理应该给哪个方法增加事务行为。这样的属性最重要的部份是传播行为。)
PROPAGATION_REQUIRED --这个是支持当前事务,如果当前没有事务,就会新建一个事务
PROPAGATION_SUPPORTS --支持当前事物,如果当前没有事务,就会以非事务方式进行
PROPAGATION_MANDATORY --支持当前事物,如果当前没有事务,就会抛出异常
PROPAGATION_REQUIRES_NEW --新建事务,如果当前事务存在,那就回把当前事物挂起
PROPAGATION_NOT_SUPPORTED --以非事务方式执行操作,如果当前存在事务就会把当前事务挂起
PROPAGATION_NEVER–以非事务方式执行,如果当前存在事务,就会抛出异常 --未完待续

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值