Spring Aop基础理解内容




说明

  因为是个人复习java的总结,所以结构稍显杂乱,有些语句过于口语化.
  最近实习忙了一点,就忽略了学习,真的反思不应该打乱自己的学习节奏,积攒资本才是最重要的。
  最近也在反思,其实写博客根本就不是为了记录或者说是分享。记录来说回忆起来其实也不那么方便,毕竟回忆都是零碎的深入内容,肯定还得另外查。
  至于说分享,其实也没什么人看,凌乱的个人记录,也没什么人看的下去。其实感觉写博客只是在花费时间坚固自己学习的习惯,让自己会想去做一件事而去学习。唉,逝者如斯夫。

回顾一下动态代理

  其实一般就是之前提过几次的面向接口的动态代理,也就是使用Proxy的newProxyInstance来实现动态代理。这种方式其实写法很固定,但是又很容易忘。
其实其中只有三个参数,第一个是类加载器,那肯定代理谁就用谁的类加载器。第二个是字节码数组,其实就是代理的接口,因为是动态加载的,所以需要接口的字节码信息来确定方法信息。第三个就是InvocationHandler,一般用匿名内部类实现,需要重写其中的invoke()方法,主要就是写增强方法的内容。

  然后其实最需要明白的是invoke()中的参数,主要也是三个,第一个是代理对象的引用,但是一般没有什么用,就是用来获取第二第三个参数的。第二个是当前执行的方法,因为这部分其实是动态生成的,调用代理对象去调用方法的时候,方法参数会通过反射传入这里。第三个就是方法的参数列表,也是跟方法一样传入的。

final B b = new BImpl();
B c = (B)Proxy.newProxyInstance(b.getClass().getClassLoader(),b.getClass().getInterfaces(),
        new InvocationHandler() {
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                return method.invoke(b,args);
            }
        });
c.say();

  写起来就大概是这个样子。

  其实还有一种基于子类的动态代理
  使用的话需要导入第三方cglib库,然后调用Enhancer下的create()方法,有兴趣就了解一下。其实本质上只是创建代理对象的方式不同,一种是创建一个实现相同接口中方法的对象,一个是创建其子类对象。


Aop前先思考一个问题

  这个问题就是我们之前写的服务层,持久层的案例,其实都只是考虑到了简单的增删改查。并且编程的时候都是通过一层套一层实现的。在学习完Ioc之后,虽然我们可以做到给每一层的连接采用控制反转减少耦合。但是我们还是在简单增删改查中思考问题。

  如果现在需要实现和事务相关的操作,比如说转账之类的一系列操作,需要事务进行控制。这时候就发现了问题,之前写的都是持久层提交了事务,这明显不合理。这部分逻辑应该是写在服务层的。

  如果要实现转账,那么肯定需要先将线程和数据库连接池中获取的资源绑定,这一操作独立出来,需要写一个类,主要就是写获取当前的线程,然后判断是否有连接,绑定连接。

  然后对于事务的相关操作肯定也得独立出来,其中主要就是写事务的开启,提交,回滚,释放连接操作。这里为什么要写释放,因为我们在上面将线程和连接绑定,那么在这里肯定得写一个关闭连接给上层。这里面主要的操作就是获取连接然后进行相关的设置。注意释放连接的时候需要接触线程和连接的绑定。

  在接下来就是更麻烦的问题,怎么实现在服务层控制事务,可能会有这种思考,上面既然已经将事务的管理封装成了一个类,那么就在服务层调用这个类,然后进行事务管理就好了。逻辑看上去是没什么问题,但是这里涉及到一个问题,那就是服务层中会有很多方法,这些方法都需要事务管理,那就每个方法都要重复书写事务管理操作,这样代码重复,而且耦合性太高了。

  比较好的实现方式应该是通过动态代理实现,之前学习动态代理可能一直觉得用处不大,那么现在就是很好的应用。既然我们不想让服务层的方法加上一堆事务管理,又想要扩展其中方法的功能,那这个需要刚好符合代理模式的思想。我们只要创建服务层的代理对象,然后在其中书写事务控制作为方法的增强。那就可以达到写一遍事务控制,代理对象中所有方法能够增强。

  其实这时候还可以思考,这个代理对象要在哪里创建,在展示层,这明显不好。这时候可以使用经典工厂模式,创建服务层对象的工厂,其中创建的是服务层对象的代理对象,然后在上层调用工厂获取对象。一层套一层,耦合性降低了很多。


Aop概念

  可能理解完上面的内容,感觉很懵,其实很重要,其中包含的就是Aop的相关思想。

  Aop叫做面向切面编程,其实就是说面向一个功能流程的时候,针对其中的某项业务流程进行整体整合,使其具有更好的复用性。

  针对上面的案例,其实就是将事务控制这个业务流程进行了抽取,使得服务层中共通的部分能够复用。

  这个思想,其实感觉很平常,就像是平时写代码发现某些语句经常用,那就干脆封装成一个方法去调用,懒得写那么多次,还方便改。只是Aop中不是针对这种简单的不变的语句情况,而是可能针对前后存在一些变化的情况,使用动态的方式进行解决。

  而且很巧合,Aop的实现方式就是动态代理。所以理解上面的内容很重要。

  但是说实话,说的直白一点,Aop跟junit中的@Before,@After差不多。其实际作用,就是在方法前面后面增加一部分自动触发的事件,从而达到不用修改源码,而增加业务流程。或者综合多个流程中的通用代码部分。其实你把这部分代码封装然后各个地方调用,效果也差不多,只是开发成本变得高了。而使用Spring框架则会自动去帮你解析你设置那些业务中需要添加那些流程,然后去触发这些业务流程。其实效率也就摆在那,但是对于大型项目这明显可以省时省力。

  其实有一些关于Aop的术语概念,但我感觉没什么好记录的,没见过就去查就好了。


Spring Aop的简单使用

  其实Aop的使用跟Ioc差不多,或者说Aop就是建立在Ioc的基础上的。

  使用的前提,那肯定得先有一个业务流程。这里就先假设一个简单的业务流程,表现层不写,持久层不写。毕竟最难写的是服务层,其他都是固定套路。

  假设一个账户的服务类,分接口和实现,然后这时候需要在账户的业务流程中加一点日志。日志这东西肯定是只写一遍的工具类,那就可以使用Aop将这个业务切面添加进去。这部分代码就不写了。

  其实使用的时候主要就是xml文件的配置,然后一些配置的细节。

<bean id="accountServiceImpl" class="cjlu.cct.service.impl.AccountServiceImpl"></bean>

<bean id="logger" class="cjlu.cct.utils.Logger"></bean>

<aop:config>
    <aop:aspect id="logAdvice" ref="logger">
        <aop:before method="start" pointcut="execution(public void cjlu.cct.service.impl.AccountServiceImpl.saveAccount())"></aop:before>
    </aop:aspect>
</aop:config>

  上面的配置中还写了Ioc的配置,主要是为了方便看,用注解配置也没关系。

  使用aop之前肯定需要命名空间

xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/aop
       http://www.springframework.org/schema/aop/spring-aop.xsd"

  然后标签的具体意义也很容易理解,最外层就是asp的设置外层标签,然后就是aop中需要添加的业务层面,这里就是指向日志的工具类对象。然后就是这个添加层面的执行位置。其中设置的属性也很好理解,就是具体方法和具体插入的流程方法。这个也叫做切入点


  其实这里用日志来举例,主要是加强对于日志的注意。之前自己写项目的时候真的根本不会注意到这个方面。毕竟自己写的项目自己对于业务流程都很熟悉,甚至能精确到每一个变量的用处。但是最近接触维护实际项目的时候就感受到了日志的重要性。一个大一点的项目都是需要协作进行开发的,就十分需要注意日志,注释,版本控制。注释能够帮助理解代码,主要是在开发的时候能够协作复用一些功能,而日志则是记录错误的重要途经,会为之后的维护减少很多麻烦。版本控制更是方便开发。

  虽然报错能让你找到出现问题的位置,但是少了日志,对于一个业务流程就真的很难发现问题到底是出现在什么地方。这里的错误很可能是因为前面流程进入了一个错误的分支,而报错不会出现在那部分,如果有日志进行详细的记录,就很容易能分析出业务的具体问题所在。


aop中xml的相关配置

  其实上面部分中根本没有什么需要理解的点,实际使用中其实也只关心具体怎么配置。这段时间接触实际项目,真的是维护的时候根本不用理解代码,能看懂配置,稍微理解一下项目结构就能维护。


pointcut的配置

  上面最奇特的其实就是切入点这一段的配置,其表达的其实就是某种权限修饰的某种返回值的某个包中类下的方法。

  那实际使用中肯定不可能一条一条的去配,如果全选可以设置为

* *..*.*(..)

  看着好像很抽象,其实挺好理解的。首先权限修饰符可以省略。那么剩下的就可以跟上面的语句进行一一的匹配。

void cjlu.cct.service.impl.AccountServiceImpl.saveAccount()

  第一个通配符肯定对应的是返回值,所以后面空一格。然后注意点代表的是匹配一项,那么在包名这一层面,几层包就写几层。如果想要省略的写,那就得用.的方式。
  包名后面肯定需要跟类名,那就又是一个*,再是方法名又一个*
  最后就是参数列表,这就像是可变参数列表…

  这种类似的配置方式之后还有挺多的,像spring-boot里面的定时器之类的。我想实际上都是框架读取字符串进行解析,效率其实就这样。越学越感觉还没接触到前沿的东西,现在这些都是针对大型一点的项目,单独部署服务。


切入点的配置

  上面的配置很容看出来,对于切入点的设置每次都要写,很麻烦。那框架肯定提供了复用的切入点设置方式。具体如下:

<aop:pointcut id="pt1" express="execution(public void cjlu.cct.service.impl.AccountServiceImpl.saveAccount())" ></aop:pointcut>

  使用到的时候根据id配置就好了,但是使用的时候需要注意其位置,如果设置在aop的config设置中,那就是全局config可以使用的。必须遵守写在切面之前,这里肯定是因为框架写的时候顺序解析,倒叙会出错,然后设置了xml的限制。
  如果设置在某个切面中,那么作用范围只有这个切面,超出这个切面就无效。


方法切入位置配置

  其实就是<aop:before>的设置,这里主要就是设置方法是在切入点的哪个顺序位置执行。总的其实就五种,前置,后置,异常,最终,环绕。

  分别对应的是before,after-returning,after-throwing,after,around五个配置。
  其实就是对应运行流程的五种情况,运行之前,运行之后,运行抛出异常,最后的最后终会运行的finally,不论前后我全都要。


  前四种都很正常,就是写好方法,然后xml中配置一下,就可以实现。

  第五种环绕通知很魔鬼,他相当于是要求你在实际调用的方法中设置一个它实现的切入点的类参数,然后你自己去设置怎么补充这个方法。简单点就是帮你写好了创建代理对象的流程,只需要你自己写想要怎么增强方法。实例如下:

public Object aroundPointLog(ProceedingJoinPoint pjp){
	Object result = null;
	try{
	Object[] args = pjp.getArgs();
	//前置
	result = pjp.proceed(args);
	//后置
	return result
}catch(Throwable t){
	throw new RuntimeException(t);
	//异常
}finally{
	//最终
}
}

  整体像上面这样实现,但是感觉实际效用不大,为什么要想不开自己去设置呢。


注解配置方式

  首先肯定是和前面Ioc的注解连通实现完全注解。

@EnableAspectJAutoProxy

  配置开启Aop注解方式

  当然实际使用中完全注解方式其实也不太好,后期维护都找不到进入点,可以再xml中配置下面语句开启。

<aop:aspectj-autoproxy></aop:aspectj-autoproxy>

注解主要就是那么几个

@Aspect

  设置在类上,表示该类为切面类

@Pointcut("execution(* cjlu.cct.service.impl.*.*(..))")

  设置切入点表达式,也就是用来设置复用的切入点表达式的注解,但是使用起来很坑,得先额外设置一个方法作为切入点设置,然后再配置到具体注解中,实例如下:

@Pointcut("execution(* cjlu.cct.service.impl.*.*(..))")
private void pt1(){}

@Before("pt1()")
public void start(){
    System.out.println("日志记录开启");
}

@Before
@AfterReturning
@AfterThrowing
@After
@Around

  切入方式的五种设置,从上面对于切入点表达式的设置应该可以知道,需要传入一个表达式或者是传入设置了切入点表达式的方法。


如有错误欢迎读者批评指正!!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值