Spring中的AOP

什么是AOP

Aspect Oriented Programming 的缩写,翻译为:面向切面编程。
利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。

用一个例子解释下AOP,我们现在有一个完整的登录功能代码,但我们想在加入一个权限控制,在原始方式,我们需要去修改源代码,加入具体逻辑,不需要的时候还需要逐行修改,很麻烦。现在我们利用AOP,先写一个权限管理的模块,通过AOP的方式加入到源代码中,不需要的时候把,对该模块的使用删掉即可,很方便。

在这里插入图片描述

使用AOP主要意图就是:
将日志记录,性能统计,安全控制,事务处理,异常处理等代码从业务逻辑代码中划分出来,通过对这些行为的分离,我们希望可以将它们独立到非指导业务逻辑的方法中,进而改变这些行为的时候不影响业务逻辑的代码。

AOP底层原理

AOP底层使用动态代理

1. 第一种情况,有接口,使用 JDK动态代理

在这里插入图片描述

简单编写代码

写一个接口和实现类

public interface UserDao {
    int add(int a,int b);
    String select(String id);
}
public class UserDaoImpl implements UserDao {
    @Override
    public int add(int a, int b) {
        return a+b;
    }

    @Override
    public String select(String id) {
        return id;
    }
}

在这里插入图片描述

public class JdkProxy {
    public static void main(String[] args) {
        Class[] interfaces={UserDao.class};
        UserDao userDao = new UserDaoImpl();
        UserDao userDaoProxy =(UserDao) Proxy.newProxyInstance(JdkProxy.class.getClassLoader(), interfaces, new UserDaoHolder(userDao));
        int res = userDaoProxy.add(1, 2);
    }
}

class UserDaoHolder implements InvocationHandler{
    //创建谁的代理对象,就把谁传过来
    private Object obj;
    //有参构造传递
    public UserDaoHolder(Object obj){
        this.obj = obj;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

        //方法执行前
        System.out.println("方法执行前....."+method.getName()+"传递的参数:...."+ Arrays.toString(args));
        //被增强的方法执行
        Object res = method.invoke(obj, args);
        System.out.println(res.toString());
        //方法执行后
        System.out.println("方法执行后....."+obj);

        return res;
    }
}

2. 第二种情况,没有接口,使用CGLIB动态代理
在这里插入图片描述

CGLIB动态代理

我们了解到,“代理”的目的是构造一个和被代理的对象有同样行为的对象,一个对象的行为是在类中定义的,对象只是类的实例。所以构造代理,不一定非得通过持有、包装对象这一种方式。

通过“继承”可以继承父类所有的公开方法,然后可以重写这些方法,在重写时对这些方法增强,这就是cglib的思想。根据里氏代换原则(LSP),父类需要出现的地方,子类可以出现,所以cglib实现的代理也是可以被正常使用的。

先看下代码

package proxy;
 
import java.lang.reflect.Method;
 
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
 
public class CglibProxy implements MethodInterceptor
{
    // 根据一个类型产生代理类,此方法不要求一定放在MethodInterceptor中
    public Object CreatProxyedObj(Class<?> clazz)
    {
        Enhancer enhancer = new Enhancer();
        
        enhancer.setSuperclass(clazz);
        
        enhancer.setCallback(this);
        
        return enhancer.create();
    }
    
    @Override
    public Object intercept(Object arg0, Method arg1, Object[] arg2, MethodProxy arg3) throws Throwable
    {
        // 这里增强
        System.out.println("收钱");
        
        return arg3.invokeSuper(arg0, arg2);
    } 
}

从代码可以看出,它和jdk动态代理有所不同,对外表现上看CreatProxyedObj,它只需要一个类型clazz就可以产生一个代理对象, 所以说是“类的代理”,且创造的对象通过打印类型发现也是一个新的类型。不同于jdk动态代理,jdk动态代理要求对象必须实现接口(三个参数的第二个参数),cglib对此没有要求。

cglib的原理是这样,它生成一个继承B的类型C(代理类),这个代理类持有一个MethodInterceptor,我们setCallback时传入的。 C重写所有B中的方法(方法名一致),然后在C中,构建名叫“CGLIB”+“ 父 类 方 法 名 父类方法名 ”的方法(下面叫cglib方法,所有非private的方法都会被构建),方法体里只有一句话super.方法名(),可以简单的认为保持了对父类方法的一个引用,方便调用。

这样的话,C中就有了重写方法、cglib方法、父类方法(不可见),还有一个统一的拦截方法(增强方法intercept)。其中重写方法和cglib方法肯定是有映射关系的。

C的重写方法是外界调用的入口(LSP原则),它调用MethodInterceptor的intercept方法,调用时会传递四个参数,第一个参数传递的是this,代表代理类本身,第二个参数标示拦截的方法,第三个参数是入参,第四个参数是cglib方法,intercept方法完成增强后,我们调用cglib方法间接调用父类方法完成整个方法链的调用。

这里有个疑问就是intercept的四个参数,为什么我们使用的是arg3而不是arg1?

    @Override
    public Object intercept(Object arg0, Method arg1, Object[] arg2, MethodProxy arg3) throws Throwable
    {
        System.out.println("收钱");
        
        return arg3.invokeSuper(arg0, arg2);
    }

因为如果我们通过反射 arg1.invoke(arg0, …)这种方式是无法调用到父类的方法的,子类有方法重写,隐藏了父类的方法,父类的方法已经不可见,如果硬调arg1.invoke(arg0, …)很明显会死循环。

所以调用的是cglib开头的方法,但是,我们使用arg3也不是简单的invoke,而是用的invokeSuper方法,这是因为cglib采用了fastclass机制,不仅巧妙的避开了调不到父类方法的问题,还加速了方法的调用。

fastclass基本原理是,给每个方法编号,通过编号找到方法执行避免了通过反射调用。

对比JDK动态代理,cglib依然需要一个第三者分发请求,只不过jdk动态代理分发给了目标对象,cglib最终分发给了自己,通过给method编号完成调用。cglib是继承的极致发挥,本身还是很简单的,只是fastclass需要另行理解。

JDK动态代理和CGLIB的区别

DK动态代理只能对实现了接口的类生成代理,而不能针对类。

CGLIB是针对类实现代理,主要是对指定的类生成一个子类,覆盖其中的方法,并覆盖其中方法实现增强,但是因为采用的是继承,所以该类或方法最好不要声明成final, 对于final类或方法,是无法继承的

使用CGLib实现动态代理,CGLib底层采用ASM字节码生成框架,使用字节码技术生成代理类,在jdk6之前比使用Java反射效率要高。唯一需要注意的是,CGLib不能对声明为final的方法进行代理,因为CGLib原理是动态生成被代理类的子类

在jdk6、jdk7、jdk8逐步对JDK动态代理优化之后,在调用次数较少的情况下,JDK代理效率高于CGLIB代理效率,只有当进行大量调用的时候,jdk6和jdk7比CGLIB代理效率低一点,但是到jdk8的时候,jdk代理效率高于CGLIB代理,总之,每一次jdk版本升级,jdk代理效率都得到提升,而CGLIB代理消息确有点跟不上步伐Spring如何选择用

JDK还是CGLIB?

  • 当Bean实现接口时,Spring就会用JDK的动态代理。
  • 当Bean没有实现接口时,Spring使用CGlib是实现。

为什么继承只能使用CGLib,因为JDK代理生成的代理类,默认会继承一个类,由于java是单继承,所以当原始类继承一个类的时候,只能使用CGLib动态代理

总结

  • 如果目标对象实现了接口,默认情况下会采用JDK的动态代理实现AOP。
  • 如果目标对象实现了接口,可以强制使用CGLIB实现AOP。
  • 如果目标对象没有实现了接口,必须采用CGLIB库,Spring会自动在JDK动态代理和CGLIB之间转换

JDK代理是不需要第三方库支持,只需要JDK环境就可以进行代理,使用条件:

  • 实现InvocationHandler

  • 使用Proxy.newProxyInstance产生代理对象

  • 被代理的对象必须要实现接口

CGLib必须依赖于CGLib的类库,但是它需要类来实现任何接口代理的是指定的类生成一个子类,

AOP官方术语

  1. 连接点
  2. 切入点
  3. 通知(增强)
  4. 切面
    在这里插入图片描述

AOP操作

引入AOP相关依赖

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

切入点表达式
execution(【权限修饰符】 【 返回类型 】【类全路径】 【方法名称】(【参数列表】))

对UserDaoImpl中的add方法进行增强
execution(* com.aop.mapper.impl.UserDaoImpl.add(…))

对UserDaoImpl中的所有方法进行增强
execution(* com.aop.mapper.impl.UserDaoImpl.*(…))

对mapper包中的add方法进行增强
execution(* com.aop.mapper. * . *(…))

1. 使用注解方式实现

<?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
       https://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="com.aop"/>
     <!--开启Aspectj生成代理对象-->
    <aop:aspectj-autoproxy/>
</beans>

原始的类

@Component
public class User {
    publicvoid add(){
        System.out.println("User中的add方法");
    }
    public void update(){
        System.out.println("User中的update方法");
    }
}

增强的类

@Component
@Aspect
public class UserProxy {

    //公共切入点提取
    @Pointcut(value = "execution(* com.aop.entity.User.add(..))")
    public void pointCutAdd(){

    }
    @Pointcut(value = "execution(* com.aop.entity.User.update(..))")
    public void pointCutUpdate() {

    }

    //前置方法
    // @Before注解表示前置通知
    @Before(value = "pointCutAdd()")
    public void beforeUser(){
        System.out.println("方法执行前。。。。。。。");
    }

    //后置方法
    // @After注解表示前置通知
    @After(value = "execution(* com.aop.entity.User.add(..))")
    public void afterUser(){
        System.out.println("方法执行后。。。。。。。");
    }

    @AfterReturning(value = "execution(* com.aop.entity.User.add(..))")
    public void afterUserReturning(){
        System.out.println("afterUserReturning。。。。。。。");
    }

    @AfterThrowing(value = "execution(* com.aop.entity.User.add(..))")
    public void afterUserThrowing(){
        System.out.println("afterUserThrowing。。。。。。。");
    }

    //环绕通知
    @Around(value = "pointCutUpdate()")
    public void around(ProceedingJoinPoint point) throws Throwable {
        System.out.println("环绕里方法执行前。。。。。。。");

        //被增强的方法执行
        point.proceed();
        Object[] args = point.getArgs();//拿到被增强方法的参数
        System.out.println(Arrays.toString(args));

        System.out.println("环绕里方法执行后。。。。。。。");
    }

}

测试

	@Test
    public void testAop() {
        ApplicationContext context = new ClassPathXmlApplicationContext("TestBean1.xml");
        User user = context.getBean("user", User.class);
        user.add();
        user.update();
    }

注意

有多个类对同一方法进行增强时,在增强类上加上注解 @Order(数字类型值),数字类型值越小优先级越高。

2. 基于xml方式实现

同样准备一个原始类,一个增强类

public class Book {
    public void buy() {
        System.out.println("Book中的buy方法");
    }
}
public class BookProxy {
    public void before() {
        System.out.println("方法执行前");
    }
}

配置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
       https://www.springframework.org/schema/context/spring-context.xsd
       http://www.springframework.org/schema/aop
       https://www.springframework.org/schema/aop/spring-aop.xsd">
    <!--对象注入-->
    <bean id="book" class="com.aop.entity.Book"/>
    <bean id="bookProxy" class="com.aop.entity.BookProxy"/>
    <!--配置aop增强-->
    <aop:config>
        <!--切入点-->
        <aop:pointcut id="myPointCut" expression="execution(* com.aop.entity.Book.buy(..))"/>
        <!--配置切面,就是把增强配置到切入点-->
        <aop:aspect ref="bookProxy">
            <!--增强要应用在具体的方法上-->
            <aop:before method="before" pointcut-ref="myPointCut"/>
        </aop:aspect>
    </aop:config>

</beans>

测试

	@Test
    public void testAopXml() {
        ApplicationContext context = new ClassPathXmlApplicationContext("TestBean1.xml");
        Book book = context.getBean("book", Book.class);
        book.buy();
    }
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值