AOP(1)

来源:互联网 发布:网络平台建设方案 编辑:程序博客网 时间:2024/05/29 06:29

1、是什么

        AOP:(Aspect Oriented Programming) 面向切面编程。是目前软件开发中的一个热点,也是spring框架中容。

         AOP可以通过预编译方式和运行期动态代理实现在不修改源代码的情况下给程序动态统一添加功能的一种技术 。

2、为什么用他:

1)就是为了方便,看一个国外很有名的大师说,编程的人都是“懒人”,因为他把自己做的事情都让程序去做了。用了AOP能让你少写很多代码,这点就够充分了吧。
2)就是为了更清晰的逻辑,可以让你的业务逻辑去关注自己本身的业务,而不去想一些其他的事情。这些其他的事情包括:安全,事物,日志等等。

AOP:术语概览:

Advice通知 :(增强处理)    表示 啥时候用。 (就是想切入的功能的具体代码逻辑 ,也就是切面里定义的几种增强什么时候使用)。

  • Before: 在方法执行之前。
  • AfterReturning: 只有成功返回后,才会织入该Advice
  • AfterThrowing: 只有抛出异常后,才会织入该Advice
  • After: 不管是抛出异常后,还是成功返回,都会织入该Advice
  • Around: 在目标方法执行之前、之后,都织入该Advice。

JoinPoint:连接点:作为目标方法和 增强方法的连接点,可以理解成就是spring允许你实现切入通知(Adcvice)的地   方,spring只支持方法连接点。其他如AspectJ还可以让你在构造器或属性注入时都行,不过那不是咱们关注的,只要记住,和方法有关的前前后后都是连接点。

访问目标方法最简单的做法是定义增强处理方法时,将第一个参数定义为JoinPoint类型,当该增强处理方法被调用时,该JoinPoint参数就代表了织入增强处理的连接点。JoinPoint里包含了如下几个常用的方法:

Object[] getArgs           :返回目标方法的参数

Signature getSignature:返回目标方法的签名

Object getTarget          :返回被织入增强处理的目标对象

Object getThis             :返回AOP框架为目标对象生成的代理对象

具体方法如下

public interface JoinPoint {

String toString(); //连接点所在位置的相关信息
String toShortString(); //连接点所在位置的简短相关信息
String toLongString(); //连接点所在位置的全部相关信息
Object getThis(); //返回AOP代理对象
Object getTarget(); //返回目标对象
Object[] getArgs(); //返回被通知方法参数列表
Signature getSignature(); //返回当前连接点签名
SourceLocation getSourceLocation();//返回连接点方法所在类文件中的位置
String getKind(); //连接点类型
StaticPart getStaticPart(); //返回连接点静态部分

}

注意:当使用@Around处理时,我们需要将第一个参数定义为ProceedingJoinPoint类型,该类是JoinPoint的子类。

Pointcut:切点:  表示具体谁用,即是用来让切点来筛选连接点,选中那几个你实际想要切入的地方。

多种定义方式:(待查)

Aspert:切面:  是通知和切入点的结合。现在发现了吧,没连接点什么事,链接点就是为了让你好理解切点搞出来的,明白这个概念就行了。通知说明了干什么和什么时候干(什么时候通过方法名中的befor,after,around等就能知道),而切入点说明了在哪干(指定到底是哪个方法),这就是一个完整的切面定义。


introduction引入允许我们向现有的类添加新方法属性。这不就是把切面(也就是新方法属性:通知定义的)用到目标类中吗

Target:目标:引入中所提到的目标类,也就是要被通知的对象,也就是真正的业务逻辑,他可以在毫不知情的情况下,被咱们织入切面。而自己专注于业务本身的逻辑。

proxy代理:怎么实现整套AOP机制的,都是通过代理


weaving织入:把切面应用到目标对象来创建新的代理对象的过程。有三种方式,spring采用的是运行时


附:

  1. 目标对象 = 项目原始的Java组件。
  2. AOP代理对象  =  由AOP框架生成java对象。
  3. AOP代理对象的方法 = advice + 目标对象的方法。

我所理解的AOP原理:
spring用代理类包裹切面,吧他们织入到Spring管理的bean中,也就是说代理类伪装成目标类,它会截取对目标类中方法的调用,然调用者对目标类的调用都先变成伪装类,伪装类这就先执行了切面,再把调用转发给真正的目标bean。
现在可以自己想一想,怎么搞出来这个伪装类,才不会被调用者发现(过JVM的检查,JAVA是强类型检查,哪里都要检查类型)。
1)实现和目标类相同的接口。
我也实现和你一样的接口,反正上层都是接口级别的调用,这样我就伪装成了和目标类一样的类(实现了同意接口,咱是兄弟了),也就逃过了类型检查,到java运行期的时候,利用多态的后期绑定(所以spring采用运行时),伪装类(代理类)就变成了接口的真正实现,而他里面包裹了真实的那个目标类,最后实现具体功能的还是目标类,只是不过伪装在之前干了点事情(写日志,安全检查,事物等)。
//这就好比一个人让你办事,每次这个时候,你弟弟就会出来,当然他分不出来了,以为是你,你这个弟弟虽然办不了这个事,但是她知道你能办,所以就答应下来了,并且收了点礼物(写日志),收完礼物了,给把事给人家办了啊,所以你弟弟又找你这个哥哥来了,最后把这事办了还是你自己。但是你自己并不知道你弟弟已经收了礼物了,你只是专心把这件事做好。
顺着这个思想,要是本身这个类就没实现一个接口呢,你怎么伪装我,我就压给没有机会让你搞出这个双胞胎弟弟,那么就用第2种代理方式,创建一个目标类的子类,生个儿子,让儿子伪装我。
2)生成子类调用。
这次用子类来做伪装,当然这样也能逃过JVM的强类型检查,我继承的吗,当然查不出来了,子类重写了目标类的所有方法,当然在这些重写的方法中,不仅实现了目标类的功能,还在这些功能之前,实现了一些其他的(写日志,安全检查,事物等)。
//这次的对比就是,儿子先从爸爸那儿把本事都学会了,所有人都找儿子办事,但是儿子每次办和爸爸同样的事之前,都要收点小礼物(写日志),然后才去办真正的事。当然爸爸是不知道儿子这么干的了。这里就有事情要说,某些本事是爸爸独有(final的),儿子学不会,学不了就办不了这个事,办不了这个事情,自然就不能收人家的礼物了。

前一种兄弟模式,spring会使用JDK的java.lang.reflect.Proxy类,它允许Spring动态生成一个新类来实现必要的接口,织入通知,并且把这些接口的任何调用都转发到目标类。
后一种父子模式,spring使用CGLIB库生成目标类的一个子类,在创建这个子类的时候,spring织入通知,并且把对这个子类的调用委托到目标类。
相比之下,还是兄弟模式好一些,她能更好的实现松耦合,尤其在今天都高喊着面向接口编程的情况下,父子模式只是在没有实现接口的时候,也能织入通知,应该当做一种例外。

3、怎么用:

spring-aop   —— 动态代理AOP框架    

1)需引入的jar包:

                    除了spring的一些相关jar包外 , 还需引入两个 (AOP用到的)第三方 jar包

                    aspectjrt 和 aspectjweaver

2)spring配置文件中:

                  《第一种》:可在spring中直接定义切面,切点,增强

                    《第二种》:引入aspectj注解在Java代码中定义 切面,切点,增强(这里讲第二种)

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

<!-- 开启解析器 启动对@AspectJ注解的支持 --><!--proxy-target-class属性值决定是基于接口的还是基于类的代理被创建。如果proxy-target-class 属性值被设置为true,那么基于类的代理将起作用(这时需要cglib库)。如果proxy-target-class属值被设置为false或者这个属性被省略,那么标准的JDK 基于接口的代理--><!--expose-proxy="true" 实现同类中相互调用,AOP也能切入.前提示上面属性生成的代理类代替本类调用本类中的方法--><aop:aspectj-autoproxy proxy-target-class="true" expose-proxy="true"/>

3)切面

   3.1)定义切面 :

                @Aspect  注解在类上即可

   3.2)定义切点  :必须放在一个返回值为void的方法上添加@Pointcut 注解

                《一》@Pointcut("execution(public * com.dao.impl..*.*(..))")

                                       excution():包下或接口下 包含子包或类 中对的任意方法

                                       within()    :包或其子包中的任意连接点(在Spring AOP中只是方法执行)

                                       this()          : 指定到接口代理对象   他其中的任意连接点        

                                       等等: http://lavasoft.blog.51cto.com/62575/172292/

                       

                        附:*********在Spring AOP中只是方法执行 *************       

         《二》@Pointcut("@annotation(com.max.rrs.app.web.annotation.LoggingConfig)")
                

                  注:@Pointcut 括号内是 切入连接点的定义 ,第一种 路径限定,第二种是 前提定义了一个注解

                          以@annotation存在的位置作为切入连接点。(***推荐***)

 

   3.3)定义增强方法 :          

   《例一》

//这个可用来替代以后重复出现的. 直接在后面的Before("myMethod()")就行了.
    @Pointcut("execution(public * com.dao.impl..*.*(..))")
    public void myMethod(){}
    
    //下面用到的是织入点语法, 看文档里面有. 就是指定在该方法前执行
//    @Before("execution(public void com.dao.impl.UserDAOImp.save(com.model.User))")
    //记住下面这种通用的, *表示所有
    @Before("execution(public * com.dao.impl..*.*(..))")
    public void beforeMethod()
    {
        System.out.println("save start......");
    }
    //正常执行完后
    @AfterReturning("execution(public * com.dao.impl..*.*(..))")
    public void afterReturnning()
    {
        System.out.println("after save......");
    }
    //抛出异常时才调用
    @AfterThrowing("myMethod()")
    public void afterThrowing()
    {
        System.out.println("after throwing......");
    }
    //环绕, 这个特殊点.
    @Around("myMethod()")
    public void aroundMethod(ProceedingJoinPoint pjp) throws Throwable
    {
        //加逻辑的时候, 不要依赖执行的的先后顺序
        System.out.println("method around start!");
        Object object = pjp.getThis().getClass().getName();
        System.out.println(object.toString());
        System.out.println("method around end!");
    }

           《例二》

    /**     * 对请求传入和返回参数进行记录     */    @AfterReturning(pointcut = "loggingAspect()", returning = "o")    public void logAfter(JoinPoint joinPoint, Object o) throws Throwable {        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();        System.out.println(joinPoint.getSignature().getDeclaringTypeName());        System.out.println(joinPoint.getSignature().getName());        System.out.println(request.getParameterMap());        HashMap<String, Object> logConfig = getLogConfig(joinPoint);        if (logConfig.get("type").equals(LoggingType.Controller)) {            ModelAndView modelAndView = (ModelAndView) o;            String.valueOf(modelAndView.getModel());        }    }    /**     * 截获日常信息进行日志记录     */    @AfterThrowing(pointcut = "loggingAspect()", throwing = "e")    public  void logAfterThrowing(JoinPoint joinPoint, Throwable e) {        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();        //获取请求ip        String ip = request.getRemoteAddr();        try {              /*========控制台输出=========*/            System.out.println("=====异常通知开始=====");            System.out.println("异常代码:" + e.getClass().getName());            System.out.println("异常信息:" + e.getMessage());            System.out.println("异常方法:" + (joinPoint.getTarget().getClass().getName() + "." + joinPoint.getSignature().getName() + "()"));//            System.out.println("方法描述:" + getServiceMthodDescription(joinPoint));//            System.out.println("请求人:" + user.getName());            System.out.println("请求IP:" + ip);//            System.out.println("请求参数:" + params);            System.out.println("=====异常通知结束=====");        }  catch (Exception ex) {            //记录本地异常日志            logger.error("==异常通知异常==");            logger.error("异常信息:{}", ex.getMessage());        }    }
             @AfterReturning(
value="切入点表达式或命名切入点",
pointcut="切入点表达式或命名切入点",
argNames="参数列表参数名",

returning="返回值对应参数名")


@afterThrowing(Method method, Object[] args,Object cObj, Exception e);
value="切入点表达式或命名切入点",
pointcut="切入点表达式或命名切入点",
argNames="参数列表参数名",

throwing ="返回值对应参数名")


4、什么场景下用 AOP :

1 0
原创粉丝点击