1.AOP编程

1.1 AOP概念

AOP (Aspect Oriented Programing) 面向切面编程 Spring动态代理开发
以切面为基本单位的程序开发,通过切面间的彼此协同,相互调用,完成程序的构建
切面 = 切入点 + 额外功能

OOP (Object Oritened Programing) 面向对象编程 Java
以对象为基本单位的程序开发,通过对象间的彼此协同,相互调用,完成程序的构建

POP (Producer Oriented Programing) 面向过程(方法、函数)编程 C
以过程(方法、函数) 为基本单位的程序开发,通过过程间的彼此协同,相互调用,完成程序的构建

1.2 AOP编程的开发步骤

  1. 原始对象
  2. 额外功能 (MethodInterceptor)
  3. 切入点
  4. 组装切面 (额外功能+切入点)

1.3 切面的名词解释

切面 = 切入点 + 额外功能

几何学
面 = 点 + 相同的性质

2. 动态代理类的创建

2.1 JDK的动态代理

java 同一个类方法调用_java 同一个类方法调用

public class TestJDKProxy {

    public static void main(String[] args) {
        //1 创建原始对象
        UserService userService = new UserServiceImpl();

        //2 JDK创建动态代理
       
        InvocationHandler handler = new InvocationHandler(){
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                System.out.println("------proxy  log --------");
                //原始方法运行
                Object ret = method.invoke(userService, args);
                return ret;
            }
        };

        UserService userServiceProxy = (UserService)Proxy.newProxyInstance(UserServiceImpl.class.getClassLoader(),userService.getClass().getInterfaces(),handler);

        userServiceProxy.login("suns", "123456");
        userServiceProxy.register(new User());
    }
}

2.2 CGlib的动态代理

CGlib创建动态代理的原理:父子继承关系创建代理对象,原始类作为父类,代理类作为子类,这样既可以保证2者方法一致,同时在代理类中提供新的实现(额外功能+原始方法)

java 同一个类方法调用_java_02

public void test4(){
        AdminService adminService=new AdminService();

        Enhancer enhancer=new Enhancer();

        //设置类加载器
        enhancer.setClassLoader(String.class.getClassLoader());
        //设置父类
        enhancer.setSuperclass(adminService.getClass());


        MethodInterceptor methodInterceptor = new MethodInterceptor() {
            @Override
            public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
                System.out.println("===== cglib  log =====");
                Object ret = method.invoke(adminService,objects);

                return ret;
            }
        };
        //设置额外功能
        enhancer.setCallback(methodInterceptor);
        //通过字节码技术动态创建子类实例
        AdminService adminServiceProxy = (AdminService) enhancer.create();

        adminServiceProxy.login("xiaohahaha","123123");
        adminServiceProxy.register(new User("lilei","666666"));
    }

总结:

  1. JDK动态代理 Proxy.newProxyInstance() 通过接口创建代理的实现类
  2. Cglib动态代理 Enhancer 通过继承父类创建的代理类

3. AOP的底层实现原理(简易版)

3.1 核心问题

  1. AOP如何创建动态代理类
借用了其他类的类加载器,通过动态字节码技术,进行创建
  1. Spring工厂如何加工创建代理对象
通过原始对象的id值,获得的是代理对象
   底层是Bean的后置处理技术(BeanPostProcessor)对对象进行处理生成代理对象, 且最终是返回的代理对象

3.2 Spring工厂如何加工原始对象

public class ProxyBeanPostProcessor implements BeanPostProcessor {
    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        return bean;
    }

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        InvocationHandler invocationHandler=new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                System.out.println("====BeanPostProcessor====");
                Object ret = method.invoke(bean, args);
                return ret;
            }
        };

        Object proxyInstance = Proxy.newProxyInstance(String.class.getClassLoader(), bean.getClass().getInterfaces(), invocationHandler);

        return proxyInstance;
    }
}
<!--1. 实现BeanPostProcessor 进行加工
        2. 配置文件中对BeanPostProcessor进行配置
    -->

    <bean id="studentService" class="com.tcgroup.service.StudentServiceImpl"/>

    <bean id="proxyBeanPostProcessor" class="com.tcgroup.beanPost.ProxyBeanPostProcessor"/>

4、基于注解的AOP编程

4.1 基于注解的AOP编程的开发步骤

  1. 原始对象
  2. 额外功能
  3. 切入点
  4. 组装切面

原来额外功能的实现:通过实现MethodInterceptor接口,在invoke方法里面实现

public class MyArround implements MethodInterceptor{

    public Object invoke(MethodInvocation invocation){
            Object ret = invocation.proceed();
			return ret;
    }
}

原来切入点是在aop:pointcut标签里面定义

<aop:config
                 <aop:pointcut id=""  expression="execution(* login(..))"/>

通过注解编程:只要在类上面加上@Aspect注解 就可以告知Spring这是一个额外功能的实现类,在方法加上@Around注解,就可以在注解里面定义切入点了,不需要实现任何接口

@Aspect
public class MyAspect {

    @Around("execution(* login(..))")
    public Object arround(ProceedingJoinPoint joinPoint) throws Throwable {
        System.out.println("----- Aspect  log -----");
        Object ret =joinPoint.proceed();
        return ret;
    }
}

接下来在Spring配置文件里面告知Spring基于注解进行AOP编程

<bean id="studentService" class="com.tcgroup.service.StudentServiceImpl"/>

<!--    <bean id="proxyBeanPostProcessor" class="com.tcgroup.beanPost.ProxyBeanPostProcessor"/>-->

<!--  创建额外功能的实现类的实例  -->
    <bean id="arround" class="com.tcgroup.aspect.MyAspect"/>

    <!--告知Spring基于注解进行AOP编程-->
    <aop:aspectj-autoproxy />

实现结果和预期一致,跟之前不使用注解的结果是一样的

java 同一个类方法调用_spring_03

4.2 切入点复用 @Pointcut注解

切入点复用:在切面类中定义一个函数 上面@Pointcut注解 通过这种方式,定义切入点表达式,后续更加有利于切入点复用。

  1. 定义一个空参空方法体的方法
  2. 在方法上加上@Pointcut注解,在注解中定义切入点
  3. 可以在其他方法上通过 @Around注解的value属性引用这个切入点
@Aspect
public class MyAspect {

    @Pointcut("execution(* login(..))")
    public void loginPointCut(){}

    @Around(value = "loginPointCut()")
    public Object arround(ProceedingJoinPoint joinPoint) throws Throwable {
        System.out.println("----- Aspect  log -----");
        Object ret =joinPoint.proceed();
        return ret;
    }

    @Around(value = "loginPointCut()")
    public Object arround2(ProceedingJoinPoint joinPoint) throws Throwable {
        System.out.println("----- 其他功能 -----");
        Object ret =joinPoint.proceed();
        return ret;
    }
}

5. 指定动态代理的创建方式

AOP底层实现有2种代理创建方式

  1. JDK 通过实现接口 做新的实现类方式 创建代理对象
  2. Cglib通过继承父类 做新的子类 创建代理对象
    默认情况下 AOP编程底层都是应用JDK动态代理创建方式
    如果需要切换Cglib通过继承父类的动态代理创建方式
  1. 基于注解AOP开发
    在告知Spring基于注解进行AOP编程时, 通过proxy-target-class=“true” 设定成Cglib的创建方式
<aop:aspectj-autoproxy proxy-target-class="true"/>
  1. 传统的AOP开发
    aop:config标签里面 同样通过proxy-target-class=“true” 设定成Cglib的创建方式
<aop:config proxy-target-class="true">
<!--    expression:切入点表达式
        execution(*空格*(..)) 所有类的所有方法都作为切入点-->
        <aop:pointcut id="pc" expression="execution(public boolean com.tcgroup.service.UserService.login(String,String))"/>
<!--     组装:目的:把切入点与额外功能进行整合   -->
        <aop:advisor advice-ref="arrount" pointcut-ref="pc"/>
    </aop:config>

6.AOP开发中的一个坑

坑:在同一个业务类中,进行业务方法间的相互调用,只有最外层的方法,才是加入了额外功能(内部的方法,通过普通的方式调用,都调用的是原始方法)。
如果想让内层的方法也调用代理对象的方法,就要AppicationContextAware获得工厂,进而获得代理对象。

给StudentService的login和register方法都加了额外功能

java 同一个类方法调用_java_04


如果从register方法中调用login方法, 应该两个方法都实现额外功能的, 但是实际只有register方法实现了额外功能,但是login方法没有实现, 这是因为当register方法调用login方法时,调用的时原始对象的login方法,而原始对象的login方法是没有实现额外功能的。

java 同一个类方法调用_aop_05


如果想让内层的方法也调用代理对象的方法,就要让原始类实现AppicationContextAware接口 获得工厂,进而获得代理对象。再通过工厂再次getBean拿到对象,这是拿到的还是原来的代理对象,然后在通过代理对象进行调用其他方法

public class StudentServiceImpl implements StudentService, ApplicationContextAware {

    private ApplicationContext applicationContext ;

    @Override
    public void register(User user) {
        System.out.println("=====StudentServiceImpl.register =====");
        StudentService studentService=(StudentService) applicationContext.getBean("studentService");
        studentService.login("zhangsan","999666");
    }

    @Override
    public boolean login(String name, String password) {
        System.out.println("=====StudentServiceImpl.login=====");
        return false;
    }

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

7. AOP 总结

java 同一个类方法调用_aop_06