Interviewer: apart from Spring, how to implement Spring AOP by yourself?

|Introduction

Open to-do, and note that the authentication allows you to explain the agent mode.

I happen to encounter a question like this: how to implement Spring AOP without Spring?

I like such questions. I can PK those who never think about adding, deleting, changing and checking every day. Today, I will learn everything from you about proxy mode and Spring AOP.

|Agent and decorator

Scene description

Proxy, which means substitution, can replace all functions, that is, it implements the same specification as the original class.

The agent mode is very similar to the decorator mode. The previous decorator is not very good. Let's talk about it again with another example.

In a quiet afternoon, I came to the cafe and wanted to have a cup of coffee.

Basic implementation

Give you a coffee interface:

public interface Coffee {

    /**
     * Print the raw materials of the current coffee, i.e. what is in the coffee
     */
    void printMaterial();
}

A default bitter coffee implementation:

public class BitterCoffee implements Coffee {

    @Override
    public void printMaterial() {
        System.out.println("Coffee");
    }
}

Default ordering logic:

public class Main {

    public static void main(String[] args) {
        Coffee coffee = new BitterCoffee();
        coffee.printMaterial();
    }
}

Order a cup of coffee.

Decorator mode

The elegant waiter brought up the coffee and took a sip. It was bitter.

To add some sugar, he said to the waiter, "Hello, please add some sugar to my coffee.".

/**
 * Sugar decorator for adding sugar to coffee
 */
public class SugarDecorator implements Coffee {

    /**
     * Coffee object held
     */
    private final Coffee coffee;

    public SugarDecorator(Coffee coffee) {
        this.coffee = coffee;
    }

    @Override
    public void printMaterial() {
        System.out.println("sugar");
        this.coffee.printMaterial();
    }
}

Then the waiter took my coffee, used sugar decorator to add sugar to the coffee, and finally gave me the coffee with sugar.

public class Main {

    public static void main(String[] args) {
        Coffee coffee = new BitterCoffee();
        coffee = new SugarDecorator(coffee);
        coffee.printMaterial();
    }
}

Take a look at the ingredients of coffee. Yes, it does add sugar!

Look at these two lines:

Coffee coffee = new BitterCoffee();        // Ordered a cup of bitter coffee
coffee = new SugarDecorator(coffee);       // Add some sugar to the coffee

What scene is the decorator mode suitable for? I have an object, but the function of this object can't satisfy me. I'll take the decorator to decorate it.

proxy pattern

At the weekend, I came to the cafe with my iPad, ready to enjoy a quiet afternoon.

"What would you like to drink, sir?" asked the polite waiter.

The coffee I ordered last time was too bitter. I'll take one with sugar this time.

"I'd like a cup of coffee with sugar."

public class CoffeeWithSugar implements Coffee {

    private final Coffee coffee;

    public CoffeeWithSugar() {
        this.coffee = new BitterCoffee();
    }

    @Override
    public void printMaterial() {
        System.out.println("sugar");
        this.coffee.printMaterial();
    }
}

This is sweetened coffee. In fact, it is still coffee inside. Just adding some formulas, it produces a new category, a new drink that can be presented on the menu.

Order coffee:

public class Main {

    public static void main(String[] args) {
        Coffee coffee = new CoffeeWithSugar();
        coffee.printMaterial();
    }
}

Just to my liking, I spent a beautiful afternoon with coffee.

difference

After the story is finished, both realize the packaging of the original object and hold the instance of the original object. The difference lies in the external performance.

Decorator mode: after ordering coffee, I found it too bitter and not what I wanted, and then added some sugar with the decorator.

Coffee coffee = new BitterCoffee();
coffee = new SugarDecorator(coffee);

Agent mode: directly order sweetened coffee.

Coffee coffee = new CoffeeWithSugar();

Very subtle differences. I hope you don't confuse them.

criticism

Go to see the information related to the agency model. There are all kinds of information about how to understand it.

I think the proxy mode in the rookie tutorial is the most authentic: in the proxy mode, we create objects with existing objects to provide functional interfaces to the outside world.

In addition, many design pattern articles on the Internet are copied by you, me and you. One is wrong, all are wrong.

I think I need to correct it. Who said that the proxy mode must use the interface? Agent mode is a design mode. Design mode does not distinguish between languages. If there is no interface in a language, can it not be agent mode? Only the interface in Java allows us to develop in accordance with the principle of dependency inversion and reduce coupling. Can I use abstract classes? sure. Is class inheritance OK? it's fine too.

Thought understand, what to write is not like playing?

| AOP

Design pattern is an idea, so the proxy pattern I mentioned above is not only applicable to interfaces, but also closely related to Spring AOP.

AOP: Aspect Oriented Programming is a supplement to object-oriented programming. If you don't understand this sentence, learn object-oriented and you'll know why.

We will declare facets, that is, they are executed before, after, or before and after a method. The implementation of Spring AOP is the proxy pattern.

scene

Just recently wrote a text message verification code. Take this as an example.

public interface SMSService {

    void sendMessage();
}
public class SMSServiceImpl implements SMSService {

    @Override
    public void sendMessage() {
        System.out.println("[Mengyunzhi] you are resetting your password. Your verification code is 1234. It is valid within 5 minutes. Please do not forward the verification code to others.");
    }
}

Main function:

public class Main {

    public static void main(String[] args) {
        SMSService smsService = new SMSServiceImpl();
        smsService.sendMessage();
        smsService.sendMessage();
    }
}

Cost statistics

The boss changed his demand. Sending verification codes costs money. The boss wants to see how much money he spent on text messages.

Normally, according to the idea of Spring, it must be to declare a section to cut and send SMS, and then count the SMS cost in the section.

But now there is no framework, that is, the question: how to implement Spring AOP without Spring?

It's natural to think more about writing a framework. The agent I mentioned above is a static agent, which is decided during compilation. The framework implementation is a dynamic proxy, which needs to generate proxy objects at runtime, because it needs to scan classes to see which classes have aspects to generate proxy objects.

JDK dynamic agent

Write a class for counting SMS expenses to implement InvocationHandler interface.

After writing this, I finally understand why I jump to the invoke method every time I Debug in the background.

public class MoneyCountInvocationHandler implements InvocationHandler {

    /**
     * Target of the agent
     */
    private final Object target;

    /**
     * Total amount of money stored internally
     */
    private Double moneyCount;

    public MoneyCountInvocationHandler(Object target) {
        this.target = target;
        this.moneyCount = 0.0;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        Object result = method.invoke(target, args);
        moneyCount += 0.07;
        System.out.println("Sending SMS succeeded. It took:" + moneyCount + "element");
        return result;
    }
}

Replace the smsService in the main function with the proxy object processed by MoneyCountInvocationHandler.

public class Main {

    public static void main(String[] args) {
        SMSService smsService = new SMSServiceImpl();
        smsService = (SMSService) Proxy.newProxyInstance(Main.class.getClassLoader(),
                                            new Class[]{SMSService.class},
                                            new MoneyCountInvocationHandler(smsService));
        smsService.sendMessage();
        smsService.sendMessage();
    }
}

Dynamically generate a class according to the invoke method in InvocationHandler. This class implements the SMSService interface, and the proxy object is instantiated with this class.

AOP implementation

All the above have been realized? Is it not difficult to write an AOP?

The code of the main function should be placed in the IOC container initialization. Scan the package to see which classes need to generate proxy objects, and then construct proxy objects into the container.

Then, in the invoke method, wouldn't it be good to change the logic of cost statistics into faceted logic?

Deficiency analysis

Is that over? Of course not, the above method implementation is only valid for interfaces.

Because the dynamic proxy of JDK is to generate a proxy class that implements the corresponding interface. But Spring is not only injected through the interface.

@Autowired
private Type xxxxx;

Spring's @ Autowired uses declared types to find matching objects in the container and inject them. Interfaces are types, and classes are also types?

@Autowired
private SMSService smsService;

So you can inject it in.

@Autowired
private SMSServiceImpl smsService;

What about this? Can also be injected.

Therefore, JDK dynamic proxy cannot be used for directly injecting class types.

cglib dynamic proxy

Since ancient times, it has always been the times that created heroes, not heroes that created the times.

When problems arise, heroes will naturally come out to solve them. cglib is the one that saves the world.

If the JDK dynamic agent can't solve it, leave it all to cglib.

In this case:

@Autowired
private SMSServiceImpl smsService;

Instead of using interface injection, JDK dynamic agent can't solve it. How did cglib solve it? It will dynamically generate a subclass according to the current class, and weave slice logic into the subclass.

Then use the subclass object to proxy the parent object. That's why I said above: proxy mode, don't stick to the interface.

Therefore, the successful weaving is the method that the subclass can override the parent class.

Therefore, cglib is not omnipotent. The method is final. Subclasses cannot be rewritten. Of course, there is nothing it can do.

Posted on Wed, 01 Dec 2021 22:42:15 -0500 by R0CKY