Spring AOP与动态代理

AOP(Aspect-Oriented Programming:面向切面编程)能够将那些与业务无关,却为业务模块所共同调用的逻辑或责任(例如事务处理、日志管理、权限控制等)封装起来,便于减少系统的重复代码,降低模块间的耦合度,并有利于未来的可拓展性和可维护性

Spring AOP使用的动态代理,所谓的动态代理就是说AOP框架不会去修改字节码,而是在内存中临时为方法生成一个AOP对象,这个AOP对象包含了目标对象的全部方法,并且在特定的切点做了增强处理,并回调原对象的方法。

Spring AOP中的动态代理主要有两种方式,JDK动态代理和CGLIB动态代理。JDK动态代理通过反射来接收被代理的类,并且要求被代理的类必须实现一个接口。JDK动态代理的核心是InvocationHandler接口和Proxy类。

如果目标类没有实现接口,那么Spring AOP会选择使用CGLIB来动态代理目标类。CGLIB(Code Generation Library),是一个代码生成的类库,可以在运行时动态的生成某个类的子类。注意,CGLIB是通过继承的方式做的动态代理,因此如果某个类被标记为final,那么它是无法使用CGLIB做动态代理的

SpringAOPProcess

使用 AOP 之后我们可以把一些通用功能抽象出来,在需要用到的地方直接使用即可,这样大大简化了代码量。我们需要增加新功能时也方便,这样也提高了系统扩展性。日志功能、事务管理等等场景都用到了 AOP 。

(1)JDK动态代理

自定义注解PersonAnnotation

package com.calvin.exercise.annotation;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface PersonAnnotation {

    String language() default "english";

}

定义Aspect

package com.calvin.exercise.aspect;

import com.calvin.exercise.annotation.PersonAnnotation;
import java.lang.reflect.Method;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

@Aspect
@Component
public class PersonAspect {

    private Logger logger = LoggerFactory.getLogger(PersonAspect.class);

    /**
     * 定义切点
     */
    @Pointcut("@annotation(com.calvin.exercise.annotation.PersonAnnotation)")
    public void pointCut() {

    }

    /**
     * 切点前执行
     */
    @Before("pointCut()")
    public void before() {
        logger.info("before method proceed............");

    }

    /**
     * 切点后执行
     */
    @After("pointCut()")
    public void after() {
        logger.info("after method proceed............");
    }

    @Around("pointCut()")
    public void around(ProceedingJoinPoint joinPoint) throws Throwable {
        logger.info("around before............");
		// 执行完成目标方法
		joinPoint.proceed();
        logger.info("around after..............");
    }
}

定义接口PersonService及接口实现类

package com.calvin.exercise.service;

public interface PersonService {

    void say(String name);

}
package com.calvin.exercise.service.impl;

import com.calvin.exercise.annotation.PersonAnnotation;
import com.calvin.exercise.service.PersonService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

@Component
public class PersonServiceImpl implements PersonService {

    private Logger logger = LoggerFactory.getLogger(PersonServiceImpl.class);

    public PersonServiceImpl() {
        super();
        logger.info("正在生成一个PersonService实例");
    }

    /**
     * 该注解是用来定义切点
     * @param name
     */
    @Override
    @PersonAnnotation(language = "Chinese")
    public void say(String name) {
        logger.info("say method : say {}", name);
    }

}

测试

@RunWith(SpringRunner.class)
@SpringBootTest
public class SpringAopTest {

    @Autowired
    private PersonService personService;

    @Test
    public void testJdkAopSay() throws Exception{
        personService.say("chinese");
    }

}

控制台输出结果:
在这里插入图片描述
可以看到,环绕的执行顺序是@Around→@Before→@After→@Around执行ProceedingJoinPoint.proceed() 。因此 ,@Around可以实现@Before和@After的功能,并且只需要在一个方法中就可以实现。

当然,如果我们想获取注解中的值 ,可以使用反射来获取。

将@Around注解的方法改为:

@Around("pointCut()")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
    // 方法签名
        Signature signature = joinPoint.getSignature();
        Method method = ((MethodSignature) signature).getMethod();
        // 这个方法才是目标对象上有注解的方法
        Method realMethod = joinPoint.getTarget().
                getClass().getDeclaredMethod(signature.getName(),
                method.getParameterTypes());
        // 通过反射获取注解
        PersonAnnotation annotation = realMethod.getDeclaredAnnotation(PersonAnnotation.class);
        String language = annotation.language();
        logger.info("language:{}", language);
        // 执行完成目标方法
        return joinPoint.proceed();
}

再次测试,查看控制台 输出,发现可以获取到注解的值。
在这里插入图片描述
然而通过Debug我们发现动态代理生成的对象是CGLIB动态代理生成的。
在这里插入图片描述
而我们希望的是对象通过jdk动态代理生成 。原因其实是笔者基于Spring Boot2.0开发 ,而在 Spring Boot 2.0 中,Spring Boot现在默认使用CGLIB动态代理(基于类的动态代理), 包括AOP。 如果需要基于接口的动态代理(JDK基于接口的动态代理) ,只需要在application.properties设置spring.aop.proxy-target-class属性为false即可。
配置文件添加该属性并重启后,再次Debug发现,对象已经是基于JDK动态代理生成。
在这里插入图片描述
(2)CGLIB动态代理

创建一个不实现接口的类WorkerService

package com.calvin.exercise.service;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class WorkerService {

    private Logger logger = LoggerFactory.getLogger(WorkerService.class);

    public WorkerService() {
        super();
        logger.info("正在生成一个WorkerService实例");
    }


    public void say(String name) {
        logger.info("say method : say {}", name);
    }

}

创建动态代理类CglibProxy

代理类实现方法拦截器,我们可以在重写的intercept()方法中织入执行内容,类似于切面。

package com.calvin.exercise.cjlib;

import java.lang.reflect.Method;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;

/**
 * @Title CglibProxy
 * @Description Cglib动态代理
 * @author calvin
 * @date: 2020/2/23 6:17 PM 
 */
public class CglibProxy implements MethodInterceptor {

    private Logger logger = LoggerFactory.getLogger(CglibProxy.class);

    private Enhancer enhancer = new Enhancer();

    /**
     * 产生一个代理对象
     * @param clazz 目标对象
     * @return
     */
    public Object createProxyInstance(Class clazz) {
        // 利用enhancer动态生成class对象的实例
        // 设置目标对象为父类
        enhancer.setSuperclass(clazz);
        // 设置回调,在这里指本身,MethodInterceptor 实现了callback接口,如果回调会调用intercept方法
        enhancer.setCallback(this);
        // 使用字节码技术动态创建子类的实例
        return enhancer.create();
    }

    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        logger.info("say before");
        Object result = methodProxy.invokeSuper(o, objects);
        logger.info("say after");
        return result;
    }
}

测试

@RunWith(SpringRunner.class)
@SpringBootTest
public class SpringAopCjLibTest {

    @Test
    public void testCjLibAopSay() {
        CglibProxy proxy = new CglibProxy();
        WorkerService workerService = (WorkerService) proxy.createProxyInstance(WorkerService.class);
        workerService.say("english");
    }

}

控制台输出结果:在这里插入图片描述
同样的,我们通过Debug也来看看,当前对象是否通过CGLIB动态代理生成。
在这里插入图片描述
可以看到,此时生成的对象是基于CGLIB动态代理生成。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值