JavaWeb基础2-2 六、Interceptor 1、概念 概念:是一种动态拦截方法调用的机制,类似于过滤器。Spring框架中提供的,用来动态拦截控制器方法的执行。 作用:拦截请求,在指定的方法调用前后,根据业务需要执行预先设定的代码。
1.定义拦截器,实现HandlerInterceptor接口, 并重写其所有方法。
2.注册拦截器
1 2 3 4 5 6 @Override public boolean preHandle@Override public void postHandle@Override public void afterCompletion
3.交给AOP容器管理,加上@component
2、入门
定义拦截器,实现HandlerInterceptor接口, 并重写其所有方法。
注册拦截器
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 @Slf4j @Component public class LoginCheckInterceptor implements HandlerInterceptor { @Override public boolean preHandle (HttpServletRequest req, HttpServletResponse resp, Object handler) throws Exception { String url = req.getRequestURL().toString(); log.info("请求的url: {}" ,url); if (url.contains("login" )){ log.info("登录操作, 放行..." ); return true ; } String jwt = req.getHeader("token" ); if (!StringUtils.hasLength(jwt)){ log.info("请求头token为空,返回未登录的信息" ); Result error = Result.error("NOT_LOGIN" ); String notLogin = JSONObject.toJSONString(error); resp.getWriter().write(notLogin); return false ; } try { JwtUtils.parseJWT(jwt); } catch (Exception e) { e.printStackTrace(); log.info("解析令牌失败, 返回未登录错误信息" ); Result error = Result.error("NOT_LOGIN" ); String notLogin = JSONObject.toJSONString(error); resp.getWriter().write(notLogin); return false ; } log.info("令牌合法, 放行" ); return true ; } @Override public void postHandle (HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { System.out.println("postHandle ..." ); } @Override public void afterCompletion (HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { System.out.println("afterCompletion..." ); } }
3.拦截器-拦截路径
拦截器可以根据需求,配置不同的拦截路径:
4.执行流程
5.Filter与Interceptor:
接口规范不同:过滤器需要实现Filter接口,而拦截器需要实现HandleaInterceptor接口。
拦截范围不同:过滤器Filter会拦截所有的资源,而Interceptor只 会拦截Spring环境中的资源。
3、登录校验案例 4、全局异常处理器 编写一个exception包,其中包含GlobalExceptionHandler类,相关方法实现如下:
1 2 3 4 5 6 7 8 @RestControllerAdvice public class GlobalExceptionHandler { @ExceptionHandler(Exception.class) public Result ex (Exception ex) { ex.printStackTrace(); return Result.error("对不起,操作失败,请联系管理员" ); } }
七、事务管理 1、概念 事务是一组操作的集合,它是一个不可分割的工作单位,这些操作要么同时成功,要么同时失败。
操作:
开启事务(- -组操作开始前,开启事务) : start transaction / begin ;
提交事务(这组操作全部成功后,提交事务) : commit ;
回滚事务(中间任何-一个操作出现异常,回滚事务) : rollback ;
2、Spring事物管理 案例分析:删除部门的时候,删除部门下的全部员工。
会出现的问题:即使程序运行抛出了异常 / 部门依然删除了 / 但是部门下的员工却没有删除 , 造成了数据的不一致。
@Transactional 注解: @Transactional
位置:业务(service) 层的方法上、类上、接口上
作用:将当前方法交给spring进行事务管理,方法执行前,开启事务;成功执行完毕,提交事务;出现异常,回滚事务
作用在接口/类,在调用此接口/类的,都会被执行。
一般都在增删改的方法上加此注解。
案例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 @Transactional @Override public void delete (Integer id) throws Exception { try { deptMapper.deleteById(id); int i = 1 /0 ; empMapper.deleteByDeptId(id); } finally { DeptLog deptLog = new DeptLog (); deptLog.setCreateTime(LocalDateTime.now()); deptLog.setDescription("执行了解散部门的操作,此次解散的是" +id+"号部门" ); deptLogService.insert(deptLog); } }
事务进阶 rollbackFor 默认情况下,只有出现RuntimeException才回滚异常。rollbackFor属性用于控制出现何种异常类型,回滚事务 。
设置回滚异常类型:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 @Transactional(rollbackFor = Exception.class) @Override public void delete (Integer id) throws Exception { try { deptMapper.deleteById(id); int i = 1 /0 ; empMapper.deleteByDeptId(id); } finally { DeptLog deptLog = new DeptLog (); deptLog.setCreateTime(LocalDateTime.now()); deptLog.setDescription("执行了解散部门的操作,此次解散的是" +id+"号部门" ); deptLogService.insert(deptLog); } }
propagation 事务传播行为:指的就是当一个事务方法被另一个事务方法调用时,这个事务方法应该如何进行事务控制。
属性值
含义
REQUIRED
[默认值]需要事务,有则加入,无则创建新事务
REQUIRES_ NEW
需要新事务,无论有无,总是创建新事务
SUPPORTS
支持事务,有则加入,无则在无事务状态中运行
NOT_SUPPORTED
不支持事务,在无事务状态下运行,如果当前存在已有事务,则挂起当前事务
MANDATORY
必须有事务,否则抛异常
NEVER
必须没事务,否则抛异常
使用try-finally语句,即使上方执行异常,下方仍然可以正常操作(保障日志记录正常):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 @Transactional @Override public void delete (Integer id) throws Exception { try { deptMapper.deleteById(id); int i = 1 /0 ; empMapper.deleteByDeptId(id); } finally { DeptLog deptLog = new DeptLog (); deptLog.setCreateTime(LocalDateTime.now()); deptLog.setDescription("执行了解散部门的操作,此次解散的是" +id+"号部门" ); deptLogService.insert(deptLog); } }
如果只写一个@Transactional,如果出错,会导致日志写入后回滚,把日志删除
因此,在创建日志的记录中,使用@Transactional(propagation = Propagation.REQUIRES_NEW),使其开启一个新事务这样就不会没有日志了。
1 2 3 4 5 6 7 8 9 10 11 12 @Service public class DeptLogServiceImpl implements DeptLogService { @Autowired private DeptLogMapper deptLogMapper; @Transactional(propagation = Propagation.REQUIRES_NEW) @Override public void insert (DeptLog deptLog) { deptLogMapper.insert(deptLog); } }
注意:正在运行的事务只能有一个,原运行的事物会被挂起,而运行新事务。
REQUIRED:大部分情况下都是用该传播行为即可。
REQUIRES_ NEW:当我们不希望事务之间相互影响时,可以使用该传播行为。比如:下订单前需要记录日志,不论订单保存成功与否,都需要保证日志记录能够记录成功。
八、AOP 1、基础 AOP:Aspect Oriented Programming(面向切面编程面向方面编程),其实就是面向特定方法编程。
2、SpringAOP快速入门 导入依赖:在pom.xml中导入AOP的依赖
SpringAOP是基于动态代理技术实现的。
1 2 3 4 <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-aop</artifactId > </dependency >
编写AOP程序:对于特定方法根据业务需要进行编程
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 @Slf4j @Component public class TimeAspect { @Around("com.itheima.aop.MyAspect1.pt()") public Object recordTime (ProceedingJoinPoint joinPoint) throws Throwable { long begin = System.currentTimeMillis(); Object result = joinPoint.proceed(); long end = System.currentTimeMillis(); log.info(joinPoint.getSignature()+"方法执行耗时: {}ms" , end-begin); return result; } }
3、AOP基础 1.连接点:JoinPoint,可以被 AOP 控制的方法(暗含方法执行时的相关信息)
2.通知: Advice,指哪些重复的逻辑,也就是共性功能(最终体现为一个方法)
3.切入点:PointCut,匹配连接点的条件,通知仅会在切入点方法执行时被应用
4.切面:Aspect,描述通知与切入点的对应关系(通知+切入点)
5.目标对象:Target,通知所应用的对象
4、AOP执行流程
5、AOP通知类型
@Around:环绕通知,此注解标注的通知方法在目标方法前、后都被执行
@Before:前置通知,此注解标注的通知方法在目标方法前被执行
@After:后置通知,此注解标注的通知方法在目标方法后被执行,无论是否有异常都会执行
@AfterReturning:返回后通知,此注解标注的通知方法在目标方法后被执行,有异常不会执行
@AfterThrowing:异常后通知,此注解标注的通知方法发生异常后执行
实例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 @Slf4j @Component public class MyAspect1 { @Pointcut("execution(* com.itheima.service.impl.DeptServiceImpl.*(..))") public void pt () {} @Before("pt()") public void before () { log.info("before ..." ); } @Around("pt()") public Object around (ProceedingJoinPoint proceedingJoinPoint) throws Throwable { log.info("around before ..." ); Object result = proceedingJoinPoint.proceed(); log.info("around after ..." ); return result; } @After("pt()") public void after () { log.info("after ..." ); } @AfterReturning("pt()") public void afterReturning () { log.info("afterReturning ..." ); } @AfterThrowing("pt()") public void afterThrowing () { log.info("afterThrowing ..." ); } }
6、AOP通知顺序 1.不同切面类中,默认按照切面类的类名字母排序: 目标方法前的通知方法:字母排名靠前的先执行
目标方法后的通知方法:字母排名靠前的后执菏
2.用@Order(数字)加在切面类上来控制顺序 令目标方法前的通知方法:数字小的先执行
目标方法后的通知方法:数字小的后执行
7、切入点表达式 切入点表达式:描述切入点方法的一种表达式 作用:主要用来决定项目中的哪些方法需要加入通知 常见形式 :
execution (…) :根据方法的签名来匹配
@annotationc(…) :根据注解匹配
1.切入点表达式-execution(最常用) execution主要根据方法的返回值、包名、类名、方法名、方法参数等信息来匹配,语法为:
其中带?的表示可以省略的部分
访问修饰符:可省略(比如: public、 protected)
包名.类名:可省略
throws异常:可省略(注意是方法上声明抛出的异常,不是实际抛出的异常)
常用的切入点表达式:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 @Slf4j @Component public class MyAspect7 { @Pointcut("execution(* com.itheima.service.DeptService.list()) || execution(* com.itheima.service.DeptService.delete(java.lang.Integer))") private void pt () {} @Before("pt()") public void before () { log.info("MyAspect7 ... before ..." ); } }
可以使用通配符描述切入点:
单个独立的任意符号,可以通配任意返回值、包名、类名、方法名、任意类型的一个参数,也可以通配包、类、方法名的一部分
1 execution(* com.*. service.*.update*(*))
多个连续的任意符号,可以通配任意层级的包,或任意类型、任意个数的参数
1 execution(* com.itheima..DeptService.*(..))
注意事项
根据业务需要,可以使用且(&&)、或(|)、非(!)来组合比较复杂的切入点表达式。
2.切入点表达式-@annotation(多一步,但灵活) @annotation 切入点表达式,用于匹配标识有特定注解的方法。
1 @annotation(com.itheima.anno.Log)
(1)编写一个注解
1 2 3 4 @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) public @interface MyLog { }
(2)将方法添加注解
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 @Slf4j @Component public class MyAspect7 { @Pointcut("@annotation(com.itheima.aop.MyLog)") private void pt () {} @Before("pt()") public void before () { log.info("MyAspect7 ... before ..." ); } }
8、连接点及其相关信息 在Spring中用JoinPoint抽象了连接点,用它可以获得方法执行时的相关信息,如目标类名、方法名、方法参数等。
对于@Around 通知,获取连接点信息只能使用ProceedingJoinPoint
对于其他四种通知,获取连接点信息只能使用JoinPoilt,它是ProceedingJoinPoint的父类型
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 @Slf4j @Aspect @Component public class MyAspect8 { @Pointcut("execution(* com.itheima.service.DeptService.*(..))") private void pt () {} @Before("pt()") public void before (JoinPoint joinPoint) { log.info("MyAspect8 ... before ..." ); } @Around("pt()") public Object around (ProceedingJoinPoint joinPoint) throws Throwable { log.info("MyAspect8 around before ..." ); String className = joinPoint.getTarget().getClass().getName(); log.info("目标对象的类名:{}" , className); String methodName = joinPoint.getSignature().getName(); log.info("目标方法的方法名: {}" ,methodName); Object[] args = joinPoint.getArgs(); log.info("目标方法运行时传入的参数: {}" , Arrays.toString(args)); Object result = joinPoint.proceed(); log.info("目标方法运行的返回值: {}" ,result); log.info("MyAspect8 around after ..." ); return result; } }
9、AOP案例 【稍后更新】
九、Bean管理 1、配置优先级 案例:
同一个属性,不同地方配置3次,最终哪一个生效
优先级:
1.application.properties
2.application.yml
3.application.yaml
注意事项:
虽然SpringBoot支持多种格式配置文件,但是在项目开发时,推荐统一使用一 种格式的配置(yml是主流)。
配置:
SpringBoot 除了支持配置文件属性配置,还支持Java系统属性和命令行参数的方式进行属性配置。(按顺序如下)
1.命令行参数
2.Java系统属性
①执行maven打包指令package
tlias-web-management-0.0.1-SNAPSHOT.jar
②.执行java指令,运行jar包
1 java -Dserver.port=9000 -jar tlias-web- management-0 .0 .1 -SNAPSHOT.jar --server.port=10010
注意事项:
Springboot项目进行打包时,需要引入插件spring-boot -maven-plugin (基于官网骨架创建项目,会自动添加该插件)
优先级:
2、Bean 管理 1.获取bean 默认情况下,Spring项目启动时, 会把bean都创建好放在I0C容器中,如果想要主动获取这些bean,可以通过如下
方式:
根据name获取bean:
1 Object getBean (String name)
根据类型获取bean:
1 <T> T getBean (Class<T> requiredType)
根据name获取bean (带类型转换):
1 <T> T getBean (String name, Class<T> requiredType)
注意事项
上述所说的【Spring项目启动时,会把其中的bean都创建好】还会受到作用域及延迟初始化影响,这里主要针对于默认的单例非延迟加载的bean而言。
2.Bean作用域
作用域
说明
singleton*
容器内同名称的bean 只有一个实例(单例)(默认)
prototype*
每次使用该bean时会创建新的实例(非单例)
request
每个请求范围内会创建新的实例(web环境中,了解)
session
每个会话范围内会创建新的实例(web环境中,了解)
application
每个应用范围内会创建新的实例(web环境中,了解)
可以用@Scope注解来进行配置作用域:
1 2 3 4 5 @Scope("prototype") @RestController @RequestMapping("/depts") public class DeptController { }
注意事项:
默认singleton的bean,在容器启动时被创建,可以使用@Lazy注解来延迟初始化( 延迟到第一次使用时) 。
prototype的bean,每一次使 用该bean的时候都会创建一个新的实例。
实际开发当中,绝大部分的Bean是单例的,也就是说绝大部分Bean不需要配置scope属性。
3、第三方Bean 第三方bean用:@Bean
如果要管理的bean对象来自于第三方(不是自定义的),是无法用@Component及衍生注解声明bean的,就需要用到@Bean注解。
直接定义到启动类当中(不建议):
1 2 3 4 5 6 7 @SpringBootApplication public class SpringbootWebConfig2App1ication { @Bean public SAXReader saxReader () { return new SAXReader (); } }
配置到方法中:(加注释)
1 2 3 4 5 6 7 @Configuration public class CommonConfig { @Bean public SAXReader saxReader () { return new SAXReader () ; } }
@Bean注释:
将当前方法的返回值对象交给IOC容器管理, 成为IOC容器bean
通过@Bean注解的name/value属性指定bean名称, 如果未指定, 默认是方法名
1 2 3 4 5 6 @Test public void testGetBean2 () { Object saxReader = applicationContext.getBean("reader" ); System.out.println(saxReader); }
需要依赖其他Bean对象,直接在方法中定义形参,容器会自动装配
1 2 3 4 5 6 7 8 9 10 @Configuration public class CommonConfig { @Bean public SAXReader reader (DeptService deptService) { System.out.println(deptService); return new SAXReader (); } }
总结:
通过@Bean注解的name或value属性’ 可以声明bean的名称,如果不指定,默认bean的名称就是方法名。
如果第三方bean需要依赖其它bean对象,直接在bean定义方法中设置形参即可,容器会根据类型自动装配。
问题思考:
@Component及衍生注解与@Bean注解使用场景?
项目中自定义的,使用@Component及其衍生注解
项目中引入第三方的,使用@Bean注解
十、SpringBoot原理 1、Spring Spring Framework框架:相对繁琐
Spring Boot框架:相对方便
原因:起步依赖(依赖传递)、自动配置
2、自动配置 SpringBoot的自动配置就是当Spring容器启动后,一些配置类、bean对象就自动存入到了IOC容器中,不需要我们手动去声明,从而简化了开发,省去了繁琐的配置操作。
自动配置原理
方案一: @ComponentScan 组件扫描(使用繁琐,性能低)
包扫描范围:
1 2 3 4 5 6 7 8 @ComponentScan({"com.example","com.itheima"}) @SpringBootApplication public class SpringbootWebConfig2Application { public static void main (String[] args) { SpringApplication.run(SpringbootWebConfig2Application.class, args); } }
方案二:@lmport 导入。使用@Import导入的类会被Spring加载到IOC容器中,导入形式主要有以下几种:
导入普通类
导入配置类
导入ImportSelector接口实现类
@EnableXxxx注解,封装@Import注解(最方便)
1 2 3 4 @Import({TokenParser.class, HeaderConfig.class}) @SpringBootApplication public class SpringbootWebConfig2Application { }
3、@Conditional 作用:按照- -定的条件进行判断,在满足给定条件后才会注册对应的bean对象到Spring I0C容器中。
位置:方法、类
@Conditional本身是一个父注解,派生出大量的子注解:
@ConditionalOnClass:判断环境中是否有对应字节码文件,才注册bean到IOC容器。
@ConditionalOnMissingBean:判断环境中没有对应的bean (类型或名称),才注册bean到IOC容器。
@ConditionalOnProperty:判断配置文件中有对应属性和值,才注册bean到IOC容器。
举例:
1 2 3 4 5 6 7 8 9 10 @Bean @ConditionalOnClass(name = "io.jsonwebtoken.Jwts") public HeaderParser headerParser () { return new HeaderParser (); }
全部注册为IOC容器的bean??? NO!
SpringBoot会根据@Conditional注解条件装配
4、自定义starter 在实际开发中,经常会定义-些公共组件,提供给各个项目团队使用。而在SpringBoot的项目中,一般会将这些公共组件封装为SpringBoot的starter。
案例: 需求:自定义aliyun-oss-spring-boot-starter,完成阿里云OSS操作工具类AliyunOSSUtils 的自动配置。 目标:引入起步依赖引入之后,要想使用阿里云OSS,注入AliyunOSSUtils直接使用即可。
十一、Maven高级