[Java Zero to Architect Season 3] [17] Spring-AOP

Continuous Learning & Continuous Updating...

Break away

1. Deficiencies of BeanPostProcessor+JDK (CGLib)

  • Proxy objects generated using JDK/CGLib will, by default, proxy all beans (that is, beans managed by the Ioc container) that get the Object through getBean, and all methods of the target Object (that is, beans), which will certainly include the toString, hashCode, equals of the Object, as well. It is not possible to precisely control which objects the agent targets and which methods. In real-world development, it is certain that some methods of certain classes require additional code from the proxy.

  • Of course, you can write control code manually to achieve precise control over which bean s (target objects) are proxied, but it can be cumbersome and difficult to maintain for the following reasons.

1. Writing Troubles

  • Consider using JDK (implements: the target object that requires proxy must implement an interface) or CGLib (extends: when the target object has no parent interface) to implement dynamic proxy

    Note: CGLib can be used to implement dynamic proxy even if the target object has a parent interface.

  • Not only do you need to implement BeanPostProcessor yourself, you also need to manually implement it in the applicationContext. Register the BeanBeanPostProcessor in xml.

    Registration code:

    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
    
    
        <!-- register BeanPostProcessor -->
        <!-- <bean class="programmer.lp.processor.LogProcessor"/> -->
        <bean class="programmer.lp.processor.LogProcessor2"/>
    
        <bean id="userService" class="programmer.lp.service.impl.UserServiceImpl"/>
        <bean id="skillService" class="programmer.lp.service.SkillService"/>
    
    </beans>
    

2. Code complexity

  • In the postProcessAfterInitialization method, you need to consider which beans need proxy and which beans do not need proxy and write the appropriate control code.

        @Override
        public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
            // Consider which bean s need proxy and which do not need proxy
            if (!bean.getClass().getName().startsWith("programmer.lp.service.")) {
                return bean;
            }
            Enhancer enhancer = new Enhancer();
            enhancer.setClassLoader(getClass().getClassLoader());
            enhancer.setSuperclass(bean.getClass());
            enhancer.setCallback(new LogMethodInterceptor(bean));
            return enhancer.create();
        }
    
  • In MethodInterceptor's intercept (CGLib) or InvocationHandler's invoke (JDK) methods, you need to consider which methods need proxy, which do not need proxy, and write corresponding control code.

        private static class LogMethodInterceptor implements MethodInterceptor {
            Object target;
            public LogMethodInterceptor(Object target) {
                this.target = target;
            }
    
            // An example of CGLib-MethodInterceptor-intercept method
            @Override
            public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
                // Suppose you only want to proxy boolean login(String username, String password); This method
                String methodName = method.getName();
                Class<?> methodReturnType = method.getReturnType();
                Class<?>[] methodParameterTypes = method.getParameterTypes();
                if ("login".equals(methodName)
                        && methodParameterTypes.length == 2
                        && methodParameterTypes[0] == String.class
                        && methodParameterTypes[1] == String.class
                        && methodReturnType == Boolean.class) {
                    System.out.println("agent-----------start-----------Journal/affair...");
    
                    Object result = method.invoke(target, args);
    
                    System.out.println("agent-----------end-----------Journal/affair...");
                    return result;
                }
                return method.invoke(target, args);
            }
        }
    

3. Problem solving

  • Use Spring encapsulated AOP Technology

2. AOP-Aspect Oriented Programming

Agent: Add additional code (add additional, additional, enhanced features) to a feature without modifying (business and control) code.

AOP: Aspect Oriented Programming, Face Oriented Programming.

Dependency:

        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjrt</artifactId>
            <version>1.9.6</version>
        </dependency>
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
            <version>1.9.6</version>
        </dependency>

3. AOP-the underlying implementation of dynamic proxy

4. AOP-MethodBeforeAdvice

MethodBeforeAdvice:

public class LogAdvice implements MethodBeforeAdvice {

    @Override
    public void before(Method method, Object[] objects, Object o) throws Throwable {
        System.out.println("MethodBeforeAdvice-LogAdvice-before-------------" + method.getName());
    }

}

applicationContext.xml:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd">
    <!-- Additional Code -->
    <bean id="logAdvice" class="programmer.lp.advice.LogAdvice"/>
    <!-- section -->
    <aop:config>
        <!-- Start point: Which methods of which classes should additional code be added? -->
        <!-- execution(* *(..)) On behalf of all bean All methods will be cut in -->
        <aop:pointcut id="pc" expression="execution(* *(..))"/>
        <!-- Cut in additional code for entry points -->
        <aop:advisor advice-ref="logAdvice" pointcut-ref="pc"/>
    </aop:config>

    <bean id="userService" class="programmer.lp.service.impl.UserServiceImpl"/>
    <bean id="skillService" class="programmer.lp.service.SkillService"/>

</beans>


Use:

	ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
	UserService userService = ctx.getBean("userService", UserService.class);
	SkillService skillService = ctx.getBean("skillService", SkillService.class);
	userService.login("xxxx", "yyzz");
	skillService.delete(10);

5. AOP-MethodInterceptor

MethodInterceptor:

public class LogInterceptor implements MethodInterceptor {

    @Override
    public Object invoke(MethodInvocation methodInvocation) throws Throwable {
        System.out.println("agent start ------------------------------------");
        Object result = methodInvocation.proceed();
        System.out.println("agent end ------------------------------------");
        return result;
    }

}

applicationContext.xml:

    <!-- Additional Code -->
    <bean id="logInterceptor" class="programmer.lp.advice.LogInterceptor"/>
    <!-- section -->
    <aop:config>
        <!-- Start point: Which methods of which classes should additional code be added? -->
        <!-- execution(* *(..)) All methods representing all classes will be cut in -->
        <aop:pointcut id="pc" expression="execution(* *(..))"/>
        <!-- Cut in additional code for entry points -->
        <aop:advisor advice-ref="logInterceptor" pointcut-ref="pc"/>
    </aop:config>

6. AOP-entry point expression

https://docs.spring.io/spring/docs/current/spring-framework-reference/core.html#aop-pointcuts

Cut-in point expression formula:

 expression="Indicator(Describe content)"
A complete entry point expression:
 execution(public boolean programmer.lp.service.UserService.login(String, String))
  • Indicators: execution, args, within, @annotation

  • Description: * * (..)

    • Previous*: Return value of permission modifier
    • Following*: Package name. Class name. Method Name
    • .): Method parameter type (multiple use, separated)
    A complete description:
    public boolean programmer.lp.service.UserService.login(String, String)
    

7. Use of AOP-entry point expression

Use:

  • execution(public boolean programmer.lp.service.UserService.login(String, String)): Complete
  • execution(public boolean programmer.lp.service.UserService.login(.)): Unlimited parameters
  • execution(public boolean programmer.lp.service.UserService.login()): Restricted parameter - no parameter
  • execution(public * programmer.lp.service.UserService.login(String, String)): unrestricted return values

  • execution(public * *(String, String)): A method whose formal parameter is (String, String) for all publics

  • execution(public * * (.)): Methods for all publics

  • execution(* programmer.lp.service.UserService.login(String, String)): No restrictions on permission modifiers and return values (no restrictions on permission modifiers alone are supported)
  • execution(* login(String, String)): All login(String, String) methods
  • execution(* *(String, String)): Method with all formal parameters (String, String)
  • execution(public boolean *.UserService.login(String, String)): Do not restrict the package in which the class resides
  • execution(public boolean programmer.lp.service.*.login(String, String)): programmer. Lp. public boolean login(String, String) method for all classes under the service package
  • Execution (* programmer.lp.service. *.login(String, String)): programmer. Lp. login(String, String) methods for all classes under service packages (including subpackages)
  • execution(* *.login(String, String)): the login(String, String) method for all classes
  • execution(* *.login(.)): Method named login for all classes
  • execution(* login(.)): Method named login for all classes
  • execution(* log*(.)): All methods whose method names begin with log
  • execution(* *log(.)): Methods with all method names ending in log
  • args(String, String): Method with all formal parameters (String, String)
  • Equivalent, execution(* *(String, String))
  • execution(* *(.)): All methods of all classes

  • args(.): All methods of all classes

Be careful:

  • Java. Classes under the Lang package only need to write the simpleName of the class:

    1. java.lang.Integer -> Integer(int)
    2. java.lang.String -> String
    3. ...
  • The execution indicator is recommended

8. AOP - The entry point indicator is @annotation

  • First, customize a note:

    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.METHOD)
    public @interface Log {}
    
  • Then add custom comments to the methods that need proxy

    public class SkillService {
    
        @Log
        public void delete(Integer id) {
            System.out.println("SkillService-core business-delete");
        }
    
    }
    
    public class UserServiceImpl implements UserService {
        @Override
        @Log
        public boolean login(String username, String password) {
            // ...
            // Operations such as dao
            // ...
            System.out.println("UserService Core Business-login");
            return "lpruoyu".equals(username) && "123456".equals(password);
        }
    	// ...
    }
    
  • applicationContext.xml

        <bean id="logInterceptor" class="programmer.lp.advice.LogInterceptor"/>
    
        <aop:config>
            <aop:pointcut id="pc" expression="@annotation(programmer.lp.annotation.Log)"/>
            <aop:advisor advice-ref="logInterceptor" pointcut-ref="pc"/>
        </aop:config>
    
        <bean id="userService" class="programmer.lp.service.impl.UserServiceImpl"/>
        <bean id="skillService" class="programmer.lp.service.SkillService"/>
    

9. AOP - combinatorial entry point expression

  • You can use && (and), ||(or),! To combine the entry point expressions:

  • Character entities are required when using &&amp:& Amp;

  • Example 1:

        <aop:config>
            <aop:pointcut id="pc"
                          expression="within(programmer.lp.service.impl.UserServiceImpl) and args(String, String)"/>
            <aop:advisor advice-ref="logInterceptor" pointcut-ref="pc"/>
        </aop:config>
    
  • Example 2:

    	<bean id="logInterceptor" class="programmer.lp.advice.LogInterceptor"/>
        <aop:config>
            <aop:pointcut   id="pc"
                            expression=
                            "execution(public boolean programmer.lp.service.UserService.login(String, String))
                             ||
                             execution(* programmer.lp.service.SkillService.delete(..))"/>
            <aop:advisor advice-ref="logInterceptor" pointcut-ref="pc"/>
        </aop:config>
    
  • Example 3:

        <aop:config>
            <aop:pointcut id="pc"
                          expression="!execution(* logout(..))"/>
            <aop:advisor advice-ref="logInterceptor" pointcut-ref="pc"/>
        </aop:config>
    

10. AOP - Details

Target method: A method that requires entry, proxy, and additional code to enhance functionality.

Problem Showing:

Target class:

public class UserServiceImpl implements UserService {
    @Override
    @Log // Target Method: Proxy Required
    public boolean login(String username, String password) {
        System.out.println("--------------------login--------------------");
        // Another target method is called here
        if (register(username, password)) {
            return "lpruoyu".equals(username) && "123456".equals(password);
        }
        return false;
    }

    @Override
    @Log // Target Method: Proxy Required
    public boolean register(String username, String password) {
        System.out.println("--------------------register--------------------");
        return username.equals(password);
    }
}

applicationContext.xml:

    <bean id="logInterceptor" class="programmer.lp.advice.LogInterceptor"/>

    <aop:config>
        <aop:pointcut id="pc" expression="@annotation(programmer.lp.annotation.Log)"/>
        <aop:advisor advice-ref="logInterceptor" pointcut-ref="pc"/>
    </aop:config>

    <bean id="userService" class="programmer.lp.service.impl.UserServiceImpl"/>

Call:

public class MainTest {
    UserService userService;
    {
        ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
        userService = ctx.getBean("userService", UserService.class);
    }

    @Test
    public void userService() {
        userService.login("xxxx", "yyzz");
    }
}

Problem solving:

public class UserServiceImpl implements UserService, ApplicationContextAware , BeanNameAware {

    private ApplicationContext applicationContext;
    private String beanName;

    @Override
    @Log // Target Method: Proxy Required
    public boolean login(String username, String password) {
        System.out.println("--------------------login--------------------");
        // Objects with scope s of the same id and container that are singleton s are always the same
        // It can be understood that an ApplicationContext is a container

        // Take out the proxy object and let it call another target method
        UserService userService =  applicationContext.getBean(beanName, UserService.class);
        if (userService.register(username, password)) {
            return "lpruoyu".equals(username) && "123456".equals(password);
        }
        return false;
    }

    @Override
    @Log // Target Method: Proxy Required
    public boolean register(String username, String password) {
        System.out.println("--------------------register--------------------");
        return username.equals(password);
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }

    @Override
    public void setBeanName(String name) {
        beanName = name;
    }

}

11. AOP - Configure multiple pointcut s, Advisors

    <bean id="logAdvice" class="programmer.lp.advice.LogAdvice"/>
    <bean id="logInterceptor" class="programmer.lp.advice.LogInterceptor"/>

    <aop:config>
        <aop:pointcut id="pc1"
                      expression="execution(public boolean programmer.lp.service.UserService.login(String, String))"/>
        <aop:pointcut id="pc2"
                      expression="execution(* programmer.lp.service.SkillService.delete(..))"/>
        <!-- by pc1 This entry point cuts into two additional sections of code -->
        <aop:advisor advice-ref="logAdvice" pointcut-ref="pc1"/>
        <aop:advisor advice-ref="logInterceptor" pointcut-ref="pc1"/>
        <aop:advisor advice-ref="logInterceptor" pointcut-ref="pc2"/>
    </aop:config>

    <bean id="userService" class="programmer.lp.service.impl.UserServiceImpl"/>
    <bean id="skillService" class="programmer.lp.service.SkillService"/>
    <aop:config>
        <aop:pointcut id="pc1"
                      expression="execution(public boolean programmer.lp.service.UserService.login(String, String))"/>
        <aop:advisor advice-ref="logAdvice" pointcut-ref="pc1"/>
        <aop:advisor advice-ref="logInterceptor" pointcut-ref="pc1"/>
    </aop:config>

    <aop:config>
        <aop:pointcut id="pc2"
                      expression="execution(* programmer.lp.service.SkillService.delete(..))"/>
        <aop:advisor advice-ref="logInterceptor" pointcut-ref="pc2"/>
    </aop:config>

Attention and some details

  • Love programming, code
  • Programming is an art. If you love it, love it!
  • Multiple Strike Code
  • Try more, dare to try
  • Be good at finding and solving problems
  • Summarize More
  • Think more
  • Proxy objects generated by AOP technology can only be acquired by interface, not by implementation class

    	Write correctly:
    	UserService userService =  applicationContext.getBean(beanName, UserService.class);
    
    	Wrong Writing:
    	UserService userService =  applicationContext.getBean(beanName, UserServiceImpl.class);
    
  • It doesn't matter if you use the proxy object generated by JDK/CGLib yourself, either of the above can be written.

  • Generally speaking, the API (interface, function) that comes with JDK is simpler and more efficient than that of third-party libraries, so the functions that can be implemented with JDK's own API should be implemented with JDK's own API as much as possible.

Reference resources

Xiao Ma Ge-Li Mingjie: Java from 0 to Architect 3 Advanced Internet Architect.

Thank you for your attention and support!

Keywords: Java Spring

Added by khefner on Sun, 09 Jan 2022 19:47:54 +0200