Spring Boot实践——Spring AOP实现之动态代理
Spring AOP 介绍
AOP的介绍可以查看 Spring Boot实践——AOP实现
与AspectJ的静态代理不同,Spring AOP使用的动态代理,所谓的动态代理就是说AOP框架不会去修改字节码,而是在内存中临时为方法生成一个AOP对象,这个AOP对象包含了目标对象的全部方法,并且在特定的切点做了增强处理,并回调原对象的方法。
Spring AOP中的动态代理主要有两种方式,JDK动态代理和CGLIB动态代理。JDK动态代理通过反射来接收被代理的类,并且要求被代理的类必须实现一个接口。JDK动态代理的核心是InvocationHandler
接口和Proxy
类。
如果目标类没有实现接口,那么Spring AOP会选择使用CGLIB来动态代理目标类。CGLIB(Code Generation Library),是一个代码生成的类库,是利用asm开源包,可以在运行时动态的生成某个类的子类。注意,CGLIB是通过继承的方式做的动态代理,因此如果某个类被标记为final
,那么它是无法使用CGLIB做动态代理的。
这里有注意的几点如下:
- 从Spring 3.2以后不再将CGLIB放在项目的classpath下,而是将CGLIB类打包放在spring-core下面的org.springframework中。这个就意味着基于CGLIB的动态代理与JDK的动态代理在支持“just works”就一样了。
- 在Spring 4.0中,因为CGLIB代理实例是通过Objenesis创建的,所以代理对象的构造器不再有两次调用。
- 在 Spring Boot 2.0 中,Spring Boot现在默认使用CGLIB动态代理(基于类的动态代理), 包括AOP. 如果需要基于接口的动态代理(JDK基于接口的动态代理) , 需要设置spring.aop.proxy-target-class属性为false。
实现方式
一、验证Spring AOP动态代理
博主使用的是Spring Boot 2.0版本
1、JDK动态代理
定义接口
public interface Person { String say(String name); void eat(String food); }
实现类
@Component public class Chinese implements Person { private Logger logger = LoggerFactory.getLogger(Person.class); public Chinese() { super(); logger.info("Chinese ==> Chinese method : 正在生成一个Chinese实例"); } @Override @PersonAnnotation(name="Chinese")//该注解是用来定义切点 public String say(String name) { logger.info("Chinese ==> say method : say {}", name); return name + " hello, JDK implement AOP"; } @Override public void eat(String food) { logger.info("Chinese ==> eat method : eat {}", food); } }
定义Aspect
@Aspect @Component public class PersonAspect { private Logger logger = LoggerFactory.getLogger(PersonAspect.class); @Pointcut("@annotation(com.only.mate.springboot.annotation.PersonAnnotation)") public void pointCut(){ } @Before("pointCut()") public void before(JoinPoint joinPoint) throws Throwable { logger.info("PersonAspect ==> before method : {}", joinPoint.getClass()); } @After("pointCut()") public void after(JoinPoint joinPoint){ logger.info("PersonAspect ==> after method : {}", joinPoint.getClass()); } }
2、CGLIB动态代理
定义一个类
@Component public class American { private Logger logger = LoggerFactory.getLogger(American.class); public American() { super(); logger.info("American ==> American method : 正在生成一个American实例"); } @PersonAnnotation(name="American")//该注解是用来定义切点 public String say(String name) { logger.info("American ==> say method : say {}", name); return name + " hello, CGLIB implement AOP"; } public void eat(String food) { logger.info("American ==> eat method : eat {}", food); } }
3、配置
<!-- 自动扫描使用了aspectj注解的类 --> <aop:aspectj-autoproxy/>
或
@Configuration @ComponentScan("com.only.mate.springboot.aop") @EnableAspectJAutoProxy//开启AspectJ注解 public class CustomAopConfigurer { }
4、运行
@Controller @RequestMapping(value="/aop") public class AopImplementController { private Logger logger = LoggerFactory.getLogger(AopImplementController.class); @Autowired private Person chinese; @Autowired private American american; @ResponseBody @RequestMapping(value="/talk") public String talk() { chinese.say("中国人说汉语"); american.say("American say english"); logger.info("AopImplementController ==> talk method : {}", chinese.getClass()); logger.info("AopImplementController ==> talk method : {}", american.getClass()); return "success"; } }
5、效果图
问题出现了,按照之前的说法,Chinese应该是用JDK动态代理,American使用CGLIB动态代理才对。
由于博主使用的是Spring Boot 2.0 ,在 Spring Boot 2.0 中,Spring Boot现在默认使用CGLIB动态代理(基于类的动态代理), 包括AOP。 如果需要基于接口的动态代理(JDK基于接口的动态代理) ,需要设置spring.aop.proxy-target-class属性为false。
因此在application.properties加上配置spring.aop.proxy-target-class=false。重启访问
如图:
这样才达到了预期的目的
通过网上查找:
- 想要强制使用CGLIB,那么就设置
<aop:config>
下面的proxy-target-class
属性为true
<aop:config proxy-target-class="true"> <!-- other beans defined here... --> </aop:config>
此处没有验证,仅供参考。
- 要是使用@AspectJ强制使用CGLIB的话,可以配置
<aop:aspectj-autoproxy>
下的proxy-target-class
属性为true
<aop:aspectj-autoproxy proxy-target-class="true"/>
此处博主用Spring Boot 2.0,加以上配置无效,还是使用默认的CGLIB动态代理。失效的具体原因有待查验!
- 要是使用@AspectJ强制使用CGLIB的话,向
@EnableAspectJAutoProxy
注解中添加属性proxyTargetClass = true
此处博主用Spring Boot 2.0,加以上配置无效,还是使用默认的CGLIB动态代理。失效的具体原因有待查验!
二、Spring AOP动态代理实现
1、JDK动态代理
import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * @Description: Spring AOP之JDK动态代理实现 * @ClassName: JDKDynamicSubject * @author OnlyMate * @Date 2018年9月11日 下午4:47:53 * */ public class JDKDynamicObject implements InvocationHandler { private Logger logger = LoggerFactory.getLogger(JDKDynamicObject.class); private Object target; public JDKDynamicObject() { } /** * @Description: 绑定对象,并生成代理对象 * @Title: bind * @author OnlyMate * @Date 2018年9月11日 下午4:48:31 * @param target * @return */ public Object bind(Object target) { this.target = target; // 取得代理对象 return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), this); } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { logger.info("JDKDynamicObject ==> invoke method : {},{},{}", proxy.getClass(), method.getName(), args.toString()); method.invoke(target, args); return null; } }
2、CGLIB动态代理
import java.lang.reflect.Method; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.cglib.proxy.Enhancer; import org.springframework.cglib.proxy.InvocationHandler; /** * @Description: Spring AOP之CGLIB动态代理实现 * @ClassName: CGLIBDynamicObject * @author OnlyMate * @Date 2018年9月11日 下午4:57:30 * */ public class CGLIBDynamicObject implements InvocationHandler { private Logger logger = LoggerFactory.getLogger(CGLIBDynamicObject.class); private Object target; public CGLIBDynamicObject() { } /** * @Description: 绑定对象,并生成代理对象 * @Title: bind * @author OnlyMate * @Date 2018年9月11日 下午4:48:31 * @param target * @return */ public Object bind(Object target) { this.target = target; Enhancer enhancer = new Enhancer(); enhancer.setSuperclass(this.target.getClass()); // 回调方法 enhancer.setCallback(this); // 创建代理对象 return enhancer.create(); } @Override public Object invoke(Object arg0, Method arg1, Object[] arg2) throws Throwable { logger.info("CGLIBDynamicObject ==> invoke method : {},{},{}", arg0.getClass(), arg1.getName(), arg2.toString()); arg1.invoke(target, arg2); return null; } }
3、运行
@Controller @RequestMapping(value="/aop") public class AopImplementController { private Logger logger = LoggerFactory.getLogger(AopImplementController.class); @Autowired private Person chinese; @Autowired private American american; @ResponseBody @RequestMapping(value="/talk") public String talk() { // chinese.say("中国人说汉语"); // american.say("American say english"); // // logger.info("AopImplementController ==> talk method : {}", chinese.getClass()); // logger.info("AopImplementController ==> talk method : {}", american.getClass()); //自定义JDK动态代理 // Chinese chinese1 = new Chinese(); // InvocationHandler dsc = new JDKDynamicObject(chinese1); // Person person = (Person) Proxy.newProxyInstance(chinese1.getClass().getClassLoader(), chinese1.getClass().getInterfaces(), dsc); // person.say("中国人说汉语"); // logger.info("AopImplementController ==> talk method : JDKDynamicObject {}", person.getClass()); JDKDynamicObject dsc1 = new JDKDynamicObject(); Person person1 = (Person)dsc1.bind(new Chinese()); person1.say("中国人说汉语"); logger.info("AopImplementController ==> talk method : JDKDynamicObject {}", person1.getClass()); //自定义CGLIB动态代理 CGLIBDynamicObject dsm = new CGLIBDynamicObject(); American american1 = (American) dsm.bind(new American()); american1.say("American say english"); logger.info("AopImplementController ==> talk method : CGLIBDynamicObject {}", american1.getClass()); return "success"; } }
4、效果图
简单的实现了Spring AOP的JDK动态代理和CGLIB动态代理。