目录
一.功能拓展与自定义
1、接口和基类
2、查找依赖关系
3、阅读源代码
二.库或框架的拓展
1、@Bean
2、SpringBoot使用拦截器
3、SpringBoot使用Servlet
4、Spring boot使用执行器
5、自定义oauth2的密码验证逻辑
6、SpringMVC初始化与配置
三.Java基础拓展
1、分类
2、继承(inheritance)
3、封装(encapsulation)
4、多态(polymorphism)
5、重写(Override)
6、重载(Overload)
7、向上转型和向下转型
8、接口与抽象类
公共mapper的写法
9、泛型
1.常用泛型通配符 T,E,K,V,?
2.Object和T区别
3.自定义泛型
4.无限通配符?
一.功能拓展与自定义
搞明白因为什么需要拓展和自定义,拓展什么功能
1、接口和基类
如何寻找库或框架对外拓展的接口和基类?
要了解Java库或框架可继承或拓展的配置类,接口或基类,你可以采取以下几种途径:
官方文档:检查相关中间件或框架的官方文档。官方文档通常提供了详细的信息,包括可继承的接口、抽象类和类。
在文档中查找“Extending”“Inheritance”、“Interfaces”等关键词可能会直接指向相关的信息。
源代码: 如果中间件或框架是开源的,你可以查看其源代码。通过阅读源代码,你可以深入了解内部的类层次结构,找到可以继承或拓展的接口或基类
示例和教程:检查中间件或框架的示例和教程。有时,它们会提供一些示例代码,展示如何使用和拓展框架。这些示例代码可能包含了继承或实现的关键接口和类
社区讨论: 查看相关社区论坛、问答网站或社交媒体上的讨论。其他开发者可能已经分享了关于如何扩展框架的经验和建议
查找设计文档:一些中大型框架可能有详细的设计文档,其中包括了框架的设计理念、架构和可扩展性的信息
总体而言,通过深入研究官方文档和源代码,你应该能够找到中间件或框架提供的可继承或拓展的接口或基类的信息
例如:
1.不管你愿意不愿意,都要到框架官网或spring官网上看下英文文档介绍,扒一扒内容,这是最省时高效的办法,
mongodb的官网: https://www.mongodb.com/
spring集成mongo文档:
https://docs.spring.io/spring-data/data-mongodb/docs/current/reference/html/#mongo.core
2.看框架结构与目录,查找依赖关系阅读源代码时,着重找过滤器,拦截器,配置类,一般配置类在诸如configuration的报下,带有XXXAdapter的字眼.
3.然后再在日志中可以大概的了解执行了哪些文件
2、查找依赖关系
- Idea中,接口下右键Diagrams->Show Diagram,快捷键Ctrl+Alt+Shift+U
3、阅读源代码
阅读源代码是软件开发中非常重要的技能之一。理解代码背后的逻辑和结构有助于提高编程能力、解决问题以及学习新的编程语言和框架。以下是一些阅读源代码的原因以及一些建议:
为什么要读源代码:
学习新技术和框架: 阅读源代码是学习新技术和框架的有效途径。通过直接查看源代码,你可以深入了解其实现方式、设计思想和最佳实践。
解决问题: 当你在使用某个库或框架时遇到问题,查看其源代码可以帮助你更好地理解问题的根本原因,并找到解决方案。
提高编程技能: 阅读高质量的源代码可以提高你的编程技能,帮助你更好地理解算法、数据结构和设计模式。
了解最佳实践: 通过阅读开源项目的源代码,你可以学到一些编码和设计的最佳实践,有助于提高代码质量。
参与开源社区: 阅读和理解开源项目的代码是参与开源社区的第一步。这有助于你理解项目的内部工作方式,为项目做出贡献。
其他:解决问题(BUG),知其所以然,学习,改造,借鉴,找工作
如何阅读源代码:
了解项目结构: 在开始阅读源代码之前,先了解项目的整体结构和组织。查看项目的文档、README 文件以及主要的目录结构。
选择关键部分: 不必阅读整个代码库,选择项目的关键部分进行深入阅读。这可以包括主要算法、核心功能或与你的兴趣相关的部分。
注重关键文件和函数: 阅读重要的文件和函数,这有助于你理解代码的核心逻辑。关注那些命名有意义、注释清晰的部分。
使用调试器: 在阅读代码的过程中,使用调试器来单步执行代码,观察变量的值和程序流程。这有助于更好地理解代码的执行过程。
查找依赖关系: 理解代码之间的依赖关系和调用关系是很重要的。查看函数和模块之间的关系,了解它们是如何协同工作的。
参考文档和注释: 阅读文档和源代码中的注释,这些信息对于理解代码的设计思想、用法和约定非常有帮助。
与社区互动: 如果你在阅读开源项目的过程中遇到问题,可以与项目的社区互动,提出问题或参与讨论。这有助于更深入地理解代码。
其他:先看文档,整体把握,理解代码组织,文件名,类名,关注一个问题,从问题追踪代码,
解决一个issue,调试,加注释,做笔记
阅读源代码需要时间和耐心,但这是一个非常有价值的学习过程。逐渐地,你会发现自己能够更快地理解和编写高质量的代码。
参看:为什么要读源代码,如何阅读源代码-CSDN博客
阅读源码的着重点
1.框架生命周期中提供的拓展点,事件等,比如sprngbean生命周期的@PostConstruct,mongodb事件onBeforeSave,生命周期中的拓展点一般为框架触发,即从初始化到销毁过程由框架管理,事件一般手动触发,即主动调用.
2.xx器-拦截器,过滤器,监听器,执行器,aop切面拓展,配置类的重写或实现等.
注意:框架的接口或类不管有多少个实现类,我们自定义的实现类如果想要被执行(框架触发或手动触发),需要被spring容器扫描并注册.扫描用@ComponentScan(包名)等,注册用@Configuration,@Bean等
二.库或框架的拓展
阅读重要的文件,函数,关键部分,加注释,做笔记
1、@Bean
在Spring框架中,@Bean注解用于告诉Spring容器,一个带有该注解的方法将产生一个由Spring容器所管理的Bean。通常,@Bean注解标注在方法上,该方法返回一个对象,这个对象将被Spring容器注册为一个Bean。以下是一些关于@Bean注解的重要信息:
基本用法: @Bean注解可以用在Java配置类的方法上,也可以用在XML配置文件中。在Java配置类中,@Bean标注的方法返回一个对象,这个对象将成为Spring应用上下文中的一个Bean。
@Configuration
public class AppConfig {@Beanpublic MyBean myBean() {return new MyBean();}}
指定Bean的名称: 默认情况下,Bean的名称将是方法名。你可以通过name属性指定Bean的名称。
@Bean(name = "customName")
public MyBean myBean() {return new MyBean();
}
指定初始化和销毁方法: 通过initMethod和destroyMethod属性,你可以指定Bean的初始化和销毁方法。
@Bean(initMethod = "init", destroyMethod = "cleanup")
public MyBean myBean() {return new MyBean();
}
作用域: 你可以使用@Scope注解指定Bean的作用域,例如singleton、prototype等。
@Bean
@Scope("prototype")
public MyBean myBean() {return new MyBean();}
懒加载: 使用@Lazy注解可以指定Bean是否应该被懒加载。
@Bean
@Lazy
public MyBean myBean() {return new MyBean();}
条件化装配: 通过@Conditional注解,你可以根据条件来决定是否创建某个Bean。
@Bean
@Conditional(MyCondition.class)
public MyBean myBean() {return new MyBean();}
@Bean注解是Spring框架中配置Bean的核心之一,它提供了丰富的配置选项,使得开发者可以灵活地定义Bean的创建和管理方式。在使用@Bean注解时,建议查阅官方文档以获取更详细的信息和选项。
配置token存储方式
@Configuration
public class AuthServerConfig extends AuthorizationServerConfigurerAdapter {/***TokenStore接口的实现类* InMemoryTokenStore* JdbcTokenStore* JwkTokenStore* JwtTokenStore* RedisTokenStore* @return*/@Beanpublic TokenStore tokenStore() {return new RedisTokenStore(redisConnection);//改造RedisTokenStore}@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception{endpoints.addInterceptor(customAuthPreInterceptor());endpoints.authenticationManager(authenticationManager).tokenStore(tokenStore())}
}
2、SpringBoot使用拦截器
1.自定义拦截器HandlerInterceptor
Spring提供了HandlerInterceptor(拦截器)。它的功能跟过滤器类似,但是提供更精细的的控制能力:在request被响应之前、request被响应之后、视图渲染之前以及request全部结束之后。我们不能通过拦截器修改request内容,但是可以通过抛出异常(或者返回false)来暂停request的执行。
spring boot提供了一些拦截器,我们可以直接拿来使用,比如:
ConversionServiceExposingInterceptorCorsInterceptorLocaleChangeInterceptorPathExposingHandlerInterceptorResourceUrlProviderExposingInterceptorThemeChangeInterceptorUriTemplateVariablesHandlerInterceptorUserRoleAuthorizationInterceptor
我们也可以自己定义拦截器,定义拦截器的步骤大致分为:
1.创建我们自己的拦截器类并实现 HandlerInterceptor 接口。
2.创建一个Java类继承WebMvcConfigurerAdapter抽象类(失效)或直接实现WebMvcConfigurer接口,并重写addInterceptors 方法。
3.实例化我们自定义的拦截器,然后将对像手动添加到拦截器链中(在addInterceptors方法中添加)。
定义拦截器
//MyInterceptor2
public class MyInterceptor1 implements HandlerInterceptor {@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)throws Exception {System.out.println("MyInterceptor1==========>在请求处理之前进行调用(Controller方法调用之前)");return true;// 只有返回true才会继续向下执行,返回false取消当前请求}@Overridepublic void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,ModelAndView modelAndView) throws Exception {System.out.println("MyInterceptor1==========>请求处理之后进行调用,但是在视图被渲染之前(Controller方法调用之后)");}@Overridepublic void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)throws Exception {System.out.println("MyInterceptor1==========>在整个请求结束之后被调用,也就是在DispatcherServlet 渲染了对应的视图之后执行(主要是用于进行资源清理工作)");}}//--------------------------------------------------------------------//MyInterceptor2
public class MyInterceptor2 implements HandlerInterceptor {@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)throws Exception {System.out.println("MyInterceptor2==========>在请求处理之前进行调用(Controller方法调用之前)");return true;// 只有返回true才会继续向下执行,返回false取消当前请求}@Overridepublic void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,ModelAndView modelAndView) throws Exception {System.out.println("MyInterceptor2==========>请求处理之后进行调用,但是在视图被渲染之前(Controller方法调用之后)");}@Overridepublic void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)throws Exception {System.out.println("MyInterceptor2==========>在整个请求结束之后被调用,也就是在DispatcherServlet 渲染了对应的视图之后执行(主要是用于进行资源清理工作)");}}
创建我们自己的拦截器
添加拦截器
创建一个Java类继承抽象类或者直接实WebMvcConfigurer接口,并重写addInterceptors 方法,添加拦截器。WebMvcConfigurerAdapter
//MyWebAppConfigurer
@Configuration
public class MyWebAppConfigurer extends WebMvcConfigurerAdapter {@Overridepublic void addInterceptors(InterceptorRegistry registry) {// 多个拦截器组成一个拦截器链// addPathPatterns 用于添加拦截规则// excludePathPatterns 用户排除拦截registry.addInterceptor(new MyInterceptor1()).addPathPatterns("/**");registry.addInterceptor(new MyInterceptor2()).addPathPatterns("/**");super.addInterceptors(registry);}}
测试:运行项目,随便访问一个路径,比如http://localhost:8080/test
值得注意的是:只有经过DispatcherServlet 的请求,才会走拦截器链,我们自定义的Servlet 请求是不会被拦截的,比如我们自定义的Servlet地址是不会被拦截器拦截的。
但是过滤器不同。不管是属于哪个Servlet 只要符合过滤器的过滤规则,过滤器都会拦截。
参考:https://www.cnblogs.com/javaxiaoxin/p/8275348.html
继承WebMvcConfigurerAdapter抽象类添加拦截器已经过时,WebMvcConfigurerAdapter实现了WebMvcConfigurer接口
/*** An implementation of {@link WebMvcConfigurer} with empty methods allowing* subclasses to override only the methods they're interested in.** @author Rossen Stoyanchev* @since 3.1* @deprecated as of 5.0 {@link WebMvcConfigurer} has default methods (made* possible by a Java 8 baseline) and can be implemented directly without the* need for this adapter*/
@Deprecated
public abstract class WebMvcConfigurerAdapter implements WebMvcConfigurer {...}
查看WebMvcConfigurationAdapter源码,Doc规范告诉我们过时类都会在源码注释中给出推荐使用的替代类。意思就是Java8给出了新的特性,使得接口方法允许拥有默认实现。所以你现在可以直接实现WebMvcConfigurer而不用像以前那样通过继承它的实现类来达到目的。
不过新的疑问又出来了,为什么Java8要弄个接口默认方法呢,这有什么特别的地方吗?这个倒是很容易想明白。进入WebMvcConfigurer类查看源码,可以发现其中定义了大量的方法。与WebMvcConfigurerAdapter进行对比可以发现,虽然WebMvcConfigurerAdapter实现了WebMvcConfigurer接口,但是大量的实现都是空实现啊。
...
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {}/*** {@inheritDoc}* <p>This implementation is empty.*/
@Override
public void addCorsMappings(CorsRegistry registry) {}/*** {@inheritDoc}* <p>This implementation is empty.*/
@Override
public void addViewControllers(ViewControllerRegistry registry) {}/*** {@inheritDoc}* <p>This implementation is empty.*/
@Override
public void configureViewResolvers(ViewResolverRegistry registry) {}...
做成这样是因为Java的单继承多实现规则,一个类可能同时需要WebMvcConfigurer和其他类中的方法,用继承的方式限制了这一点。而WebMvcConfigurer中的方法也不是在每个地方都会用到,所以写了一些特定场合的适配,这样就可以按需继承对应的适配器,而自己的定制实现通过多态性对外展示为WebMvcConfigurer,使框架能够降低耦合度。
但对于追求完美的编程人员,这显然无法令人满意。于是出现了带有默认实现的接口,这样在使用的时候只需要实现自己想要的方法就行了,不用再去手动空实现或编写适配器。果然懒是第一创造力。例如只实现addInterceptors接口
@Component
public class MyWebMvcConfigurerAdapter implements WebMvcConfigurer {@Overridepublic void addInterceptors(InterceptorRegistry registry) {registry.addInterceptor(new CustomInterceptor());}
}
WebMvcConfigurer 有很多的接口方法,但是我们只需要实现自己想要的接口就行
public interface WebMvcConfigurer {/*** Helps with configuring HandlerMappings path matching options such as trailing slash match,* suffix registration, path matcher and path helper.* Configured path matcher and path helper instances are shared for:* <ul>* <li>RequestMappings</li>* <li>ViewControllerMappings</li>* <li>ResourcesMappings</li>* </ul>* @since 4.0.3*/default void configurePathMatch(PathMatchConfigurer configurer) {}/*** Configure content negotiation options.*/default void configureContentNegotiation(ContentNegotiationConfigurer configurer) {}/*** Configure asynchronous request handling options.*/default void configureAsyncSupport(AsyncSupportConfigurer configurer) {}.....
参考:
WebMvcConfigurationSupport、WebMvcConfigurerAdapter、WebMvcConfigurer_webmvcconfigurationsupport 配置 objectmapper-CSDN博客
3、SpringBoot使用Servlet
spring boot 利用Controller响应数据与响应页面。一般的Web开发使用 Controller 基本上可以完成大部分需求,但是有的时候我们还是会用到 Servlet、Filter、Listener 等等。
普通 java web 应用中 servlet 是在 web.xml 中配置,但 springBoot 总并不会读取 web.xml,所以所需要的 servlet、listener、filter 都需要单独在 springBoot 中单独配置。
在spring boot中添加自己的Servlet、Filter、Listener有两种方法
代码注册:
通过ServletRegistrationBean、 FilterRegistrationBean 和ServletListenerRegistrationBean 获得控制。
注解注册:
在SpringBootApplication 上使用@ServletComponentScan注解后,Servlet、Filter、Listener 可以直接通过 @WebServlet、@WebFilter、@WebListener 注解自动注册,无需其他代码。
1.代码注册
创建Servlet类:AaServlet.java。
public class AaServlet extends HttpServlet {@Overrideprotected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {System.out.println("doGet");doPost(req, resp);}@Overrideprotected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {System.out.println("doPost()");resp.setContentType("text/html");PrintWriter out = resp.getWriter();out.println("<h1>AaServlet</h1>");}
}
通过ServletRegistrationBean注册。
//Project2Application.java
@Bean
public ServletRegistrationBean AaServletRegistration() {ServletRegistrationBean registration = new ServletRegistrationBean(new AaServlet());registration.addUrlMappings("/a");return registration;
}
运行测试。
访问:http://localhost:8080/a
2.注解注册
创建Servlet类:BbServlet.java。
@WebServlet(urlPatterns = "/b")
public class BbServlet extends HttpServlet {@Overrideprotected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {System.out.println("doGet");doPost(req, resp);}@Overrideprotected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {System.out.println("doPost()");resp.setContentType("text/html");PrintWriter out = resp.getWriter();out.println("<h1>BbServlet</h1>");}
}
添加注解@ServletComponentScan。
@ServletComponentScan
@SpringBootApplication
public class Project2Application {public static void main(String[] args) {SpringApplication.run(Project2Application.class, args);}// @Bean// public ServletRegistrationBean AaServletRegistration() {// ServletRegistrationBean registration = new ServletRegistrationBean(new AaServlet());// registration.addUrlMappings("/a");// return registration;// }}
运行测试。
访问:http://localhost:8080/b
filter 和 listener的注册和servlet一样。这里就不多说了。
参考:https://www.cnblogs.com/javaxiaoxin/p/8275348.html
过滤器和拦截器的区别:
Spring 的拦截器与 servlet 的 Filter 有相似之处,都能实现权限检查、日志记录等。不同的是:
1.使用范围不同: Filter 是 Servlet 规范规定的,只能用于 Web 程序中。而拦截器既可以用于 web 程序,也可以用于 Application 、 Swing 程序中。
2.规范不同: Filter 是在 Servlet 规范中定义的,是 Servlet 容器支持的。而拦截器是在 Spring 容器内的,是 Spring 框架支持的。
3.使用的资源不同:同其他的代码块一样,拦截器也是一个 Spring 的组件,归 Spring 管理,配置在 Spring 文件中,因此能使用 Spring 里的任何资源、对象,例如 Service 对象、数据源、事务管理等,通过 IOC 注入到拦截器即可:而 Filter 则不能。
4.深度不同: Filter 在只在 Servlet 前后起作用。而拦截器能够深入到方法前后、异常抛出前后等,因此拦截器的使用具有更大的弹性.所以在 Spring 构架的程序中,要优先使用拦截器。
其他:
拦截器是一种面向方面/切面编程(AOP Aspect-Oriented Programming)
执行顺序:
SpringMVC的机制是由同一个Servlet来分发请求给不同的Controller,其实这一步是在Servlet的service()方法中执行的。所以过滤器、拦截器、service()方法,dispatc()方法的执行顺序应该是这样的,大致画了个图:其实非常好测试,自己写一个过滤器,一个拦截器,然后在这些方法中都加个断点,一路F8下去就得出了结论。
写了点测试代码,顺便整理一下思路,搞清楚这几者之间的顺序:
1.过滤器是JavaEE标准,采用函数回调的方式进行。是在请求进入容器之后,还未进入Servlet之前进行预处理,并且在请求结束返回给前端这之间进行后期处理。
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {System.out.println("before...");chain.doFilter(request, response);System.out.println("after...");
}
chain.doFilter(request, response);这个方法的调用作为分水岭。事实上调用Servlet的doService()方法是在chain.doFilter(request, response);这个方法中进行的。
2.拦截器是被包裹在过滤器之中的。
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {System.out.println("preHandle");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");
}
a.preHandle()这个方法是在过滤器的chain.doFilter(request, response)方法的前一步执行,也就是在 [System.out.println("before...")]与[chain.doFilter(request, response)]之间执行。
b.preHandle()方法之后,在return ModelAndView之前进行,可以操控Controller的ModelAndView内容。
c.afterCompletion()方法是在过滤器返回给前端前一步执行,也就是在[chain.doFilter(request, response)]与[System.out.println("after...")]之间执行。
总结:
拦截器功在对请求权限鉴定方面确实很有用处,第三方的远程调用每个请求都需要参与鉴定,所以这样做非常方便,而且他是很独立的逻辑,这样做让业务逻辑代码很干净。和框架的其他功能一样,原理很简单,使用起来也很简单。
我们项目中仅仅用到了preHandle这个方法,而未用其他的,框架提供了一个已经实现了拦截器接口的适配器类HandlerInterceptorAdapter,继承这个类然后重写一下需要用到的方法就行了,可以少几行代码,这种方式Java中很多地方都有体现。
4、Spring boot使用执行器
Spring boot的CommandLineRunner接口主要用于应用启动后,去执行一段代码块逻辑,这段初始化代码在整个应用生命周期内只会执行一次,比如初始化线程池,提前加载好加密证书等.
SpringBoot提供的一种简单的实现方案就是添加一个实体类并实现CommandLineRunner接口,实现功能的代码放在实现的run方法中.
SpringBoot在应用启动后会遍历所有实现CommandLineRunner的实体类并执行run方法,我们可以在run()方法里使用任何依赖,因为它们已经初始化好了, 如果有多个类实现CommandLineRunner接口,如何保证顺序?如果需要按照一定的顺序去执行,那么就需要在实体类上使用一个@Order注解来表明顺序,@Order 注解的执行优先级是按value值从小到大顺序.
@Component
@Order(1)
public class MyStartupRunner implements CommandLineRunner {@Overridepublic void run(String... args) throws Exception {System.out.println(">>>>>>>>>>>>>>>服务启动执行,执行加载数据等操作 MyStartupRunner order <<<<<<<<<<<<<");}
}
参考:
https://www.cnblogs.com/chenpi/p/9696310.html
https://www.cnblogs.com/myblogs-miller/p/9046425.html
5、自定义oauth2的密码验证逻辑
继承:WebSecurityConfigurerAdapter
@Configuration
public class WebSecurityConfigurerAdapterExt extends WebSecurityConfigurerAdapter {protected void configure(AuthenticationManagerBuilder auth) throws Exception {auth.authenticationProvider(new LoginAuthenticationProvider(loginService));//注册配置类并加入自己的代码}}@Component
public class LoginAuthenticationProvider extends DaoAuthenticationProvider {//有参构造方法,让自己重写的类注入框架必需的类,构造方法public LoginAuthenticationProvider(UserDetailsService userDetailsService) {super();// 这个地方一定要对userDetailsService赋值,不然userDetailsService是null (这个坑有点深)setUserDetailsService(userDetailsService);setPasswordEncoder(createDelegatingPasswordEncoder());
}//重写父配置类的方法,加入自己的逻辑
protected void additionalAuthenticationChecks(UserDetails userDetails, UsernamePasswordAuthenticationToken authentication) throws AuthenticationException {if (authentication.getCredentials() == null) {throw new BadCredentialsException(messages.getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"));}String presentedPassword = authentication.getCredentials().toString();PasswordEncoder passwordEncoder = getPasswordEncoder();System.out.println(userDetails.getPassword());System.out.println(passwordEncoder.matches(presentedPassword, userDetails.getPassword()));if ("654321".equals(authentication.getCredentials().toString())) {} else {if (!passwordEncoder.matches(presentedPassword, userDetails.getPassword())) {logger.debug("Authentication failed: password does not match stored value");throw new BadCredentialsException(messages.getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials","Bad credentials"));}}
}public static PasswordEncoder createDelegatingPasswordEncoder() {return new BCryptPasswordEncoder(12);//和系统保持一致的加密算法}}
为什么会想到继承DaoAuthenticationProvider,因为代码一步步的跟踪到源码这块,看到的密码对比逻辑,然后在网上搜索DaoAuthenticationProvider,继而找到下面这篇文章
Security 自定义DaoAuthenticationProvider 实现手动验证_security.authentication.dao.daoauthenticationprovi-CSDN博客
6、SpringMVC初始化与配置
SpringMVC 容器的生命周期(9 个阶段)
阶段 1:Servlet 容器初始化
阶段 2:创建父容器
阶段 3:创建 springmvc 容器
阶段 4:Servlet 容器中注册 DispatcherServlet
阶段 5:启动父容器:ContextLoaderListener
阶段 6:启动 springmvc 容器:DispatcherServlet#init()
阶段 7:springmvc 容器启动过程中处理@WebMVC
阶段 8:组装 DispatcherServlet 中各种 SpringMVC 需要的组件
阶段 9:销毁 2 个容器
初始化:AbstractAnnotationConfigDispatcherServletInitializer
代码如下,这个类需要继承 AbstractAnnotationConfigDispatcherServletInitializer,会有 web 容器来调用,这个类中有 4 个方法需要实现,干了 4 件事情
getRootConfigClasses():获取父容器的配置类
getServletConfigClasses():获取 springmvc 容器的配置类,这个配置类相当于 springmvc xml 配置文件的功能
getServletMappings():获取 DispatcherServlet 能够处理的 url,相当于 web.xml 华国 servlet 指定的 url-pattern
getServletFilters():定义所有的 Filter
web.xml替代:WebApplicationInitializer
从起初的Spring配置文件,到后来的Spring支持注解到后来的SpringBoot,Spring框架在一步步的使用注解的方式来去除Spring的配置的发展过程。WebApplicationInitializer就是取代web.xml配置的一个接口。
配置:WebMvcConfigurer
替代废弃的WebMvcConfigurerAdapter
WebMvcConfigurer配置类其实是Spring内部的一种配置方式,采用JavaBean的形式来代替传统的xml配置文件形式进行针对框架个性化定制,可以自定义一些Handler,Interceptor,ViewResolver,MessageConverter。基于java-based方式的spring mvc配置,需要创建一个配置类并实现WebMvcConfigurer 接口;
在Spring Boot 1.5版本都是靠重写WebMvcConfigurerAdapter的方法来添加自定义拦截器,消息转换器等。SpringBoot 2.0 后,该类被标记为@Deprecated(弃用)。官方推荐直接实现WebMvcConfigurer或者直接继承WebMvcConfigurationSupport,方式一实现WebMvcConfigurer接口(推荐),方式二继承WebMvcConfigurationSupport类,具体实现可看这篇文章。继承WebMvcConfigurationSupport后自动配置不生效的问题及如何配置拦截器
下面是一些WebMvcConfigurer接口中常用的方法:
addViewControllers:用于注册简单的视图控制器。
addInterceptors:用于注册拦截器,可以在请求处理之前或之后执行一些逻辑。
addResourceHandlers:用于注册静态资源处理器,可以将静态资源映射到指定的URL路径。
configureViewResolvers:用于配置视图解析器,可以将逻辑视图名称解析为实际的视图。
configureContentNegotiation:用于配置内容协商策略,可以根据请求头中的Accept字段来返回不同的响应格式。
configureDefaultServletHandling:用于配置静态资源的处理方式,可以将请求转发给默认的Servlet。
addArgumentResolvers:用于注册自定义的方法参数解析器,可以将请求参数解析为控制器方法的参数。
addReturnValueHandlers:用于注册自定义的返回值处理器,可以将控制器方法的返回值转换为响应体。
总之,WebMvcConfigurer接口提供了很多方法来定制Spring MVC的行为,可以满足不同的需求。
SpringMVC初始化与配置简介-CSDN博客
三.Java基础拓展
继承,封装,多态,泛型是java的基础,一切拓展基于基础
1、分类
我们可以把JAVA中的类分为以下三种:
类:使用class定义且不含有抽象方法的类。
抽象类:使用abstract class定义的类,它可以含有,也可以不含有抽象方法。
接口:使用interface定义的类。
继承与实现的区别
在这三种类型之间存在下面的继承规律:
1.类可以继承(extends)类,可以继承(extends)抽象类,可以继承(implements)接口。
2.抽象类可以继承(extends)类,可以继承(extends)抽象类,可以继承(implements)接口。
3.接口只能继承(extends)接口。
英文含义
Extends:继承,扩充,延伸
Implements:实现,执行,落实,使生效
类继承extends类,实现implements接口
接口继承extends接口
总结:
- 类可以implements 多个接口,只extends单个类,接口可以extends多个接口,但不能implements 接口,接口extends接口时可以不实现父接口中的方法,可以声明自己的新方法
- 抽象类实现接口,可以不实现接口中的抽象方法,但是谁extends了这个抽象类,不但要实现抽象类的抽象方法,还要实现接口的抽象方法.
- 类实现接口时,一定要实现接口中声明的方法,如果接口中没有定义抽象方法则不需要,但是要注意,类实现了一个接口A,如果B是A的父接口,且B中有抽象方法,则该类必须实现A和B中的所有抽象方法;
2、继承(inheritance)
继承是使用已存在的类的定义作为基础建立新类的技术,新类的定义可以增加新的数据或新的功能,也可以用父类的功能,但不能选择性地继承父类。这种技术使得复用以前的代码非常容易,能够大大缩短开发周期,降低开发费用。
继承是为了重用父类代码,同时为实现多态性作准备。
构造方法在实例化的时候调用的,而子类既然继承了父类,那就具备了父类所有的属性以及方法,当子类实例化的时候就先调用父类的构造了,如果子类的构造方法中没有通过super显式调用父类的有参构造方法,也没有通过this显式调用自身的其它构造方法,则会默认先调用父类的无参构造方法。
你想那么子类中从父类继承的字段,要谁来初始化呢?
父类中有这些字段的初始化方式,所以最好的选择就是用父类的构造方法。
java创建对象的三个步骤就是,申请内存,调用构造方法,返回对象引用。
3、封装(encapsulation)
类使得数据和对数据的操作集成在一起,从而对使用该类的其他人来说,可以不管它的实现方法,而只管用它的功能,从而实现所谓的信息隐藏。 封装隐藏了类的内部实现机制,从而可以在不影响使用者的前提下改变类的内部结构,同时保护了数据。
4、多态(polymorphism)
方法的重写、重载与动态连接构成多态性。Java之所以引入多态的概念
对于多态,可以总结它为:
一、使用父类类型的引用指向子类的对象;该引用只能调用父类中定义的方法和变量;
二、如果子类中重写了父类中的一个方法,那么在调用这个方法的时候,将会调用子类中的这个方法;(动态连接、动态调用)
三、变量不能被重写(覆盖),”重写“的概念只针对方法。
5、重写(Override)
英文名是overriding,是指在继承情况下,子类中定义了与其基类中方法具有相同型构的新方法,就叫做子类把基类的方法重写了。这是实现多态必须的步骤。
重写是子类对父类的允许访问的方法的实现过程进行重新编写, 返回值和形参都不能改变。即外壳不变,核心重写!
重写的好处在于子类可以根据需要,定义特定于自己的行为。 也就是说子类能够根据需要实现父类的方法。
重写方法不能抛出新的检查异常或者比被重写方法申明更加宽泛的异常。例如: 父类的一个方法申明了一个检查异常 IOException,但是在重写这个方法的时候不能抛出 Exception 异常,因为 Exception 是 IOException 的父类,只能抛出 IOException 的子类异常。
在面向对象原则里,重写意味着可以重写任何现有方法。
方法的重写规则
参数列表必须完全与被重写方法的相同。
返回类型与被重写方法的返回类型可以不相同,但是必须是父类返回值的派生类(java5 及更早版本返回类型要一样,java7 及更高版本可以不同)。
访问权限不能比父类中被重写的方法的访问权限更低。例如:如果父类的一个方法被声明为 public,那么在子类中重写该方法就不能声明为 protected。
父类的成员方法只能被它的子类重写。
声明为 final 的方法不能被重写。
声明为 static 的方法不能被重写,但是能够被再次声明。
子类和父类在同一个包中,那么子类可以重写父类所有方法,除了声明为 private 和 final 的方法。
子类和父类不在同一个包中,那么子类只能够重写父类的声明为 public 和 protected 的非 final 方法。
重写的方法能够抛出任何非强制异常,无论被重写的方法是否抛出异常。但是,重写的方法不能抛出新的强制性异常,或者比被重写方法声明的更广泛的强制性异常,反之则可以。
构造方法不能被重写。
如果不能继承一个方法,则不能重写这个方法。
6、重载(Overload)
英文名是overloading,是指在同一个类中定义了一个以上具有相同名称,但是型构不同的方法。在同一个类中,是不允许定义多于一个的具有相同型构的方法的。
重载(overloading) 是在一个类里面,方法名字相同,而参数不同。返回类型可以相同也可以不同。
每个重载的方法(或者构造函数)都必须有一个独一无二的参数类型列表。
最常用的地方就是构造器的重载。
重载规则:
被重载的方法必须改变参数列表(参数个数或类型不一样);
被重载的方法可以改变返回类型;
被重载的方法可以改变访问修饰符;
被重载的方法可以声明新的或更广的检查异常;
方法能够在同一个类中或者在一个子类中被重载。
无法以返回值类型作为重载函数的区分标准。
总结
方法的重写(Overriding)和重载(Overloading)是java多态性的不同表现,重写是父类与子类之间多态性的一种表现,重载可以理解成多态的具体表现形式。
(1)方法重载是一个类中定义了多个方法名相同,而他们的参数的数量不同或数量相同而类型和次序不同,则称为方法的重载(Overloading)。
(2)方法重写是在子类存在方法与父类的方法的名字相同,而且参数的个数与类型一样,返回值也一样的方法,就称为重写(Overriding)。
(3)方法重载是一个类的多态性表现,而方法重写是子类与父类的一种多态性表现。
7、向上转型和向下转型
要转型,首先要有继承。继承是面向对象语言中一个代码复用的机制,简单说就是子类继承了父类中的非私有属性和可以继承的方法,然后子类可以继续扩展自己的属性及方法。
向上转型:子类对象转为父类,父类可以是接口。公式:Father f = new Son();Father是父类或接口,son是子类。
向下转型:父类对象转为子类。公式:Son s = (Son)f;
package com.cgfy.product.mongodb.controller;
public class Human {public void sleep() {System.out.println("Human sleep..");}public static void main(String[] args) {Human h = new Male();// 向上转型h.sleep();//调用子类的sleep方法// h.speak();//此方法不能编译,报错说Human类没有此方法,需要向下转型,否则不能调用speak方法。Human h1 = new Human();Male m = (Male) h;//向下转型m.speak();//Male m1 = (Male)h1;//m1.speak(); //此时会出现运行时错误,所以可以用instanceOF判断if (h1 instanceof Male){Male m1 = (Male)h1;m1.speak();}}
}class Male extends Human {@Overridepublic void sleep() {System.out.println("Male sleep..");}public void speak() {System.out.println("I am Male");}
}class Female extends Human {@Overridepublic void sleep() {System.out.println("Female sleep..");}public void speak() {System.out.println("I am Female");}
}
1.把子类对象直接赋给父类引用叫upcasting向上转型,向上转型不用强制转型。
如Father father = new Son();
2、把指向子类对象的父类引用赋给子类引用叫向下转型(downcasting),要强制转型,要向下转型,必须先向上转型为了安全可以用instanceof判断。
如father就是一个指向子类对象的父类引用,把father赋给子类引用son 即Son son =(Son)father;其中father前面的(Son)必须添加,进行强制转换。
3、upcasting 会丢失子类特有的方法,但是子类overriding 父类的方法,子类方法有效,向上转型只能引用父类对象的属性,要引用子类对象属性,则要写getter函数。
8、接口与抽象类
abstract class和interface是Java语言中对于抽象类定义进行支持的两种机制。
在abstract class方式中, 可以有自己的数据成员,也可以有非abstarct的成员方法,而在interface方式的实现中, 只能够有静态的不能被修改的数据成员(也就是必须是static final的,不过在interface中一般不定义数据成员),所有的成员方法都是abstract的。从某种意义上说,interface是一种特殊形式的abstract class。
抽象类里面可以有非抽象方法,但接口里只能有抽象方法, 声明方法的存在而不去实现它的类被叫做抽象类(abstract class),它用于要创建一个体现某些基本行为的类,并为该类声明方法,但不能在该类中实现该类的情况。不能创建abstract 类的实例。接口(interface)是抽象类的变体。
在接口中,所有方法都是抽象的。多继承性可通过实现这样的接口而获得。接口中的所有方法都是抽象的(abstract),没有一个有程序体。接口只可以定义static final成员变量,instanceof 运算符可以用来决定某对象的类是否实现了接口。
java抽象类的构造方法和普通类的构造方法一样,都是用来初始化类,只是不能直接创建抽象类的实例对象而已。
在继承了抽象类的子类中通过super(参数列表)调用抽象类中的构造方法,可以这么理解吧 抽象类就是一个不能实例化的类
不过如果方法加了abstract,那么就必须在子类里面重写,因为父类中的抽象方法没有实现,除非子类也是一个抽象类!
公共mapper的写法
接口定义方法,抽象类实现接口,实现的过程中定义属性,抽象方法或非抽象方法,如果业务拓展,就继承这个抽象类,这个继承者可以是一个抽象类或普通类
public abstract class CommonService<T> implements Base {@Autowiredprotected BaseMapper<T> mapper;//构造公共mapperpublic CommonService() {this.mapper = getMapper();}//获取详情public T getEntity(Serializable id) {return null != id ? mapper.getEntity(id) : null;}//删除public int delete(Serializable id) {return mapper.delete(id);}//更新public int update(T dto) {return mapper.update(dto);}//新增public Serializable save(T dto) {UUIDHexGenerator.setObjectId(init(dto));int result = mapper.save(dto);return result;}//获取个数public long getCount(T dto) {return mapper.getCount(dto);}/*** 如果有初始化实体需求,重写此方法*/protected T init(T dto) {return dto;}// 在子类中设置私有mapper,子类需要事先此接口,返回子类mapperprotected abstract BaseMapper<T> getMapper();
}
9、泛型
泛型,即“参数化类型”,就是将类型由原来的具体的类型参数化,类似于方法中的变量参数,此时类型也定义成参数形式(可以称之为类型形参),然后在使用/调用时传入具体的类型(类型实参)。
二、泛型的好处
①将运行时出现的错误提前到了编译时
②避免了类型强转的麻烦
接口、类和方法也都可以使用泛型自定义,以及相应的使用,在具体使用时,可以分为泛型接口、泛型类和泛型方法。
我们在定义泛型类,泛型方法,泛型接口的时候经常会碰见很多不同的通配符,比如 T,E,K,V 等等,这些通配符又都是什么意思呢?
1.常用泛型通配符 T,E,K,V,?
本质上这些个都是通配符,没啥区别,只不过是编码时的一种约定俗成的东西。比如代码中的 T ,我们可以换成 A-Z 之间的任何一个 字母都可以,将T换成了A,在执行效果上是没有任何区别的,只不过我们约定好了T代表type,所以还是按照约定规范来比较好,增加了代码的可读性。并不会影响程序的正常运行,通常情况下,T,E,K,V,? 是这样约定的:
- ? - 表示不确定的java类型,是类型通配符,代表所有类型。?不会进行类型推断
- T:Type表示java类型,T代表在调用时的指定类型。会进行类型推断
- K V :分别代表java键值中的Key Value。
- E : Element ,E是对各方法中的泛型类型进行限制,以保证同一个对象调用不同的方法时,操作的类型必定是相同的。E可以用其它任意字母代替
2.Object和T区别
Object和T不同点在于
Object是一个实打实的类,并没有泛指谁,而T可以泛指Object,比如public void printList(List<T> list){}方法中可以传入List<Object> list类型参数,也可以传入List<String> list类型参数.
但是public void printList(List<Object> list){}就只可以传入List<Object> list类型参数,因为Object类型并没有泛指谁,是一个确定的类型
3.自定义泛型
自定义泛型类
① 定义格式: class 类名<自定义泛型>{ }
② 如果静态的方法需要使用自定义泛型,那么需要在方法上自己声明使用。
③ 泛型类注意的事项:
a.在类上自定义的泛型的具体数据类型是在创建对象的时候指定的。
b.在类上自定义了泛型,如果创建该类的对象时没有指定泛型的具体类型,那么默认是Object类型。
//自定义类上的泛型
public class Demo1<T> {//自定义带泛型的方法 public <T>T funtion(T t) {return null;}public <T,E,K>void b(T t,E e,K k) {}//静态方法泛型定义在static后 public static<T> void c(T t) {}
}//多个泛型参数
//如果要定义超过两个,三个或三个以上的泛型参数可以使用T1, T2, ..., Tn
public class Test<T1,T2,T3> {public void print(T1 t1,T2 t2,T3 t3){System.out.println(t1.getClass());System.out.println(t2.getClass());System.out.println(t3.getClass());}
}//案例1
public interface BaseDao<T>{
public T getById(Integer id);
}public class BaseDaoImpl<T> implements BaseDao<T>{
@Override
public T getById(Integer id){
T t = (T) this.getSession().get(getEntityClass(), id);
return t;
}//范类转换,转换成对应的对象
protected Class<T> getEntityClass() {
return (Class<T>) ((ParameterizedType) getClass().getGenericSuperclass()).getActualTypeArguments()[0];
}//根据实体获取类名
protected String getEntityName() {
return getEntityClass().getSimpleName();
}
}public interface DeviceDao<T> extends BaseDao<T>{…}
public class DeviceDaoImpl extends BaseDaoImpl<DeviceBean> implements DeviceDao<DeviceBean> {…}//案例2
public interface BaseMapper<T> extends Mapper<T>, MySqlMapper<T> {}@Mapper
public interface StaffInfoMapper extends BaseMapper<StaffInfo> {}
//案例3
//通过泛型获取实体类
public BaseModel getFormsInstance() {BaseModel model = null;try {Class<T> classT = (Class<T>) ((ParameterizedType) getClass().getGenericSuperclass()).getActualTypeArguments()[0];model = (BaseModel) classT.newInstance();} catch (Exception e) {e.printStackTrace();}return model;
}//如果是接口,参考ApplicationContext的public static <T> T getBean(Class<T> clazz)方法
自定义泛型接口
① 定义格式: interface 接口名<声明自定义泛型>{ }
② 延长接口自定义泛型的具体数据类型到类: class 类名<T> implements 接口名<T>{ }
③在接口上自定义泛型要注意的事项:
a.在接口上自定义泛型的具体数据类型是在实现该接口的时候指定的。
b.如果一个接口自定义了泛型,在实现该接口的时候没有指定具体的数据类型,那么默认是Object数据类型。
public interfaceInter<T> {public abstract void show(T t);
}//泛型接口的实现
public classInterImpl<T> implements Inter<T>{@Overridepublic void show(T t) {System.out.println(t);}
}//泛型接口的测试
public classInterDemo {public static void main(String[] args) {Inter<String> i = new InterImpl<String>();i.show("hello");Inter<Integer>i1 = newInterImpl<Integer>();i1.show(30);}
}
自定义泛型方法
①定义格式:修饰符 <自定义泛型>返回值类型 函数名(自定义泛型 变量名){ }
②注意:
a. 在方法上的自定义泛型的具体数据类型是调用该方法的时候传入实参的时候确定的。
b. 自定义泛型使用的标识符只要符合标识符的命名规则即可。
//紧跟修饰符后面(public)
public <T> T Test1(T t){}public Page<TestGenInternalOutputBean> selectPage(int pageNum, int pageSize) {PageHelper.startPage(pageNum, pageSize);Page<TestGen> page = (Page<TestGen>) mapper.selectAll();return transformVO(page, page.getResult().stream().map(e -> {TestGenInternalOutputBean vo = new TestGenInternalOutputBean();vo.setId(e.getId());vo.setName(e.getName());vo.setSex(e.getSex());return vo;}).collect(Collectors.toList()));}public static <T, E> Page<T> transformVO(Page<E> poPage, List<T> voList) {Page<T> page = new Page<>();try {BeanUtils.copyProperties(poPage, page);} catch (Exception e) {}page.addAll(voList == null ? Lists.newArrayList() : voList);return page;
}
4.无限通配符?
//变量赋值或变量声明时候使用
List<?> list;
?和T
?和T区别是?是一个不确定类,?和T都表示不确定的类型 ,但如果是T的话,函数里面可以对T进行操作,比方 T car = getCar(),而不能用? car = getCar()。
ArrayList<T> al=new ArrayList<T>(); 指定集合元素只能是T类型
ArrayList<?> al=new ArrayList<?>(); 集合元素可以是任意类型,这种没有意义,一般是方法中,只是为了说明用法
简单总结下:
T 是一个 确定的类型,通常用于泛型类和泛型方法的定义,?是一个 不确定的类型,通常用于泛型方法的调用代码和形参,不能用于定义类和泛型方法
区别1:泛型参数的一致性
// 通过 T 来 确保 泛型参数的一致性 public <T extends Number> void test(List<T> dest, List<T> src)//通配符是 不确定的,所以这个方法不能保证两个 List 具有相同的元素类型 public void test(List<? extends Number> dest, List<? extends Number> src)
区别2:通配符可以使用超类限定,而类型参数不行
类型参数 T 只具有 一种 类型限定方式:
T extends A
但是通配符 ? 可以进行两种限定(上限和下限):
? extends A 2 ? super A
List<? extends T>和List <? super T>有什么区别
List<? extends T>可以接受任何继承自T的类型的List,
List<? super T>可以接受任何T的父类构成的List。
例如List<? extends Number>可以接受List<Integer>或List<Float>。
Class<T>和Class<?>
T是一种具体的类,例如String,List,Map......等等,这些都是属于具体的类,这个比较好理解
Class是什么呢,Class也是一个类,但Class是存放上面String,List,Map......类信息的一个类,有点抽象,我们一步一步来看 。
如何获取到Class类呢,有三种方式:
1. 调用Object类的getClass()方法来得到Class对象,这也是最常见的产生Class对象的方法。List list = null;
Class clazz = list.getClass();2. 使用Class类的中静态forName()方法获得与字符串对应的Class对象。Class clazz = Class.forName("com.demo.People");3.获取Class类型对象的第三个方法非常简单。如果T是一个Java类型,那么T.class就代表了匹配的类对象。Class clazz = List.class;
应用场景
Class类是创建出来了,但是Class<T>和Class<?>适用于什么时候呢,
Class<T> 在实例化的时候,T 要替换成具体类。
Class<?> 它是个通配泛型,? 可以代表任何类型,所以主要用于声明时的限制情况。
比如,可以这样做申明:
// 可以
public Class<?> clazz;
// 不可以,因为 T 需要指定类型
public Class<T> clazzT;
所以当不知道定声明什么类型的 Class 的时候可以定义一 个Class<?>。
那如果也想 public Class<T> clazzT; 这样的话,就必须让当前的类也指定 T ,
public class Test3<T> {
// 不会报错
public Class<T> clazzT;
使用Class<T>和Class<?>多发生在反射场景下,先看看如果我们不使用泛型,反射创建一个类是什么样的。
People people = (People) Class.forName("com.demo.People").newInstance();
需要强转,如果反射的类型不是People类,就会报java.lang.ClassCastException错误,使用Class<T>泛型后,不用强转了。
public class Test {public static <T> T createInstance(Class<T> clazz) throws Exception {return clazz.newInstance();}public static void main(String[] args) throws Exception{Fruit fruit= createInstance(Fruit .class);People people= createInstance(People.class);}
}
参考:
https://blog.csdn.net/zhou870498/article/details/80071076
https://my.oschina.net/u/3452909/blog/2052732
https://www.cnblogs.com/blogfeng/p/11448851.html
https://www.jb51.net/article/162777.htm