aop原理浅析

AOP(动态代理)

简单一句话:在不改变目标对象的情况下对方法进行增强

简单例子
现在想在不改动student的代码前提下,修改student的study方法,让其输出 “学习真好~~”

// 定义一个接口,后续使用jdk的proxy类,用的就是接口才行
public interface studentInterface {
    void eat(String a);

    void study() ;
}



public class student implements studentInterface{
    public void eat(String a) {
        System.out.println("colo z; " + a);
    }

    public void study() {
        System.out.println("cool study");
    }
}

//测试
public class test {
    public static void main(String[] args) {
        // 现在想在不改动student的代码前提下,修改student的study方法,让其输出 "学习真好~~"
        student std = new student();
        // 第一个参数:类加载器 ,和被代理的对象使用相同的类加载器
        // 接口类型:class数组 和被代理对象使用相同的接口
        // 代理规则L:完成代理增强的功能
        studentInterface proxyInstance = (studentInterface) Proxy.newProxyInstance(student.class.getClassLoader(), new Class[]{studentInterface.class}, new InvocationHandler() {
            /**
             *
             * @param proxy
             * @param method
             * @param args
             * @return
             * @throws Throwable
             *
             * 执行student的所有方法,都会经过这个invoke方法
             * 现在就是修改study方法,如果方法名匹配上了,则调用,否则还是传递原来的方法和参数
             */
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                if (method.getName() == "study") {
                    System.out.println("学习真好~~");
                    return null;
                } else {
                    return method.invoke(std, args);
                }
            }
        });
        proxyInstance.eat("cool");
        proxyInstance.study();

    }
}

java的动态代理,对于oop这种设计理念,aop能够更好地减少代码的冗余,并且相比于纵向的功能增加,aop能够提供横向功能的扩展。纵向的功能的增加其实很好理解,就是几个对象所用代码有类似,但是又不是完全一样,这时我们可以通过一个父类,把类似代码归为一个方法,这样当不同对象继承父类时,调用相同逻辑的方法并加上自身的不同的业务代码即可。
在这里插入图片描述

因为随着软件开发的系统越来越复杂,工程师认识到,传统的OOP程序经常表现出一些不自然的现象,核心业务中总掺杂着一些不相关联的特殊业务(不会影响核心业务),如日志记录,权限验证,事务控制,性能检测,错误信息检测等等,这些特殊业务可以说和核心业务没有根本上的关联而且核心业务也不关心它们,比如在用户管理模块中,该模块本身只关心与用户相关的业务信息处理,至于其他的业务完全可以不理会,我们看一个简单例子协助理解这个问题。

/**
 * Created by zejian on 2017/2/15.
 * Blog : http://blog.csdn.net/javazejian [原文地址,请尊重原创]
 */
public interface IUserService {

    void saveUser();

    void deleteUser();

    void findAllUser();
}
//实现类
public class UserServiceImpl implements IUserService {

    //核心数据成员

    //日志操作对象

    //权限管理对象

    //事务控制对象

    @Override
    public void saveUser() {

        //权限验证(假设权限验证丢在这里)

        //事务控制

        //日志操作

        //进行Dao层操作
        userDao.saveUser();

    }

    @Override
    public void deleteUser() {

    }

    @Override
    public void findAllUser() {

    }
}

上述代码中我们注意到一些问题,权限,日志,事务都不是用户管理的核心业务,也就是说用户管理模块除了要处理自身的核心业务外,还需要处理权限,日志,事务等待这些杂七杂八的不相干业务的外围操作,而且这些外围操作同样会在其他业务模块中出现,这样就会造成如下问题

代码混乱:核心业务模块可能需要兼顾处理其他不相干的业务外围操作,这些外围操作可能会混乱核心操作的代码,而且当外围模块有重大修改时也会影响到核心模块,这显然是不合理的。
代码分散和冗余:同样的功能代码,在其他的模块几乎随处可见,导致代码分散并且冗余度高。
代码质量低扩展难:由于不太相关的业务代码混杂在一起,无法专注核心业务代码,当进行类似无关业务扩展时又会直接涉及到核心业务的代码,导致拓展性低。

事实上我们知道诸如日志,权限,事务,性能监测等业务几乎涉及到了所有的核心模块,如果把这些特殊的业务代码直接到核心业务模块的代码中就会造成上述的问题,而工程师更希望的是这些模块可以实现热插拔特性而且无需把外围的代码入侵到核心模块中,这样在日后的维护和扩展也将会有更佳的表现,假设现在我们把日志、权限、事务、性能监测等外围业务看作单独的关注点(也可以理解为单独的模块),每个关注点都可以在需要它们的时刻及时被运用而且无需提前整合到核心模块中,这种形式相当下图:
在这里插入图片描述

/**
 * Created by zejian on 2017/2/15.
 * Blog : http://blog.csdn.net/javazejian [原文地址,请尊重原创]
 * 切面类
 */
public aspect MyAspectJDemo {
    /**
     * 定义切点,日志记录切点
     */
    pointcut recordLog():call(* HelloWord.sayHello(..));

    /**
     * 定义切点,权限验证(实际开发中日志和权限一般会放在不同的切面中,这里仅为方便演示)
     */
    pointcut authCheck():call(* HelloWord.sayHello(..));

    /**
     * 定义前置通知!
     */
    before():authCheck(){
        System.out.println("sayHello方法执行前验证权限");
    }

    /**
     * 定义后置通知
     */
    after():recordLog(){
        System.out.println("sayHello方法执行后记录日志");
    }
}

定义一个切面类,这样能够在执行业务代码之前和之后进行相应的方法的调用,就比如before和after,非常方便,这就是横向切片。这样的好处就是日志的方法,鉴权的方法不用写多次,可以理解为继承这个aspect之后,会触发相应的方法,那么这个方法就是pointcut,根据这个pointcut进行前后的逻辑处理。

切面Aspect的概念

切面:切面由切点和通知组成,包括了横切逻辑的定义也包括连接点的定义
joincut:这个就更好解释了,就是spring允许你是通知(Advice)的地方,那可就真多了,基本每个方法的前、后(两者都有也行),或抛出异常是时都可以是连接点,spring只支持方法连接点。和方法有关的前前后后都是连接点
pointcut:切点,上面说的连接点的基础上,来定义切入点,你的一个类里,有15个方法,那就有十几个连接点了对吧,但是你并不想在所有方法附件都使用通知(使用叫织入,下面再说),你只是想让其中几个,在调用这几个方法之前、之后或者抛出异常时干点什么,那么就用切入点来定义这几个方法,让切点来筛选连接点,选中那几个你想要的方法
weaving:织入, 把切面应用到目标对象(target)来创建新的代理对象的过程,意思就是代理对象调用被代理对象的方法时,会动态的增强该方法,通过这种代理的模式,过滤出该方法,然后进行相应的功能扩展,打日志等等。有三种方式,spring采用的是运行时
advice:通知,这个好理解,就是在哪个方法的前还是后,还是环绕的方式进行内容的增强,比如上面例子before():authCheck()就是打了一行日志

aop一般采用jdk原生方法或者gclib的方式,生成一个增强了的代理对象。

这里顺带讲一下通知,有五种:
1、before advice:目前方法调用前执行,无论目标方法是否遇到异常都会执行
2、after returning advice:目标方法调用后执行,目标方法有异常不执行
3、after throwing advice:目标方法调用后抛出异常执行,可以获取异常信息
4、after finally advice:目标方法调用后无论是否异常,都执行
5、around advice:环绕通知,可以控制目标方法执行(通过调用ProceedingJoinPoint。proceed()),可以在目标方法执行全过程中执行,也就是前后都可以加的

如何定义一个切面
1、@Component@Aspect两个注解
2、定义切点、@Pointcut(public * com.xxx.xxx.*.*(…))
修饰符可以不写,但不能用* + 返回类型 + 哪些包下的类 + 哪些方法 + 方法参数。“ * ”代表着不限方法,“…”代表参数不限
3、定义advice通知,@Before、@After、@AfterReturning、@AfterThrowing、@Around来完成某些切点的增强动作,比如某一个方法加入了这些注解,其实就是相当于在第二点中定义的目标方法的前或者后执行该增强方法,如@Before(“myPointcut()”)

// 定义日志切面类
@Aspect
@Component
public class LoggingAdvice {
	private Logger logger = LoggerFactory.getLogger(LoggingAdvice.class);
}
// 定义切点, 不限返回类型,不限参数个数,所有方法
@Pointcut(value = "execution(* com.zjx.aop.controller.*.*(..))")
public void myPointcut(){}
// 定义通知,参数为ProceedingJoinPoint只能用于环绕通知,一般是JoinPoint
@Around("mypointcut()")
public Object applicationLogger(ProceedingJoinPoint pjp) throws Throwable {
	String methodName = pjp.getSignature().getName();
	String className = pjp.getTarget().getClass().toString();
	ObjectMapper m = new ObjectMapper();
	Object[] array = pjp.getArgs();
	logger.info("before method called :" + className + ":" + methodName + " args:" + m.writeValesAsString(array));
	// 执行目标方法
	Object obj = proceedingJoinPoint,proceed();
	logger.info("after method called :" + className + ":" + methodName + " return:" + m.writeValesAsString(obj));
	return obj;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值