AOP为Aspect Oriented Programming的缩写,意为:面向切面编程
是Spring的核心内容之一,另一个是IoC(控制反转),AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。
/login
和/logout
接口aop
:SpringAOP核心依赖
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-aopartifactId>
dependency>
thymeleaf
:模板引擎
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-thymeleafartifactId>
dependency>
web
:web项目核心依赖
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
mybatis-springboot
:mybatis与SpringBoot整合核心依赖
<dependency>
<groupId>org.mybatis.spring.bootgroupId>
<artifactId>mybatis-spring-boot-starterartifactId>
<version>2.1.3version>
dependency>
mysql
:mysql数据库驱动
<dependency>
<groupId>mysqlgroupId>
<artifactId>mysql-connector-javaartifactId>
<scope>runtimescope>
dependency>
lombok
,因为我需要直接用lombok插件使用注解完成pojo类
<dependency>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
<optional>trueoptional>
dependency>
user表,存放用户基本信息:
userLog表,存放用户日志信息(登入和登出):
user.java:
@Data
@Component
@NoArgsConstructor
@AllArgsConstructor
public class User {
/**
* 用户编号
*/
private Integer uid;
/**
* 用户名
*/
private String username;
/**
* 密码
*/
private String password;
/**
* 年龄
*/
private Integer age;
}
userLog.java:
@Data
@Component
@NoArgsConstructor
@AllArgsConstructor
public class UserLog {
/**
* 日志编号
*/
private Integer id;
/**
* 产生该日志的用户编号
*/
private Integer uid;
/**
* 登录的时间
*/
private Date loginTime;
/**
* 登出的时间
*/
private Date logoutTime;
}
由于只是登录用,我们只需要对表里的数据查询出来然后比对一下就可以了
UserMapper.java:
@Mapper
@Repository
public interface UserMapper {
/**
* 用于用户登陆时的验证
* @return 如果用户名和密码同时匹配则返回一个User对象
*/
User selectUserByUsername(User user);
}
UserLogMapper.java:
@Mapper
@Repository
public interface UserLogMapper {
/**
* 新增用户日志(新增用户编号uid,登入时间loginTime)
* @return
*/
int insertUserLog(UserLog userLog);
/**
* 修改用户日志(根据日志编号id修改登出时间logoutTime)
* @return
*/
int updateUserLog(UserLog userLog);
}
需要配置Tomcat默认的端口号,配置Mapper映射地址,以及pojo的别名
#########################################################
#Tomcat容器(默认8080)
server:
port: 8080
#########################################################
#########################################################
#数据源
spring:
datasource:
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://localhost:3306/um?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
username: root
password:
#########################################################
#########################################################
#thymeleaf模板引擎
thymeleaf:
cache: false
#########################################################
#########################################################
#Mybatis相关配置
mybatis:
type-aliases-package: cn.wqk.springbootaop.pojo
mapper-locations: mybatis/mapper/*Mapper.xml
#########################################################
UserService.java:
@Service
public interface UserService {
//根据用户名查询是否存在该用户(shiro)
User selectUserByUsername(String username);
}
UserLogService.java:
我的逻辑是用户登录成功后直接根据用户的编号然后插入日志表里,日志的日志编号自增,登录的时候就插入用户登入时间,然后登出的时候就根据日志编号,直接将登出时间字段改为用户登出时间
@Service
public interface UserLogService {
//新增用户日志,成功后返回该日志的编号
int insertUserLog(int uid);
//修改用户日志
int updateUserLog(int id);
}
UserServiceImpl.java:
@Service
public class UserServiceImpl implements UserService {
@Autowired
private UserMapper userMapper;
@Autowired
private User user;
@Override
public User selectUserByUsername(String username) {
user.setUsername(username);
return userMapper.selectUserByUsername(user);
}
}
UserLogServiceImpl.java:
@Service
public class UserLogServiceImpl implements UserLogService {
@Autowired
private UserLogMapper userLogMapper;
@Autowired
private UserLog userLog;
@Override
public int insertUserLog(int uid) {
userLog.setUid(uid);
//获得当前系统时间
Timestamp loginTime = DateUtils.nowDateTime();
userLog.setLoginTime(loginTime);
userLogMapper.insertUserLog(userLog);
return userLog.getId();
}
@Override
public int updateUserLog(int id) {
userLog.setId(id);
//获得当前系统时间
Timestamp logoutTime = DateUtils.nowDateTime();
userLog.setLogoutTime(logoutTime);
return userLogMapper.updateUserLog(userLog);
}
}
因为Java自带的Date类型是java.util.date
无法直接存入MySQL数据库,所以我写了一个工具类,获取到系统的时间并且转换为MySQL能够存储的Timestamp类型
DateUtils.java:
public class DateUtils {
/**
* 获取系统时间并且转换为能存入MySQL的datetime的格式
* @return timestamp(能存入MySQL)
*/
public static Timestamp nowDateTime(){
//获取系统当前时间,格式:Tue Jun 23 19:57:59 CST 2020
Date date = new Date();
//获取系统当前时间的时间戳,格式:1592913479942
long dataTime = date.getTime();
//把时间戳转换为能够存入MySQL的datetime的时间格式
Timestamp timestamp = new Timestamp(dataTime);
return timestamp;
}
}
/login
和/logout
接口login接口的作用就是从前端接收username和password参数,然后通过selectUserByUsername()方法查询到用户再根据接收到的密码进行匹配,匹配成功后则登录成功,并且把User对象存入session,还有status赋值为login存入session
logout接口就是先判断用户的登录状态status是否为login,是则允许退出并且清除seesion,否则不进行任何实际操作
@Controller
public class UserController {
@Autowired
private UserService userService;
@RequestMapping("/login")
public String login(@RequestParam("username")String username,
@RequestParam("password")String password,
HttpSession session){
User user = userService.selectUserByUsername(username);
if (password.equals(user.getPassword())){
//登录成功
//将user对象存入session里
session.setAttribute("user",user);
//将status状态设置为login登录并且存入session
session.setAttribute("status","login");
return "index";
}else {
//登录失败
return "redirect:/toLogin";
}
}
@RequestMapping("/logout")
@ResponseBody
public String logout(HttpSession session){
String status = (String) session.getAttribute("status");
if (status.equals("login")){
//退出成功
session.invalidate();
return "logout,success!";
}else {
//退出失败
return "logout,failure!";
}
}
}
配置LoginAop切面,切点为UserController下的login()方法,该切面的逻辑是通过前置通知得到用户请求login接口传入的参数1:username,2:password,3:session,其中再通过session得到存入的User对象和status状态,再在环绕通知里首先判断status的状态,用户登录成功后,得到User对象里的uid再通过UserLogService插入到用户日志信息表里,并且将该条日志的编号作为返回值存到session里
@Aspect
@Component
public class LoginAop {
@Autowired
private UserLogServiceImpl userLogService;
//提高作用域
private Object proceed;
private String username;
private String password;
private HttpSession session;
//定义切点为controller包下的UserController类里的login()方法
@Pointcut("execution(* cn.wqk.springbootaop.controller.UserController.login(..))")
public void pointCut(){
}
//前置通知,在前置通知里一般是给变量赋值
@Before("pointCut()")
public void before(JoinPoint joinPoint){
System.out.println("前置通知-----------------------");
String className = joinPoint.getTarget().getClass().getName();
String methodName = joinPoint.getSignature().getName();
Object[] args=joinPoint.getArgs();
System.out.println("类:"+className);
System.out.println("方法:"+methodName);
System.out.println("传入参数:");
for (int i=0;i<args.length;i++){
System.out.println("参数"+(i+1)+":"+args[i]);
}
//将第一个参数赋值给username
username=(String) args[0];
//将第二个参数赋值给password
password=(String) args[1];
//第三个参数赋值给session,让我们能够从session中取到uid
session=(HttpSession)args[2];
System.out.println("前置通知完--------------------");
}
//环绕通知
/**
* 环绕通知:
* proceed为执行方法后返回的值
*/
@Around("pointCut()")
public Object Around(ProceedingJoinPoint pjp) throws Throwable {
System.out.println("环绕通知:---------------------");
//获得方法执行后的返回值
proceed = pjp.proceed();
//System.out.println("执行的方法后的返回值:"+proceed+"");
String status = (String) session.getAttribute("status");
if (status.equals("login")){
User user = (User) session.getAttribute("user");
Integer uid = user.getUid();
System.out.println(uid);
int logId = userLogService.insertUserLog(uid);
session.setAttribute("logId",logId);
}
System.out.println("环绕通知完--------------------");
return proceed;
}
}
配置LogoutAop切面,切点是UserController下的logout()方法,该切面的逻辑是通过前置通知得到session,但是有一点需要特别注意,因为我在logout接口里配置了session.invalidate()方法,所以必须在前置通知里就把session拿到,然后才能拿到session里存的status和logId,所以环绕通知只是进行一个善后操作,通过logId获取到对应的日志编号,然后将其登出时间修改为系统当前时间
@Aspect
@Component
public class LogoutAop {
@Autowired
private UserLogServiceImpl userLogService;
//提高作用域
private Object proceed;
private HttpSession session;
private int logId;
private String status;
@Pointcut("execution(* cn.wqk.springbootaop.controller.UserController.logout(..))")
public void pointCut(){
}
//前置通知
@Before("pointCut()")
public void before(JoinPoint joinPoint){
System.out.println("前置通知-----------------------");
String className = joinPoint.getTarget().getClass().getName();
String methodName = joinPoint.getSignature().getName();
Object[] args=joinPoint.getArgs();
System.out.println("类:"+className);
System.out.println("方法:"+methodName);
System.out.println("传入参数:");
for (int i=0;i<args.length;i++){
System.out.println("参数"+(i+1)+":"+args[i]);
}
//第一个参数赋值给session,让我们能够从session中取到uid
session=(HttpSession)args[0];
//从session中获得uid
logId = (int) session.getAttribute("logId");
//从session中获得status
status = (String) session.getAttribute("status");
System.out.println("前置通知完--------------------");
}
@Around("pointCut()")
public Object Around(ProceedingJoinPoint pjp) throws Throwable {
System.out.println("环绕通知:---------------------");
//获得方法执行后的返回值
proceed = pjp.proceed();
if (status.equals("login")) {
System.out.println("logId:"+logId);
userLogService.updateUserLog(logId);
}
/*if (proceed.equals("logout,success!")){
System.out.println("logId:"+logId);
userLogService.updateUserLog(logId);
}*/
System.out.println("执行的方法后的返回值:"+proceed+"");
System.out.println("环绕通知完--------------------");
return proceed;
}
}
拦截器的逻辑就是拿到session里的user对象,如果没有user对象代表用户登录失败或者未登录,则返回false就是拦截住,否则返回true,放行。
@Component
public class LoginInterceptor implements HandlerInterceptor {
//访问接口之前调用,返回true放行,返回false拦截住
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("LoginInterceptor.preHandle()前-----------------------");
//获得session
HttpSession session = request.getSession();
//获得session里的user对象
User user = (User) session.getAttribute("user");
System.out.println("LoginInterceptor.preHandle()后-----------------------");
if (user==null){
return false;
}else {
return true;
}
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
}
}
因为配置了拦截器,所以还需要自定WebConfig并且注册拦截器,过滤/**的请求,不过滤/login请求
@Configuration
public class WebConfigurer implements WebMvcConfigurer {
@Autowired
private LoginInterceptor loginInterceptor;
//配置资源过滤,比如js、css、html
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
}
//配置拦截器,写好的拦截器需要到这里注册才行
@Override
public void addInterceptors(InterceptorRegistry registry) {
//过滤/**下的所有请求,不过滤/login,/,/toLogin请求
registry.addInterceptor(loginInterceptor).addPathPatterns("/**").excludePathPatterns("/login","/","toLogin");
}
}
登录
登录我是用的一个form表单发起/login
请求
登出