4

如果Controller里有私有的方法,能成功访问吗? - eaglelihh

 2 years ago
source link: https://www.cnblogs.com/eaglelihh/p/16559415.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

如果Controller里有私有的方法,能成功访问吗?

写代码的时候,复制粘贴的时候,没注意到方法的属性,就导致了Controller里有了一个私有的方法,然后访问这个接口的时候就报了空指针异常,找了好久才找到原因。

来看一个例子

@Service
public class MyService {
    public String hello() {
        return "hello";
    }
}

@Slf4j
@RestController
@RequestMapping("/test")
public class MyController {

    @Autowired
    private MyService myService;

    @GetMapping("/public")
    public Object publicHello() {
        return myService.hello();
    }

    @GetMapping("/protected")
    protected Object protectedHello() {
        return myService.hello();
    }

    @GetMapping("/private")
    private Object privateHello() {
        return myService.hello();
    }
}

@EnableAspectJAutoProxy
@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})
public class MyApplication {
    public static void main(String[] args) {
        SpringApplication.run(MyApplication.class, args);
    }
}
访问 
http://127.0.0.1:8081/test/public 200
http://127.0.0.1:8081/test/protected 200
http://127.0.0.1:8081/test/private 200

如果在这个基础之上再加一个切面:

@Slf4j
@Aspect
@Component
public class MyAspect {

    @Pointcut("execution(* cn.eagle.li.controller..*.*(..))")
    public void controllerSayings() {
    }

    @Before("controllerSayings()")
    public void sayHello() {
        log.info("注解类型前置通知");
    }
}
访问 
http://127.0.0.1:8081/test/public 200
http://127.0.0.1:8081/test/protected 200
http://127.0.0.1:8081/test/private 500:报空指针异常,原因是myService为null的
  • public 方法

    1656633-20220807172431066-545485776.png
  • protected 方法

    1656633-20220807172357812-383447742.png
  • private 方法

    1656633-20220807172028932-1384260975.png

大致可以看到原因,public方法和protected方法访问的时候,它的类都是真实的类

而private方法是代理的类

cglib代理的锅

Spring Boot 2.0 开始,默认使用的是cglib代理

@Configuration
@ConditionalOnClass({ EnableAspectJAutoProxy.class, Aspect.class, Advice.class,
		AnnotatedElement.class })
@ConditionalOnProperty(prefix = "spring.aop", name = "auto", havingValue = "true",
		matchIfMissing = true)
public class AopAutoConfiguration {
	@Configuration
	@EnableAspectJAutoProxy(proxyTargetClass = false)
	@ConditionalOnProperty(prefix = "spring.aop", name = "proxy-target-class",
			havingValue = "false", matchIfMissing = false)
	public static class JdkDynamicAutoProxyConfiguration {
	}

	@Configuration
	@EnableAspectJAutoProxy(proxyTargetClass = true)
	@ConditionalOnProperty(prefix = "spring.aop", name = "proxy-target-class",
			havingValue = "true", matchIfMissing = true)
	public static class CglibAutoProxyConfiguration {
	}
}

入口

1656633-20220807192047907-319592889.png
1656633-20220807204712766-1903636182.png

不管public还是private的方法,都是这样执行的。

生成代理类字节码

    public static void main(String[] args) {
        /** 加上这句代码,可以生成代理类的class文件*/
        System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "org/springframework/cglib"); 
        SpringApplication.run(MyApplication.class, args);
    }

部分代理类字节码如下:

    protected final Object protectedHello() {
        try {
            MethodInterceptor var10000 = this.CGLIB$CALLBACK_0;
            if (var10000 == null) {
                CGLIB$BIND_CALLBACKS(this);
                var10000 = this.CGLIB$CALLBACK_0;
            }

            return var10000 != null ? var10000.intercept(this, CGLIB$protectedHello$1$Method, CGLIB$emptyArgs, CGLIB$protectedHello$1$Proxy) : super.protectedHello();
        } catch (Error | RuntimeException var1) {
            throw var1;
        } catch (Throwable var2) {
            throw new UndeclaredThrowableException(var2);
        }
    }

   public final Object publicHello() {
        try {
            MethodInterceptor var10000 = this.CGLIB$CALLBACK_0;
            if (var10000 == null) {
                CGLIB$BIND_CALLBACKS(this);
                var10000 = this.CGLIB$CALLBACK_0;
            }

            return var10000 != null ? var10000.intercept(this, CGLIB$publicHello$0$Method, CGLIB$emptyArgs, CGLIB$publicHello$0$Proxy) : super.publicHello();
        } catch (Error | RuntimeException var1) {
            throw var1;
        } catch (Throwable var2) {
            throw new UndeclaredThrowableException(var2);
        }
    }

public和protected方法会生成上述的方法,而private方法是不会生成这样的方法

	private static class DynamicAdvisedInterceptor implements MethodInterceptor, Serializable {
	        @Override
		@Nullable
		public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
			Object oldProxy = null;
			boolean setProxyContext = false;
			Object target = null;
			TargetSource targetSource = this.advised.getTargetSource();
			try {
				if (this.advised.exposeProxy) {
					// Make invocation available if necessary.
					oldProxy = AopContext.setCurrentProxy(proxy);
					setProxyContext = true;
				}
				// Get as late as possible to minimize the time we "own" the target, in case it comes from a pool...
				target = targetSource.getTarget();
				Class<?> targetClass = (target != null ? target.getClass() : null);
				List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);
				Object retVal;
				// Check whether we only have one InvokerInterceptor: that is,
				// no real advice, but just reflective invocation of the target.
				if (chain.isEmpty() && Modifier.isPublic(method.getModifiers())) {
					// We can skip creating a MethodInvocation: just invoke the target directly.
					// Note that the final invoker must be an InvokerInterceptor, so we know
					// it does nothing but a reflective operation on the target, and no hot
					// swapping or fancy proxying.
					Object[] argsToUse = AopProxyUtils.adaptArgumentsIfNecessary(method, args);
					retVal = methodProxy.invoke(target, argsToUse);
				}
				else {
					// We need to create a method invocation...
					retVal = new CglibMethodInvocation(proxy, target, method, args, targetClass, chain, methodProxy).proceed();
				}
				retVal = processReturnType(proxy, target, method, retVal);
				return retVal;
			}
			finally {
				if (target != null && !targetSource.isStatic()) {
					targetSource.releaseTarget(target);
				}
				if (setProxyContext) {
					// Restore old proxy.
					AopContext.setCurrentProxy(oldProxy);
				}
			}
		}
    }

public和protected方法会调用DynamicAdvisedInterceptor.intercept方法,这里面的this.advised.getTargetSource()可以获得真实的目标类,这个目标类是注入成功。

换成JDK动态代理呢

增加配置:

spring:
  aop:
    proxy-target-class: false

增加接口:

@RestController
public interface MyControllerInterface {
    @RequestMapping("/hello/public")
    Object publicHello();

    @RequestMapping("/hello/default")
    default Object defaultHello() {
        return "hi default";
    }
}

@Slf4j
@RestController
@RequestMapping("/test")
public class MyController implements MyControllerInterface {

    @Autowired
    public MyService myService;

    @Override
    @GetMapping("/public")
    public Object publicHello() {
        return myService.hello();
    }

    @GetMapping("/protected")
    protected Object protectedHello() {
        return myService.hello();
    }

    @GetMapping("/private")
    private Object privateHello() {
        return myService.hello();
    }
}

MyControllerInterface头上加@RestController的原因是:

	protected boolean isHandler(Class<?> beanType) {
		return (AnnotatedElementUtils.hasAnnotation(beanType, Controller.class) ||
				AnnotatedElementUtils.hasAnnotation(beanType, RequestMapping.class));
	}
http://127.0.0.1:8081/test/public 404
http://127.0.0.1:8081/test/protected 404
http://127.0.0.1:8081/test/private 404

http://127.0.0.1:8081/hello/public 200
http://127.0.0.1:8081/hello/default 200

只能使用接口里的@RequestMapping,实现类里的不生效

听说SpringAOP 有坑?那就来踩一踩
源码角度深入理解JDK代理与CGLIB代理


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK