【有一说一,不懂就问】之 什么是Spring的AOP机制

转载请注明出处,否则侵权必究

  • Q:什么是AOP?

AOP:Aspect Oriented Programming,面向切面编程
还有POP面向过程(C,C++),OOP面向对象(JAVA),IOP面向接口

  • Q:什么是切面?

把业务主流程比成一个纵面的话,那么一些与主业务无关的功能作为横切面插入系统各处,称之为切面。

我们需要知道的还有:

  1. 连接点(Joinpoint):程序执行的某个特殊位置,如类初始化前,方法调用前。即切面可以切入的点
    Spring仅支持方法的连接点,既仅能在方法调用前,方法调用后,方法抛出异常时等这些程序执行点进行织入增强。

  2. 切点(Pointcut):就spring来说,每个方法都有很多连接点,但是只有某个连接点满足指定要求时候,才能成为切点,即切面真正切入的地方。
    如何使用表达式定义切入点,是AOP的核心,Spring默认使用AspectJ切入点语法。

  3. 增强(Advice):即在切点做的一些事情,有“around”,“before”,“after”等类型

  4. 目标对象(Target) :被AOP框架进行增强处理的对象

  • Q:AOP的工作流程是什么样的?

AOP是把已经编译好的一段代码拿过来,在前面或后面执行一些操作,然后合并成一个文件,编译出来。

image.png

  • Q:AOP是如何配置的?

spring的配置文件


    

    
        
            

            
        
    
  • Q:execution表达式?

例: execution (* com.sample.service..*. *(..))
1、execution()::表达式主体。

2、第一个*号:表示返回类型, *号表示所有的类型。

3、包名:表示需要拦截的包名,后面的两个句点表示当前包和当前包的所有子包,com.sample.service包、子孙包下所有类的方法。

4、第二个*号:表示类名,*号表示所有的类。

5、*(..):最后这个星号表示方法名,*号表示所有的方法,后面括弧里面表示方法的参数,两个句点表示任何参数

  • Q:AOP有几种通知方式?

前置通知:在我们执行目标方法之前运行(@Before)

后置通知:在我们目标方法运行结束之后,不管有没有异常(@After)

返回通知:在我们的目标方法正常返回值后运行(@AfterReturning)

异常通知:在我们的目标方法出现异常后运行(@AfterThrowing)

环绕通知:环绕通知围绕在连接点前后,比如一个方法调用的前后。这是最强大的通知类型,能在方法调用前后自定义一些操作。

  • Q:当一个方法被多个切面切入时,执行顺序?

aspect1 和 aspect2 的执行顺序也是未定的
如何指定每个 aspect 的执行顺序

  • 实现org.springframework.core.Ordered接口,实现它的getOrder()方法
  • 给aspect添加@Order注解,该注解全称为:org.springframework.core.annotation.Order
    不管采用上面的哪种方法,都是值越小的 aspect 越先执行。
@Component
@Aspect
@Order(1)
public class FirstAspect{
    
    @Before(value="execution(* com.xj.mvc.service..*.*(..))")
    public void befor(){
        System.out.println("firstAspect");
    }
 
}


@Component
@Aspect
public class FirstAspect implements Ordered{
    
    @Before(value="execution(* com.xj.mvc.service..*.*(..))")
    public void befor(){
        System.out.println("firstAspect");
    }
 
    public int getOrder() {
        return 1;
    }
 

  • Q:AOP的实现原理?

动态代理

  • Q:什么叫代理?

Proxy
通俗的说,让别人帮助你做你并不关心的事情,叫做代理,生活中常见的如中介
代理模式的定义:给某一个对象提供一个代理,并由代理对象控制对原对象的引用

代理模式的通用类图


image.png

Subject:抽象主题角色,可以是抽象类,也可以是接口
RealSubject:具体主题角色,也叫做被委托角色或被代理角色,是业务逻辑的具体执行者
Proxy:代理主题角色,也叫做委托类或代理类,负责对真实角色的应用,把所有抽象主题定义的方法委托给真实主题角色实现
(太拗口了!)
举个例子:我们通过手机APP买火车票,我们知道APP并不卖票,只有火车站才真正卖票,APP只是代理,APP买的票其实是到火车站买的
那么,我们就是客户,APP就是代理角色,火车站是具体主题角色,卖票称为抽象主题角色

/**
 * 抽象主题角色
 */
public interface ISubject {
    //买票
    void buyTicket();
}

/**
 * 具体主题角色
 */
public class RealSubjectImpl   implements ISubject {
   //去火车站售票点买
    @Override
    public void buyTicket() {
        System.out.println("买火车票!");
    }
}
/**
 * 代理角色 APP
 */
public class ProxySubjectImpl  implements ISubject {

    private ISubject real;
    //构造注入,注入具体主题类,构造函数传入的是接口类型的参数,是因为不知道具体的具体主题类是哪一个,所以传一个接口类型的
    public ProxySubjectImpl(ISubject real) {
        this.real = real;
    }

    public void before(){
        System.out.println("app付钱!");
    }

    @Override
    public void buyTicket() {
        before();
        this.real.buyTicket();
    }
}

public class ProxyTest {

    /**代理模式
     * @param args
     */
    public static void main(String[] args) {
        //实现了APP代理买火车票
        ISubject sub = new ProxySubjectImpl(new RealSubjectImpl());
        sub.buyTicket();
    }

}

打印结果:

app付钱!
买火车票!

  • Q:什么叫静态代理?与动态代理的区别

动态代理和静态代理都是对目标方法进行增强,而且让增强的动作和目标动作分开,达到解耦的目的
静态代理:
显示声明代理对象,在编译期间就生成了代理类
由程序员创建或特定工具自动生成源代码,再对其编译。在程序运行之前,代理类的类文件就已经创建好了
优点:简单,实用,高效
缺点:
1.由于静态代理中的代理类是针对某一个类去做代理的,假设系统中有一百个service,则需要创建100个代理类
2.如果一个service中有很多方法需要事务,发现代理对象中的方法有很多重复代码。
即重用性不强

动态代理:
在程序运行时通过反射机制动态的创建代理类
优点:灵活,减少代码冗余
缺点:效率较低

  • Q:动态代理的实现
  • 基于JDK实现:
    jdk动态代理是JRE提供给我们的类库,可以直接使用,不依赖第三方
    利用反射机制生成一个实现代理接口的匿名类,在调用具体方法前调用InvokeHandler来处理
    中间主要使用到了一个接口InvocationHandler与Proxy.newProxyInstance静态方法
//接口 抽象主题角色
public interface PersonService {

    public String savePerson();

    public void upodatePerson();

    public void deletePerson();

}



//具体主题角色
public class PersonServiceImpl implements PersonService{
    @Override
    public String savePerson() {
        System.out.println("添加");
        return "保存成功!";
    }

    @Override
    public void upodatePerson() {
        System.out.println("修改");
    }

    @Override
    public void deletePerson() {
        System.out.println("删除");
    }
}



//增强类
public class MyTransaction {

    public void beginTransaction(){
        System.out.println("开启事务");
    }

    public void commit(){
        System.out.println("提交事务");
    }
}


//动态代理类
public class PersonServiceInterceptor implements InvocationHandler {
    //目标类
    private Object target;

    //增强类
    private MyTransaction myTransaction;

    //构造函数注入目标类和增强类
    public PersonServiceInterceptor(Object target, MyTransaction myTransaction) {
        this.target = target;
        this.myTransaction = myTransaction;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        this.myTransaction.beginTransaction();
        Object returnValue = method.invoke(this.target,args);
        this.myTransaction.commit();
        return returnValue;
    }
}



public class ProxyTest {

    public static void main(String[] args) {
        //目标类 具体主题角色
        Object target = new PersonServiceImpl();
        //增强类
        MyTransaction myTransaction = new MyTransaction();
       //组装
        PersonServiceInterceptor interceptor = new PersonServiceInterceptor(target,myTransaction);
        PersonService personService = (PersonService) Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(),interceptor);
        personService.upodatePerson();

        System.out.println(personService instanceof Proxy);
    }

}


//运行结果
开启事务
修改
提交事务
true

使用内置的Proxy实现动态代理有一个问题:被代理的类必须实现接口,未实现接口则没办法完成动态代理
如果项目中有些类没有实现接口,则不应该为了实现动态代理而刻意去抽出一些没有实例意义的接口,通过cglib可以解决该问题。

  • CGLIB代理
    强制使用CGlib


是cglib的jar包实现的
cglib动态代理产生的代理对象是目标对象的子类,并覆盖其中的方法,这种通过继承类的实现方式,不能代理final修饰的类。
代码就不贴了,就是Proxy动态代理类实现MethodInterceptor接口(net.sf.cglib.proxy.MethodInterceptor),重写intercept方法调用methodProxy.invoke(targetObject, args)方法

  • 总结
    1.JDK代理使用的是反射机制实现aop的动态代理,CGLIB代理使用字节码处理框架asm,通过修改字节码生成子类。所以jdk动态代理的方式创建代理对象效率较高,执行效率较低,cglib创建效率较低,执行效率高;
    2.JDK动态代理机制是委托机制,具体说动态实现接口类,在动态生成的实现类里面委托hanlder去调用原始实现类方法,CGLIB则使用的继承机制,具体说被代理类和代理类是继承关系,所以代理类是可以赋值给被代理类的,如果被代理类有接口,那么代理类也可以赋值给接口。

你可能感兴趣的:(【有一说一,不懂就问】之 什么是Spring的AOP机制)