动态代理和AOP

1、动态代理

在面向对象的思想中,一些重复的代码可以被封装成一个方法或者一个类供以后使用,那么对于一个核心业务的边缘业务,也可以进行“封装”,让这些边缘业务可以自动在核心业务运行时生效,我们只需要专注核心业务即可,这种需求可以利用代理来实现,JDK为我们提供的其中一种代理就是动态代理。

例如租房,个人可以看房和签合同,他委托中介为他寻找房源,只需要关键时候来看房签合同就可以,中介在租房后还提供售后服务,并且中介可以为任何人提供服务。这种行为就是动态代理,中介就是代理类完成非核心业务,而最终的核心业务(看房和签合同)需要借助个人即被代理类完成,所以表面看上去中介可以完成租房的所有操作。下面是代码示例:

1、首先,我们需要一个租房的核心业务接口

public interface IRentHouse {
    //核心业务是租房
    void rent();
}

2、其次,需要一个人实现租房接口

public class Tenant implements IRentHouse {
    @Override
    public void rent() {
        System.out.println("个人看房,签订租房合同。");
    }
}

3、需要一个中介进行租房代理

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

//中介需要实现JDK提供的InvocationHandler接口,实现被代理方法执行前后的拦截
public class HousingAgency implements InvocationHandler {
    //被代理的类 可以是任意类型 所以用Object定义
    private Object object;

    public HousingAgency(Object object) {
        this.object = object;
    }

    @Override
    //Object proxy JDk提供的参数 不能使用
    //Method method 被代理的方法
    //Object[] args 方法里的参数 不知道什么类型 所以用Object类型的数组
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        Object result = null;
        //这是非核心业务 
        System.out.println("中介寻找房源,联系房东。。。");
        //执行核心业务 接受可能有的返回值
        result = method.invoke(object, args);
        //这也是非核心业务
        System.out.println("中介提供售后,解决争执。。。");
        return result;
    }
}

4、通过Proxy类创建代理对象(测试)

import com.fan.spring.proxy.HousingAgency;
import com.fan.spring.service.IRentHouse;
import com.fan.spring.service.Tenant;
import org.junit.Test;

import java.lang.reflect.Proxy;

public class TestProxy {
    @Test
    public void testProxy(){
        //new Tenant()就是被代理的对象 核心业务通过这个对象来完成
        HousingAgency housingAgency=new HousingAgency(new Tenant());
        //生成具有附加功能的IRentHouse代理对象
        //Tenant.class.getClassLoader()代理对象的类加载器
        //Tenant.class.getInterfaces()代理对象实现的接口
        IRentHouse iRentHouse= (IRentHouse) Proxy.newProxyInstance(Tenant.class.getClassLoader(),
                Tenant.class.getInterfaces(),
                housingAgency);
        //执行方法
        iRentHouse.rent();
    }
}

5、执行结果

在控制台打印输出了:

中介寻找房源,联系房东。。。
个人看房,签订租房合同。
中介提供售后,解决争执。。。

6、总结

1、JDK提供的Proxy代理的对象必须要实现一个接口 从最终生成代理对象的第二个参数可以看出,必须getInterfaces()。

2、InvocationHandler接口的实现类可以代理任何对象,也就是说这个对象可以实现不同的接口,也可以是同一堆接口的不同实现类。比如说除了租房,还可以有另一个InvocationHandler接口的实现类提供打印日志的功能,所有其他类都可以用它做代理,来实现方法执行前后打印日志。

3、根据第二点,如果租房功能既需要中介完成寻找房源提供售后,有需要打印日志,那么就要再写一个InvocationHandler接口的实现类,来同时完成这些功能。也就是说InvocationHandler接口的实现类的非核心业务是固定不变的,不够灵活。

4、Proxy生成代理类十分繁琐,需要传入各种参数并且进行类型转换。

1、Spring中的AOP

在动态代理中我们发现,每个InvocationHandler只能实现一种扩展,并且生成实例很麻烦,那么有没有一种可以直接生成被代理类对象,并且可以对其中的扩展功能进行自定义组合呢?答案是可以,Spring中AOP集成了Aspect,可以方便我们进行面向切面编程。

1、关键词解释

1、连接点:

AOP中,对一个方法有五个连接点,分别是在方法执行前(Before)、方法执行后(After)、出现异常后(AfterThrowing)、返回返回值后(AfterReturning)和环绕方法(Around)。这5个连接点是AOP为我们提供的,从名称中就可以看出它们的生效范围。在某种程度上来说,一个方法本身可以看做一个大的连接点,这个在之后的代码实例中可以看出来。

2、增强(通知)

增强(或者称为通知)是切入的扩展功能,比如说打印日志、安全验证和计算执行时间等等。

3、切点

切点是相对于连接点的一个概念,在5个连接点中,我们选择进行增强的连接点被称为切点。比如我要在增删改的异常连接点(AfterThrowing)进行事务回滚,环绕(Around)进行开启事务和事务提交,那么这两个连接点的组合就被称为切点。在比如要对某个操作方法执行前(Before)进行安全验证,那么这一个连接点就被称为切点。

4、切面

切点+增强=切面。具体来说就是在哪些地方进行了哪些扩展。

2、切面代码示例

1、需要进行切入的类

注意这个类的包名com.fan.spring.service

package com.fan.spring.service;

import com.fan.spring.mapper.PersonMapper;
import com.fan.spring.pojo.Person;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

//这是一个service层,注入了Mapper对象模拟数据操作
@Service
public class PersonServiceImpl implements IPersonService {
    @Autowired
    PersonMapper personMapper;

    @Override
    public void add(Person person) {
        personMapper.add(person);
    }

    @Override
    public void delete(Integer id) {
        personMapper.delete(id);
    }

    @Override
    public void update(Person person) {
        personMapper.update(person);
    }

    @Override
    public String test() {
        System.out.println("\n有返回值的方法\n");
        return "返回了一个字符串";
    }
}

2、Spring的配置



    
    
    
    

    
    


3、切面示例

package com.fan.spring.aop;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;

@Component
//这个注解表示这是一个切面类
@Aspect
public class MyAop {
    /*execution表达式里面就是切点 关于这个表达式的具体解析可以自行在网上搜索*/
    @Pointcut("execution(public * com.fan.spring.service.*.*(..))")
    public void myPointCut() {
    }
    //在切入之前就要在5个连接点中先选择一个,然后再输入具体方法 这就是为什么说一个方法也可以看成一个连接点
    @Before("myPointCut()")
    public void before(JoinPoint joinPoint) {
        System.out.println("前置切入===>" + joinPoint.getSignature().getName());
    }

    @After("myPointCut()")
    public void after(JoinPoint joinPoint) {
        System.out.println("后置切入===>" + joinPoint.getSignature().getName());
    }

    @Around("myPointCut()")
    public Object around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        System.out.println("前环绕切入===>" + proceedingJoinPoint.getSignature().getName());
        Object obj = proceedingJoinPoint.proceed();
        System.out.println("后环绕切入===>" + proceedingJoinPoint.getSignature().getName() + "---" + obj);
        return obj;
    }

    @AfterReturning(pointcut = "myPointCut()", returning = "result")
    public void afterReturning(JoinPoint joinPoint, Object result) {
        System.out.println("返回值切入===>" + joinPoint.getSignature().getName() + "-----" + result);
    }

    @AfterThrowing(pointcut = "myPointCut()", throwing = "e")
    public void afterThrowing(JoinPoint joinPoint, Exception e) {
        System.out.println("异常切入===>" + joinPoint.getSignature().getName() + "---" + e);
    }
}

4、代码测试

@Test
public void testAop() {
    ApplicationContext ac = new ClassPathXmlApplicationContext("beans.xml");
    //获取实例后直接调用方法 不需要向动态代理一样麻烦
    PersonServiceImpl personServiceImpl = ac.getBean("personServiceImpl", PersonServiceImpl.class);
    personServiceImpl.test();
}

输出结果

前环绕切入===>test
前置切入===>test

有返回值的方法

后环绕切入===>test---返回了一个字符串
后置切入===>test
返回值切入===>test-----返回了一个字符串

@Test
public void testAop() {
    ApplicationContext ac = new ClassPathXmlApplicationContext("beans.xml");
    PersonServiceImpl personServiceImpl = ac.getBean("personServiceImpl", PersonServiceImpl.class);
    personServiceImpl.add(null);
}

输出结果

前环绕切入===>add
前置切入===>add
增加。。。
后环绕切入===>add---null
后置切入===>add
返回值切入===>add-----null

3、总结

利用连接点进行灵活选择,根据实际代码加入不同增强,这样切面的可扩展性无疑比原生的动态代理要强很多,而且实际在使用时,只需要关注核心业务即可,实例化也很简单,易用性大大增强

你可能感兴趣的:(动态代理和AOP)