WebMvcConfigurer讲解
一句话简介
WebMvcConfigurer配置类其实是Spring内部的一种配置方式,采用JavaBean的形式来代替传统的xml配置文件形式进行针对框架个性化定制,可以自定义一些Handler,Interceptor,ViewResolver,MessageConverter。基于java-based方式的spring mvc配置,需要创建一个配置类并实现WebMvcConfigurer 接口
主要讲解
通过ViewController将一个请求转到一个页面
通过ResourceHandlers实现静态资源的地址映射
通过MessageConverter实现将@ResponseBody实体转Fastjson字符串返回,可以返回实体进行重写
通过addCorsMappings实现ajax跨域请求
通过addInterceptors添加拦截器
SpringBoot2.0(官方推荐)
@Configuration
public class WebMvcConfg implements WebMvcConfigurer {
}
SpringBoot2.0以前版本
@Configuration
public class WebMvcConfig extends WebMvcConfigurerAdapter {
}
配置ResourceHandlers
此方法用来专门注册一个Handler
,来处理静态资源的,例如:图片,js,css等。举例:
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
//可以通过http://127.0.0.1:8080/web/home.html访问resources/web/home.html页面
registry.addResourceHandler("/web/**").addResourceLocations("classpath:/web/");
}
当你请求http://127.0.0.1:8080/web/home.html时,会把resources/web/home.html返回。注意:这里的静态资源是放置在WEB-INF目录下的
配置ViewController
此方法可以很方便的实现一个请求到视图的映射,而无需书写controller
,例如:
@Override
public void addViewControllers(ViewControllerRegistry registry){
registry.addViewController("/login").setViewName("web/login.html");
}
|这是访问http://127.0.0.1:8080/login时,会直接返回login.html页面。
配置MessageConverter
这个配置一般针对于Api
接口服务程序,配置在请求返回时内容采用什么转换器进行转换,我们最常用到的就是fastJson
的转换,配置如下所示
/**
* 消息内容转换配置
* 配置fastJson返回json转换
* @param converters
*/
@Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
//创建fastJson消息转换器
FastJsonHttpMessageConverter fastConverter = new FastJsonHttpMessageConverter(){
protected void writeInternal(Object obj, HttpOutputMessage outputMessage){
try {
if(obj instanceof PreUser){
Map<String,Object> map = new HashMap<>();
map.put("preUser",obj);
map.put("result","success");
super.writeInternal( map, outputMessage);
}else{
super.writeInternal( obj, outputMessage);
}
} catch (IOException e) {
e.printStackTrace();
}
}
};
// FastJsonHttpMessageConverter fastConverter = new FastJsonHttpMessageConverter();
List<MediaType> supportedMediaTypes = new ArrayList<>();
supportedMediaTypes.add(MediaType.APPLICATION_JSON);
supportedMediaTypes.add(MediaType.APPLICATION_JSON_UTF8);
fastConverter.setSupportedMediaTypes(supportedMediaTypes);
//创建配置类
FastJsonConfig fastJsonConfig = new FastJsonConfig();
fastJsonConfig.setCharset(Charset.forName("UTF-8"));
fastJsonConfig.setDateFormat("YYYY-MM-dd");
//修改配置返回内容的过滤
fastJsonConfig.setSerializerFeatures(
SerializerFeature.DisableCircularReferenceDetect,
SerializerFeature.WriteMapNullValue,
SerializerFeature.WriteNullStringAsEmpty,
SerializerFeature.PrettyFormat
);
fastConverter.setFastJsonConfig(fastJsonConfig);
//将fastjson添加到视图消息转换器列表内
converters.add(fastConverter);
}
FastJson SerializerFeatures
WriteNullListAsEmpty :List字段如果为null,输出为[],而非null
WriteNullStringAsEmpty : 字符类型字段如果为null,输出为"",而非null
DisableCircularReferenceDetect :消除对同一对象循环引用的问题,默认为false(如果不配置有可能会进入死循环)
WriteNullBooleanAsFalse:Boolean字段如果为null,输出为false,而非null
WriteMapNullValue:是否输出值为null的字段,默认为false。
WriteNullNumberAsZero: 数值字段如果为null,则输出为0
重写writeInternal方法,可以重新构造返回对象,达到统一的返回值
setDateFormat(“YYYY-MM-dd”);设置日期类型的返回格式
httpmessageconvert在自动配置jackson,默认使用用jackson 。所以如果返回类型为application/json的数据使用jackson 。所以去除jackson的依赖。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<exclusions>
<exclusion>
<artifactId>jackson-databind</artifactId>
<groupId>com.fasterxml.jackson.core</groupId>
</exclusion>
</exclusions>
</dependency>
不去依赖的写法
@Configuration
@ConditionalOnClass({FastJsonHttpMessageConverter.class}) //1
@ConditionalOnProperty(//2
name = {"spring.http.converters.preferred-json-mapper"},
havingValue = "fastjson",
matchIfMissing = true
)
protected static class FastJson2HttpMessageConverterConfiguration {
protected FastJson2HttpMessageConverterConfiguration() {
}
@Bean
@ConditionalOnMissingBean({FastJsonHttpMessageConverter.class})//3
public FastJsonHttpMessageConverter fastJsonHttpMessageConverter() {
//创建fastJson消息转换器
FastJsonHttpMessageConverter fastConverter = new FastJsonHttpMessageConverter(){
protected void writeInternal(Object obj, HttpOutputMessage outputMessage){
try {
if(obj instanceof PreUser){
Map<String,Object> map = new HashMap<>();
map.put("result","success");
map.put("preUser",obj);
super.writeInternal( map, outputMessage);
}else{
super.writeInternal( obj, outputMessage);
}
} catch (IOException e) {
e.printStackTrace();
}
}
};
// FastJsonHttpMessageConverter fastConverter = new FastJsonHttpMessageConverter();
List<MediaType> supportedMediaTypes = new ArrayList<>();
supportedMediaTypes.add(MediaType.APPLICATION_JSON);
supportedMediaTypes.add(MediaType.APPLICATION_JSON_UTF8);
fastConverter.setSupportedMediaTypes(supportedMediaTypes);
//创建配置类
FastJsonConfig fastJsonConfig = new FastJsonConfig();
fastJsonConfig.setCharset(Charset.forName("UTF-8"));
fastJsonConfig.setDateFormat("YYYY-MM-dd");
//修改配置返回内容的过滤
fastJsonConfig.setSerializerFeatures(
SerializerFeature.DisableCircularReferenceDetect,
SerializerFeature.WriteMapNullValue,
SerializerFeature.WriteNullStringAsEmpty,
SerializerFeature.PrettyFormat
);
fastConverter.setFastJsonConfig(fastJsonConfig);
//将fastjson添加到视图消息转换器列表内
// converters.add(fastConverter);
return fastConverter;
}
}
application.yml添加配置
spring:
http:
converters:
preferred-json-mapper: fastjson
以上代码判断返回类型PreUser的对象,进行重写。返回数据为
{
"result":"success",
"preUser":{
"address":"广东广州天河",
"age":29,
"date":"2018-09-20",
"desc":"tom is 29 years old",
"hobby":[
"打球",
"写代码"
],
"name":"tom",
"phone":""
}
}
配置CORS跨域
Spring
既然为了集成了CROS
,那就证明了一点,以后前后端分离是一个开发趋势
/**
* 跨域CORS配置
* @param registry
*/
@Override
public void addCorsMappings(CorsRegistry registry) {
super.addCorsMappings(registry);
registry.addMapping("/cors/**")
.allowedHeaders("*")
.allowedMethods("POST","GET")
.allowedOrigins("*");
}
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title></title>
<script src="js/jquery.min.js"></script>
<script>
$.ajax({
type:"GET",
url: "http://localhost:8080/preUser",
dataType:"json",
success:function(data, status, xhr){
console.log(data);
console.log(xhr.getAllResponseHeaders());
},
error:function(jqXHR){
console.log("Error: "+jqXHR.status);
}
});
</script>
</head>
<body>
cores.html
</body>
</html>
验证访问:http://127.0.0.1:8080/web/cors.html,没设置addCorsMappings()报跨域异常。
配置拦截器
在之前Xml
配置形式天下的时候,我们都是在spring-mvc.xml
配置文件内添加<mvc:interceptor>
标签配置拦截器。
/**
* 拦截器配置
* @param registry
*/
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new CustomHandlerInterceptor()).addPathPatterns("/**");
}
InterceptorRegistry
内的addInterceptor
需要一个实现HandlerInterceptor
接口的拦截器实例,addPathPatterns
方法用于设置拦截器的过滤路径规则·。excludePathPatterns
表示不拦截。
@Slf4j
public class CustomHandlerInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response, Object handler)
throws Exception {
log.info("preHandle:请求前调用");
//返回 false 则请求中断
return true;
}
@Override
public void postHandle(HttpServletRequest request,
HttpServletResponse response, Object handler,
ModelAndView modelAndView) throws Exception {
log.info("postHandle:请求后调用");
}
@Override
public void afterCompletion(HttpServletRequest request,
HttpServletResponse response, Object handler, Exception ex)
throws Exception {
log.info("afterCompletion:请求调用完成后回调方法,即在视图渲染完成后回调");
}
}
每次请求时打印
2018-12-24 22:21:34.434 INFO 2156 --- [io-8080-exec-10] : preHandle:请求前调用
2018-12-24 22:21:34.436 INFO 2156 --- [io-8080-exec-10] : postHandle:请求后调用
2018-12-24 22:21:34.436 INFO 2156 --- [io-8080-exec-10] : afterCompletion:请求调用完成后回调方法,即在视图渲染完成后回调
参考代码
参考链接
Aspect配置说明
配合 Aspect实现样例 一起阅读
一句话介绍
Spring AOP面向切面编程,可以用来配置 事务、做日志、权限验证、在用户请求时做一些处理等等。用@Aspect做一个切面,就可以直接实现。
1.首先定义一个切面类,加上@Component @Aspect这两个注解
@Component
@Aspect
public class LogAspect {
private static final Logger logger = LoggerFactory.getLogger(LogAspect.class);
private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
}
2.定义切点
private final String POINT_CUT = "execution(public * com.xhx.springboot.controller.*.*(..))";
@Pointcut(POINT_CUT)
public void pointCut(){}
切点表达式中,.. 两个点表明多个,* 代表一个,上面表达式代表切入com.xhx.springboot.controller包下的所有类的所有方法,方法参数不限,返回类型不限。其中访问修饰符可以不写,不能用 *,第一个 *代表返回类型不限,第二个 *表示所有类,第三个 *表示所有方法,..两个点表示方法里的参数不限。然后用@Pointcut
切点注解,想在一个空方法上面,一会儿在Advice通知中,直接调用这个空方法就行了,也可以把切点表达式卸载Advice通知中的,单独定义出来主要是为了好管理。
3.Advice,通知增强,主要包括五个注解 Before,After,AfterReturning,AfterThrowing,Around,下面代码中关键地方都有注释,我都列出来了。
@Before 在切点方法之前执行
@After 在切点方法之后执行
@AfterReturning 切点方法返回后执行
@AfterThrowing 切点方法抛异常执行
@Around 属于环绕增强,能控制切点执行前,执行后,,用这个注解后,程序抛异常,会影响@AfterThrowing这个注解
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.SourceLocation;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
import java.util.Arrays;
@Component
@Aspect
public class LogAspect {
private static final Logger logger = LoggerFactory.getLogger(LogAspect.class);
private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
private final String POINT_CUT = "execution(public * com.xhx.springboot.controller.*.*(..))";
@Pointcut(POINT_CUT)
public void pointCut(){}
@Before(value = "pointCut()")
public void before(JoinPoint joinPoint){
logger.info("@Before通知执行");
//获取目标方法参数信息
Object[] args = joinPoint.getArgs();
Arrays.stream(args).forEach(arg->{ // 大大
try {
logger.info(OBJECT_MAPPER.writeValueAsString(arg));
} catch (JsonProcessingException e) {
logger.info(arg.toString());
}
});
//aop代理对象
Object aThis = joinPoint.getThis();
logger.info(aThis.toString()); //com.xhx.springboot.controller.HelloController@69fbbcdd
//被代理对象
Object target = joinPoint.getTarget();
logger.info(target.toString()); //com.xhx.springboot.controller.HelloController@69fbbcdd
//获取连接点的方法签名对象
Signature signature = joinPoint.getSignature();
logger.info(signature.toLongString()); //public java.lang.String com.xhx.springboot.controller.HelloController.getName(java.lang.String)
logger.info(signature.toShortString()); //HelloController.getName(..)
logger.info(signature.toString()); //String com.xhx.springboot.controller.HelloController.getName(String)
//获取方法名
logger.info(signature.getName()); //getName
//获取声明类型名
logger.info(signature.getDeclaringTypeName()); //com.xhx.springboot.controller.HelloController
//获取声明类型 方法所在类的class对象
logger.info(signature.getDeclaringType().toString()); //class com.xhx.springboot.controller.HelloController
//和getDeclaringTypeName()一样
logger.info(signature.getDeclaringType().getName());//com.xhx.springboot.controller.HelloController
//连接点类型
String kind = joinPoint.getKind();
logger.info(kind);//method-execution
//返回连接点方法所在类文件中的位置 打印报异常
SourceLocation sourceLocation = joinPoint.getSourceLocation();
logger.info(sourceLocation.toString());
//logger.info(sourceLocation.getFileName());
//logger.info(sourceLocation.getLine()+"");
//logger.info(sourceLocation.getWithinType().toString()); //class com.xhx.springboot.controller.HelloController
///返回连接点静态部分
JoinPoint.StaticPart staticPart = joinPoint.getStaticPart();
logger.info(staticPart.toLongString()); //execution(public java.lang.String com.xhx.springboot.controller.HelloController.getName(java.lang.String))
//attributes可以获取request信息 session信息等
ServletRequestAttributes attributes =
(ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = attributes.getRequest();
logger.info(request.getRequestURL().toString()); //http://127.0.0.1:8080/hello/getName
logger.info(request.getRemoteAddr()); //127.0.0.1
logger.info(request.getMethod()); //GET
logger.info("before通知执行结束");
}
/**
* 后置返回
* 如果第一个参数为JoinPoint,则第二个参数为返回值的信息
* 如果第一个参数不为JoinPoint,则第一个参数为returning中对应的参数
* returning:限定了只有目标方法返回值与通知方法参数类型匹配时才能执行后置返回通知,否则不执行,
* 参数为Object类型将匹配任何目标返回值
*/
@AfterReturning(value = POINT_CUT,returning = "result")
public void doAfterReturningAdvice1(JoinPoint joinPoint,Object result){
logger.info("第一个后置返回通知的返回值:"+result);
}
@AfterReturning(value = POINT_CUT,returning = "result",argNames = "result")
public void doAfterReturningAdvice2(String result){
logger.info("第二个后置返回通知的返回值:"+result);
}
//第一个后置返回通知的返回值:姓名是大大
//第二个后置返回通知的返回值:姓名是大大
//第一个后置返回通知的返回值:{name=小小, id=1}
/**
* 后置异常通知
* 定义一个名字,该名字用于匹配通知实现方法的一个参数名,当目标方法抛出异常返回后,将把目标方法抛出的异常传给通知方法;
* throwing:限定了只有目标方法抛出的异常与通知方法相应参数异常类型时才能执行后置异常通知,否则不执行,
* 对于throwing对应的通知方法参数为Throwable类型将匹配任何异常。
* @param joinPoint
* @param exception
*/
@AfterThrowing(value = POINT_CUT,throwing = "exception")
public void doAfterThrowingAdvice(JoinPoint joinPoint,Throwable exception){
logger.info(joinPoint.getSignature().getName());
if(exception instanceof NullPointerException){
logger.info("发生了空指针异常!!!!!");
}
}
@After(value = POINT_CUT)
public void doAfterAdvice(JoinPoint joinPoint){
logger.info("后置通知执行了!");
}
/**
* 环绕通知:
* 注意:Spring AOP的环绕通知会影响到AfterThrowing通知的运行,不要同时使用
*
* 环绕通知非常强大,可以决定目标方法是否执行,什么时候执行,执行时是否需要替换方法参数,执行完毕是否需要替换返回值。
* 环绕通知第一个参数必须是org.aspectj.lang.ProceedingJoinPoint类型
*/
@Around(value = POINT_CUT)
public Object doAroundAdvice(ProceedingJoinPoint proceedingJoinPoint){
logger.info("@Around环绕通知:"+proceedingJoinPoint.getSignature().toString());
Object obj = null;
try {
obj = proceedingJoinPoint.proceed(); //可以加参数
logger.info(obj.toString());
} catch (Throwable throwable) {
throwable.printStackTrace();
}
logger.info("@Around环绕通知执行结束");
return obj;
}
}
执行前后顺序是这样
@Around环绕通知
@Before通知执行
@Before通知执行结束
@Around环绕通知执行结束
@After后置通知执行了!
@AfterReturning第一个后置返回通知的返回值:18
org.aspectj.lang.JoinPoint:方法中的参数JoinPoint为连接点对象,它可以获取当前切入的方法的参数、代理类等信息,因此可以记录一些信息,验证一些信息等。
org.aspectj.lang.ProceedingJoinPoint:为JoinPoint的子类,多了两个方法
- public Object proceed() throws Throwable; //调用下一个advice或者执行目标方法,返回值为目标方法返回值,因此可以通过更改返回值,修改方法的返回值
- public Object proceed(Object[] args) throws Throwable; //参数为目标方法的参数 因此可以通过修改参数改变方法入参
可以用下面的方式获取request、session等对象
//attributes可以获取request信息 session信息等
ServletRequestAttributes attributes =
(ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = attributes.getRequest();
下面介绍一下切点表达式:
1.execution(方法修饰符 返回类型 方法全限定名(参数))主要用来匹配整个方法签名和返回值的
"execution(public * com.xhx.springboot.controller.*.*(..))"
\*只能匹配一级路径
..可以匹配多级,可以是包路径,也可以匹配多个参数
+ 只能放在类后面,表明本类及所有子类
还可以按下面这么玩,所有get开头的,第一个参数是Long类型的
@Pointcut("execution(* *..get*(Long,..))")
2. within(类路径) 用来限定类,同样可以使用匹配符
下面用来表示com.xhx.springboot包及其子包下的所有类方法
"within(com.xhx.springboot..*)"
3. this与target
this与target在用法上有些重合,理解上有对比性。
this表示当前切入点表达式所指代的方法的对象的实例,即代理对象是否满足this类型
target表示当前切入点表达式所指代的方法的目标对象的实例 即是否是为target类做的代理
如果当前对象生成的代理对象符合this指定的类型,则进行切面,target是匹配业务对象为指定类型的类,则进行切面。
生成代理对象时会有两种方法,一个是CGLIB一个是jdk动态代理。
用下面三个例子进行说明:
1.this(SomeInterface)或target(SomeInterface):这种情况下,无论是对于Jdk代理
还是Cglib代理
,其目标对象和代理对象都是实现SomeInterface接口的(Cglib生成的目标对象的子类也是实现了SomeInterface
接口的),因而this和target语义都是符合的,此时这两个表达式的效果一样;
2.this(SomeObject)或target(SomeObject),这里SomeObject没实现任何接口:这种情况下,Spring会使用Cglib代理生成SomeObject的代理类对象,由于代理类是SomeObject的子类,子类的对象也是符合SomeObject
类型的,因而this将会被匹配,而对于target,由于目标对象本身就是SomeObject类型,因而这两个表达式的效果一样;
3.this(SomeObject)或target(SomeObject),这里SomeObject实现了某个接口:对于这种情况,虽然表达式中指定的是一种具体的对象类型,但由于其实现了某个接口,因而Spring默认会使用Jdk代理为其生成代理对象,Jdk代理
生成的代理对象与目标对象实现的是同一个接口,但代理对象与目标对象还是不同的对象,由于代理对象不是SomeObject类型的,因而此时是不符合this语义的,而由于目标对象就是SomeObject
类型,因而target语义是符合的,此时this和target的效果就产生了区别;这里如果强制Spring使用Cglib代理
,因而生成的代理对象都是
SomeObject子类的对象,其是
SomeObject`类型的,因而this和target的语义都符合,其效果就是一致的。
4.args(paramType)
args无论其类路径或者是方法名是什么,表达式的作用是匹配指定参数类型和指定参数数量的方法,类型用全路径
args(java.lang.String,..,java.lang.Integer)
5.@within(annotationType) 匹配带有指定注解的类,,within为配置指定类型的类实例
下面匹配含有 @Component注解的类
"@within(org.springframework.stereotype.Component)"
6.@annotation(annotationType) 匹配带有指定注解的方法
@Around("execution(public * *(..)) && @annotation(sc.whorl.system.commons.preventresubmit.PreventResubmitLock)")
7.@args(annotationType)
@args表示使用指定注解标注的类作为某个方法的参数时该方法将会被匹配,可以使用&&、||、!、三种运算符来组合切点表达式,表示与或非的关系。
@Around(value = "pointcut1() || pointcut2()")
JoinPoint的用法
JoinPoint 对象
JoinPoint对象封装了SpringAop中切面方法的信息
,在切面方法中添加JoinPoint参数
,就可以获取到封装了该方法信息的JoinPoint对象
.
常用api:
方法名 | 功能 |
---|---|
Signature getSignature(); | 获取封装了署名信息的对象,在该对象中可以获取到目标方法名,所属类的Class等信息 |
Object[] getArgs(); | 获取传入目标方法的参数对象 |
Object getTarget(); | 获取被代理的对象 |
Object getThis(); | 获取代理对象 |
ProceedingJoinPoint对象
ProceedingJoinPoint对象是JoinPoint的子接口,该对象只用在@Around的切面方法中, 添加了
- Object proceed() throws Throwable //执行目标方法
- Object proceed(Object[] var1) throws Throwable //传入的新的参数去执行目标方法 两个方法.
Demo
切面类
@Aspect
@Component
public class aopAspect {
/**
* 定义一个切入点表达式,用来确定哪些类需要代理
* execution(* aopdemo.*.*(..))代表aopdemo包下所有类的所有方法都会被代理
*/
@Pointcut("execution(* aopdemo.*.*(..))")
public void declareJoinPointerExpression() {}
/**
* 前置方法,在目标方法执行前执行
* @param joinPoint 封装了代理方法信息的对象,若用不到则可以忽略不写
*/
@Before("declareJoinPointerExpression()")
public void beforeMethod(JoinPoint joinPoint){
System.out.println("目标方法名为:" + joinPoint.getSignature().getName());
System.out.println("目标方法所属类的简单类名:" + joinPoint.getSignature().getDeclaringType().getSimpleName());
System.out.println("目标方法所属类的类名:" + joinPoint.getSignature().getDeclaringTypeName());
System.out.println("目标方法声明类型:" + Modifier.toString(joinPoint.getSignature().getModifiers()));
//获取传入目标方法的参数
Object[] args = joinPoint.getArgs();
for (int i = 0; i < args.length; i++) {
System.out.println("第" + (i+1) + "个参数为:" + args[i]);
}
System.out.println("被代理的对象:" + joinPoint.getTarget());
System.out.println("代理对象自己:" + joinPoint.getThis());
}
/**
* 环绕方法,可自定义目标方法执行的时机
* @param pjd JoinPoint的子接口,添加了
* Object proceed() throws Throwable 执行目标方法
* Object proceed(Object[] var1) throws Throwable 传入的新的参数去执行目标方法
* 两个方法
* @return 此方法需要返回值,返回值视为目标方法的返回值
*/
@Around("declareJoinPointerExpression()")
public Object aroundMethod(ProceedingJoinPoint pjd){
Object result = null;
try {
//前置通知
System.out.println("目标方法执行前...");
//执行目标方法
//result = pjd.proeed();
//用新的参数值执行目标方法
result = pjd.proceed(new Object[]{"newSpring","newAop"});
//返回通知
System.out.println("目标方法返回结果后...");
} catch (Throwable e) {
//异常通知
System.out.println("执行目标方法异常后...");
throw new RuntimeException(e);
}
//后置通知
System.out.println("目标方法执行后...");
return result;
}
}
被代理类
/**
* 被代理对象
*/
@Component
public class TargetClass {
/**
* 拼接两个字符串
*/
public String joint(String str1, String str2) {
return str1 + "+" + str2;
}
}
测试类
public class TestAop {
@Test
public void testAOP() {
//1、创建Spring的IOC的容器
ApplicationContext ctx = new ClassPathXmlApplicationContext("classpath:bean.xml");
//2、从IOC容器中获取bean的实例
TargetClass targetClass = (TargetClass) ctx.getBean("targetClass");
//3、使用bean
String result = targetClass.joint("spring","aop");
System.out.println("result:" + result);
}
}
输出结果
目标方法执行前...
目标方法名为:joint
目标方法所属类的简单类名:TargetClass
目标方法所属类的类名:aopdemo.TargetClass
目标方法声明类型:public
第1个参数为:newSpring
第2个参数为:newAop
被代理的对象:aopdemo.TargetClass@4efc180e
代理对象自己:aopdemo.TargetClass@4efc180e
目标方法返回结果后...
目标方法执行后...
result:newSpring+newAop
参考链接
Java反射说明
一句话介绍
JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意方法和属性;这种动态获取信息以及动态调用对象方法的功能称为java语言的反射机制。
用途
在日常的第三方应用开发过程中,经常会遇到某个类的某个成员变量、方法或是属性是私有的或是只对系统应用开放,这时候就可以利用Java的反射机制通过反射来获取所需的私有成员或是方法。当然,也不是所有的都适合反射,之前就遇到一个案例,通过反射得到的结果与预期不符。阅读源码发现,经过层层调用后在最终返回结果的地方对应用的权限进行了校验,对于没有权限的应用返回值是没有意义的缺省值,否则返回实际值起到保护用户的隐私目的。
反射机制的相关类
与Java反射相关的类如下:
类名 | 用途 |
---|---|
Class类 | 代表类的实体,在运行的Java应用程序中表示类和接口 |
Field类 | 代表类的成员变量(成员变量也称为类的属性) |
Method类 | 代表类的方法 |
Constructor类 | 代表类的构造方法 |
Class类
Class代表类的实体,在运行的Java应用程序中表示类和接口。在这个类中提供了很多有用的方法,这里对他们简单的分类介绍。
- 获得类相关的方法
方法 | 用途 |
---|---|
asSubclass(Class clazz) | 把传递的类的对象转换成代表其子类的对象 |
Cast | 把对象转换成代表类或是接口的对象 |
getClassLoader() | 获得类的加载器 |
getClasses() | 返回一个数组,数组中包含该类中所有公共类和接口类的对象 |
getDeclaredClasses() | 返回一个数组,数组中包含该类中所有类和接口类的对象 |
forName(String className) | 根据类名返回类的对象 |
getName() | 获得类的完整路径名字 |
newInstance() | 创建类的实例 |
getPackage() | 获得类的包 |
getSimpleName() | 获得类的名字 |
getSuperclass() | 获得当前类继承的父类的名字 |
getInterfaces() | 获得当前类实现的类或是接口 |
- 获得类中属性相关的方法
方法 | 用途 |
---|---|
getField(String name) | 获得某个公有的属性对象 |
getFields() | 获得所有公有的属性对象 |
getDeclaredField(String name) | 获得某个属性对象 |
getDeclaredFields() | 获得所有属性对象 |
- 获得类中注解相关的方法
方法 | 用途 |
---|---|
getAnnotation(Class<A> annotationClass) | 返回该类中与参数类型匹配的公有注解对象 |
getAnnotations() | 返回该类所有的公有注解对象 |
getDeclaredAnnotation(Class<A> annotationClass) | 返回该类中与参数类型匹配的所有注解对象 |
getDeclaredAnnotations() | 返回该类所有的注解对象 |
- 获得类中构造器相关的方法
方法 | 用途 |
---|---|
getConstructor(Class…<?> parameterTypes) | 获得该类中与参数类型匹配的公有构造方法 |
getConstructors() | 获得该类的所有公有构造方法 |
getDeclaredConstructor(Class…<?> parameterTypes) | 获得该类中与参数类型匹配的构造方法 |
getDeclaredConstructors() | 获得该类所有构造方法 |
- 获得类中方法相关的方法
方法 | 用途 |
---|---|
getMethod(String name, Class…<?> parameterTypes) | 获得该类某个公有的方法 |
getMethods() | 获得该类所有公有的方法 |
getDeclaredMethod(String name, Class…<?> parameterTypes) | 获得该类某个方法 |
getDeclaredMethods() | 获得该类所有方法 |
- 类中其他重要的方法
方法 | 用途 |
---|---|
isAnnotation() | 如果是注解类型则返回true |
isAnnotationPresent(Class<? extends Annotation> annotationClass) | 如果是指定类型注解类型则返回true |
isAnonymousClass() | 如果是匿名类则返回true |
isArray() | 如果是一个数组类则返回true |
isEnum() | 如果是枚举类则返回true |
isInstance(Object obj) | 如果obj是该类的实例则返回true |
isInterface() | 如果是接口类则返回true |
isLocalClass() | 如果是局部类则返回true |
isMemberClass() | 如果是内部类则返回true |
Field类
Field代表类的成员变量(成员变量也称为类的属性)。
方法 | 用途 |
---|---|
equals(Object obj) | 属性与obj相等则返回true |
get(Object obj) | 获得obj中对应的属性值 |
set(Object obj, Object value) | 设置obj中对应属性值 |
Method类
Method代表类的方法。
方法 | 用途 |
---|---|
invoke(Object obj, Object… args) | 传递object对象及参数调用该对象对应的方法 |
Constructor类
Constructor代表类的构造方法。
方法 | 用途 |
---|---|
newInstance(Object… initargs) | 根据传递的参数创建类的对象 |
示例
为了演示反射的使用,首先构造一个与书籍相关的model——Book.java,然后通过反射方法示例创建对象、反射私有构造方法、反射私有属性、反射私有方法,最后给出两个比较复杂的反射示例——获得当前ZenMode和关机Shutdown。
- 被反射类Book.java
public class Book{ private final static String TAG = "BookTag"; private String name; private String author; @Override public String toString() { return "Book{" + "name='" + name + '\'' + ", author='" + author + '\'' + '}'; } public Book() { } private Book(String name, String author) { this.name = name; this.author = author; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getAuthor() { return author; } public void setAuthor(String author) { this.author = author; } private String declaredMethod(int index) { String string = null; switch (index) { case 0: string = "I am declaredMethod 1 !"; break; case 1: string = "I am declaredMethod 2 !"; break; default: string = "I am declaredMethod 1 !"; } return string; } }
- 反射逻辑封装在ReflectClass.java
public class ReflectClass { private final static String TAG = "peter.log.ReflectClass"; // 创建对象 public static void reflectNewInstance() { try { Class<?> classBook = Class.forName("com.android.peter.reflectdemo.Book"); Object objectBook = classBook.newInstance(); Book book = (Book) objectBook; book.setName("Android进阶之光"); book.setAuthor("刘望舒"); Log.d(TAG,"reflectNewInstance book = " + book.toString()); } catch (Exception ex) { ex.printStackTrace(); } } // 反射私有的构造方法 public static void reflectPrivateConstructor() { try { Class<?> classBook = Class.forName("com.android.peter.reflectdemo.Book"); Constructor<?> declaredConstructorBook = classBook.getDeclaredConstructor(String.class,String.class); declaredConstructorBook.setAccessible(true); Object objectBook = declaredConstructorBook.newInstance("Android开发艺术探索","任玉刚"); Book book = (Book) objectBook; Log.d(TAG,"reflectPrivateConstructor book = " + book.toString()); } catch (Exception ex) { ex.printStackTrace(); } } // 反射私有属性 public static void reflectPrivateField() { try { Class<?> classBook = Class.forName("com.android.peter.reflectdemo.Book"); Object objectBook = classBook.newInstance(); Field fieldTag = classBook.getDeclaredField("TAG"); fieldTag.setAccessible(true); String tag = (String) fieldTag.get(objectBook); Log.d(TAG,"reflectPrivateField tag = " + tag); } catch (Exception ex) { ex.printStackTrace(); } } // 反射私有方法 public static void reflectPrivateMethod() { try { Class<?> classBook = Class.forName("com.android.peter.reflectdemo.Book"); Method methodBook = classBook.getDeclaredMethod("declaredMethod",int.class); methodBook.setAccessible(true); Object objectBook = classBook.newInstance(); String string = (String) methodBook.invoke(objectBook,0); Log.d(TAG,"reflectPrivateMethod string = " + string); } catch (Exception ex) { ex.printStackTrace(); } } // 获得系统Zenmode值 public static int getZenMode() { int zenMode = -1; try { Class<?> cServiceManager = Class.forName("android.os.ServiceManager"); Method mGetService = cServiceManager.getMethod("getService", String.class); Object oNotificationManagerService = mGetService.invoke(null, Context.NOTIFICATION_SERVICE); Class<?> cINotificationManagerStub = Class.forName("android.app.INotificationManager$Stub"); Method mAsInterface = cINotificationManagerStub.getMethod("asInterface",IBinder.class); Object oINotificationManager = mAsInterface.invoke(null,oNotificationManagerService); Method mGetZenMode = cINotificationManagerStub.getMethod("getZenMode"); zenMode = (int) mGetZenMode.invoke(oINotificationManager); } catch (Exception ex) { ex.printStackTrace(); } return zenMode; } // 关闭手机 public static void shutDown() { try { Class<?> cServiceManager = Class.forName("android.os.ServiceManager"); Method mGetService = cServiceManager.getMethod("getService",String.class); Object oPowerManagerService = mGetService.invoke(null,Context.POWER_SERVICE); Class<?> cIPowerManagerStub = Class.forName("android.os.IPowerManager$Stub"); Method mShutdown = cIPowerManagerStub.getMethod("shutdown",boolean.class,String.class,boolean.class); Method mAsInterface = cIPowerManagerStub.getMethod("asInterface",IBinder.class); Object oIPowerManager = mAsInterface.invoke(null,oPowerManagerService); mShutdown.invoke(oIPowerManager,true,null,true); } catch (Exception ex) { ex.printStackTrace(); } } public static void shutdownOrReboot(final boolean shutdown, final boolean confirm) { try { Class<?> ServiceManager = Class.forName("android.os.ServiceManager"); // 获得ServiceManager的getService方法 Method getService = ServiceManager.getMethod("getService", java.lang.String.class); // 调用getService获取RemoteService Object oRemoteService = getService.invoke(null, Context.POWER_SERVICE); // 获得IPowerManager.Stub类 Class<?> cStub = Class.forName("android.os.IPowerManager$Stub"); // 获得asInterface方法 Method asInterface = cStub.getMethod("asInterface", android.os.IBinder.class); // 调用asInterface方法获取IPowerManager对象 Object oIPowerManager = asInterface.invoke(null, oRemoteService); if (shutdown) { // 获得shutdown()方法 Method shutdownMethod = oIPowerManager.getClass().getMethod( "shutdown", boolean.class, String.class, boolean.class); // 调用shutdown()方法 shutdownMethod.invoke(oIPowerManager, confirm, null, false); } else { // 获得reboot()方法 Method rebootMethod = oIPowerManager.getClass().getMethod("reboot", boolean.class, String.class, boolean.class); // 调用reboot()方法 rebootMethod.invoke(oIPowerManager, confirm, null, false); } } catch (Exception e) { e.printStackTrace(); } } }
- 调用相应反射逻辑方法
try { // 创建对象 ReflectClass.reflectNewInstance(); // 反射私有的构造方法 ReflectClass.reflectPrivateConstructor(); // 反射私有属性 ReflectClass.reflectPrivateField(); // 反射私有方法 ReflectClass.reflectPrivateMethod(); } catch (Exception ex) { ex.printStackTrace(); } Log.d(TAG," zenmode = " + ReflectClass.getZenMode());
- Log输出结果如下:
08-27 15:11:37.999 11987-11987/com.android.peter.reflectdemo D/peter.log.ReflectClass: reflectNewInstance book = Book{name='Android进阶之光', author='刘望舒'} 08-27 15:11:38.000 11987-11987/com.android.peter.reflectdemo D/peter.log.ReflectClass: reflectPrivateConstructor book = Book{name='Android开发艺术探索', author='任玉刚'} 08-27 15:11:38.000 11987-11987/com.android.peter.reflectdemo D/peter.log.ReflectClass: reflectPrivateField tag = BookTag 08-27 15:11:38.000 11987-11987/com.android.peter.reflectdemo D/peter.log.ReflectClass: reflectPrivateMethod string = I am declaredMethod 1 ! 08-27 15:11:38.004 11987-11987/com.android.peter.reflectdemo D/peter.log.ReflectDemo: zenmode = 0
总结
本文列举了反射机制使用过程中常用的、重要的一些类及其方法,更多信息和用法需要近一步的阅读Google提供的相关文档和示例。
在阅读Class类文档时发现一个特点,以通过反射获得Method对象为例,一般会提供四种方法,getMethod(parameterTypes)、getMethods()、getDeclaredMethod(parameterTypes)和getDeclaredMethods()。getMethod(parameterTypes)用来获取某个公有的方法的对象,getMethods()获得该类所有公有的方法,getDeclaredMethod(parameterTypes)获得该类某个方法,getDeclaredMethods()获得该类所有方法。带有Declared修饰的方法可以反射到私有的方法,没有Declared修饰的只能用来反射公有的方法。其他的Annotation、Field、Constructor也是如此。
在ReflectClass类中还提供了两种反射PowerManager.shutdown()的方法,在调用的时候会输出如下log,提示没有相关权限。之前在项目中尝试反射其他方法的时候还遇到过有权限和没权限返回的值不一样的情况。如果源码中明确进行了权限验证,而你的应用又无法获得这个权限的话,建议就不要浪费时间反射了。
W/System.err: java.lang.reflect.InvocationTargetException
W/System.err: at java.lang.reflect.Method.invoke(Native Method)
W/System.err: at .ReflectClass.shutDown(ReflectClass.java:104)
W/System.err: at .MainActivity$1.onClick(MainActivity.java:25)
W/System.err: at android.view.View.performClick(View.java:6259)
W/System.err: at android.view.View$PerformClick.run(View.java:24732)
W/System.err: at android.os.Handler.handleCallback(Handler.java:789)
W/System.err: at android.os.Handler.dispatchMessage(Handler.java:98)
W/System.err: at android.os.Looper.loop(Looper.java:164)
W/System.err: at android.app.ActivityThread.main(ActivityThread.java:6592)
W/System.err: at java.lang.reflect.Method.invoke(Native Method)
W/System.err: at com.android.internal.os.Zygote$MethodAndArgsCaller.run(Zygote.java:240)
W/System.err: at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:769)
W/System.err: Caused by: java.lang.SecurityException: Neither user 10224 nor current process has android.permission.REBOOT.
W/System.err: at android.os.Parcel.readException(Parcel.java:1942)
W/System.err: at android.os.Parcel.readException(Parcel.java:1888)
W/System.err: at android.os.IPowerManager$Stub$Proxy.shutdown(IPowerManager.java:787)
W/System.err: ... 12 more
四款数据校验的类(Validate)
1. org.apache.commons.lang3.Validate;
- 不支持注解的形式,
- 可用方法巨多
举例
Validate.notNull(obj, "object can't be null"); Validate.notBlank(fieldName, "fieldName can't be blank");
可以使用的方法如图
2. org.hibernate.validator.constraints
- 注解形式使用
- 主要在应用在DAO中
可以使用的方法如图
常用注解
Constraint | 详细信息 |
---|---|
被注释的元素必须是电子邮箱地址 | |
@Length | 被注释的字符串的大小必须在指定的范围内 |
@NotEmpty | 被注释的字符串的必须非空 |
@Range | 被注释的元素必须在合适的范围内 |
3. javax.validation.constraints(推荐)
- 注解形式使用
- 主要应用在业务中
可以使用的方法如图
常用注解
Constraint | 详细信息 | |
---|---|---|
2020/02/10 | mybatis | |
@Null | 被注释的元素必须为 null | |
@NotNull | 被注释的元素必须不为 null | |
@AssertTrue | 被注释的元素必须为 true | |
@AssertFalse | 被注释的元素必须为 false | |
@Min(value) | 被注释的元素必须是一个数字,其值必须大于等于指定的最小值 | |
@Max(value) | 被注释的元素必须是一个数字,其值必须小于等于指定的最大值 | |
@DecimalMin(value) | 被注释的元素必须是一个数字,其值必须大于等于指定的最小值 | |
@DecimalMax(value) | 被注释的元素必须是一个数字,其值必须小于等于指定的最大值 | |
@Size(max, min) | 被注释的元素的大小必须在指定的范围内 | |
@Digits (integer, fraction) | 被注释的元素必须是一个数字,其值必须在可接受的范围内 | |
@Past | 被注释的元素必须是一个过去的日期 | |
@Future | 被注释的元素必须是一个将来的日期 | |
@Pattern(value) | 被注释的元素必须符合指定的正则表达式 |
定制化注解
@Price 的 annotation 部分
// @Max 和 @Min 都是内置的 constraint
@Max(10000)
@Min(8000)
@Constraint(validatedBy = {})
@Documented
@Target( { ElementType.ANNOTATION_TYPE, ElementType.METHOD, ElementType.FIELD })
@Retention(RetentionPolicy.RUNTIME)
public @interface Price {
String message() default "错误的价格";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
@Status 的 annotation 部分
@Constraint(validatedBy = {StatusValidator.class})
@Documented
@Target( { ElementType.ANNOTATION_TYPE, ElementType.METHOD, ElementType.FIELD })
@Retention(RetentionPolicy.RUNTIME)
public @interface Status {
String message() default "不正确的状态 , 应该是 'created', 'paid', shipped', closed'其中之一";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
4. org.springframework.util.Assert;
- 不支持注解的形式,
- 可用方法巨多
举例
@RequestMapping(value = "/", method = RequestMethod.GET) public String list(Model model, HttpSession session, HttpServletRequest request) { User user = getUserFromSession(session); org.springframework.util.Assert.notNull(user,"未登录用户,非法操作"); Page<Order> page = new Page<>(request); orderService.findOrders(page, user.getId()); model.addAttribute("page", page); return "order/orderList"; }
可以使用的方法如图
参考链接
时间类(TimeUnit)
一句话简介
TimeUnit是java.util.concurrent包下面的一个类,表示给定单元粒度的时间段
主要使用
- 时间颗粒度转换
- 延时
常用的颗粒度
TimeUnit.DAYS //天
TimeUnit.HOURS //小时
TimeUnit.MINUTES //分钟
TimeUnit.SECONDS //秒
TimeUnit.MILLISECONDS //毫秒
1、时间颗粒度转换
public long toMillis(long d) //转化成毫秒
public long toSeconds(long d) //转化成秒
public long toMinutes(long d) //转化成分钟
public long toHours(long d) //转化成小时
public long toDays(long d) //转化天
例子
import java.util.concurrent.TimeUnit;
public class Test {
public static void main(String[] args) {
//1天有24个小时 1代表1天:将1天转化为小时
System.out.println( TimeUnit.DAYS.toHours( 1 ) );
//结果: 24
//1小时有3600秒
System.out.println( TimeUnit.HOURS.toSeconds( 1 ));
//结果3600
//把3天转化成小时
System.out.println( TimeUnit.HOURS.convert( 3 , TimeUnit.DAYS ) );
//结果是:72
}
}
2、延时
- 一般的写法
public class Test2 { public static void main(String[] args) { new Thread( new Runnable() { @Override public void run() { try { Thread.sleep( 5 * 1000 ); System.out.println( "延时完成了"); } catch (InterruptedException e) { e.printStackTrace(); } } }).start(); ; } }
- TimeUnit 写法
import java.util.concurrent.TimeUnit; public class Test2 { public static void main(String[] args) { new Thread( new Runnable() { @Override public void run() { try { TimeUnit.SECONDS.sleep( 5 ); System.out.println( "延时5秒,完成了"); } catch (InterruptedException e) { e.printStackTrace(); } } }).start(); ; } }
参考链接
Checked Exception还是Unchecked Exception?
一句话简介
- Checked异常必须被显式地捕获或者传递,如Basic try-catch-finally Exception Handling一文中所说。而unchecked异常则可以不必捕获或抛出。
- Checked异常继承java.lang.Exception类。Unchecked异常继承自java.lang.RuntimeException类。
/**
* 将反射时的checked exception转换为unchecked exception.
*/
public static RuntimeException convertReflectionExceptionToUnchecked(Exception e) {
if ((e instanceof IllegalAccessException) || (e instanceof IllegalArgumentException)
|| (e instanceof NoSuchMethodException)) {
return new IllegalArgumentException(e);
} else if (e instanceof InvocationTargetException) {
return new RuntimeException(((InvocationTargetException) e).getTargetException());
} else if (e instanceof RuntimeException) {
return (RuntimeException) e;
}
return new RuntimeException("Unexpected Checked Exception.", e);
}
/**
* 将CheckedException转换为UncheckedException.
*/
public static RuntimeException unchecked(Throwable ex) {
if (ex instanceof RuntimeException) {
return (RuntimeException) ex;
} else {
return new RuntimeException(ex);
}
}
参考链接
反射工具类(Reflections)
一句话简介
JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意方法和属性;这种动态获取信息以及动态调用对象方法的功能称为java语言的反射机制
使用场景
- 在日常的第三方应用开发过程中,经常会遇到某个类的某个成员变量、方法或是属性是私有的或是只对系统应用开放,这时候就可以利用Java的反射机制通过反射来获取所需的私有成员或是方法
- 主要应用于框架中
package com.javaniuniu.scshorlsweb.system.utils.reflect;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.Validate;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
/**
* 反射工具类.
*
* 提供调用getter/setter方法, 访问私有变量, 调用私有方法, 获取泛型类型Class, 被AOP过的真实类等工具函数.
* //TODO 反射工具类
*/
public class Reflections
{
private static final String SETTER_PREFIX = "set";
private static final String GETTER_PREFIX = "get";
private static final String CGLIB_CLASS_SEPARATOR = "$$";
private static Logger logger = LoggerFactory.getLogger(Reflections.class);
/**
* 调用Getter方法.
*/
public static Object invokeGetter(Object obj, String propertyName) {
String getterMethodName = GETTER_PREFIX + StringUtils.capitalize(propertyName);
return invokeMethod(obj, getterMethodName, new Class[] {}, new Object[] {});
}
/**
* 调用Setter方法, 仅匹配方法名。
*/
public static void invokeSetter(Object obj, String propertyName, Object value) {
String setterMethodName = SETTER_PREFIX + StringUtils.capitalize(propertyName);
invokeMethodByName(obj, setterMethodName, new Object[] { value });
}
/**
* 直接读取对象属性值, 无视private/protected修饰符, 不经过getter函数.
*/
public static Object getFieldValue(final Object obj, final String fieldName) {
Field field = getAccessibleField(obj, fieldName);
if (field == null) {
throw new IllegalArgumentException("Could not find field [" + fieldName + "] on target [" + obj + "]");
}
Object result = null;
try {
result = field.get(obj);
} catch (IllegalAccessException e) {
logger.error("不可能抛出的异常{}", e.getMessage());
}
return result;
}
/**
* 直接设置对象属性值, 无视private/protected修饰符, 不经过setter函数.
*/
public static void setFieldValue(final Object obj, final String fieldName, final Object value) {
Field field = getAccessibleField(obj, fieldName);
if (field == null) {
throw new IllegalArgumentException("Could not find field [" + fieldName + "] on target [" + obj + "]");
}
try {
field.set(obj, value);
} catch (IllegalAccessException e) {
logger.error("不可能抛出的异常:{}", e.getMessage());
}
}
/**
* 直接调用对象方法, 无视private/protected修饰符.
* 用于一次性调用的情况,否则应使用getAccessibleMethod()函数获得Method后反复调用.
* 同时匹配方法名+参数类型,
*/
public static Object invokeMethod(final Object obj, final String methodName, final Class<?>[] parameterTypes,
final Object[] args) {
Method method = getAccessibleMethod(obj, methodName, parameterTypes);
if (method == null) {
throw new IllegalArgumentException("Could not find method [" + methodName + "] on target [" + obj + "]");
}
try {
return method.invoke(obj, args);
} catch (Exception e) {
throw convertReflectionExceptionToUnchecked(e);
}
}
/**
* 直接调用对象方法, 无视private/protected修饰符,
* 用于一次性调用的情况,否则应使用getAccessibleMethodByName()函数获得Method后反复调用.
* 只匹配函数名,如果有多个同名函数调用第一个。
*/
public static Object invokeMethodByName(final Object obj, final String methodName, final Object[] args) {
Method method = getAccessibleMethodByName(obj, methodName);
if (method == null) {
throw new IllegalArgumentException("Could not find method [" + methodName + "] on target [" + obj + "]");
}
try {
return method.invoke(obj, args);
} catch (Exception e) {
throw convertReflectionExceptionToUnchecked(e);
}
}
/**
* 循环向上转型, 获取对象的DeclaredField, 并强制设置为可访问.
*
* 如向上转型到Object仍无法找到, 返回null.
*/
public static Field getAccessibleField(final Object obj, final String fieldName) {
Validate.notNull(obj, "object can't be null");
Validate.notBlank(fieldName, "fieldName can't be blank");
for (Class<?> superClass = obj.getClass(); superClass != Object.class; superClass = superClass.getSuperclass()) {
try {
Field field = superClass.getDeclaredField(fieldName);
makeAccessible(field);
return field;
} catch (NoSuchFieldException e) {// NOSONAR
// Field不在当前类定义,继续向上转型
}
}
return null;
}
/**
* 循环向上转型, 获取对象的DeclaredMethod,并强制设置为可访问.
* 如向上转型到Object仍无法找到, 返回null.
* 匹配函数名+参数类型。
*
* 用于方法需要被多次调用的情况. 先使用本函数先取得Method,然后调用Method.invoke(Object obj, Object... args)
*/
public static Method getAccessibleMethod(final Object obj, final String methodName,
final Class<?>... parameterTypes) {
Validate.notNull(obj, "object can't be null");
Validate.notBlank(methodName, "methodName can't be blank");
for (Class<?> searchType = obj.getClass(); searchType != Object.class; searchType = searchType.getSuperclass()) {
try {
Method method = searchType.getDeclaredMethod(methodName, parameterTypes);
makeAccessible(method);
return method;
} catch (NoSuchMethodException e) {
// Method不在当前类定义,继续向上转型
}
}
return null;
}
/**
* 循环向上转型, 获取对象的DeclaredMethod,并强制设置为可访问.
* 如向上转型到Object仍无法找到, 返回null.
* 只匹配函数名。
*
* 用于方法需要被多次调用的情况. 先使用本函数先取得Method,然后调用Method.invoke(Object obj, Object... args)
*/
public static Method getAccessibleMethodByName(final Object obj, final String methodName) {
Validate.notNull(obj, "object can't be null");
Validate.notBlank(methodName, "methodName can't be blank");
for (Class<?> searchType = obj.getClass(); searchType != Object.class; searchType = searchType.getSuperclass()) {
Method[] methods = searchType.getDeclaredMethods();
for (Method method : methods) {
if (method.getName().equals(methodName)) {
makeAccessible(method);
return method;
}
}
}
return null;
}
/**
* 改变private/protected的方法为public,尽量不调用实际改动的语句,避免JDK的SecurityManager抱怨。
*/
public static void makeAccessible(Method method) {
if ((!Modifier.isPublic(method.getModifiers()) || !Modifier.isPublic(method.getDeclaringClass().getModifiers()))
&& !method.isAccessible()) {
method.setAccessible(true);
}
}
/**
* 改变private/protected的成员变量为public,尽量不调用实际改动的语句,避免JDK的SecurityManager抱怨。
*/
public static void makeAccessible(Field field) {
if ((!Modifier.isPublic(field.getModifiers()) || !Modifier.isPublic(field.getDeclaringClass().getModifiers()) || Modifier
.isFinal(field.getModifiers())) && !field.isAccessible()) {
field.setAccessible(true);
}
}
/**
* 通过反射, 获得Class定义中声明的泛型参数的类型, 注意泛型必须定义在父类处
* 如无法找到, 返回Object.class.
* eg.
* public UserDao extends HibernateDao<User>
*
* @param clazz The class to introspect
* @return the first generic declaration, or Object.class if cannot be determined
*/
public static <T> Class<T> getClassGenricType(final Class clazz) {
return getClassGenricType(clazz, 0);
}
public static <T> Class<T> getClassGenricType(final Type type){
if (type instanceof ParameterizedType) {
ParameterizedType paramType = (ParameterizedType) type;
Type[] argTypes = paramType.getActualTypeArguments();
if (argTypes.length > 0) {
if (logger.isDebugEnabled()){
logger.debug("Generic type is: {}",argTypes[0]);
}
return (Class<T>) argTypes[0];
}
}
return (Class<T>) type;
}
/**
* 通过反射, 获得Class定义中声明的父类的泛型参数的类型.
* 如无法找到, 返回Object.class.
*
* 如public UserDao extends HibernateDao<User,Long>
*
* @param clazz clazz The class to introspect
* @param index the Index of the generic ddeclaration,start from 0.
* @return the index generic declaration, or Object.class if cannot be determined
*/
public static Class getClassGenricType(final Class clazz, final int index) {
Type genType = clazz.getGenericSuperclass();
if (!(genType instanceof ParameterizedType)) {
logger.warn(clazz.getSimpleName() + "'s superclass not ParameterizedType");
return Object.class;
}
Type[] params = ((ParameterizedType) genType).getActualTypeArguments();
if ((index >= params.length) || (index < 0)) {
logger.warn("Index: " + index + ", Size of " + clazz.getSimpleName() + "'s Parameterized Type: "
+ params.length);
return Object.class;
}
if (!(params[index] instanceof Class)) {
logger.warn(clazz.getSimpleName() + " not set the actual class on superclass generic parameter");
return Object.class;
}
return (Class) params[index];
}
public static Class<?> getUserClass(Object instance) {
Validate.notNull(instance, "Instance must not be null");
Class clazz = instance.getClass();
if ((clazz != null) && clazz.getName().contains(CGLIB_CLASS_SEPARATOR)) {
Class<?> superClass = clazz.getSuperclass();
if ((superClass != null) && !Object.class.equals(superClass)) {
return superClass;
}
}
return clazz;
}
/**
* 将反射时的checked exception转换为unchecked exception.
*/
public static RuntimeException convertReflectionExceptionToUnchecked(Exception e) {
if ((e instanceof IllegalAccessException) || (e instanceof IllegalArgumentException)
|| (e instanceof NoSuchMethodException)) {
return new IllegalArgumentException(e);
} else if (e instanceof InvocationTargetException) {
return new RuntimeException(((InvocationTargetException) e).getTargetException());
} else if (e instanceof RuntimeException) {
return (RuntimeException) e;
}
return new RuntimeException("Unexpected Checked Exception.", e);
}
static class Users{
private int id = 1;
private String name = "minp";
private int getId() {
return 1;
}
//
// private void setId(int i) {
// id =i;
// }
}
public static void main(String[] args) {
invokeGetter(new Users(),"id");
// getAccessibleMethod(new Users(),"getId",null);
//
// getUserClass(new Users());
}
}
参考链接
339 post articles, 43 pages.