Introduction to Spring Series__04AOP

Keywords: Spring JDK xml Java

AOP introduction

Let me introduce AOP today. AOP, which is often translated as "Face-Oriented Programming" in Chinese, is an extension of OOP. Its idea is not only applied in Spring, but also a good design method. Usually, a software system, in addition to the normal business logic code, often has some functional code, such as: logging, data validation and so on. The most original way is to write these functional codes directly in your business logic code, but in addition to being more convenient at the time of development, the readability and maintainability of the code will be greatly reduced. Also, when you need to use a feature frequently (such as logging), you need to repeat it. The advantage of using AOP, in short, is that it can extract the repetitive functional code, and provide enhanced functions without modifying the source code through dynamic proxy technology when needed.
Advantages:

  • Reduce duplication of code
  • Improving Development Efficiency
  • The code is cleaner and maintainable
    Having said so much, let's briefly demonstrate that we assume that an account transfer function will be implemented now. There will be some transaction control involved. From the point of view of code rationality, we will put it in the service layer.
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@ToString
@EqualsAndHashCode
public class Account implements Serializable {
    private Integer id;
    private String name;
    private Float money;
}

public interface AccountDao {
    /**
     * Query all
     * @return
     */
    List<Account> findAllAccount();

    /**
     * Query one
     * @return
     */
    Account findAccountById(Integer accountId);

    /**
     * Preservation
     * @param account
     */
    void saveAccount(Account account);

    /**
     * To update
     * @param account
     */
    void updateAccount(Account account);

    /**
     * delete
     * @param acccountId
     */
    void deleteAccount(Integer acccountId);

    /**
     * Search accounts by name
     * @param accountName
     * @return  If there is only one result, it returns, and if there is no result, it returns null.
     *          Throw an exception if the result set exceeds one
     */
    Account findAccountByName(String accountName);
}

public class AccountServiceImpl_OLD implements AccountService {

    private AccountDao accountDao;
    private TransactionManager txManager;

    public void setTxManager(TransactionManager txManager) {
        this.txManager = txManager;
    }

    public void setAccountDao(AccountDao accountDao) {
        this.accountDao = accountDao;
    }

    @Override
    public List<Account> findAllAccount() {
        try {
            //1. Open a transaction
            txManager.beginTransaction();
            //2. Perform operations
            List<Account> accounts = accountDao.findAllAccount();
            //3. Submission
            txManager.commit();
            //4. Return results
            return accounts;
        }catch (Exception e){
            //5. Rollback operation
            txManager.rollback();
            throw new RuntimeException(e);
        }finally {
            //6. Release connections
            txManager.release();
        }

    }

    @Override
    public Account findAccountById(Integer accountId) {
        try {
            //1. Open a transaction
            txManager.beginTransaction();
            //2. Perform operations
            Account account = accountDao.findAccountById(accountId);
            //3. Submission
            txManager.commit();
            //4. Return results
            return account;
        }catch (Exception e){
            //5. Rollback operation
            txManager.rollback();
            throw new RuntimeException(e);
        }finally {
            //6. Release connections
            txManager.release();
        }
    }

    @Override
    public void saveAccount(Account account) {
        try {
            //1. Open a transaction
            txManager.beginTransaction();
            //2. Perform operations
            accountDao.saveAccount(account);
            //3. Submission
            txManager.commit();
        }catch (Exception e){
            //4. Rollback operation
            txManager.rollback();
        }finally {
            //5. Release connection
            txManager.release();
        }

    }

    @Override
    public void updateAccount(Account account) {
        try {
            //1. Open a transaction
            txManager.beginTransaction();
            //2. Perform operations
            accountDao.updateAccount(account);
            //3. Submission
            txManager.commit();
        }catch (Exception e){
            //4. Rollback operation
            txManager.rollback();
        }finally {
            //5. Release connection
            txManager.release();
        }

    }

    @Override
    public void deleteAccount(Integer acccountId) {
        try {
            //1. Open a transaction
            txManager.beginTransaction();
            //2. Perform operations
            accountDao.deleteAccount(acccountId);
            //3. Submission
            txManager.commit();
        }catch (Exception e){
            //4. Rollback operation
            txManager.rollback();
        }finally {
            //5. Release connection
            txManager.release();
        }

    }

    @Override
    public void transfer(String sourceName, String targetName, Float money) {
        try {
            //1. Open a transaction
            txManager.beginTransaction();
            //2. Perform operations

            //2.1 Query the Transfer Account by Name
            Account source = accountDao.findAccountByName(sourceName);
            //2.2 Transfer to Account by Name Query
            Account target = accountDao.findAccountByName(targetName);
            //2.3 Transfer Account Reduction
            source.setMoney(source.getMoney()-money);
            //2.4 Transfer to Account to Increase Money
            target.setMoney(target.getMoney()+money);
            //2.5 Update Transfer Account
            accountDao.updateAccount(source);

            int i=1/0;

            //2.6 Update Transfer Account
            accountDao.updateAccount(target);
            //3. Submission
            txManager.commit();

        }catch (Exception e){
            //4. Rollback operation
            txManager.rollback();
            e.printStackTrace();
        }finally {
            //5. Release connection
            txManager.release();
        }


    }

Here, we see a lot of disgusting code: a lot of repetitive logging code, and when you change it, you find it inconvenient. We will rewrite this code later.

Implementation of AOP

AOP is implemented by dynamic proxy.

Introduction to Dynamic Agent

Here we briefly introduce dynamic proxy: use a proxy to wrap the object, and then replace the original object with the proxy object. Any call to the original object is made through the proxy. The proxy object decides whether and when to transfer the method call to the original object. Its invocation process is shown in the following figure:

Characteristic:

  • Bytecode is created on-demand and loaded on-demand.
  • This is also the difference between static proxy and static proxy. Because static proxy is bytecode created and loaded.
  • Decorator mode is a reflection of static agent.

There are two forms of dynamic proxy

  • Interface-based dynamic proxy
    Provider: JDK's official Proxy class.
    Requirement: The proxy class implements at least one interface
  • Subclass-based dynamic proxy
    Provider: The third party's CGLib needs to import asm.jar if it reports an asmxxxx exception.
    Requirement: The proxy class cannot be modified with final (final class).
    Following is an example to illustrate how these two dynamic agents are implemented:
    I recently replaced a new computer. Take buying a computer as an example. Nowadays, when people buy computers, they seldom go to physical stores, mostly through e-commerce channels. Whatever it is, it is purchased from intermediaries. This behavior embodies the idea of agency model invisibly.
    In the beginning, a computer manufacturer not only manufactured and assembled computers, but also sold them to consumers or distributors (agents). For customers, he needed to complete two kinds of services: selling goods and after-sales services. When the industry develops to a certain stage and the number of computer manufacturers increases, people will formulate some industry specifications to let everyone abide by (that is, Abstract interfaces). Moreover, in order to save costs, computer manufacturers no longer provide direct and consumer sales services, so our consumers can only buy new computers from agents. This is the typical agency model.

Creating proxy objects using JDK's official Proxy class

public interface IProducer {
 
    public void saleProduct(float money);

    public void afterService(float money);
}

public class Producer implements IProducer {
 
    @Override
    public void saleProduct(float money) {
        System.out.println("Sell products and get money:" + money);
    }

  
    @Override
    public void afterService(float money) {
        System.out.println("Provide after-sales service and get money:" + money);
    }
}

//Consumer
public class Client {
    public static void main(String[] args) {
        final Producer producer = new Producer();
        /**
         *  How to create proxy objects:
         *  Using the newProxyInstance method in the Proxy class
         *  Requirements for creating proxy objects:
         *      The proxy class implements at least one interface and cannot use it if it does not.
         *  newProxyInstance The parameters of the method are as follows:
         *      ClassLoader: classloader
         *          It is used to load proxy object bytecodes. Use the same class loader as the proxy object. Fixed writing.
         *      Class[]: Bytecode array
         *          It is used to make the proxy object and the proxy object have the same method. Fixed writing.
         *     InvocationHandler: Code for providing enhancements
         *          It lets us write how to represent. We usually have implementation classes for this interface, usually anonymous internal classes, but not necessarily.
         *          Who writes the implementation classes of this interface?
         */

        IProducer proxyProducer = (IProducer) Proxy.newProxyInstance(producer.getClass().getClassLoader(),
                producer.getClass().getInterfaces(), new InvocationHandler() {
                    /**
                     * Role: Any interface method that executes the proxy object will pass through this method
                     * Meaning of Method Parameters
                     * @param proxy   References to proxy objects
                     * @param method  Current method of execution
                     * @param args    Current parameters required to execute methods
                     * @return Have the same return value as the proxy object method
                     * @throws Throwable
                     */
                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        //Provide enhanced code
                        Object returnValue = null;

                        //1. Get the parameters of method execution
                        Float money = (Float)args[0];
                        //2. Judging whether the current method is sales or not, if so, a 20% discount
                        if("saleProduct".equals(method.getName())) {
                            returnValue = method.invoke(producer, money*0.8f);
                        }
                        return returnValue;
                    }
                });
        proxyProducer.saleProduct(10000f);


    }
}

When using Proxy provided by jdk to create proxy objects, it is required that other proxy objects implement at least one interface. The proxy class needs to implement the same interface and be loaded by the same loader. If not, you can't use this method. For other details, please refer to the official documents.

cglib approach to dynamic proxy

In fact, it is easy to prove that AOP is an extension of OOP: the way jdk provides dynamic proxy is to implement the interface, while the way cglib implements is to use the inheritance of OOP. The principle is similar, but the main difference is that inheritance is used instead of interface, which also has inheritance limitations: the proxy class can not be modified by final.

public class Client {
    public static void main(String[] args) {
        final Producer producer = new Producer();
        /**
         *  create The parameters of the method are as follows:
         *      Class: Bytecode
         *          It is the bytecode used to specify the proxy object.
         *
         *      Callback: Code for providing enhancements
         *          It lets us write how to represent. We usually have implementation classes for this interface, usually anonymous internal classes, but not necessarily.
         *          Who writes the implementation classes of this interface?
         *          We usually write about the interface's sub-interface implementation class: Method Interceptor
         */
        Producer proxyProducer = (Producer) Enhancer.create(producer.getClass(), new MethodInterceptor() {
            @Override
            public Object intercept(Object o, Method method, Object[] objects,
                                    MethodProxy methodProxy) throws Throwable {
                Object result = null;
                Float price = (Float) objects[0];
                if ("saleProduct".equals(method.getName())) {
                    result = method.invoke(o, price * 0.8f);
                }
                return result;
            }
        });

        proxyProducer.saleProduct(10000f);
    }
}

ublic class Client {
    public static void main(String[] args) {
        final Producer producer = new Producer();
        /**
         *  create The parameters of the method are as follows:
         *      Class: Bytecode
         *          It is the bytecode used to specify the proxy object.
         *
         *      Callback: Code for providing enhancements
         *          It lets us write how to represent. We usually have implementation classes for this interface, usually anonymous internal classes, but not necessarily.
         *          Who writes the implementation classes of this interface?
         *          We usually write about the interface's sub-interface implementation class: Method Interceptor
         */
        Producer proxyProducer = (Producer) Enhancer.create(producer.getClass(), new MethodInterceptor() {
            @Override
            public Object intercept(Object o, Method method, Object[] objects,
                                    MethodProxy methodProxy) throws Throwable {
                Object result = null;
                Float price = (Float) objects[0];
                if ("saleProduct".equals(method.getName())) {
                    result = method.invoke(o, price * 0.8f);
                }
                return result;
            }
        });

        proxyProducer.saleProduct(10000f);
    }
}

AOP in Spring

The above two methods of generating proxy objects will be applied in Spring: default preferred jdk's own way, and cglib mode will be used when it is found that other proxy classes have not implemented interfaces.

Direct translation of technical terms

  • Joinpoint:
    The so-called connection point is every method in your business logic, which is called the connection point. Moreover, unlike AspectJ and JBoss, Spring does not support field and constructor join points, only method-level join points.
  • Pointcut (entry point):
    When you add additional functionality to a join point, the join point is the entry point.
  • Advice (notification/enhancement):
    Notification is what you do when you intercept the cut-off point. According to the time you want to do, there are pre-notification, post-notification, return notification, exception notification and surround notification.
  • Introduction:
    Introduction is a special notification that dynamically adds methods or fields to a class at runtime without modifying the class code.
  • Target (target object):
    The target object of the agent.
  • Weaving:
    Enhancement is the process of applying enhancements to target objects to create new proxy objects. spring uses dynamic proxy weaving, while AspectJ uses compile-time weaving and class-loading weaving.
  • Proxy (Agent):
    When a class is enhanced by AOP weaving, a result proxy class is generated.
  • Aspect (section):
    It is a combination of entry point and notification (introduction).

Practical drill

This time we intend to do a simple function: to implement a calculator that can add, subtract, multiply and divide operations, and to record the corresponding logs.
The process consists of the following steps:
1. Developing business logic code
2. Developing Section Code
3. Configure ioc to configure calculators and facets into Spring containers
4. Face Configuration, Open AOP
For configuration, there are mainly two ways:

Java configuration:

public interface ArithmeticCalculator {

	int add(int i, int j);
	int sub(int i, int j);
	
	int mul(int i, int j);
	int div(int i, int j);
	
}

@Component("arithmeticCalculator")
public class ArithmeticCalculatorImpl implements ArithmeticCalculator {

	@Override
	public int add(int i, int j) {
		int result = i + j;
		return result;
	}

	@Override
	public int sub(int i, int j) {
		int result = i - j;
		return result;
	}

	@Override
	public int mul(int i, int j) {
		int result = i * j;
		return result;
	}

	@Override
	public int div(int i, int j) {
		int result = i / j;
		return result;
	}

}


package com.spring.demo.springaop;

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

import java.util.Arrays;

/**
 * You can use the @Order annotation to specify the priority of the section, the smaller the value, the higher the priority.
 */
@Aspect
@Component
public class LoggingAspect {
    @Pointcut("execution(public int com.spring.demo.springaop.ArithmeticCalculator.*(..))")
    public void declareJoinPoint() {}

    @Before("declareJoinPoint()")
    public void beforeMehtod(JoinPoint joinPoint) {
        String methodName = joinPoint.getSignature().getName();
        Object[] args = joinPoint.getArgs();
        System.out.println("the " + methodName + " begins with " + Arrays.asList(args));
    }


    @AfterReturning(value = "declareJoinPoint()", returning = "result")
    public void afterMethod(JoinPoint joinPoint, Object result) {
        String methodName = joinPoint.getSignature().getName();
        System.out.println("the " + methodName + " ends successfully with result is " + result);
    }

    @AfterThrowing(value = "declareJoinPoint()", throwing = "e")
    public void afterException(JoinPoint joinPoint, Exception e) {
        String methodName = joinPoint.getSignature().getName();
        System.out.println("the " + methodName + "occurs a Exception by" + e.getMessage());
    }

    /**
     * Circumferential notifications need to carry parameters of ProceedingJoinPoint type.
     * Circumferential notification is similar to the whole process of dynamic proxy: ProceedingJoinPoint type parameters can determine whether or not the target method is executed.
     * And the circular notification must have a return value, which is the return value of the target method.
     */
	/*
	@Around("execution(public int com.spring.demo.springaop.ArithmeticCalculator.*(..))")
	public Object aroundMethod(ProceedingJoinPoint pjd){

		Object result = null;
		String methodName = pjd.getSignature().getName();

		try {
			//Before advice
			System.out.println("The method " + methodName + " begins with " + Arrays.asList(pjd.getArgs()));
			//Target implementation approach
			result = pjd.proceed();
			//Return notification
			System.out.println("The method " + methodName + " ends with " + result);
		} catch (Throwable e) {
			//Exception notification
			System.out.println("The method " + methodName + " occurs exception:" + e);
			throw new RuntimeException(e);
		}
		//after returning advise
		System.out.println("The method " + methodName + " ends");

		return result;
	}
	*/
}



@Order(1)
@Aspect
@Component
public class VlidationAspect {

	@Before("com.spring.demo.springaop.LoggingAspect.declareJoinPoint()")
	public void validateArgs(JoinPoint joinPoint){
		System.out.println("-->validate:" + Arrays.asList(joinPoint.getArgs()));
	}
	
}

@EnableAspectJAutoProxy
@Configuration
@ComponentScan
public class MainConcig {

}


public class Main {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext("com.spring.demo" +
                ".springaop");
        ArithmeticCalculator arithmeticCalculator = (ArithmeticCalculator) context.getBean("arithmeticCalculator");
        int add = arithmeticCalculator.add(100, 200);
    }
}

xml file configuration

JavaBean s are still these, just delete the annotations, and the configuration of bean s and the opening of aop functions are declared by the configuration file. To introduce aop namespaces.

<!-- To configure bean -->
	<bean id="arithmeticCalculator" 
		class="com.spring.demo.springaop.xml.ArithmeticCalculatorImpl"></bean>

	<!-- Configuration section bean. -->
	<bean id="loggingAspect"
		class="com.spring.demo.springaop.xml.LoggingAspect"></bean>

	<bean id="vlidationAspect"
		class="com.spring.demo.springaop.xml.VlidationAspect"></bean>

	<!-- To configure AOP -->
	<aop:config>
		<!-- Configuration tangent expression -->
		<aop:pointcut expression="execution(* com.spring.demo.springaop.ArithmeticCalculator.*(int, int))"
			id="pointcut"/>
		<!-- Configuration facets and notifications -->
		<aop:aspect ref="loggingAspect" order="2">
			<aop:before method="beforeMethod" pointcut-ref="pointcut"/>
			<aop:after method="afterMethod" pointcut-ref="pointcut"/>
			<aop:after-throwing method="afterThrowing" pointcut-ref="pointcut" throwing="e"/>
			<aop:after-returning method="afterReturning" pointcut-ref="pointcut" returning="result"/>
			<!--  
			<aop:around method="aroundMethod" pointcut-ref="pointcut"/>
			-->
		</aop:aspect>	
		<aop:aspect ref="vlidationAspect" order="1">
			<aop:before method="validateArgs" pointcut-ref="pointcut"/>
		</aop:aspect>
	</aop:config>

Point-of-entry expression description (referring to someone else, too lazy to write)

execution: execution of matching methods (commonly used)
Execution (expression)
Expression grammar: execution([modifier] return value type package name, class name, method name (parameter))
Description:
Full matching mode:
public void com.itheima.service.impl.AccountServiceImpl.saveAccount(com.itheima.domain.Account)
Access modifiers can be omitted
void com.itheima.service.impl.AccountServiceImpl.saveAccount(com.itheima.domain.Account)
The return value can be used as a sign to indicate any return value.
*
com.itheima.service.impl.AccountServiceImpl.saveAccount(com.itheima.domain.Account)
The package name can be used as a number to represent any package, but there are several levels of packages that need to be written.*

  • ....AccountServiceImpl.saveAccount(com.itheima.domain.Account)
    Use... To represent the current package and its subpackages
  • com...AccountServiceImpl.saveAccount(com.itheima.domain.Account)
    Class names can use the * sign to represent any class
  • com....saveAccount(com.itheima.domain.Account)
    Method names can use numbers to represent any method
  • com....( com.itheima.domain.Account)
    The parameter list can use *, indicating that the parameter can be of any data type, but it must have parameters
  • com....(*)
    The list of parameters can be used by ___________. Indicates whether there are parameters or not, and parameters can be of any type.
  • com....(...)
    Full formula:
  • ....(...)
    Note:
    Usually, we all enhance the business layer approach, so pointcut expressions are cut to the business layer implementation class.
    execution( com.itheima.service.impl..(...))

Supplementary Note: Introduction of Notice
Introducing notification is a special type of notification. It allows objects to dynamically implement interfaces by providing implementation classes for interfaces, just as objects have extended implementation classes at runtime.

Introducing notifications can use two implementation classes, MaxCalculatorImpl and MinCalculatorImpl, to enable Arithmetic CalculatorImpl to dynamically implement MaxCalculator and MinCalculator interfaces. This is the same effect as implementing multiple inheritance from MaxCalculatorImpl and MinCalculatorImpl. However, there is no need to modify Arithmetic CalculatorImpl. L Source code.
The introduction notice must also be stated in the section.

Code demo

Posted by jeppers on Tue, 17 Sep 2019 00:02:29 -0700