使用aop来实现事务功能(日志)

事务


通俗讲, AOP思想就是把中间的非主流程的代码(比如:验证、事务管理、缓存、日志记录等), 横向抽取出来放在一个公共的类中, 也就是切面.可以通过Spring框架来配置该切面

1:学习的东西是什么,解决什么问题

事务分为两种

编程式:通过手动编写代码的方式完成事务的管理

声明式:通过配置的方式完成事务的管理(底层采用aop技术, spring的事务都是通过AOP动态代理实现的,因此使用事务就要会aop )

aop(面向切面编程):aop原理为代理

(1)静态代理:【程序运行前就存在代理类的字节码文件】

(2)动态代理:JDK动态代理,CGLIB动态代理。【在程序运行期间由JVM根据反射等机制生成源码】

事务的四大特点(ACID):

  • atomictiy(原子性)

    表示一个事务内的所有操作是一个整体,要么全部成功,要么全部失败。

  • consistency(一致性)

    表示一个事务内有一个操作失败时,所有更改过的数据都必须回滚到修改前的状态。

  • isolation(隔离性)

    事务查看数据时数据所处的状态,要么是另一并发事务修改它之前的状态,要么是另一事务修改它之后的 数据,事务不会查看中间状态的数据 

  • 事务隔离级别从低到高(效率从高到低): 

    读取未提交(Read Uncommited)

读取已提交(Read commited)

复读(Repeatable Read)

序列化(serializable)(锁表)

  • durability(持久性)

    持久性事务完成之后,它对于系统的影响是永久性的。

2:什么时候使用,什么场合适合用

可以用事务写性能日志和操作日志。

性能日志:查看性能的优异。

操作日志:防止他人甩锅给自己,说程序员代码写错,而不是他自己修改了数据

3:优势

编程式事务:手动管理事务,难写(很少使用),但易于程序员维护,更加灵活

声明式事务:代码入侵小,不用在业务逻辑层(service)编写事务管理的代码,可以用基于配置的xml(Aspectj方式)或只使用注解 来管理项目。 在没有修改源代码的情况下,就给原方法增加的新的功能,这就是AOP的魅力,

注意:使用注解特别简单,但不易理解底层代码,实现原理。

4:具体使用,怎么使用,使用步骤和注意事项

编程式事务(使用):

在xml中全都是bean(懒得写了,自己百度一下呗)

声明式事务(使用):

注意:在这里,我用的无注解方式写的性能日志。用注解方式写的操作日志

性能日志:效果(结束时间—开始时间=执行时间)

进行登陆操作时自动记入数据库

(1)配置xml:

<!--  AOP配置增强类 -->
    <aop:config proxy-target-class="true">
        <!-- 配置切入点(业务层) -->
        <aop:pointcut expression="execution(* com.cws.service.impl.*.*(..))" id="pointcut"/>
        <!-- 配置切面 -->
        <aop:aspect id="myAspect"  ref="myAspect">
           <!--  method属性对应的是方法名字 -->
            <aop:before method="before" pointcut-ref="pointcut"/>
            <aop:after method="after" pointcut-ref="pointcut"/>
            <aop:around method="around" pointcut-ref="pointcut"/>
        </aop:aspect>
    </aop:config>
    <!-- 设置事务的传播特征 --> 
    <tx:advice id="advice" transaction-manager="dataSourceTransactionManager">
        <tx:attributes>
            <!-- 需要事务的方法;事务是否只读?false:否;支持当前事务,如果当前没有事务,就新建一个事务,最常见的选择 -->
            <tx:method name="login" read-only="false" propagation="REQUIRED"/>
            <tx:method name="*"/>
        </tx:attributes>
    </tx:advice>
​

(2)创建事务类:

package com.cws.aspect;
​
import java.text.SimpleDateFormat;
import java.util.Date;
​
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.springframework.jdbc.core.JdbcTemplate;
​
public class MyAspect {
   JdbcTemplate jdbcTemplate;
   //注入
   public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
    this.jdbcTemplate = jdbcTemplate;
}
   //前置方法,在目标方法执行前执行
   private void before(JoinPoint jp) {
    //System.out.println(jp.getTarget());
}
   //加上这个ProceedingJoinPoint类型的参数(环绕通知)
   public Object around(ProceedingJoinPoint pjp) throws Throwable{
    //开始时间
    String begin =new SimpleDateFormat("YYYY-MM-dd HH:mm:ss").format(new Date())+"";
    //String begin=System.currentTimeMillis()+"";获取计算机时间
    //启动目标方法
    Object obj=pjp.proceed();
    //结束时间
    String end=new SimpleDateFormat("YYYY-MM-dd HH:mm:ss").format(new Date())+"";
    //执行方法名
    String methdod_name=pjp.getSignature().getName();
    String param="";
    //获取参数
    for ( Object object : pjp.getArgs()) {
        param=param+object+",";
    }
    Object objects[]={methdod_name,begin,end,param};
    //直接在这里写sql语句,添加进数据库
String sql="insert into logri(methdod_name,begin,end,param) values(?,?,?,?)";
jdbcTemplate.update(sql, objects);
return obj;
   }
   private void after(JoinPoint jp) {
        //System.out.println(jp.getTarget());
    }
​
}

操作日志:效果

每进行一次操作(增删改查),都会记录操作人,操作命令,地址ip,操作时间以及操作结果

 (1)配置xml:

<aop:aspectj-autoproxy proxy-target-class="true"/> 等价于

(为true就是基于类,需要使用cglib库,为false就是基于接口,使用JDK动态代理)

JDK动态代理动态代理必须要有接口. 而CGLIB动态代理不管有没有接口都可以使用,因此CGLIB动态代理使用的更多

(2)创建事务类:

@Aspect (创建切面类{实则是个普通类})

@Component:泛指组件

@Aspect
@Component
public class MyAspect{
    @Autowired
    LogServie loginService;
//配置接入点
@Pointcut("execution(* com.cws.controller.*.*(..)) && !execution(* com.cws.controller.BaseController.*(..))")
private void controllerAspect(){}//定义一个切入点
​
//在和aroundAdvice结合的时候,这个方法一定要加上这个ProceedingJoinPoint类型的参数
@Around(value = "controllerAspect()")
public Object aroundTest(ProceedingJoinPoint pjp) throws Throwable {
    return this.show(pjp);
}
​
public Object show(ProceedingJoinPoint pjp) throws Throwable {
    //常见日志实体对象
    LogEntity log = new LogEntity();
    //获取登录用户账户,RequestContextHolder是使用spring获取serlvet的方法
    HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
    //这里的内容可不写,则获取登陆页面的登录名
    log.setUserid("文");
    //获取系统时间
    String time = new SimpleDateFormat("YYYY-MM-dd HH:mm:ss").format(new Date());
    log.setDates(time);
    //获取系统ip
    String ip = IPUtil.getIPAddress(request);
    log.setIp(ip);
    //方法通知前获取时间
    long start = System.currentTimeMillis();
    // 拦截的实体类,就是当前正在执行的controller
    Object target = pjp.getTarget();
    // 拦截的方法名称。当前正在执行的方法
    String methodName = pjp.getSignature().getName();
    // 拦截的方法参数
    Object[] args = pjp.getArgs();
    // 拦截的放参数类型
    Signature sig = pjp.getSignature();
    if (!(sig instanceof MethodSignature)) {
        throw new IllegalArgumentException("该注解只能用于方法");
    }
    MethodSignature msig=(MethodSignature) sig;
    Class[] parameterTypes = msig.getMethod().getParameterTypes();
    Object object = null;
    // 获得被拦截的方法
    Method method = null;
    try {
        method = target.getClass().getMethod(methodName, parameterTypes);
    } catch (Exception e) {
        e.printStackTrace();
    }
    if (null != method) {
        // 判断是否包含自定义的注解,说明一下这里的SysLog就是我自己自定义的注解
        if (method.isAnnotationPresent(SysLog.class)) {
            SysLog sysLog = method.getAnnotation(SysLog.class);
            log.setModule(sysLog.module());
            log.setMethod(sysLog.method());
            try {
                object = pjp.proceed();
                long end = System.currentTimeMillis();
                //将计算好的时间保存在实体中
                log.setResponse_date(""+(end-start));
                log.setCommit("执行成功!");
                //保存进数据库
                loginService.saveLog(log);
            } catch (Throwable e) {
                long end = System.currentTimeMillis();
                log.setResponse_date(""+(end-start));
                log.setCommit("执行失败");
                loginService.saveLog(log);
            }
        } else {//没有包含注解
            object = pjp.proceed();
        }
    } else { //不需要拦截直接执行
        object = pjp.proceed();
    }
    return object;
}
public MyAspect() {
    System.out.println("MyAspect --- 构造");
 }
}

注意:这里为了写操作日志,加入了自定义注解,至于自定义注解的内容,下次再写,下次一定!

实质就是:在方法执行前后进行拦截,然后再目标方法开始之前创建并加入事务,执行完目标方法后根据执行情况提交或回滚事务.

  • 3
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: 使用Spring的AOP可以实现缓存功能。具体实现方式是,在需要缓存的方法上添加@Cacheable注解,指定缓存的key和缓存的名称。当方法被调用时,Spring会先检查缓存中是否存在该key对应的缓存数据,如果存在则直接返回缓存数据,否则执行方法并将返回结果缓存起来。同时,还可以使用@CachePut注解更新缓存数据,或者使用@CacheEvict注解清除缓存数据。这样可以有效地提高系统性能和响应速度。 ### 回答2: 使用Spring的AOP实现缓存功能的步骤如下: 1. 首先,需要在Spring配置文件中启用AOP功能,可以通过添加`<aop:aspectj-autoproxy/>`来实现。 2. 然后,创建一个用于缓存方法调用结果的类,该类需要实现`org.springframework.cache.Cache`接口,并提供对缓存的读取、写入、删除等操作方法。 3. 还需要创建一个切面类,该类需要使用`@Aspect`注解进行标记,并在需要缓存的方法上添加`@Cacheable`注解。在切面类中,使用`@Before`和`@After`等注解来定义缓存操作的切点和通知。 4. 在Spring配置文件中,将切面类声明为一个bean,并在`<aop:config>`中指定要应用缓存的方法和切面。 5. 最后,配置`ehcache.xml`(或其他缓存配置文件),并将其指定为Spring配置文件中缓存管理器的实现类,例如`<bean id="cacheManager" class="org.springframework.cache.ehcache.EhCacheCacheManager"/>`。 这样,当被缓存的方法被调用时,AOP将切入切面类的通知方法,首先查询缓存是否存在该方法的结果,如果存在,则直接返回缓存中的结果;如果不存在,则调用原始方法,并将结果存入缓存中。在后续的调用中,如果参数相同,则直接从缓存中获取结果,从而减少了对原始方法的调用,提高了系统的性能和响应速度。 使用Spring的AOP实现缓存功能可以大大简化代码,提高项目的可维护性和可扩展性,同时还能通过缓存数据减少对数据库等资源的访问,提升系统整体的性能。 ### 回答3: 使用Spring的AOP可以很方便地实现缓存功能AOP(面向切面编程)是一种编程范式,通过在程序运行时动态地将横切逻辑(如日志记录、事务管理、异常处理等)插入到应用程序的特定位置,以提供更好的代码结构和模块化。 在使用Spring的AOP实现缓存功能时,我们可以通过以下步骤来实现: 1. 定义一个缓存注解:可以使用Spring提供的@Cacheable注解来定义缓存的方法。这个注解可以应用在方法上,用于标记被缓存的方法。 2. 配置缓存切面:通过AOP切面配置,将缓存注解和具体的缓存实现关联起来。可以使用Spring的@Aspect注解来定义一个切面类,该类可以包含多个增强方法用于处理缓存操作。 3. 配置缓存策略:在切面类中,可以通过使用Spring的缓存管理器(如Ehcache、Redis等)来定义缓存的具体策略。可以配置缓存的过期时间、缓存的存储位置等。 4. 在目标方法中使用缓存注解:在需要被缓存的方法上添加之前定义的缓存注解。当方法被调用时,AOP切面会先检查缓存中是否存在对应的缓存数据,如果存在则直接返回缓存数据,否则执行方法逻辑并将结果存入缓存。 5. 测试缓存功能:执行目标方法,观察是否从缓存中获取数据以及方法执行的时间。 通过这种方式,我们可以很方便地在应用中加入缓存功能,提高系统性能和响应速度。同时,由于使用AOP的方式,可以很好地解耦和复用缓存相关的代码逻辑。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值