JDKProxy与CGlibProxy

一、动态代理原理

1.1 简介

java动态代理是利用反射机制生成一个实现代理接口的匿名类,在调用具体方法前调用InvokeHandler来处理。

而cglib动态代理是利用asm开源包,对代理对象类的class文件加载进来,通过修改其字节码生成子类来处理。

  • 1、如果目标对象实现了接口,默认情况下会采用JDK的动态代理实现AOP
  • 2、如果目标对象实现了接口,可以强制使用CGLIB实现AOP
  • 3、如果目标对象没有实现了接口,必须采用CGLIB库,spring会自动在JDK动态代理和CGLIB之间转换

  • 总结JDK代理只能对接口进行代理,Cglib则是对实现类进行代理

如何强制使用CGLIB实现AOP?

  • (1)添加CGLIB库,SPRING_HOME/cglib/*.jar
  • (2)在spring配置文件中加入

JDK动态代理和CGLIB字节码生成的区别?

  • (1)JDK动态代理只能对实现了接口的类生成代理,而不能针对类
  • (2)CGLIB是针对类实现代理,主要是对指定的类生成一个子类,覆盖其中的方法
    因为是继承,所以该类或方法最好不要声明成final

1.2 性能比较

  • 1) 从 jdk6 到 jdk7、jdk8 ,动态代理的性能得到了显著的提升,而 cglib 的表现并未跟上,甚至可能会略微下降。

  • 2) 传言的 cglib 比 jdk动态代理高出 10 倍的情况也许是出现在更低版本的 jdk 上吧。

  • 3)在jdk7 下,情况发生了逆转!在运行次数较少(1,000,000)的情况下,jdk动态代理比 cglib 快了差不多30%;而当调用次数增加之后(50,000,000), 动态代理比 cglib 快了接近1倍。

二、代码实现

  • 接口类
package cn.yxq.service;
public interface PersonService {
    public void save(String name);
    public void update(String name, Integer personid);
    public String getPersonName(Integer personid);
}
  • 实现类
package cn.yxq.service.impl;
import cn.yxq.service.PersonService;
public class PersonServiceBean implements PersonService{
    private String user = null;

    public String getUser() {
        return user;
    }

    public PersonServiceBean(){}

    public PersonServiceBean(String user){
        this.user = user;
    }

    public String getPersonName(Integer personid) {
        System.out.println("我是getPersonName()方法");
        return "xxx";
    }

    public void save(String name) {
        System.out.println("我是save()方法");
    }

    public void update(String name, Integer personid) {
        System.out.println("我是update()方法");
    }

}
  • JDK动态代理类
/**
 * CGLIB可以生成目标类的子类,并重写父类非final修饰符的方法。
 * @author yxq
 *
 */
public class JDKProxyFactory implements InvocationHandler{
    /**代理的目标对象*/
    private Object targetObject;    
    public Object createProxyIntance(Object targetObject){
        this.targetObject = targetObject;
        /**
         * 第一个参数设置代码使用的类装载器,一般采用跟目标类相同的类装载器
         * 第二个参数设置代理类实现的接口
         * 第三个参数设置回调对象,当代理对象的方法被调用时,会委派给该参数指定的对象的invoke方法
         */
        return Proxy.newProxyInstance(
                this.targetObject.getClass().getClassLoader(), 
                this.targetObject.getClass().getInterfaces(),
                this);
    }
    public Object invoke(Object proxy, Method method, Object[] args)
            throws Throwable {//环绕通知
        PersonServiceBean bean = (PersonServiceBean) this.targetObject;
//      System.out.println("proxy:"+JSONObject.toJSONString(proxy));
//      System.out.println("method:"+JSONObject.toJSONString(method));
//      System.out.println("arg:");
//      for(Object o:args){
//          System.out.println(JSONObject.toJSONString(o));
//      }
        Object result = null; 
        if(bean.getUser()!=null){
//          System.out.println("..... advice()-->前置通知");
            //..... advice()-->前置通知
            try {
                /**把方法调委派给目标对象*/
                result = method.invoke(targetObject, args);
//              System.out.println("afteradvice() -->后置通知");
                // afteradvice() -->后置通知
            } catch (RuntimeException e) {
                //exceptionadvice()--> 例外通知
//              System.out.println("exceptionadvice()--> 例外通知");
            }finally{
//              System.out.println("finallyadvice(); -->最终通知");
                //finallyadvice(); -->最终通知
            }
        }
        return result;
    }
}
  • 测试类
package junit.test;
import org.junit.BeforeClass;
import org.junit.Test;
import cn.yxq.aop.CGlibProxyFactory;
import cn.yxq.aop.JDKProxyFactory;
import cn.yxq.service.PersonService;
import cn.yxq.service.impl.PersonServiceBean;
public class AOPTest {

    @BeforeClass
    public static void setUpBeforeClass() throws Exception {
    }

    @Test public void TestJDKProxy(){
        System.out.println("JDKProxyFactory--begin");
        JDKProxyFactory factory = new JDKProxyFactory();
        PersonService service = (PersonService) factory.createProxyIntance(new PersonServiceBean("xxx"));
        service.save("888");
    }

    @Test public void TestCGlibProxy(){
        System.out.println("CGlibProxyFactory--begin");
        CGlibProxyFactory factory = new CGlibProxyFactory();
        PersonServiceBean service = (PersonServiceBean) factory.createProxyIntance(new PersonServiceBean("xxx"));
        service.save("999");
    }
}
  • 运行结果:
JDKProxyFactory--begin
我是save()方法
CGlibProxyFactory--begin
我是save()方法
  • JDK代理是不需要以来第三方的库,只要要JDK环境就可以进行代理,它有几个要求
    实现InvocationHandler
    使用Proxy.newProxyInstance产生代理对象
    被代理的对象必须要实现接口

  • CGLib 必须依赖于CGLib的类库
    但是它需要类来实现任何接口代理的是指定的类生成一个子类,覆盖其中的方法,是一种继承但是针对接口编程的环境下推荐使用JDK的代理
    在Hibernate中的拦截器其实现考虑到不需要其他接口的条件Hibernate中的相关代理采用的是CGLib来执行。

三、AOP

  • Aspect(切面):指横切性关注点的抽象即为切面,它与类相似,只是两者的关注点不一样,类是对物体特征的抽象,而切面横切性关注点的抽象.

  • joinpoint(连接点):所谓连接点是指那些被拦截到的点。在spring中,这些点指的是方法,因为spring只支持方法类型的连接点,实际上joinpoint还可以是field或类构造器)

  • Pointcut(切入点):所谓切入点是指我们要对那些joinpoint进行拦截的定义.

  • Advice(通知):所谓通知是指拦截到joinpoint之后所要做的事情就是通知.通知分为前置通知,后置通知,异常通知,最终通知,环绕通知

  • Target(目标对象):代理的目标对象

  • Weave(织入):指将aspects应用到target对象并导致proxy对象创建的过程称为织入.

  • Introduction(引入):在不修改类代码的前提下, Introduction可以在运行期为类动态地添加一些方法或Field.

3.1 使用Spring进行面向切面(AOP)编程

要进行AOP编程,首先我们要在spring的配置文件中引入aop命名空间:

<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-2.5.xsd
           http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd">
</beans>

Spring提供了两种切面声明方式,实际工作中我们可以选用其中一种:
- 基于XML配置方式声明切面。
- 基于注解方式声明切面。

3.2 基于注解方式声明切面

@Aspect
public class LogPrint {
    @Pointcut("execution(* cn.itcast.service..*.*(..))")
    private void anyMethod() {}//声明一个切入点    
    @Before("anyMethod() && args(userName)")//定义前置通知
    public void doAccessCheck(String userName) {
    }   
    @AfterReturning(pointcut="anyMethod()",returning="revalue")//定义后置通知
    public void doReturnCheck(String revalue) {
    }
    @AfterThrowing(pointcut="anyMethod()", throwing="ex")//定义例外通知
    public void doExceptionAction(Exception ex) {
    }
    @After("anyMethod()")//定义最终通知
    public void doReleaseAction() {
    }
    @Around("anyMethod()")//环绕通知
    public Object doBasicProfiling(ProceedingJoinPoint pjp) throws Throwable {
        return pjp.proceed();
    }
}
  • 接口
package cn.yxq.service;
public interface PersonService {
    public void save(String name);
    public void update(String name, Integer id);
    public String getPersonName(Integer id);
}
  • 实现类
package cn.yxq.service.impl;

import cn.yxq.service.PersonService;

public class PersonServiceBean implements PersonService {

    public String getPersonName(Integer id) {
        System.out.println("我是getPersonName()方法");
        return "xxx";
    }

    public void save(String name) {
        //throw new RuntimeException("我爱例外");
        System.out.println("我是save()方法");
    }

    public void update(String name, Integer id) {
        System.out.println("我是update()方法");
    }

}

3.3 基于基于XML配置方式声明切面

  • 切面
package cn.yxq.service;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
/**
 * 切面
 * 
 */
@Aspect
public class MyInterceptor {
    @Pointcut("execution (* cn.yxq.service.impl.PersonServiceBean.*(..))")
    private void anyMethod() {
    }// 声明一个切入点

    @Before("anyMethod() && args(name)")
    public void doAccessCheck(String name) {
        System.out.println("前置通知:" + name);
    }
    @AfterReturning(pointcut = "anyMethod()", returning = "result")
    public void doAfterReturning(String result) {
        System.out.println("后置通知:" + result);
    }
    @After("anyMethod()")
    public void doAfter() {
        System.out.println("最终通知");
    }
    @AfterThrowing(pointcut = "anyMethod()", throwing = "e")
    public void doAfterThrowing(Exception e) {
        System.out.println("例外通知:" + e);
    }
    @Around("anyMethod()")
    public Object doBasicProfiling(ProceedingJoinPoint pjp) throws Throwable {
        // if(){//判断用户是否在权限
        System.out.println("进入方法");
        Object result = pjp.proceed();
        System.out.println("退出方法");
        // }
        return result;
    }
}
  • XML配置方式声明切面
    <aop:aspectj-autoproxy/> 
    <bean id="personService" class="cn.yxq.service.impl.PersonServiceBean"></bean>
    <bean id="aspetbean" class="cn.yxq.service.MyInterceptor"/>
    <aop:config>
        <aop:aspect id="asp" ref="aspetbean">
            <aop:pointcut id="mycut" expression="execution(* cn.yxq.service..*.*(..))"/>
            <aop:before pointcut-ref="mycut" method="doAccessCheck"/>
            <aop:after-returning pointcut-ref="mycut" method="doAfterReturning"/>
            <aop:after-throwing pointcut-ref="mycut" method="doAfterThrowing"/>
            <aop:after pointcut-ref="mycut" method="doAfter"/>
            <aop:around pointcut-ref="mycut" method="doBasicProfiling"/>
        </aop:aspect>
    </aop:config>

3.4 Springboot-Aop

  • 网关web层,统一参数校验aop
@Aspect
@Component
public class ReqValidAop {
    private static final Logger logger = LoggerFactory.getLogger(ReqValidAop.class);

    @Pointcut(value = "execution(public * com.doordu.soa.service.admin.controller.device.*.*(..)) && @annotation(io.swagger.annotations.ApiOperation)")
    public void deviceController() {
    }

    @Pointcut(value = "execution(public * com.doordu.soa.service.admin.controller.user..*(..)) && @annotation(io.swagger.annotations.ApiOperation)")
    public void userController() {
    }

    @Pointcut(value = "execution(public * com.doordu.soa.service.admin.controller.house..*(..)) && @annotation(io.swagger.annotations.ApiOperation)")
    public void houseController() {
    }

    @Pointcut(value = "execution(public * com.doordu.soa.service.admin.controller.flow..*(..)) && @annotation(io.swagger.annotations.ApiOperation)")
    public void flowController() {
    }

    @Pointcut(value = "execution(public * com.doordu.soa.service.admin.controller.parking..*(..)) && @annotation(io.swagger.annotations.ApiOperation)")
    public void parkingController() {
    }

    @Pointcut(value = "execution(public * com.doordu.soa.service.admin.controller.third..*(..)) && @annotation(io.swagger.annotations.ApiOperation)")
    public void thirdController() {
    }

    @Around(value = "deviceController() || userController()|| houseController() || flowController() || parkingController() || thirdController()")
    public Object doValid(ProceedingJoinPoint joinPoint) throws Throwable {
        for (Object args : joinPoint.getArgs()) {
            String errMsg = DataValidService.checkData(args);
            if (StringUtils.isNotBlank(errMsg)) {
                return ((BaseController) joinPoint.getThis()).checkParamError(errMsg);
            }

            if (args instanceof PageParam) {
                PageParam pageParam = (PageParam) args;
                if (Integer.valueOf(pageParam.getPage()) <= 0 || Integer.valueOf(pageParam.getLimit()) <= 0) {
                    return ((BaseController) joinPoint.getThis()).checkParamError("分页参数校验失败,正确格式为正整数");
                }
            }
        }
        return joinPoint.proceed(joinPoint.getArgs());
    }
}

猜你喜欢

转载自blog.csdn.net/hardworking0323/article/details/81459091