Spring Boot 系列(1) :初步认识、异常处理、AOP切面

1、从官网下载demo
1)网址:http://start.spring.io
2)可选择Maven/Gradle+Java+版本
3)Group填组名、Artifact填模块名、右侧Dependencies可以选择相应的依赖,因为我们要构建web项目,所以可以添加web的依赖

4)点击 Generate Project 生成下载项目


2、把下载的demo代码导入eclipse或者ide中,直接运行 DemoApplication.java 的main方法。
在eclipse导入后可能pom文件会报错,报错内容如下:
Project build error: Non-resolvable parent POM for com.example.hs:demo:0.0.1-SNAPSHOT: Failure to transfer org.springframework.boot:spring-boot-starter-parent:pom:
 2.0.3.RELEASE from https://repo.maven.apache.org/maven2 was cached in the local repository, resolution will not be reattempted until the update interval of central has
 elapsed or updates are forced. Original error: Could not transfer artifact org.springframework.boot:spring-boot-starter-parent:pom:2.0.3.RELEASE from/to central (https://
 repo.maven.apache.org/maven2): Received fatal alert: protocol_version and 'parent.relativePath' points at no local POM
解决方案:打开终端,进入出错项目的根目录,然后使用 maven bin 目录下的 mvn 编译一下,完成之后右键项目 Maven——Update Project 即可。
F:\javaProjectNew\demo>mvn compile

3、在springboot中配置除了支持application.properties,还支持application.yml的配置方式。


4、异常处理类方式(以权限为例)--springMVC

1)权限错误时抛出此异常

package com.ex.exception;

public class AuthException extends RuntimeException {

	private static final long serialVersionUID = 1L;

	public AuthException(final String msg) {
		super(msg);
	}

	public AuthException(final String msg, final Throwable exception) {
		super(msg, exception);
	}
}

2)全局异常处理

package com.ex.util;

import javax.servlet.http.HttpServletRequest;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.Status;
import javax.ws.rs.ext.ExceptionMapper;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;

import com.ex.exception.AuthException;

@ControllerAdvice(annotations = { RestController.class })
public class ExceptionUtil implements ExceptionMapper {

	private final static Logger LOGGER = LoggerFactory.getLogger(ExceptionUtil.class);

	@Override
	public Response toResponse(final Throwable exception) {
		LOGGER.error("exception:[{}]", exception);
		return Response.status(Status.BAD_REQUEST).entity(ResponseUtil.errorRes(exception.getMessage()))
				.type(MediaType.APPLICATION_JSON).build();
	}

	@ExceptionHandler
	@ResponseStatus(value = HttpStatus.OK)
	public @ResponseBody ResponseUtil exception(final HttpServletRequest request, final Exception exception) {
		final ResponseUtil resp = new ResponseUtil(false);
		final String url = request.getRequestURL().toString();
		LOGGER.error("the request is:[{}] and the exception is:[{}]", url, exception);
		if (exception instanceof AuthException) {
			resp.setErrorMsg("no permission");
		}
		resp.setErrorMsg(exception.getLocalizedMessage());
		return resp;
	}
}

其中核心内容如下:

@ControllerAdvice(annotations = { RestController.class })

  • @ControllerAdvice:对controller做advice(横切通知),配置需要拦截的类。使用 @ControllerAdvice,不用任何的配置,只要把这个类放在项目中,Spring能扫描到的地方。就可以实现全局异常的回调。这里是指定所有注释了@RestController的controller。它还可以指定特定包下所有的controller例如@ControllerAdvice("org.example.controllers"),也可以指定特定类例如@ControllerAdvice(assignableTypes = {NameListInfoService.class, AbstractController.class})。@ControllerAdvice的实现类能够通过classpath扫描自动检测到。注解了@ControllerAdvice的类可以包含@ExceptionHandler,@InitBinder和@ModelAttribute方法。这些方法能够应用到所有controller结构中的@RequestMapping方法上,而不单是在它所在的类中。
  • @ExceptionHandler:统一处理某一类异常,从而能够减少代码重复率和复杂度。这里不指明异常类,会自动映射。
  • @ResponseStatus:可以将某种异常映射为HTTP状态码。

3)自定义注解~~(PointCut连接点注解类)

package com.ex.anno;

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

@Retention(RetentionPolicy.RUNTIME)//定义注解在JVM运行时保留,在运行是通过反射机制获取
@Target(ElementType.METHOD)//定义注解的作用目标**作用范围字段、枚举的常量/方法
@Documented//说明该注解将被包含在javadoc中
public @interface Authorize {
	public String value() default "";
}

使用@interface自定义注解时,自动继承了java.lang.annotation.Annotation接口,由编译程序自动完成其他细节。在定义注解时,不能继承其他的注解或接口。

自定义注解的作用:在反射中获取注解,以取得注解修饰的类、方法或属性的相关解释。

4)AOP切面

@Aspect//定义切面
@Component
public class AuthAspect {

	@Autowired
	private transient AuthBO authBO;
	
	//环绕通知:定义@Around增强,pointcut连接点使用@annotation(xxx)定义,其中xxx对应该方法中final Authorize authorize参数authorize
	@Around(value = "@annotation(authorize)")
	public Object doAuthorize(final ProceedingJoinPoint joinPoint, final Authorize authorize) throws Throwable {
		final String value = authorize.value();
		if (value != null && !"".equals(value)) {
			// 调用方法名
			final String methodName = value.substring(0, value.indexOf('('));
			// 资源编号
			final String code = value.substring(value.indexOf('\'') + 1, value.lastIndexOf('\'')).replaceAll("\'", "").replaceAll("\\s*", "");
			final Boolean flag = new ReflectTask(authBO, methodName, new Class[] { String.class }, new Object[] { code }).call();
			if (!flag) {
				throw new AuthException("权限错误!");
			}
		}
		return joinPoint.proceed();//调用目标方法
	}
}
在上面定义@Around增强时,通过@annotation() 方式指定了pointcut,其中方法参数为连接点注解类Authorize;
  • @annotation(around):参数around应该与增强处理方法中的参数名保持一致,该声明指定了pointcut连接点,也可以使用其他方式,如:pointcut="execution(* org.crazyit.app.service.impl.*.*(..))";
  • point.proceed()调用了目标方法,并获取其返回值;
  • 如果需要对某一方法进行增强,只需要在相应的方法上添加上此注解即可,如下:
    package com.ex.service;
    
    import org.springframework.stereotype.Component;
    
    import com.ex.anno.Authorize;
    
    @Component
    public class LoginService {
    	
    	@Authorize(value="admin")//表示需要对该方法进行@Around增强处理
    	public String getSomething(){
    		return "HelloWord";
    	}
    		
    }

5、登录日志简单示例

1)自定义LoginLog注解类

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@Documented
@Inherited
public @interface LoginLog {

}

2)日志切点类

@Aspect
@Component
@ConditionalOnProperty(name = "exsystem.isLog", havingValue = "true")
public class LogAspect {

	@Autowired
	private transient LogsBO logsBO;

	private final static Logger LOGGER = LoggerFactory.getLogger(LogAspect.class);

	@Around(value = "within(cn.ex..*) && @annotation(loginLog)")
	public Object doLoginLog(final ProceedingJoinPoint joinPoint, final LoginLog loginLog) throws Throwable {
		// 调用参数
		final Object[] args = joinPoint.getArgs();
		final Object obj = joinPoint.proceed(args);
		try {
			logsBO.writeLoginLog(obj, args);
		} catch (Exception e) {
			LOGGER.error("创建登录日志异常,异常信息:{}", e.getMessage());
		}
		return obj;
	}
}

其中SpringBoot通过@ConditionalOnProperty来控制Configuration是否生效,其中name用来从application.yml中读取某个属性值,如果该值为空,则返回false;如果值不为空,则将该值与havingValue指定的值进行比较;

另外通过环绕通知@Around对cn.ex..*包下类以及所有加了LoginLog注解的方法体进行切入拦截,如上代码会进入doLoginLog里进行logsBO.writeLoginLog(obj, args);将登录日志入数据库处理。

3)切入点

@RestController
@RequestMapping("/login")
public class LoginServerResource {

	/**
	 * 登录操作
	 * 
	 * @param loginName 用户名
	 * @param password 密码
	 * @param checkCode 验证码
	 * @return
	 */
	@LoginLog
	@RequestMapping(value = "/login", method = { RequestMethod.POST }, consumes = { MediaType.APPLICATION_FORM_URLENCODED_VALUE })
	public ModelAndView login(final @ModelAttribute(value = "loginName") String loginName,
							   final @ModelAttribute(value = "password") String password,
							   final @ModelAttribute(value = "checkCode") String checkCode,
							   final @ModelAttribute(value = "phoneCode") String phoneCode) {
		return null;
	}

}

通过@LoginLog自定义注解切入。


...

你可能感兴趣的:(springboot)