一:关键词
1:Subject:当前用户
2:UsernamePasswordToken:通常情况是使用username与password来创建token(令牌)
username也叫作身份:principals
password叫作凭证:credentials
3:SecurityManager:所有的Subject交付给它管理,进行认证与授权
4:Realm:用户自定义,认证授权逻辑编写
5:AuthenticationInfo:用户的角色信息集合,认证时使用
6:AuthorzationInfo:角色权限信息集合,授权时使用
7:DefaultWebSecurityManager:安全管理器,需要将自己写的Realm注入进去才能使用
8:ShiroFilterFactoryBean:过滤器工厂
二:工作原理
(1):身份验证工作流程
1,收集用户身份/凭证,就是形成Token
2,然后Subject.login(Token),交付给SecurityManager进行管理认证,具体的通过Authenticator进行认证,其中使用AuthenticationStrategy进行多 Realm 身份验证,操作是Authenticator 会把相应的 token 传入 Realm,从 Realm 获取 身份验证信息。
3,自定义Realm认证用户身份是否合法
具体的来说:交由securityManager执行login(token)->调用authenticate()方法进一步获取认证信息->调用ModularRealmAuthenticator的doAuthenticate方法选择Realm->调用我们自定义Realm的认证方法获取数据库真实信息
(2)Realm的工作流程
1,自定义MyRealm类并继承AuthorzingRealm(授权),其继承了 AuthenticatingRealm(即身份验证),而且也间接继承了 CachingRealm(带有缓存实现)
2,需要重新 doGetAuthorizationInfo与doGetAuthenticationInfo方法。
(3)Shiro配置类编写流程
myRealm注入到DefaultWebSecurityManager再注入到ShiroFilterFactoryBean中
public ShiroFilterFactoryBean shiroFilterFactoryBean(@Qualifier("defaultWebSecurityManager") DefaultWebSecurityManager defaultWebSecurityManager) { ShiroFilterFactoryBean factoryBean =new ShiroFilterFactoryBean(); factoryBean.setSecurityManager(defaultWebSecurityManager); return factoryBean; } @Bean public DefaultWebSecurityManager defaultWebSecurityManager(@Qualifier("myRealm") MyRealm myRealm) { DefaultWebSecurityManager manager=new DefaultWebSecurityManager(); manager.setRealm(myRealm); return manager; } @Bean public MyRealm myRealm(){ return new MyRealm(); }
二:Vue前端跨域问题
跨域:ip、网络协议、端口都一样的时候,就是同一个域,否则就是跨域
在SpringBoot后端进行解决
@Configuration public class CrosConfig implements WebMvcConfigurer { //跨域问题 @Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping("/**") .allowedOriginPatterns("*") .allowedMethods("GET","HEAD","POST","PUT","DELETE","OPTION") .allowCredentials(true) .maxAge(3600) .allowedHeaders("*"); } }
第二种方法是
在请求方法上@CrossOrigin补:解释WebMvcConfigurer配置类
WebMvcConfigurer是SpringBoot中一个配置类,来实现里面接口中方法,相当于xml中的配置。
CorsRegistry registry:跨域资源共享登记,是spring中jar包,可以直接使用第三种方法是:进行请求拦截
@Override protected boolean preHandle(ServletRequest request, ServletResponse response) throws Exception { HttpServletRequest httpServletRequest = WebUtils.toHttp(request); HttpServletResponse httpServletResponse = WebUtils.toHttp(response); // 跨域资源共享( cors ) httpServletResponse.setHeader("Access-control-Allow-Origin", httpServletRequest.getHeader("Origin")); //允许的方法 httpServletResponse.setHeader("Access-Control-Allow-Methods", "GET,POST,OPTIONS,PUT,DELETE"); //允许的头部参数 httpServletResponse.setHeader("Access-Control-Allow-Headers", httpServletRequest.getHeader("Access-Control-Request-Headers")); // 跨域时会首先发送一个OPTIONS请求,这里我们给OPTIONS请求直接返回正常状态 //在正式跨域之前,浏览器会根据需要发起一次预检(options请求),用来让服务端返回允许的方法(如get、post),被跨域访问的Origin(来源或者域),还有是否需要Credentials(认证信息)等。 if (httpServletRequest.getMethod().equals(RequestMethod.OPTIONS.name())) { httpServletResponse.setStatus(org.springframework.http.HttpStatus.OK.value()); return false; } return super.preHandle(request, response); }
三:yaml文件
mybatis-plus: //Mapper位置 mapper-locations: classpath*:/mapper/**Mapper.xml //实体类的位置 type-aliases-package: com.chen.back.entity configuration: //驼峰 map-underscore-to-camel-case: true //自动生成主键 use-generated-keys: true
四,配置
1,在启动类上添加注解扫描所有的Mapper
@MapperScan("com/chen/back/mapper")2,IDEA中多行注释的快捷键
shift+ctel+/
五:整合JWT
1,添加依赖
//jwt依赖 <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt</artifactId> <version>0.9.1</version> </dependency> //AOP切面 <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId> </dependency>
2:为什么要整合JWT?
在Shrio中Token的工作流程。
(1)客户端使用用户和密码请求登录。
(2)服务器接收请求,进行认证用户身份。
(3)验证成功后,服务器会签发一个Token,并发送给客户端
(4)客户端收到Token后储存起来,比如放到cookie中
(5)客户端每次向服务器请求资源时需要携带服务器端签发的Token。
(6)服务端收到请求后,回去验证客户端发送的Token,如果验证成功,就向客户端返回发送请求数据。
3:什么是cookie?
(1)Cookie的英文直译是饼干,在计算机术语中是指一种能够让网站服务器把少量数据储存到客户端的硬盘或内存,它可以记录你的用户ID、密码、浏览过的网页、停留的时间等信息,比如说是否让浏览器记住你的密码.
(2)cookie的好处:如果没有Cookie的存在,网络对于我们来说不会如此便捷。它存储着你的网站登录信息,如果没有他们,你将不能够登陆网站。网站通过Cookie信息来记忆以及辨认你的帐号,它可以记忆你的偏好设置。它还可以使网站提供个性化的内容,举个例子,如果你在淘宝上购物,淘宝可以记忆你所查看过的产品并据此来向你来推荐商品,即便你没有登陆个人帐号。
(3)坏处:站点利用Cookie收集大量用户信息
4:什么是JSON?
是JavaScript 对象标记法,一种轻量级的数据交换格式,格式是纯文本的,它能够轻松地在服务器浏览器之间传输,并用作任何编程语言的数据格式。
5:JWT的实质是一个字符串,它是将用户信息保存到一个Json字符串中,然后进行编码后得到一个JWT Token,并且这个
JWT token
带有签名信息,接收后可以校验是否被篡改,所以可以用于在各方之间安全地将信息作为Json对象传输。6,JWT工作流程。
(1)前端发送登录请求。
(2)后端进行验证成功后,形成JWT Token,并将用户的信息保存在payload中,然后返回给前端。
(3)前端在每次请求时将
JWT Token
放入HTTP请求头中的Authorization
属性中(4)后端检查前端传过来的
JWT Token
,验证其有效性,比如检查签名是否正确、是否过期、token的接收方是否是自己等等,验证通过后,后端解析出JWT Token
中包含的用户信息,进行其他逻辑操作(一般是根据用户信息得到权限等),返回结果7,session,token,jwt三者的关系
(1)http本身是无状态协议,服务器无法识别每一次HTTP请求的出处(不知道来自于哪个终端),它只会接受到一个请求信号,所以就存在一个问题:将用户的响应发送给相应的用户,必须有一种技术来让服务器知道请求来自哪,这就是会话技术,
会话就是客户端和服务器之间发生的一系列连续的请求和响应的过程。会话状态指服务器和浏览器在会话过程中产生的状态信息,借助于会话状态,服务器能够把属于同一次会话的一系列请求和响应关联起来。
Session通过在服务器端记录信息确定用户身份,相应的也增加了服务器的存储压力。客户端浏览器访问服务器的时候,服务器把客户端信息以某种形式记录在服务器上,这就是Session。客户端浏览器再次访问时只需要通过sessionID从Session中查找该客户的状态就可以了,可以通过设置cookie的方式返回给客户端
Cookie是服务端在HTTP响应中附带传给浏览器的一个小的文本文件,一旦浏览器保存了某个Cookie,在之后的请求和响应过程中,会将此Cookie来回传递,这样就可以通过Cookie这个载体完成客户端和服务端的数据交互。
使用Session进行用户认证时,当用户第一次通过浏览器使用用户名和密码访问服务器时,服务器会验证用户数据,验证成功后在服务器端写入session数据,向客户端浏览器返回sessionid,浏览器将sessionid保存在cookie中,当用户再次访问服务器时,会携带sessionid,服务器会拿着sessionid从服务器获取session数据,然后进行用户信息查询,查询到,就会将查询到的用户信息返回,从而实现状态保持
token 是无状态的,后端不需要记录信息,每次请求过来进行解密就能得到对应信息。
session 是有状态的,需要后端每次去检索id的有效性。不同的session都需要进行保存,但也可以设置单点登录,减少保存的数据。
session与token的选择是空间与时间博弈,为什么这么说呢,是因为token不需要保存,不占存储空间,但每次访问都需要进行解密,消耗了一定的时间。
在一般的前后端分离项目中,token展现出了它的优势,成为了比session更好的选择
六,JWT-过期时间:Calender类的基本使用方法
public static void main(String []args){ //Calender是抽象类,所以不能使用new Calendar calendar = Calendar.getInstance(); //calender是一个对象并非是具体的时间;time=1649729416714,areFieldsSet=true,areAllFieldsSet=true,lenient=true,zone=sun.util.calendar.ZoneInfo[id="Asia/Shanghai",offset=28800000,dstSavings=0,useDaylight=false,transitions=29,lastRule=null],firstDayOfWeek=1,minimalDaysInFirstWeek=1,ERA=1,YEAR=2022,MONTH=3,WEEK_OF_YEAR=16,WEEK_OF_MONTH=3,DAY_OF_MONTH=12,DAY_OF_YEAR=102,DAY_OF_WEEK=3,DAY_OF_WEEK_IN_MONTH=2,AM_PM=0,HOUR=10,HOUR_OF_DAY=10,MINUTE=10,SECOND=16,MILLISECOND=714,ZONE_OFFSET=28800000,DST_OFFSET=0] System.out.println(calendar); //月份是从0开始表示一月,11表示十二月,从星期天表示1开始,星期六表示7 System.out.println("年份:"+calendar.get(Calendar.YEAR)); System.out.println("月份:"+calendar.get(Calendar.MONTH) + 1); System.out.println("星期:"+calendar.get(Calendar.DAY_OF_WEEK)); // 时间的在转换 SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); Date date = calendar.getTime(); System.out.println(format.format(date)); //增加时间,当前的时间增加5天 calendar.add(Calendar.DATE,5); //设置时间 calendar.set(2022,5,3); }
2,Jwt的生成与解密
public class JWTUtils { // 签名密钥 private static final String SECRET = "!DAR$"; /** * 生成token * @param payload token携带的信息 * @return token字符串 */ public static String getToken(Map<String,String> payload){ // 指定token过期时间为7天 Calendar calendar = Calendar.getInstance(); calendar.add(Calendar.DATE, 7); JWTCreator.Builder builder = JWT.create(); // 构建payload payload.forEach((k,v) -> builder.withClaim(k,v)); // 指定过期时间和签名算法 String token = builder.withExpiresAt(calendar.getTime()).sign(Algorithm.HMAC256(SECRET)); return token; } /** * 解析token * @param token token字符串 * @return 解析后的token */ public static DecodedJWT decode(String token){ JWTVerifier jwtVerifier = JWT.require(Algorithm.HMAC256(SECRET)).build(); DecodedJWT decodedJWT = jwtVerifier.verify(token); return decodedJWT; } } //得到payload中的数据 Claim userId = decodedJWT.getClaim("userId"); Claim userName = decodedJWT.getClaim("userName"); System.out.println(userId.asInt()); System.out.println(userName.asString()); // 输出超时时间 System.out.println(decodedJWT.getExpiresAt());
七,Shiro—HttpServletRequest,HttpServletResPonse理解
一,HttpServletRequest:
当浏览器向服务器发送请求时,web容器会把浏览器的请求信息封装到一个HttpServletRequest对象中去,用户可以通过这个对象获取请求的头信息。
常用的使用方法
获取客户端信息
(1)getRequestURL():返回客户端发出请求时完整的URL地址。
(2)getRemoteAddr():返回客户端的ip地址。
(3)getRemoteHost():返回客户端的完整的主机名。
获取客户机的请求头信息
getHeader(String name)
解释:http中拥有Authentication部分用来进行认证的,就是JWT,而JWT以后端的形式传入客户端的,所以只要其中的key就能获得value值,这是由于在产生jwt传入前端,就存储到浏览器中,在请求的Header之中。
获取客户机请求参数
getParameter(String name):根据name获取请求参数的值
二,HttpServletResPonse理解
1,web服务器接收到http的请求,对请求作出响应,这个类包括向客户端发送响应数据,响应状态码,响应头。
2,响应的方式是以json形式,也就是说如果需要将数据发送到前端中那么就应该数据转为json的形式。
三,当方法中没有请求/响应封装类,那么如何得到请求,
RequestContextHolder:抽象类,含有静态getRequestAttributes()方法,返回接口RequestAttributes- RequestAttributes:接口
AbstractRequestAttributes:抽象类,实现RequestAttributes接口 ServletRequestAttributes :类,实现AbstractRequestAttributes抽象类,含有getRequest()方法,得到HttpServletRequest接口- HttpServletRequest:接口,继承了ServletRequest接口
四,图示;
八,shiro-Jwt-Authorization为什么使用?
因为Authorization是对http的认证,因为http协议是无状态的,所以要认证的话需要将认真信息防到Authorization,而信息是JWT,通过后端传入前段中使用key-value形式就能拿到jwt
九,shiro拦截器类是继承关系
十:Shiro前端拦截
/*对请求进行过滤*/ public class JwtFilter extends AuthenticatingFilter { /*AuthenticatingFilter中是抽象类,因为要注入shiro中,抽象类不能实列化,所以要将它两方法都要实现*/ @Override protected AuthenticationToken createToken(ServletRequest servletRequest, ServletResponse servletResponse) throws Exception { //由于shiro中传统的UsernamePassWordToken,是realm验证的依据,现在使用jwt,所以将用户的名直接放 // UsernamePasswordToken中不安全,所以要将username属性放到jwt中的payload中,而realm所以依据的是 // 新的JwtToken,jwt来代替username //将servletRequest强转HttpServletRequest HttpServletRequest httpServletRequest=(HttpServletRequest)servletRequest; String jwt = httpServletRequest.getHeader("token"); if (StringUtils.isEmpty(jwt)) { return null; } return new JwtToken(jwt); } /*是否拒绝登录,用来进行jwt验证逻辑操作*/ @Override protected boolean onAccessDenied(ServletRequest servletRequest, ServletResponse servletResponse) throws Exception { //将servletRequest转换为HttpServletRequest HttpServletRequest httpServletRequest = WebUtils.toHttp(servletRequest); //得到后台生成的jwt String jwt = httpServletRequest.getHeader("token"); //判断是否是第一次登录,然后放行 if (StringUtils.isEmpty(jwt)) { return true; } // 进行认证 else { DecodedJWT parseJwt = JwtUtil.parseJwt(jwt); if (parseJwt == null) { HttpServletResponse httpServletResponse = WebUtils.toHttp(servletResponse); //向前端返回信息 httpServletResponse.setContentType("application/plain,charset=utf-8"); PrintWriter writer = httpServletResponse.getWriter(); //转为json的形式,通过打印字符 writer.write(JSON.toJSONString("身份已经过期,请重新登录")); //拒绝登录 return false; } //继续执行 return executeLogin(servletRequest,servletResponse); } } }
十一:Shrio之字符串等数据类型的相互之间的转换问题
1,字符串 -> 基本数据类型、包装类
Integer包装类的public static int parseInt(String s):可以将由“数字”字 符组成的字符串转换为整型。
2,基本数据类型、包装类 ->字符串
调用String类的public String valueOf(int n)可将int型转换为字符串
3,字符数组 -> 字符串
String 类的构造器:String(char[]) 和 String(char[],int offset,int length) 分别用字符数组中的全部字符和部分字符创建字符串对象
4,字符串 ->字符数组
public char[] toCharArray():将字符串中的全部字符存放在一个字符数组 中的方法。
5,字节数组 ->字符串
String(byte[]):通过使用平台的默认字符集解码指定的 byte 数组,构 造一个新的 String。
6,字符串 ->字节数组
public byte[] getBytes() :使用平台的默认字符集将此 String 编码为 byte 序列,并将结果存储到一个新的 byte 数组中。
7,封装数值型类,使用new构造函数,将数值放到 类对象中去即可。
十二,Shiro之使用mybatis-plus查询
(1)查找两个字段
queryWrapper.eq("username",us.getUsername()).eq("password",us.getPassword());
十三:Shiro-注解的基本含义
1;当callSuper=false时,继承时还要比较类是否一致,当callSuper=true时,只比较子类是否一致 @EqualsAndHashCode(callSuper = false)2,@Order标记定义了组件的加载顺序,值越小拥有越高的优先级,可为负数。
例如:
@Order(-1)优先于@Order(0)
@Order(1)优先于@Order(2)3,@Bean
- @Bean注解用于告诉方法,产生一个Bean对象,然后这个Bean对象交给Spring管理。随后这个Spring将会将这个Bean对象放在自己的IOC容器中。
- SpringIOC 容器管理一个或者多个bean,这些bean都需要在@Configuration注解下进行创建,在一个方法上使用@Bean注解就表明这个方法需要交给Spring进行管理。
4,@AutoWired
- 当我们在将一个类上标注@Service或者@Controller或@Component或@Repository注解之后,spring的组件扫描就会自动发现它,并且会将其初始化为spring应用上下文中的bean。 当需要使用这个bean的时候,例如加上@Autowired注解的时候,这个bean就会被创建。而且初始化是根据无参构造函数。
- @Autowired可以标注在属性上、方法上和构造器上,来完成自动装配。
5, @Nullable:对应的值可以为空,@NonNull:对应的值不可以为空
6,@ResponseBoby:是返回的数据以json的形式。
十四:Shrio-Redis基本使用
1,可以跨类之间的存值,可以将一个一个类中的
System.out.println(str3);传入到别的类中了
//需要存取值在类中先注入 @Autowired private RedisTemplate redisTemplate; //存入值 redisTemplate.opsForValue().set(key,value); //获取值 (String)redisTemplate.opsForValue().get(key);
十五,shiro什么时候会进入doGetAuthorizationInfo(PrincipalCollection principals)
会进入授权方法一共有三种情况!
1、subject.hasRole(“admin”) 或 subject.isPermitted(“admin”):自己去调用这个是否有什么角色或者是否有什么权限的时候;2、@RequiresRoles("admin") :在方法上加注解的时候;
3、[@shiro.hasPermission name = "admin"][/@shiro.hasPermission]:在页面上加shiro标签的时候,即进这个页面的时候扫描到有这个标签的时候。
十六,shiro-全局异常处理
1,为什么设置全局异常后,前端就能收到异常处理的返回信息?
是因为启动应用后,被 @ExceptionHandler、@InitBinder、@ModelAttribute 注解的方法,都会作用在 被 @RequestMapping 注解的方法上。
2,形式:@ExceptionHandler 配置的 value 指定需要拦截的异常类型,上面拦截了 Exception.class 这种异常。
如:
@ExceptionHandler(value = IllegalArgumentException.class)3,如果全部异常处理返回json,那么可以使用 @RestControllerAdvice 代替 @ControllerAdvice ,这样在方法上就可以不需要添加 @ResponseBody。
十七:shiro-控制翻转(IOC)
1,前提:Spring框架的核心是IoC容器和AOP模块。IoC容器是Spring框架的底层基础,负责管理对象的创建和对象依赖关系的维护。
2,什么是IOC:程序代码直接操控的对象的调用权交给容器,通过容器来实现对象组件的装配和管理,就是对组件对象控制权的转移,从程序代码本身转移到了外部容器
3,好处:
解耦,由容器去维护具体的对象
管理对象的创建和依赖关系的维护
4,核心问题;
- 谁控制谁,控制什么:由Ioc容器来控制对象的创建,主要控制了外部资源获取(不只是对象包括比如文件等)
- 为何是反转,哪些方面反转了:由容器帮我们查找及注入依赖对象,对象只是被动的接受依赖对象,所以是反转;依赖对象的获取被反转了
5,依赖注入(DI)
- 谁依赖于谁:当然是应用程序依赖于IoC容器;
- 为什么需要依赖:应用程序需要IoC容器来提供对象需要的外部资源;
- 谁注入谁:很明显是IoC容器注入应用程序某个对象,应用程序依赖的对象;
- 注入了什么:就是注入某个对象所需要的外部资源(包括对象、资源、常量数据)。
6,设计
- BeanFactory 简单粗暴,可以理解为就是个 HashMap,Key 是 BeanName,Value 是 Bean 实例。通常只提供注册(put),获取(get)这两个功能。我们可以称之为 “低级容器”
- ApplicationContext 可以称之为 “高级容器”。
- “低级容器” 和 “高级容器” 的关系,这里通过常用的 ClassPathXmlApplicationContext 类
7,总结
这里小结一下:IoC 在 Spring 里,只需要低级容器就可以实现,2 个步骤:
加载配置文件,解析成 BeanDefinition 放在 Map 里。
调用 getBean 的时候,从 BeanDefinition 所属的 Map 里,拿出 Class 对象进行实例化,同时,如果有依赖关系,将递归调用 getBean 方法 —— 完成依赖注入。
十八,shiro-Aop模块(面向切面编程)
1,前提:
- 面向切面编程的思想里面,把功能分为核心业务功能,和周边功能
- 所谓的核心业务,比如登陆,增加数据,删除数据都叫核心业务
- 所谓的周边功能,比如性能统计,日志,事务管理等等
- 周边功能在 Spring 的面向切面编程AOP思想里,即被定义为切面
- 在面向切面编程AOP的思想里面,核心业务功能和切面功能分别独立进行开发,然后把切面功能和核心业务功能 "编织" 在一起,这就叫AOP
2;目的:便于减少系统的重复代码,降低模块间的耦合度,并有利于未来的可拓展性和可维护性。
3,定义:
- 切入点(Pointcut)
在哪些类,哪些方法上切入(where)- 通知(Advice)
在方法执行的什么实际(when:方法前/方法后/方法前后)做什么(what:增强的功能)- 切面(Aspect)
切面 = 切入点 + 通知,通俗点就是:在什么时机,什么地方,做什么增强!- 织入(Weaving)
把切面加入到对象,并创建出代理对象的过程。(由 Spring 来完成)4,切点的主要形式有
- execution(* com.sample.service.impl..*.*(..)) ..表示当前包及子包
@Pointcut("execution(* com.ye.back.service.*.*(..))") @Pointcut("execution(* com.chen.demo.aspect.Landlord.service())")5,常用的类
- JoinPoint对象封装了SpringAop中切面方法的信息,在切面方法中添加JoinPoint参数,就可以获取到封装了该方法信息的JoinPoint对象.
getDeclaringTypeName():代理类的路径 getName()代理类名 getSignature:Signature 封装了被代理方法的全部信息(方法命、注解),以及该方法所属类的信息。- ProceedingJoinPoint 是继承了JoinPoint,
ProceedingJoinPoint 是专为环绕通知服务的,既然是环绕通知,那么调用被代理方法的动作就必须我们手动触发了,所以ProceedingJoinPoint 增加了两个新的方法:
- proceed():调用被代理方法(无参)
- proceed(Object[] var1):调用被代理方法(有参)
@Component @Aspect public class Broker { //实现方法一 // @Around("execution(* com.chen.demo.aspect.Landlord.service())") // public void around(ProceedingJoinPoint joinPoint) { // System.out.println("带租客看房"); // System.out.println("谈价格"); // // try { // joinPoint.proceed(); // } catch (Throwable throwable) { // throwable.printStackTrace(); // } // // System.out.println("交钥匙"); // } //实现方法二 @Pointcut("execution(* com.chen.demo.aspect.Landlord.service())") public void lService() { System.out.println("这里的内容是不显示的"); } @Before("lService()") public void before() { System.out.println("在业务逻辑之前实现"); } @After("lService()") public void after(){ System.out.println("业务逻辑之后实现"); } }
注意:在检验切面是否成功,不能简单的主函数测试,要使用@AutoWried的方式,再调用正式的业务逻辑事务
十九,shiro-注解与反射
1,注解:可以对程序进行解释或被其他程序读取
2,自定义形式:
@Target
(ElementType.METHOD)
//作用在方法上
@Retention
(RetentionPolicy.RUNTIME)
//运行时有效
@interface
Myannotation1{
//注解的参数:参数类型 + 参数名();
String Parameter1() ;
int
Parameter2()
default
-
1
;
//默认值为-1代表不存在
}
3,反射是java被视为动态语言的关键,并能直接操作任意对象的内部属性及方法
- 针对任何想动态加载、运行的类,唯有先获得相应的Class对象
- 一个加载的类在JVM中只会有一个Class实例
- Class对象只能由系统建立对象
Person person =
new
Person();
//方式一、通过包名获取
Class class1 = Class.forName(
"com.reflection.Person"
);
//方式二、通过对象获取
Class class2 = person.getClass();
//方式三、通过类获取
Class class3 = Person.
class
;
二十,shiro- 异常处理
1,思想:AOP面向切面编程,当发送请求http://localhost:8089/user/exception时,如果有异常了,那么返回的信息,就会传到前端去,不管什么情况都会将异常的信息发送至前端,会忽略请求的正常内容
2,
//全局异常处理 @RestControllerAdvice public class MyException { @ExceptionHandler(value = ArithmeticException.class) public Map<String,Object> handler(){ Map<String, Object> map = new HashMap<>(); map.put("msg","通过切面的异常算术异常"); map.put("state",false); return map; } } //请求处理逻辑 @RequestMapping(value = "/exception", method = RequestMethod.GET) public String testD(){ System.out.println("jsakhfk"); int a=45/0; return "ajfl"; }
说明
- 在出现异常之前可以正常逻辑处理System.out.println("jsakhfk");
- 只要有异常语句,那么就会以aop思想,直接去异常处理,返回到前端去
- 异常之后的语句就不会再执行了。
- 在springboot中前后端分离,后端进行请求拦截处理,而前端进行响应拦截处理
二十一:list结合的转变
情景1:list是对象的集合,从该集合中抽取某一个属性自成一个list集合 list.stream().collect(Collector.toList()) 情景2:将list集合转变中map list.stream().collect(Collector.toMap(list中对象::key,item->item.value)) 或者 list.stream().collect(Collector.toMap(item->item.key,item->item.value)) 情景3:将list集合进行过滤操作 list.stream().filter(retuen list对象需要满足的条件).collect(Collector.toList()) 情景4,将list集合转为map映射集合,对象发生了改变。与情景2是不同的,这依然是集合,而情景2是变成map list.stream().map(item->{return 指定对象}).collect(Collector.toList()).将list原来的对象集合转为新对象的集合
二十二,多对象的类的使用
方法一,将一个类的作为另一个类的属性 方法二,将一个类采用嵌套的思想使用static放进另一个类中。 这两个方法都可以将类放进另一个类中。
二十三,枚举类
说明:枚举就相当于是静态方法,可以直接通过类名.枚举名的形式进行调用。 情景一:一个枚举类中只要一个枚举,需要将枚举的参数,写成枚举类的属性。 情景二:使用一个普通类,将所有的枚举类都写在这个普通类中,然后再这个普通类写多个枚举类。 调用:类名.枚举名
二十四,nginx
1,nginx中主要的功能是,反向代理与动静分离(静态请求(照片,js,css),动态请求(业务需求)) 2,在nginx中在代理处需要加上proxy_set_header Host $host,解决请求头丢失问题。
二十五,使用json
情景1,使用json将json字符串转为复杂的对象 json.parseObject(string,new TypeReference<Map<String,List<类名>>>(){}) 情景2:普通的转为对象 json.parseObject(String,类名.class).
二十六,spring cache
情景1:工作流程:cacheautoconfiguration->当指定redis名时->rediscacheconfiguration->redisCacheManager->初始化缓存->根据Rediscacheconfiguration中的配置进行初始化。 优点:就是通过在方法头上注解的方式将方法的结果存入redis中,不需要手动加入,这样就能直接调用方法拿到缓存数据 总结:常规数据(读多写少,数据一致性要求不高) 情景2,使用缓存需要了解不同的注解功能 方式1;@cacheable(value="缓存分局",key="#root.mothodname",sync=true):建立缓存,并增加本地锁 方式2:@enableConfigurationproperty(cachepropert.class):使用配置文件中的信息 方式3:@cacheevict(value="缓存分局",key="'方法的名字'"): 删除缓存-失效模式;只能失效一个key 方式4:@caching(evict{方式3-1,,方式3-2});进行多个删除操作 方式5:@cacheevict(value="缓存分局",allentyies=true):将缓存分局中的缓存数据全部删除
二十七,链式调用
类似于 Json.toString().setData().setMenage();
二十八,异步
情景一:异步-thread:继承Thread类,重新run方法,使用start()方法启动 异步-runable;实现runable接口,重写run方法,将实现类放入thread()中,使用start()方法启动 异步-Callable:配合使用futuretask类,实现Callable接口,重写call方法后将实现类放入futuretask类中,再调用get()方法,得到call()中发返回结果。 异步-线程池:提交线程任务。 情景二:总结:(1)thread,runable没有返回值 (2)thread,runable,Callable中不能控制资源。 情景三-线程池 参数一:corepoolsize:核心线程数,当创建完成,一直存在,就算闲着也不释放 参数二:maxipoolsize:线程池找那个线程数量最大数量 参数三:keepalivetime:存活时间,线程空闲时间过长就会停止该线程,释放的是除了核心线程数 参数四:unit:时间单位 参数五:blockingqueue:阻塞队列,将任务放入对象中,当线程空闲就继续执行。 参数六:threadfactory:线程的创建工厂 参数七:handler:如果队列满了怎么办 情景四-工作流程 corepoolsize->满了->blockingqueue->满了->maxipoolsize开辟新的线程->max满了就使用决 绝策略->max没有满->在keepalivetime之后释放线程 情景五-实现过程 步骤一:executors.newFixedThreadPool:创建线程池 步骤二:completableFuture.runAsync/supplyAsync(具体业务,线程池):创建异步任务,get ()方法可以拿到返回值 注意:async特点是开启另一个线程,若没有则是用原来的线程 步骤三:handle,异步完成后可继续处理;whencomplete是监听异步完成。其中参数都是(结 果,异常) 步骤四:线程串行化方法 方式一:thenrunasync:不能接受上一个线程的结果 方式二:thenacceptasync:能接受上一个线程的结果,无返回结果 方式三:thenapplyasync:能接受上一个线程的结果,并且返回结果 步骤五:两任务组合 方式一:线程1.thenacceptbothasync(线程2,(线程1结果参数,线程2结果参数)->{},线程 池):可携带组合线程结果,无结果返回 方式二:thencombineasync,同理方式一,还可以处理结果并返回 步骤六:两任务完成之一 方式一:applytoether :两任务有一个完成,得到返回值,并处理任务有新的返回值 方式二:acceptether:两任务有一个完成,去返回值,并处理,但是没有新的返回值 方式三:runafterether:两任务有一个完成,不取返回结果,直接处理任务,没有新的返回值 步骤七:多任务组合 方式一:allof(线程1,线程2,。。。):所有的任务完成,在最后要使用get()方法,才能起 到阻塞的效果(就是等待全部完成) 方式二:anyod:只要有一个任务完成 情景六:实战 步骤一:在配置类进行设置,创建自定义threadpoolexecute类 步骤二:在自定义类中定义 分支1:在配置类中的自定类@Bean注解 分支2:可以使用@configurationProperty (prefix=“gulimall.thread”)类进行绑定,并添加 @compent那么产生 的结果就是在source文件夹下的property文件能够显示了。 分支三:在配置类中,直接引入绑定类作为参数,就可以使用 说明:就可以在配置文件中直接就能动态设置,其中@configurationproperty注解是关键 步骤三,异步编排使用completableFuture.supplyasunc()进行编排 步骤四:thenacceptasync-接收上一步的结果在继续执行 步骤五:要让所有的任务完成Completable.allof(任务一,任务二,。。。).get():阻塞等结果
二十九,消息中间件RabbitMQ
情景一:工作流程:生产者产生信息->broker信息代理的交换机->每个交换机都绑定多个队列,交换机根据 信息头中的路由键,将信息保存在队列中->消费者再从队列中去取信息 情景二:交换机的种类: 方式一:direct exchange:直接交换机->完全匹配模式 方式二:fanout exchange:不关心路由键,将信息都发送给队列,广播模式 方式三:topic exchange :发布订阅模式,与客户端连接 情景三:与springboot整合 步骤1:引入amqp依赖 步骤2:在properties文件中配置基本信息 步骤3:在主函数中加@enablerabbit注解,进行开启 步骤4:注入ampqadmin,进行创建交换机,队列等 步骤5:创建交换机:ampqadmin.declearexchange(new 交换机类); 步骤六:创建队列:ampqadmin.declearexchange(new Queue() ) 步骤七:创建绑定:ampqadmin.declearbinding(new binding()); 步骤八:发送信息:注入RabbitTemplate,rabbittemplate.convertandsend() 步骤九:监听信息;方法或者类上加注解@RabbitListener(queue={"队列的名字"}) 注意1:消息是一个一个接受的 注意2:@RabbitListener放到类上,@RabbitHandler可以重载区分不同的消息放到方法上进行组合使用, 步骤九:监听信息:在方法上加注解@RabbitHandler 情景四:信息确认机制-可抵达行 方式一:发送端 步骤一:配置文件 plusher.confirms=true 步骤2:服务端收到信息回调:rabbittempelate.setComfirmcallback(new rabbittemplate.comfirmcallback() {}) 方式二:信息抵达队列确认 步骤一:配置文件 plusher.returns=true;template.madatory=ture 步骤二:rabbittemplate.setreturncallback(new rabbittemplate.returncallback(){}) 注意;回调成功但不会打印,有错误才启用 方式二 消费端确认信息-ack机制 步骤一:在配置文件中simple.ackownledge-mode=manual(手动模式)-消息不会丢失 步骤二:channel.basicack(标签,是否批量处理)
三十:webmvc
情景一:添加拦截器 步骤一:实现 webmvcconfiguration接口 步骤二:重写 addInterceptors()方法 步骤三:编写自己的拦截器,要实现 HandlerInterceptors接口 步骤四:prehandler-前置拦截
三十一:事务使用seata 工作流程
问题一:@transactional事务注解,只能完成本地事务,进行回滚,却不能完成分布式事务回滚 问题二:分布式事务最大的问题是网络问题。 情景一:本地事务,就是连接一个数据库,一个服务 特征:acid 原子性,一致性,持久性,隔离性 隔离级别: 方式一:读未提交:产生脏数据;级别最小 方式二:读提交:只要提交了就可以读;orcal 方式三:可重复读:事务没有完成,读的数据都是一样的;mysql 方式四:序列化:事务都是串行顺序执行,没有并发能力了;级别最大 传播行为: required:公用一个事务,只要设置这个属性,其他属性不生效,都是由主事务决定一切, requires_new:不公用一个事务,与主事务没有关系,开启新的事务 注意:在springboot中,同一个对象内的事务方法互调默认失效,都是以required的形式; 原因:绕过了代理对象 解决:使用代理对象来调用 步骤1:添加start-aop依赖,目的是引入aspectj 步骤2:在主函数中添加@enableaspectjautoproxy(exposeproxy=true):开启动态代理 步骤3:新建aopcontext.currentproxy()类,方式再调用其他事务注解方法即可。其他事务方法中是注解 功能也能生效了 情景2:分布式事务 特点:cap定理 特点1:一致性,某一时刻的数据是一致的 特点2:可用性,集群整体是一起的,一起可用, 特点3:分区容错性:网络出现的错误; 总结:三者不可兼得,但是应该要满足特点3,然后选择特点1或者特点2。则在分布式中出现cp或者ap系统 情景3:base理论:强一致性,软一致行,最终一致性 情景4:解决方案,优选2 方式一:柔性事务-ttc补偿事务-最终一致性 方式二:柔性事务-可靠消息+最终一致性方案 方式三:柔性事务-最大努力通知 情景5:实战 步骤1:使用aliba中开源seata分布式事务 步骤2:每个微服务:创建 UNDO_LOG 表 步骤3:下载服务器软件包 步骤4:加入依赖,seata, 注意:此时应该打开依赖包,查看SeaTa的版本,然后开启软件包 步骤5:加入数据源 步骤6:在seata文件中将file,register文件复制到springboot中resource下 步骤7:在全局事务使用@GlobalTransaction 远程服务@Transaction 情景3:由于seata使用at模式,对高并发不友好 解决:使用可靠消息+最终一致方式+给队列设置过期时间 方式:使用消息队列,延时队列来起到定时的作用。 分支一:使用rabbitmp来实现定时任务。 问题:增加内存消耗,数据库压力,最主要是时间误差。 关键词:ttl:消息的过期时间;DLX:死信路由-收集死信 步骤1:在设置的配置类中,创建queue exchange,binding,使用@Bean在方法上就能就自动创建