第一章:导读以及学习目标
- 认证与授权
登录的认知:
- 同时支持多种认证方式
- 同时支持多种前端渠道
- 支持集群环境、跨应用工作,Session控制、控制用户权限,防护与身份认证相关的攻击。
学习目标:
- 可重用的、企业级的、认证和授权模块
- 深入理解Spring Security及相关框架的原理、功能和代码。
- 可以基于Spring Security及相关框架独立开发认证授权相关功能。
- 掌握抽象和封装的常见技巧,可以编写可重用的模块供他人使用。
前置知识
- JavaWeb基础
- Maven基础
- Spring基础
第二章、开发环境的准备
1.代码结构
- seapp-security:主模块
- seapp-security-core: 核心业务逻辑
- seapp-security-browser:浏览器安全特定代码
- seapp-security-app:app相关特定代码
- seapp-security-demo:样例程序
parent工程pom.xml maven依赖的引入:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.0.RELEASE</version>
<relativePath/>
</parent>
<groupId>com.seapp.security</groupId>
<artifactId>seapp-security</artifactId>
<version>1.0-SNAPSHOT</version>
<modules>
<module>security-core</module>
<module>security-browser</module>
<module>security-app</module>
<module>security-demo</module>
</modules>
<packaging>pom</packaging>
<properties>
<java.version>1.8</java.version>
<spring-cloud.version>Hoxton.M3</spring-cloud.version>
<seapp-security-version>1.0-SNAPSHOT</seapp-security-version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-config-server</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<!-- <build>-->
<!-- <plugins>-->
<!-- <plugin>-->
<!-- <groupId>org.springframework.boot</groupId>-->
<!-- <artifactId>spring-boot-maven-plugin</artifactId>-->
<!-- </plugin>-->
<!-- </plugins>-->
<!-- </build>-->
<repositories>
<repository>
<id>spring-milestones</id>
<name>Spring Milestones</name>
<url>https://repo.spring.io/milestone</url>
</repository>
</repositories>
</project>
第三章、使用Spring MVC开发RESTful API
学习目标:
使用Spring MVC编写Restful API
使用Spring MVC处理其它web应用常见的需求和场景
Restful API开发常用辅助框架
1.RESTful API简介
- 用URL描述资源
- 使用HTTP方法描述行为。使用HTTP状态码来表示不同的结果。
- 使用json交互数据。
- Restful只是一种风格,并不是强制的标准。
2.开发基础的增删改查接口
- 编写针对RestfulApi的测试用例
- 使用注解声明RestfulAPI
- 在RestfulAPI中传递参数
2.1 常用注解
- @RestController 标明此Controller提供了RestApi
- @RequestMapping及其变体。映射http请求url到java方法。
- @RequestParam 映射请求参数到java方法的参数。
required:true,该参数必传。false,该参数可选。
defaultValue:当该参数可选时,若没有传值,则使用默认值。
value: 指定参数名称(当传参与形参不同时使用),等同于name属性。 - @PageableDefault 指定分页参数默认值。
page:页数
size: 每页条目数
sort:设定排序字段以及排序方式 - @pathVariable 映射url片段到java方法的参数
- RequestBody 映射请求体到java方法的参数
- 日期类型参数的处理
以时间戳为准,具体如何展示前台界面控制。
2.2 模拟前端测试入门(MockMvc )
package com.seapp.web;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.http.MediaType;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.MockMvcBuilder;
import org.springframework.test.web.servlet.ResultHandler;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
import org.springframework.test.web.servlet.result.MockMvcResultMatchers;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.context.WebApplicationContext;
/**
* 测试用例
* @author seapp
* @date 2020/8/4 17:33
*/
@RunWith(SpringRunner.class)
@SpringBootTest
public class UserControllerTest {
@Autowired
private WebApplicationContext wac;
private MockMvc mockMvc;
@Before
public void before(){
mockMvc = MockMvcBuilders.webAppContextSetup(wac).build();
}
@Test
public void whenQuerySuccess() throws Exception {
//模拟测试get请求
mockMvc.perform(MockMvcRequestBuilders.get("/user")
//请求类型设定
.contentType(MediaType.APPLICATION_JSON_UTF8))
//请求状态码设定
.andExpect(MockMvcResultMatchers.status().isOk())
//请求返回集合长度设定(json返回数据)
//jsonpath的使用规则:https://github.com/json-path/JsonPath
.andExpect(MockMvcResultMatchers.jsonPath("$.length()")
.value(3));
}
}
3. 在url声明中使用正则表达式
示例如下:限制传递的id只能为数字。
@RequestMapping(value = "/user/{id:\\d+}", method = RequestMethod.GET)
public User getUserInfo(@PathVariable("id") String id) {
System.out.println("id = " + id);
User user = new User();
user.setUsername("tom");
return user;
}
4. @JsonView控制json输出内容
目的:在不同需求下对获取到的同一个对象,输出不同的内容。
@JsonView的使用步骤:
- 使用接口来声明多个视图
- 在值对象的get方法上指定视图
package com.seapp.dto;
import com.fasterxml.jackson.annotation.JsonView;
/**
* @author seapp
* @date 2020/8/4 17:49
*/
public class User {
//声明简单视图
public interface UserSimpleView{};
//声明详细视图,并集成简单视图
public interface UserDetailView extends UserSimpleView{};
private String username;
private String password;
//在get方法上,指定username在简单视图上展示
@JsonView(UserSimpleView.class)
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
//在get方法上,指定password在详情视图上展示
@JsonView(UserDetailView.class)
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
}
- 在Controller方法上指定视图
package com.seapp.web.controller;
import com.fasterxml.jackson.annotation.JsonView;
import com.seapp.dto.User;
import org.springframework.data.web.PageableDefault;
import org.springframework.web.bind.annotation.*;
import java.awt.print.Pageable;
import java.lang.invoke.MethodType;
import java.net.Authenticator;
import java.util.ArrayList;
import java.util.List;
/**
* @author seapp
* @date 2020/8/4 17:32
*/
@RestController
@RequestMapping("/user")
public class UserController {
/**
* get请求,路径为"/user"
*
* @return
*/
// @RequestMapping(value = "/user", method = RequestMethod.GET)
@GetMapping
@JsonView(User.UserSimpleView.class)//指定返回json数据为简单视图
public List<User> query(@RequestParam(required = false, defaultValue = "tom", name = "username") String username) {
System.out.println("username = " + username);
List<User> userList = new ArrayList<>();
userList.add(new User());
userList.add(new User());
userList.add(new User());
return userList;
}
// @RequestMapping(value = "/user/{id:\\d+}", method = RequestMethod.GET)
@GetMapping("/{id:\\d+}")
@JsonView(User.UserDetailView.class)//指定返回json数据为详细视图
public User getUserInfo(@PathVariable("id") String id) {
System.out.println("id = " + id);
User user = new User();
user.setUsername("tom");
return user;
}
}
- 测试类的实现,以及调用
package com.seapp.web;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.http.MediaType;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.MockMvcBuilder;
import org.springframework.test.web.servlet.ResultHandler;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
import org.springframework.test.web.servlet.result.MockMvcResultMatchers;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.context.WebApplicationContext;
import java.lang.annotation.Target;
/**
* 测试用例
* @author seapp
* @date 2020/8/4 17:33
*/
@RunWith(SpringRunner.class)
@SpringBootTest
public class UserControllerTest {
@Autowired
private WebApplicationContext wac;
private MockMvc mockMvc;
@Before
public void before(){
mockMvc = MockMvcBuilders.webAppContextSetup(wac).build();
}
@Test
public void whenQuerySuccess() throws Exception {
//模拟测试get请求
String string = mockMvc.perform(MockMvcRequestBuilders.get("/user")
.param("username", "seapp")
//请求类型设定
.contentType(MediaType.APPLICATION_JSON_UTF8))
//请求状态码设定
.andExpect(MockMvcResultMatchers.status().isOk())
//请求返回集合长度设定(json返回数据)
//jsonpath的使用规则:https://github.com/json-path/JsonPath
.andExpect(MockMvcResultMatchers.jsonPath("$.length()")
.value(3))
.andReturn().getResponse().getContentAsString();
System.out.println("string = " + string);
}
/**
* 测试查询指定用户id的接口
* @throws Exception
*/
@Test
public void whenGetInfoSuccess() throws Exception {
String string = mockMvc.perform(MockMvcRequestBuilders.get("/user/1")
.contentType(MediaType.APPLICATION_JSON_UTF8))
.andExpect(MockMvcResultMatchers.status().isOk())
.andExpect(MockMvcResultMatchers.jsonPath("$.username")
.value("tom"))
.andReturn().getResponse().getContentAsString();
System.out.println("string = " + string);
}
/**
* 正则表达式限制,当查询id不是字符串时,提示参数异常。
* @throws Exception
*/
@Test
public void whenGetInfoFail() throws Exception {
String string = mockMvc.perform(MockMvcRequestBuilders.get("/user/a")
.contentType(MediaType.APPLICATION_JSON_UTF8))
.andExpect(MockMvcResultMatchers.status().is4xxClientError())
.andReturn().getResponse().getContentAsString();
System.out.println("string = " + string);
}
}
5. 校验
- @Valid注解和BindingResult验证请求参数的合法性并处理校验结果
//注解@NotBlank 元素值不为空
@NotBlank
private String password;
//使用@Valid注解实行校验,并通过BindingResult对象来获取校验结果
@PostMapping
public User create(@RequestBody @Valid User user, BindingResult errors) {
if (errors.hasErrors()) {
errors.getAllErrors().stream()
.forEach(error -> System.out.println(error.getDefaultMessage()));
}
System.out.println(user.getId());
System.out.println(user.getUsername());
System.out.println(user.getPassword());
System.out.println(user.getBirthday());
user.setId(1);
return user;
}
//测试调用方法
@Test
public void whenCreateSuccess() throws Exception {
//post请求的json字符串参数
String content = "{\"username\":\"tom\",\"password\":\"\"}";
String string = mockMvc.perform(MockMvcRequestBuilders.post("/user")
.contentType(MediaType.APPLICATION_JSON_UTF8).content(content))
.andExpect(MockMvcResultMatchers.status().isOk())
.andExpect(MockMvcResultMatchers.jsonPath("$.id").value(1))
.andReturn().getResponse().getContentAsString();
System.out.println("string = " + string);
}
-
常用的验证注解
自定义校验注解
①注解类的实现:
package com.seapp.validator;
import javax.validation.Constraint;
import javax.validation.Payload;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**自定义校验注解
* @author seapp
* @date 2020/8/5 9:56
*/
@Target({ElementType.METHOD,ElementType.FIELD})//可标注在方法上或字段上
@Retention(RetentionPolicy.RUNTIME)//在运行时进行校验
@Constraint(validatedBy = MyConstraintValidator.class)//指定校验逻辑所在的类
public @interface MyConstraint {
String message();
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
②校验规则类的实现:
package com.seapp.validator;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
/**
* 自定义校验注解的校验逻辑
* @author seapp
* @date 2020/8/5 10:00
*
* 两个泛型:
* 第一个:指定自己定义的注解
* 第二个:指定校验注解可以注解在什么类型的参数上。
*/
public class MyConstraintValidator implements ConstraintValidator<MyConstraint, Object> {
/**
*注意在该类中,可通过@Autowired注解可以注入任意bean类。
*/
@Override
public void initialize(MyConstraint constraintAnnotation) {
//valid进行初始化
System.out.println("myvalid is init");
}
/**
*
* @param value
* @param constraintValidatorContext
* @return true:校验通过,false:校验失败
*/
@Override
public boolean isValid(Object value, ConstraintValidatorContext constraintValidatorContext) {
//实行校验
System.out.println(value);
return false;
}
}
③校验结果的获取
@PutMapping("/{id:\\d+}")
public User update(@RequestBody @Valid User user, BindingResult errors) {
if (errors.hasErrors()) {
errors.getAllErrors().stream()
.forEach(error -> {
//获取校验字段名称
FieldError fieldError = (FieldError) error;
//拼接校验结果展示字符串
String message = fieldError.getField() + ":" + error.getDefaultMessage();
System.out.println("message = " + message);
System.out.println(error.getDefaultMessage());
});
}
System.out.println(user.getId());
System.out.println(user.getUsername());
System.out.println(user.getPassword());
System.out.println(user.getBirthday());
user.setId(1);
return user;
}
第四章 RESTful API错误处理
学习目标:
- Spring Boot中默认的错误处理机制
- 自定义异常处理
1. Spring Boot对异常的处理机制
//根据发出请求头中是否包含html/text来确定返回数据类型为ModelAndView,还是ResponseEntity转换的json数据。
@Controller
@RequestMapping("${server.error.path:${error.path:/error}}")
public class BasicErrorController extends AbstractErrorController
...
@Override
public String getErrorPath() {
return this.errorProperties.getPath();
}
@RequestMapping(produces = MediaType.TEXT_HTML_VALUE)
public ModelAndView errorHtml(HttpServletRequest request, HttpServletResponse response) {
HttpStatus status = getStatus(request);
Map<String, Object> model = Collections
.unmodifiableMap(getErrorAttributes(request, isIncludeStackTrace(request, MediaType.TEXT_HTML)));
response.setStatus(status.value());
ModelAndView modelAndView = resolveErrorView(request, response, status, model);
return (modelAndView != null) ? modelAndView : new ModelAndView("error", model);
}
@RequestMapping
public ResponseEntity<Map<String, Object>> error(HttpServletRequest request) {
HttpStatus status = getStatus(request);
if (status == HttpStatus.NO_CONTENT) {
return new ResponseEntity<Map<String, Object>>(status);
}
Map<String, Object> body = getErrorAttributes(request, isIncludeStackTrace(request, MediaType.ALL));
return new ResponseEntity<>(body, status);
}
...
2. 自定义异常,以及异常处理器的配置
- 自定义异常:
package com.seapp.exception;
/**自定义运行时异常
* @author seapp
* @date 2020/8/5 14:34
*/
public class UserNotExistException extends RuntimeException {
private String id;
public UserNotExistException(String message, String id) {
super(message);
this.id = id;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
}
- 异常处理器的配置
package com.seapp.exception;
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 java.util.HashMap;
import java.util.Map;
/**
* @author seapp
* @date 2020/8/5 14:35
*/
@ControllerAdvice
public class ControllerExceptionHandler {
@ExceptionHandler(UserNotExistException.class)//绑定指定异常类
@ResponseBody//将返回数据类型转换为json数据格式
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)//指定返回状态码
public Map<String,Object> handlerUserNotExistException(UserNotExistException ex){
Map<String,Object> result = new HashMap<>();
result.put("id",ex.getId());
result.put("message",ex.getMessage());
return result;
}
}
3. RESTful API的拦截
- 过滤器(Filter)
①自定义过滤器
package com.seapp.web.filter;
import org.springframework.stereotype.Component;
import javax.servlet.*;
import java.io.IOException;
import java.util.Date;
/**
* @author seapp
* @date 2020/8/5 14:56
*/
@Component
public class TimeFilter implements Filter {
/**
* 过滤器初始化方法
* @param filterConfig
* @throws ServletException
*/
@Override
public void init(FilterConfig filterConfig) throws ServletException {
System.out.println("tiem filter init");
}
/**
* 过滤器执行方法
* @param servletRequest
* @param servletResponse
* @param filterChain
* @throws IOException
* @throws ServletException
*/
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse,
FilterChain filterChain) throws IOException, ServletException {
System.out.println("time filter start");
long start = new Date().getTime();
filterChain.doFilter(servletRequest,servletResponse);//执行下一个拦截器
System.out.println("time filter:" + (new Date().getTime() - start));
System.out.println("time filter finish");
}
/**
* 过滤器销毁
*/
@Override
public void destroy() {
System.out.println("time filter destory");
}
}
②过滤器配置生效
方式一:
直接在拦截器类上添加@Component注解,注入Spring 容器即可。
方式二:
通过@Configuration Java代码注入的方式实现,这种方式可控制过滤指定的url访问路径。
package com.seapp.config;
import com.seapp.web.filter.TimeFilter;
import com.seapp.web.interceptor.TimeInterceptor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
import java.util.ArrayList;
import java.util.List;
/**
* @author seapp
* @date 2020/8/5 15:40
*/
@Configuration
public class WebConfig extends WebMvcConfigurerAdapter {
@Autowired
private TimeInterceptor timeInterceptor;
/**
* 配置拦截器
* @param registry
*/
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(timeInterceptor);
}
/**
* 通过Java配置过滤器
* @return
*/
@Bean
public FilterRegistrationBean timeFilter(){
FilterRegistrationBean registrationBean = new FilterRegistrationBean();
//将指定的filter加入到bean中。
TimeFilter timeFilter = new TimeFilter();
registrationBean.setFilter(timeFilter);
//指定该过滤器对那些url访问路径生效
List<String> urls = new ArrayList<>();
urls.add("/*");//过滤所有的url路径
registrationBean.setUrlPatterns(urls);
return registrationBean;
}
}
- 拦截器(Interceptor)
①:自定义拦截器
package com.seapp.web.interceptor;
import org.springframework.stereotype.Component;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.lang.invoke.MethodHandle;
import java.util.Date;
/**自定义拦截器
* @author seapp
* @date 2020/8/5 15:49
*/
@Component
public class TimeInterceptor implements HandlerInterceptor {
/**
* 方法调用之前执行
* @param request
* @param response
* @param handler
* @return
* @throws Exception
*/
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response
, Object handler) throws Exception {
System.out.println("timeInterceptor: preHandle" );
//设定拦截器开始时间
request.setAttribute("startTime",new Date().getTime());
//获取执行方法的类名和方法名
String className = ((HandlerMethod) handler).getBean().getClass().getName();
System.out.println("className = " + className);
String methodName = ((HandlerMethod) handler).getMethod().getName();
System.out.println("methodName = " + methodName);
return true;
}
/**
* 控制器方法正常执行之后,执行该方法
* @param request
* @param response
* @param handler
* @param modelAndView
* @throws Exception
*/
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response
, Object handler, ModelAndView modelAndView) throws Exception {
System.out.println("timeInterceptor: postHandle" );
long startTime = (long)request.getAttribute("startTime");
System.out.println("postHandle 耗时:" + (new Date().getTime() - startTime));
}
/**
* 无论控制器方法是否执行,该方法都会被执行。
* 若执行的方法发生异常,则Exception对象中有值。
* @param request
* @param response
* @param handler
* @param ex
* @throws Exception
*/
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response
, Object handler, Exception ex) throws Exception {
System.out.println("timeInterceptor: afterCompletion" );
System.out.println("exception:" + ex);
long startTime = (long)request.getAttribute("startTime");
System.out.println("postHandle 耗时:" + (new Date().getTime() - startTime));
}
}
②:拦截器的配置
package com.seapp.config;
import com.seapp.web.filter.TimeFilter;
import com.seapp.web.interceptor.TimeInterceptor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
import java.util.ArrayList;
import java.util.List;
/**
* @author seapp
* @date 2020/8/5 15:40
*/
@Configuration
public class WebConfig extends WebMvcConfigurerAdapter {
@Autowired
private TimeInterceptor timeInterceptor;
/**
* 配置拦截器
* @param registry
*/
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(timeInterceptor);
}
/**
* 通过Java配置过滤器
* @return
*/
@Bean
public FilterRegistrationBean timeFilter(){
FilterRegistrationBean registrationBean = new FilterRegistrationBean();
//将指定的filter加入到bean中。
TimeFilter timeFilter = new TimeFilter();
registrationBean.setFilter(timeFilter);
//指定该过滤器对那些url访问路径生效
List<String> urls = new ArrayList<>();
urls.add("/*");//过滤所有的url路径
registrationBean.setUrlPatterns(urls);
return registrationBean;
}
}
-
切片(Aspect )
①定义切片,设定切入点。增强方法中获取请求参数
package com.seapp.web.aspect;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;
/** 使用AOP方式,实现在方法执行之前对请求参数的获取。
* @author seapp
* @date 2020/8/5 16:39
*/
@Aspect
@Component
public class TimeAspect {
/**
* @Before 方法执行之前执行
* @After 方法执行之后执行
* @Around 方法前后都执行
*/
@Around("execution(* com.seapp.web.controller.UserController.*(..))")
public Object handleControllerMethod(ProceedingJoinPoint pjp) throws Throwable {
System.out.println("aspect is start");
//在方法执行之前,获取方法参数
Object[] args = pjp.getArgs();
for (Object arg :
args) {
System.out.println("arg = " + arg);
}
//执行控制器方法,返回值为被调用方法的执行结果
Object obj = pjp.proceed();
System.out.println("aspect is finish");
return obj;
}
}