通俗易懂的Aop底层原理及Aop使用


介绍

全称为Aspect Oriented Programming,面向切面编程(横向编程),是通过预编译方式和运行期间动态代理实现程序功能的唯一维护的一种技术。利用Aop可以对业务逻辑的各个部分进行隔离,从而使的业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率
可以通过预编译方式和运行期动态代理实现在不修改源代码的情况下给程序动态统一添加某种特定功能的一种技术

用途

  • 日志记录
  • 性能统计
  • 安全控制
  • 事务处理
  • 异常处理

aop影响的是service层

Aop底层实现

jdk动态代理

要求目标类必须至少实现了一个接口,才可以使用jdk动态代理,生成的代理对象地址中$proxy…

public class JDKProxyUserDao {


    public static <T> T getProxyObject(Class<T> tClass) {

        try {
            final Object obj = tClass.newInstance();
            T t = (T) Proxy.newProxyInstance(
                    JDKProxyUserDao.class.getClassLoader(),
                    tClass.getInterfaces(),
                    new InvocationHandler() {
                        @Override
                        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                            long startTime = System.currentTimeMillis();
                            method.invoke(obj, args);
                            long endTime = System.currentTimeMillis();
                            System.out.println("执行时间为" + (endTime - startTime) + "秒");
                            return null;
                        }
                    }
            );

            return t;
        } catch (InstantiationException e) {
            throw new RuntimeException(e);
        } catch (IllegalAccessException e) {
            throw new RuntimeException(e);
        }

    }

}

cglib字节码生成

要求目标类必须继承了别的类(Object),生成的地址栏中至少有 E n h a n c e r Enhancer Enhancer
原理:子类重写父类方法进行操作的,将目标类设置为子类对象,去重写父类方法,生成代理对象

public class CglibProxyUserDao {


    public static <T> T getProxyObject(Class<T> tClass){

        try {
            final Object obj = tClass.newInstance();
            //使用cglib核心类
            Enhancer enhancer = new Enhancer();
            //cglib核心类中有方法:setSuperClass
            enhancer.setSuperclass(tClass);
            //子类重写父类方法的操作
            enhancer.setCallback(new MethodInterceptor() {
                @Override
                public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
                    long startTime = System.currentTimeMillis();
                    method.invoke(obj,objects);
                    long endTime = System.currentTimeMillis();
                    System.out.println("执行时间为" + (endTime - startTime) + "秒");
                    return null;

                }
            });

            //创建子类对象
            T proxyObject =(T)enhancer.create();

            return proxyObject;
        } catch (InstantiationException e) {
            throw new RuntimeException(e);
        } catch (IllegalAccessException e) {
            throw new RuntimeException(e);
        }
    }
}

半自动Aop

核心知识点

  • target:目标类–>如UserServiceImpl.class需要扩展的类
  • joinpoint:连接点–>目标类中所有的方法
  • pointcut:切入点–>insertUser,连接点中的某个要扩展的方法(要增强的方法)
  • advisor:增强(通知)–>切入点需要进行的扩展
  • weaver:织入–>将增强应用到切入点的过程
  • proxy:代理对象–>织入以后生成的代理对象
  • aspect:切面–>多个连接点连成一个面

AOP增强分类

  • 前置增强:在目标方法执行前进行增强
  • 后置增强:在目标方法执行后进行增强
  • 环绕增强:前后都增强
  • 异常增强:抛出异常后进行功能增强
  • 引介增强:可以为目标类增强方法和属性(基本不用)
try{
    //前置
        //目标方法
    //后置
}catch{
    //异常增强
}

实现

xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context.xsd">

    <context:component-scan base-package="cn.ry.dao,cn.ry.service"/>

<!--    半自动aop进行配置-->
<!--    1.将目标类交给spring容器来管理-->
    <bean id="service" class="cn.ry.service.impl.UserServiceImpl"></bean>
<!--    3.将增强类交给spring容器管理-->
    <bean id="advisor1" class="cn.ry.advisor.Advisor1"></bean>
<!--    2.依赖于代理工厂类进行配置-->
    <bean id="proxy" class="org.springframework.aop.framework.ProxyFactoryBean">
<!--        target:属性指的是目标类-->
        <property name="target" ref="service"></property>
<!--        向代理工厂类注入增强-->
        <property name="interceptorNames" value="advisor1"></property>
<!--        表示的是是否强制使用cglib动态代理  true:强制   false:不是-->
        <property name="optimize" value="true"></property>

    </bean>



</beans>

增强类advisor

//模拟半自动aop的环绕增强:不同增强接口不同
public class Advisor1 implements MethodInterceptor {

    @Override
    public Object invoke(MethodInvocation methodInvocation) throws Throwable {
        //编写之前增强的内容
        long before = System.currentTimeMillis();
        methodInvocation.proceed();
        long  after= System.currentTimeMillis();
        System.out.println("执行时间为"+(after-before)+"ms");
        return null;
    }
}

总结

优点

代替了jdk动态代理和cglib字节码生成两种方式,通过Spring的ProxyFactoryBean实体类中的target属性和interceptorNames属性将目标类和增强类注入其中,使其生效

缺点

  • 每一个bean都需要配置一个ProxyFactoryBean这样的配置,很麻烦
  • 增强类需要实现不同的接口,增加了耦合度
  • 同种类型bean在容器中有多个
  • 织入过程没看到,需要将增强和目标类注入到ProxyFactoryBean中,手动织入
  • 没办法找到具体的切入点,是对目标类中所有的方法进行增强,不合理

传统Aop

添加jar

需要添加jar:aopalliance.jar和aspectjweaver.jar

<dependency>
      <groupId>aopalliance</groupId>
      <artifactId>aopalliance</artifactId>
      <version>1.0</version>
    </dependency>

    <dependency>
      <groupId>org.aspectj</groupId>
      <artifactId>aspectjweaver</artifactId>
      <version>1.9.4</version>
    </dependency>

实现

xml

需要引入aop命名空间以及约束

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context.xsd 
        http://www.springframework.org/schema/aop 
        https://www.springframework.org/schema/aop/spring-aop.xsd">

    <context:component-scan base-package="cn.ry.dao,cn.ry.service"/>

<!--    半自动aop进行配置-->
<!--    1.将目标类交给spring容器来管理-->
    <bean id="service" class="cn.ry.service.impl.UserServiceImpl"></bean>
<!--    2.将增强类交给spring容器管理-->
    <bean id="advisor1" class="cn.ry.advisor.Advisor1"></bean>

<!--aop配置 主动确定切入点和织入-->
<!-- proxy-target-class为false的时候默认是jdk动态代理;为true的时候,强制使用cglib动态代理   -->
    <aop:config proxy-target-class="false">

<!--        配置aop的切入点
            id值的是唯一标识;
            expression指的是切入点表达式:
                    execution(* cn.rn.service.impl.UserServiceImpl.方法名字(..))
                    格式:修饰符  返回结果  包名.类名.方法名(形参)
                    修饰符一般省略
                    返回结果:无返回是void  *代表的是任意类型
                    cn.ry.*.impl.*.  cn.ry.下面的所有dao和service的实现类中的所有类都被选中;
 					方法: select*(..)  表示的是所有select开头的方法;-->
<!--        <aop:pointcut id="pt1" expression="execution(* cn.ry.service.impl.*.insert*(..))"/>-->

<!--        <aop:advisor advice-ref="advisor1" pointcut-ref="pt1"></aop:advisor>-->


        <aop:advisor advice-ref="advisor1" pointcut="execution(* cn.ry.service.impl.*.insert*(..))"/>
    </aop:config>

</beans>

增强类不变

总结

优点

  • 采用切入点表达式,可以找到具体的需要增强的方法,从而进行增强
  • 采用了自动织入的方式,省略了ProxyFactoryBean的配置

缺点

java代码并未改动,还存在与半自动Aop同样的耦合问题

基于AspectJ的Aop

介绍

AspectJ是一个面向切面的框架,它扩展了Java语言。AspectJ定义了AOP语法,它有一个专门的编译器用来生成遵守Java字节编码规范的Class文件。

添加依赖

 <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-aspects</artifactId>
      <version>5.2.10.RELEASE</version>
    </dependency>

增强类型

  • 前置
  • 后置
  • 环绕
  • 异常
  • 引介
  • 最终
try{
    //前置
        //目标方法
    //后置
}catch{
    //异常
}finally{
    //最终
}

核心特点

增强类本身不依赖于任何框架,也就是不需要实现任何接口,普通的java类也能作为增强类

实现

xml实现

  1. 增强类
public class Advisor2 {

    public Object around(ProceedingJoinPoint pjp) throws Throwable {
        long before = System.currentTimeMillis();
        Object obj = pjp.proceed();
        long  after= System.currentTimeMillis();
        System.out.println("执行时间为"+(after-before)+"ms");
        return obj;
    }

    public void before(JoinPoint jp){
        String name = jp.getSignature().getName();
        System.out.println("before ---------------" + name);
    }

    public void afterRunning(JoinPoint jp,Object obj){
        System.out.println("afterRunning---------------" + obj);
    }

    public void afterThrowing(JoinPoint jp,Throwable ex){
        System.out.println("afterThrowing---------------" + ex.getMessage());
    }

    public void after(){
        System.out.println("after 最终增强------");
    }
}

  1. xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd">

    <context:component-scan base-package="cn.ry.dao,cn.ry.service"/>

<!--    半自动aop进行配置-->
<!--    1.将目标类交给spring容器来管理-->
    <bean id="service" class="cn.ry.service.impl.UserServiceImpl"></bean>
<!--    2.将增强类交给spring容器管理-->
    <bean id="advisor2" class="cn.ry.advisor.Advisor2"></bean>

<!--aop配置 主动确定切入点和织入-->
<!-- proxy-target-class为false的时候默认是jdk动态代理;为true的时候,强制使用cglib动态代理   -->
    <aop:config proxy-target-class="false">

<!--        配置aop的切入点
            id值的是唯一标识;
            expression指的是切入点表达式:
                    execution(* cn.rn.service.impl.UserServiceImpl.方法名字(..))
                    格式:修饰符  返回结果  包名.类名.方法名(形参)
                    修饰符一般省略
                    返回结果:无返回是void  *代表的是任意类型
                    cn.ry.*.impl.*.  cn.ry.下面的所有dao和service的实现类中的所有类都被选中;
 					方法: select*(..)  表示的是所有select开头的方法;-->

        <aop:aspect ref="advisor2">

            <aop:pointcut id="pt2" expression="execution(* cn.ry.service.impl.*.*(..))"/>

<!--            环绕增强  method为方法名,环绕增强的方法返回值为Object-->
            <aop:around method="around" pointcut-ref="pt2"></aop:around>

            <aop:before method="before" pointcut-ref="pt2"/>

            <aop:after-returning method="afterRunning" pointcut-ref="pt2" returning="obj"/>

            <aop:after-throwing method="afterThrowing" pointcut-ref="pt2" throwing="ex"/>

            <aop:after method="after" pointcut-ref="pt2"/>

        </aop:aspect>

    </aop:config>

</beans>

注解实现

  • 需要在xml中开启包扫描
  • 设置aspectj自动代理

xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd">

<!--    包扫描-->
    <context:component-scan base-package="cn.ry.dao,cn.ry.service,cn.ry.advisor"/>


<!--  开启aop自动代理  -->
    <aop:aspectj-autoproxy proxy-target-class="false"/>

</beans>

增强类

@Aspect
@Component
public class Advisor3 {

    //主要是相当于xml中配置切入点:
    //value中写的是expression表达式
    @Pointcut(value = "execution(* cn.ry.service.impl.*.*(..))")
    public void pt(){}

    //环绕增强必须有返回值;
    //环绕增强参数必须有ProceedingJoinPoint
    //value值中也能直接写expression表达式
    @Around(value = "pt()")
    public Object around(ProceedingJoinPoint pjp) throws Throwable {
        long before = System.currentTimeMillis();
        Object obj = pjp.proceed();
        long  after= System.currentTimeMillis();
        System.out.println("执行时间为"+(after-before)+"ms");
        return obj;
    }

    //前置增强的方法:
    //方法可以有参;可以无参
    //如果有参只能是JoinPoint:主要用来获取目标方法名字;
    @Before(value = "pt()")
    public void before(JoinPoint jp){
        String name = jp.getSignature().getName();
        System.out.println("before ---------------" + name);
    }

    //后置增强:
    //后置增强的参数:
    //第一个参数JoinPoint
    //第二个参数用来接收目标方法的返回结果,放到第二参数中;
    @AfterReturning(value = "pt()",returning = "obj")
    public void afterRunning(JoinPoint jp,Object obj){
        System.out.println("afterRunning---------------" + obj);
    }

    //异常增强
    @AfterThrowing(value = "pt()",throwing = "ex")
    public void afterThrowing(JoinPoint jp,Throwable ex){
        System.out.println("afterThrowing---------------" + ex.getMessage());
    }


    //最终增强
    @After(value = "pt()")
    public void after(){
        System.out.println("after 最终增强------");
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

@Zeal

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值