0
点赞
收藏
分享

微信扫一扫

【Java从零到架构师第③季】【17】Spring-AOP

全栈学习笔记 2022-01-10 阅读 49
javaspring

持续学习&持续更新中…

守破离


【Java从零到架构师第③季】【17】Spring-AOP

一、BeanPostProcessor+JDK(CGLib)的不足

  • 使用JDK/CGLib生成的代理对象,在默认情况下,会代理所有通过getBean这种方式获取对象的bean(也就是由Ioc容器管理的bean),并且会代理目标对象(也就是这些bean)的所有方法(肯定也会包括Object的toString、hashCode、equals…),无法做到精确控制代理哪些目标对象的哪些方法。而现实开发中,肯定是某些类的某些方法才需要代理增加附加代码

  • 当然了,可以通过手动书写控制代码来实现精确控制代理哪些bean(目标对象)的哪些方法,不过,会很麻烦,很不好维护,具体原因如下。

1、书写麻烦

  • 需要考虑使用JDK(implements:需要代理的目标对象必须实现了接口)还是使用CGLib(extends:目标对象无父接口时)来实现动态代理

    :即使目标对象有父接口也可以使用CGLib来实现动态代理。

  • 不但需要自己实现BeanPostProcessor,而且还需要手动在applicationContext.xml中注册该BeanBeanPostProcessor。

    注册代码:

    <?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">
    
    
        <!-- 注册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、代码复杂

  • postProcessAfterInitialization方法中需要考虑清楚哪些bean需要代理、哪些bean不需要代理,书写相应的控制代码。

        @Override
        public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
            // 需要考虑清楚哪些bean需要代理、哪些bean不需要代理
            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();
        }
    
  • MethodInterceptorintercept(CGLib)或者InvocationHandlerinvoke(JDK)方法中,需要考虑清楚哪些方法需要代理、哪些方法不需要代理,书写相应的控制代码。

        private static class LogMethodInterceptor implements MethodInterceptor {
            Object target;
            public LogMethodInterceptor(Object target) {
                this.target = target;
            }
    
            // 以CGLib-MethodInterceptor-intercept方法为例
            @Override
            public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
                // 假设现在只想代理 boolean login(String username, String password); 这种方法
                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("代理-----------start-----------日志/事务...");
    
                    Object result = method.invoke(target, args);
    
                    System.out.println("代理-----------end-----------日志/事务...");
                    return result;
                }
                return method.invoke(target, args);
            }
        }
    

3、问题解决

  • 使用Spring封装好的AOP技术

二、AOP—Aspect Oriented Programming

在这里插入图片描述

依赖:

        <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>

三、AOP—动态代理的底层实现

在这里插入图片描述

四、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">
    <!-- 附加代码 -->
    <bean id="logAdvice" class="programmer.lp.advice.LogAdvice"/>
    <!-- 切面 -->
    <aop:config>
        <!-- 切入点:给哪些类的哪些方法增加附加代码? -->
        <!-- execution(* *(..)) 代表所有bean的所有方法都会被切入 -->
        <aop:pointcut id="pc" expression="execution(* *(..))"/>
        <!-- 为切入点切入附加代码 -->
        <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>


使用:

	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);

五、AOP—MethodInterceptor

在这里插入图片描述

MethodInterceptor:

public class LogInterceptor implements MethodInterceptor {

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

}

applicationContext.xml:

    <!-- 附加代码 -->
    <bean id="logInterceptor" class="programmer.lp.advice.LogInterceptor"/>
    <!-- 切面 -->
    <aop:config>
        <!-- 切入点:给哪些类的哪些方法增加附加代码? -->
        <!-- execution(* *(..)) 代表所有类的所有方法都会被切入 -->
        <aop:pointcut id="pc" expression="execution(* *(..))"/>
        <!-- 为切入点切入附加代码 -->
        <aop:advisor advice-ref="logInterceptor" pointcut-ref="pc"/>
    </aop:config>

六、AOP—切入点表达式

在这里插入图片描述

切入点表达式公式

 expression="指示符(描述内容)"
一个完整的切入点表达式:
 execution(public boolean programmer.lp.service.UserService.login(String, String))
  • 指示符:executionargswithin@annotation

  • 描述内容:* *(..)

    • 前面的*权限修饰符 返回值
    • 后面的*包名.类名.方法名
    • ..方法形参类型(多个使用,隔开)
    一个完整的描述内容:
    public boolean programmer.lp.service.UserService.login(String, String)
    

七、AOP—切入点表达式的使用

在这里插入图片描述

使用

  • execution(public boolean programmer.lp.service.UserService.login(String, String)):完整

  • execution(public boolean programmer.lp.service.UserService.login(..)):不限制参数
  • execution(public boolean programmer.lp.service.UserService.login()):限制参数—无参数

  • execution(public * programmer.lp.service.UserService.login(String, String)):不限制返回值

  • execution(public * *(String, String)):所有public的形参为(String, String)的方法

  • execution(public * *(..)):所有public的的方法


  • execution(* programmer.lp.service.UserService.login(String, String)):不限制权限修饰符与返回值(不支持单独不限制权限修饰符)
  • execution(* login(String, String)):所有login(String, String)的方法
  • execution(* *(String, String)):所有形参为(String, String)的方法

  • execution(public boolean *.UserService.login(String, String)):不限制类所在的包

  • execution(public boolean programmer.lp.service.*.login(String, String))programmer.lp.service包下的所有类的public boolean login(String, String)方法
  • execution(* programmer.lp.service..*.login(String, String))programmer.lp.service包(包括子包)下的所有类的login(String, String)方法

  • execution(* *.login(String, String)):所有类的login(String, String)方法
  • execution(* *.login(..)):所有类的名为login的方法
  • execution(* login(..)):所有类的名为login的方法

  • execution(* log*(..)):所有方法名以log开头的方法
  • execution(* *log(..)):所有方法名以log结尾的方法

  • args(String, String):所有形参为(String, String)的方法
  • 相当于,execution(* *(String, String))

  • execution(* *(..)):所有类的所有方法

  • args(..):所有类的所有方法

注意

  • java.lang包下的类只需写类的simpleName:

    1. java.lang.Integer -> Integer(int)
    2. java.lang.String -> String
  • 推荐使用execution指示符

八、AOP—切入点指示符为@annotation

  • 首先,自定义一个注解:

    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.METHOD)
    public @interface Log {}
    
  • 然后,给需要代理的方法加上自定义的注解

    public class SkillService {
    
        @Log
        public void delete(Integer id) {
            System.out.println("SkillService-核心业务-delete");
        }
    
    }
    
    public class UserServiceImpl implements UserService {
        @Override
        @Log
        public boolean login(String username, String password) {
            // ...
            // dao等操作
            // ...
            System.out.println("UserService的核心业务-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"/>
    

九、AOP—组合切入点表达式

  • 可以使用&&(and)||(or)!来组合切入点表达式:

  • 使用&&时需要使用字符实体:&amp;&amp;

  • 例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>
    
  • 例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>
    
  • 例3:

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

十、AOP—细节

在这里插入图片描述

问题展现:

目标类:

public class UserServiceImpl implements UserService {
    @Override
    @Log // 目标方法:需要代理
    public boolean login(String username, String password) {
        System.out.println("--------------------login--------------------");
        // 此处调用了另外一个目标方法
        if (register(username, password)) {
            return "lpruoyu".equals(username) && "123456".equals(password);
        }
        return false;
    }

    @Override
    @Log // 目标方法:需要代理
    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"/>

调用:

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");
    }
}

问题解决

public class UserServiceImpl implements UserService, ApplicationContextAware , BeanNameAware {

    private ApplicationContext applicationContext;
    private String beanName;

    @Override
    @Log // 目标方法:需要代理
    public boolean login(String username, String password) {
        System.out.println("--------------------login--------------------");
        // 通过同一个id、在同一个容器下获取到的scope为singleton的对象永远是同一个
        // 可以理解为一个ApplicationContext就是一个容器

        // 把代理对象拿出来,让代理对象去调用另外一个目标方法
        UserService userService =  applicationContext.getBean(beanName, UserService.class);
        if (userService.register(username, password)) {
            return "lpruoyu".equals(username) && "123456".equals(password);
        }
        return false;
    }

    @Override
    @Log // 目标方法:需要代理
    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;
    }

}

十一、AOP—配置多个pointcut、advisor

在这里插入图片描述

在这里插入图片描述

    <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(..))"/>
        <!-- 为pc1这个切入点切入两段附加代码 -->
        <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>

注意和一些细节

  • 热爱编程、热爱代码
  • 编程是一门艺术,如果爱,请深爱!

  • 多敲代码
  • 多尝试敢于尝试
  • 善于发现、解决问题
  • 多多总结
  • 多多思考

  • 使用AOP技术生成的代理对象,只能使用接口去获取,不能使用实现类去获取

    	正确写法:
    	UserService userService =  applicationContext.getBean(beanName, UserService.class);
    
    	错误写法:
    	UserService userService =  applicationContext.getBean(beanName, UserServiceImpl.class);
    
  • 自己使用JDK/CGLib生成的代理对象则无所谓,上面两种写法都可以。


  • 一般来说,JDK自带的API(接口、功能)都比第三方库的简洁、高效,因此能用JDK自带的API实现的功能,就尽量使用JDK自带的API来实现。

参考

小码哥-李明杰: Java从0到架构师③进阶互联网架构师.


本文完,感谢您的关注支持!


举报

相关推荐

0 条评论