2021-07-30阅读小笔记:Spring AOP 之 AOP入门

1、OOP 有什么不足之处?用什么思想设计可以改善不足?

当我们需要给已有业务代码增强某方面的能力的时候,会导致出现很多的重复代码;因为 OOP 是面向对象编程的,如果每一个对象都需要增加同一个功能,那么都需要写一遍重复的功能代码。

1.1 例子

下面学生会去上课,吃饭和睡觉三个日常生活点,但是现在老师非常关心学生是否准时去做上面的事情,那么想在学生做这三件事情打印一下日志。

增强前:

@Data
@Accessors(chain = true)
public class Student implements Serializable {

    private static final long serialVersionUID = 2392021462238048699L;

    private String name;
}

public interface StudentService {

    void goToClass(Student student);

    void eat(Student student);

    void sleep(Student student);
}

public class StudentServiceImpl implements StudentService {

    @Override
    public void goToClass(Student student) {
        System.out.println(student.getName()+" 去上课了");
    }

    @Override
    public void eat(Student student) {
        System.out.println(student.getName()+" 去吃饭了");
    }

    @Override
    public void sleep(Student student) {
        System.out.println(student.getName()+" 去睡觉了");
    }
}

增强后:

public class StudentServiceEnhanceImpl implements StudentService {

    @Override
    public void goToClass(Student student) {
        System.out.println("当前时间:"+new Date()+","+student.getName()+"去上课了");
        System.out.println(student.getName()+" 去上课了");
    }

    @Override
    public void eat(Student student) {
        System.out.println("当前时间:"+new Date()+","+student.getName()+"去吃饭了");
        System.out.println(student.getName()+" 去吃饭了");
    }

    @Override
    public void sleep(Student student) {
        System.out.println("当前时间:"+new Date()+","+student.getName()+"去睡觉了");
        System.out.println(student.getName()+" 去睡觉了");
    }

    public static void main(String[] args) {
        Student student = new Student();
        student.setName("winfun");
        StudentService studentService = new StudentServiceEnhanceImpl();
        studentService.goToClass(student);
        studentService.eat(student);
        studentService.sleep(student);
    }
}

利用工具类优化:

@AllArgsConstructor
public class StudentServiceEnhanceImplV2 implements StudentService {

    @Override
    public void goToClass(Student student) {
        LogUtils.print(student.getName(),"去上课了");
        System.out.println(student.getName()+" 去上课了");
    }

    @Override
    public void eat(Student student) {
        LogUtils.print(student.getName(),"去吃饭了");
        System.out.println(student.getName()+" 去吃饭了");
    }

    @Override
    public void sleep(Student student) {
        LogUtils.print(student.getName(),"去睡觉了");
        System.out.println(student.getName()+" 去睡觉了");
    }

    public static void main(String[] args) {
        Student student = new Student();
        student.setName("winfun");
        StudentService studentService = new StudentServiceEnhanceImplV2();
        studentService.goToClass(student);
        studentService.eat(student);
        studentService.sleep(student);
    }
}

public class LogUtils {

    public static void printNow(){
        System.out.println("当前时间:" + new Date());
    }
}

1.2 改善

即使上面使用了工具类统一封装增强功能,但是每个业务方法里面还是冗余了一些非业务性的代码。

当然了,此时我们可以利用 GoF 中的某些设计模式来优化此代码。

1.2.1 装饰者模式

在行为模式中,装饰者模式可以在原有的逻辑上扩展额外的功能:我们可以理解为装饰者里组合了原目标对象,并实现了原目标对象的接口,在实现方法里对原目标对象做增强。

代码:

@AllArgsConstructor
public class StudentServiceImplDecorate implements StudentService {

    private StudentService target;

    @Override
    public void goToClass(Student student) {
        LogUtils.print(student.getName(),"去上课了");
        target.goToClass(student);
    }

    @Override
    public void eat(Student student) {
        LogUtils.print(student.getName(),"去吃饭了");
        target.eat(student);
    }

    @Override
    public void sleep(Student student) {
        LogUtils.print(student.getName(),"去睡觉了");
        target.sleep(student);
    }

    public static void main(String[] args) {
        Student student = new Student();
        student.setName("winfun");
        StudentService studentService = new StudentServiceImpl();
        StudentService studentServiceDecorate  = new StudentServiceImplDecorate(studentService);
        studentServiceDecorate.goToClass(student);
        studentServiceDecorate.eat(student);
        studentServiceDecorate.sleep(student);
    }
}

这样虽然避免了原有业务逻辑里的非业务逻辑功能代码,但是每一个需要被增强的类都需要抽一个装饰者出来,这也是非常多的重复代码,并且代码量显得更多了。

1.2.2 模版模式

在行为模式中,可以抽取逻辑还有一个就是模版方法:我们需要抽象出一个抽象类,实现原目标对象的接口,对原目标对象需要增强的方法抽象多一个子方法,后续的子类都需要重写此方法来做业务逻辑。

代码:

/**
 * 学生Service增强第四版:利用模版模式抽取增强代码
 * @author winfun
 **/
public abstract class StudentServiceTemplate implements StudentService {

    @Override
    public void goToClass(Student student) {
        LogUtils.print(student.getName(),"去上课了");
        doGoToClass(student);
    }

    protected abstract void doGoToClass(Student student);

    @Override
    public void eat(Student student) {
        LogUtils.print(student.getName(),"去吃饭了");
        doEat(student);
    }

    protected abstract void doEat(Student student);

    @Override
    public void sleep(Student student) {
        LogUtils.print(student.getName(),"去睡觉了");
        doSleep(student);
    }

    protected abstract void doSleep(Student student);

    public static void main(String[] args) {
        Student student = new Student();
        student.setName("winfun");

    }
}

/**
 * 学生Service增强第四版:模版实现类
 * @author winfun
 **/
public class StudentServiceTemplateImpl extends StudentServiceTemplate {


    @Override
    protected void doGoToClass(Student student) {
        System.out.println(student.getName()+" 去上课了");
    }

    @Override
    protected void doEat(Student student) {
        System.out.println(student.getName()+" 去吃饭了");
    }

    @Override
    protected void doSleep(Student student) {
        System.out.println(student.getName()+" 去睡觉了");
    }

    public static void main(String[] args) {
        Student student = new Student();
        student.setName("winfun");
        StudentService studentService = new StudentServiceTemplateImpl();
        studentService.goToClass(student);
        studentService.eat(student);
        studentService.sleep(student);
    }
}

我们可以发现,即使这样优化,冗余代码也是一点都没变少,甚至还变多了;并且由于使用了继承,所以如果业务实现类想继续扩展其他功能,也就无计可施了。

2、动态代理如何解决相同逻辑的重复代码?

代理模式分为静态代理和动态代理:

而静态代理需要编写代理类,组合原有的目标对象,并实现原有目标对象的接口,以此来做原有对象的方法功能的增强。这么一看,静态代理的代码和装配者模式的基本一样,所以说,如果单纯使用静态代理,和使用装配者模式基本没啥区别。

动态代理:动态代理是在运行时动态将增强逻辑类组合原有的目标对象,然后生成代理对象,以此完成对目标对象方法的功能增强。

既然静态代理有对应的动态代理,那么如果装配者模式也有动态装配者模式的话,也是可以完美完成增强的

还是上面的例子,不过这次增强是简单的日志打印,在方法执行时,打印对应的参数和方法,借此记录学生的行为和对应的时间。

增强逻辑,实现 InvocationHandler 接口。

/**
 * invocation handler
 * @author winfun
 **/
public class LogAdvisor implements InvocationHandler {

    private Object target;

    public LogAdvisor(Object target) {
        this.target = target;
    }

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

        LogUtils.print(method.getName(),args);
        return method.invoke(target,args);
    }
}

日志工具类:

/**
 * 日志工具类
 * @author winfun
 **/
public class LogUtils {

    public static void print(String action,Object... args){

        StringJoiner argsMsg = new StringJoiner(",");
        if (args.length > 0){
            for (Object arg : args) {
                argsMsg.add(arg.toString());
            }
        }
        System.out.println("当前时间:" + new Date()+",参数:"+argsMsg+",方法:"+action);
    }
}

创建代理类进行测试:

public class Application {

    public static void main(String[] args) {
        Student student = new Student();
        student.setName("winfun");
        StudentService studentService = new StudentServiceImpl();
        // 利用Proxy创建代理类,ps:不能用 StudentService.class.getXxx()
        StudentService proxy = (StudentService) Proxy.newProxyInstance(studentService.getClass().getClassLoader(), studentService.getClass().getInterfaces(), new LogAdvisor(studentService));
        proxy.goToClass(student);
        proxy.eat(student);
        proxy.sleep(student);
    }
}

运行结果:

当前时间:Thu Jul 29 22:36:53 CST 2021,参数:Student(name=winfun),方法:goToClass
winfun 去上课了
当前时间:Thu Jul 29 22:36:53 CST 2021,参数:Student(name=winfun),方法:eat
winfun 去吃饭了
当前时间:Thu Jul 29 22:36:53 CST 2021,参数:Student(name=winfun),方法:sleep
winfun 去睡觉了

3、如何理解面向切面编程?

横切面,英文表示为 Aspect ,它表示的是分布在一个 / 多个类的多个方法中的相同逻辑。利用动态代理,将这部分相同的逻辑抽取为一个独立的 Advisor 增强器,并在原始对象的初始化过程中,动态组合原始对象并产生代理对象,同样能完成一样的功能增强。在此基础上,通过指定增强的类名、方法名(甚至方法参数列表类型等),可以更细粒度的对方法增强。使用这种方式,可以在不修改原始代码的前提下,对已有任意代码的功能增强。而这种针对相同逻辑的扩展和抽取,就是所谓的面向切面编程(Aspect Oriented Programming,AOP)。

摘自 LinkedBear 作者

4、jdk 动态代理与 Cglib 动态代理有什么区别?

jdk 动态代理是基于接口来实现的,底层利用的是 Proxy 的 newInstance 方法来创建代理对象,而 Cglib 的动态代理是基于类实现的,会基于原目标对象生成子类。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值