-
Spring Boot 提供了 RestTemplate 来辅助发起一个 REST 请求,默认通过 JDK 自带的 HttpURLConnection 来作为底层 HTTP 消息的发送方式,使用 JackSon 来序列化服务器返回的 JSON 数据。RestTemplate 是核心类, 提供了所有访问 REST 服务的接口,尽管实际上可以使用 HTTP Client 类或者 java.net.URL来完成,但 RestTemplate 提供了阻STful 风格的 API。 Spring Boot 提供了 RestTemplateBuilder 来创建一个 RestTemplate。 RestTemplate定义11个基本操作方法,大致如下:delete(): 在特定的URL上对资源执行HTTP DELETE操作exchange(): 在URL上执行特定的HTTP方法,返回包含对象的ResponseEntity,这个对象是从响应体中映射得到的 3.execute(): 在URL上执行特定的HTTP方法,返回一个从响应体映射得到的对象(所有的get、post、delete、put、options、head、exchange方法最终调用的都是excute方法),例如:@Override public <T> T getForObject(String url, Class<T> responseType, Object... urlVariables) throws RestClientException { RequestCallback requestCallback = acceptHeaderRequestCallback(responseType); HttpMessageConverterExtractor<T> responseExtractor = <span style="white-space:pre"> </span>new HttpMessageConverterExtractor<T>(responseType, getMessageConverters(), logger); return execute(url, HttpMethod.GET, requestCallback, responseExtractor, urlVariables); } 4.getForEntity(): 发送一个HTTP GET请求,返回的ResponseEntity包含了响应体所映射成的对象 5.getForObject() :发送一个HTTP GET请求,返回的请求体将映射为一个对象 6.postForEntity() :POST 数据到一个URL,返回包含一个对象的ResponseEntity,这个对象是从响应体中映射得到的 7.postForObject(): POST 数据到一个URL,返回根据响应体匹配形成的对象 8.headForHeaders(): 发送HTTP HEAD请求,返回包含特定资源URL的HTTP头 9.optionsForAllow(): 发送HTTP OPTIONS请求,返回对特定URL的Allow头信息 10.postForLocation() :POST 数据到一个URL,返回新创建资源的URL 11.put(): PUT 资源到特定的URL实际上,由于Post 操作的非幂等性,它几乎可以代替其他的CRUD操作.一、GET请求在RestTemplate中,发送一个GET请求,我们可以通过如下两种方式:第一种:getForEntitygetForEntity方法的返回值是一个ResponseEntity<T>,ResponseEntity<T>是Spring对HTTP请求响应的封装,包括了几个重要的元素,如响应码、contentType、contentLength、响应消息体等。其重载方法如下:<T> ResponseEntity<T> getForObject(URI url, Class<T> responseType) throws RestClientException;<T> ResponseEntity<T> getForObject(String url, Class<T> responseType, Object... uriVariables) throws RestClientException;<T> ResponseEntity<T> getForObject(String url, Class<T> responseType, Map<String, ?> uriVariables) throws RestClientException;比如下面一个例子:@Autowired RestTemplateBuilder restTemplateBuilder; @RequestMapping("/gethello") public String getHello() { RestTemplate client = restTemplateBuilder.build (); ResponseEntity<String> responseEntity = restTemplate.getForEntity("http://HELLO-SERVICE/hello", String.class); String body = responseEntity.getBody(); HttpStatus statusCode = responseEntity.getStatusCode(); int statusCodeValue = responseEntity.getStatusCodeValue(); HttpHeaders headers = responseEntity.getHeaders(); StringBuffer result = new StringBuffer(); result.append("responseEntity.getBody():").append(body).append("<hr>") .append("responseEntity.getStatusCode():").append(statusCode).append("<hr>") .append("responseEntity.getStatusCodeValue():").append(statusCodeValue).append("<hr>") .append("responseEntity.getHeaders():").append(headers).append("<hr>"); return result.toString(); }有时候我在调用服务提供者提供的接口时,可能需要传递参数,有两种不同的方式,如下:@RequestMapping("/sayhello") public String sayHello() { ResponseEntity<String> responseEntity = restTemplate.getForEntity("http://HELLO-SERVICE/sayhello?name={1}", String.class, "张三"); return responseEntity.getBody(); } @RequestMapping("/sayhello2") public String sayHello2() { Map<String, String> map = new HashMap<>(); map.put("name", "李四"); ResponseEntity<String> responseEntity = restTemplate.getForEntity("http://HELLO-SERVICE/sayhello?name={name}", String.class, map); return responseEntity.getBody(); }可以用一个数字做占位符,最后是一个可变长度的参数,来一一替换前面的占位符也可以前面使用name={name}这种形式,最后一个参数是一个map,map的key即为前边占位符的名字,map的value为参数值第一个调用地址也可以是一个URI而不是字符串,这个时候我们构建一个URI即可,参数神马的都包含在URI中了,如下:@RequestMapping("/sayhello3") public String sayHello3() { UriComponents uriComponents = UriComponentsBuilder.fromUriString("http://HELLO-SERVICE/sayhello?name={name}").build().expand("王五").encode(); URI uri = uriComponents.toUri(); ResponseEntity<String> responseEntity = restTemplate.getForEntity(uri, String.class); return responseEntity.getBody(); }通过Spring中提供的UriComponents来构建Uri即可。当然,服务提供者不仅可以返回String,也可以返回一个自定义类型的对象,比如我的服务提供者中有如下方法:@RequestMapping(value = "/getbook1", method = RequestMethod.GET)public Book book1() { return new Book("三国演义", 90, "罗贯中", "花城出版社"); }对于该方法我可以在服务消费者中通过如下方式来调用:@RequestMapping("/book1") public Book book1() { ResponseEntity<Book> responseEntity = restTemplate.getForEntity("http://HELLO- SERVICE/getbook1", Book.class); return responseEntity.getBody(); }第二种:getForObjectgetForObject函数实际上是对getForEntity函数的进一步封装,如果你只关注返回的消息体的内容,对其他信息都不关注,此时可以使用getForObject,重载方法如下:<T> T getForObject(URI url, Class<T> responseType) throws RestClientException;<T> T getForObject(String url, Class<T> responseType, Object... uriVariables) throws RestClientException;<T> T getForObject(String url, Class<T> responseType, Map<String, ?> uriVariables) throws RestClientException; 二、POST请求在RestTemplate中,POST请求可以通过如下三个方法来发起:第一种:postForEntity、postForObjectPOST请求有postForObject()和postForEntity()两种方法,和GET请求的getForObject()和getForEntity()方法类似。getForLocation()是POST请求所特有的。<T> T postForObject(URI url, Object request, Class<T> responseType) throws RestClientException;<T> T postForObject(String url, Object request, Class<T> responseType, Object... uriVariables) throws RestClientException;<T> T postForObject(String url, Object request, Class<T> responseType, Map<String, ?> uriVariables) throws RestClientException;上面三个方法中,第一个参数都是资源要POST到的URL,第二个参数是要发送的对象,而第三个参数是预期返回的Java类型。在URL作为String类型的两个版本中,第四个参数指定了URL变量(要么是可变参数列表,要么是一个Map)。 该方法和get请求中的getForEntity方法类似,如下例子:@RequestMapping("/book3") public Book book3() { Book book = new Book(); book.setName("红楼梦"); ResponseEntity<Book> responseEntity = restTemplate.postForEntity("http://HELLO-SERVICE/getbook2", book, Book.class); return responseEntity.getBody(); } 第二种:postForLacationpostForLacation()会在POST请求的请求体中发送一个资源到服务器端,返回的不再是资源对象,而是创建资源的位置。postForLocation(String url, Object request, Object... uriVariables) throws RestClientException;postForLocation(String url, Object request, Map<String, ?> uriVariables) throws RestClientException;postForLocation(URI url, Object request) throws RestClientException; public String postSpitter(Spitter spitter) { RestTemplate rest = new RestTemplate(); return rest.postForLocation("http://localhost:8080/Spitter/spitters", spitter).toString(); }postForLocation也是提交新资源,提交成功之后,返回新资源的URI,postForLocation的参数和前面两种的参数基本一致,只不过该方法的返回值为Uri,这个只需要服务提供者返回一个Uri即可,该Uri表示新资源的位置。 三、PUT请求在RestTemplate中,对PUT请求可以通过put方法进行调用实现,比如:RestTemplate restTemplate=new RestTemplate();Longid=100011; User user=new User("didi",40); restTemplate.put("http://USER-SERVICE/user/{l}",user,id); put函数也实现了三种不同的重载方法:put(String url,Object request,Object... urlVariables)put(String url,Object request,Map urlVariables)put(URI url,Object request)put函数为void类型,所以没有返回内容,也就没有其他函数定义的responseType参数,除此之外的其他传入参数定义与用法与postForObject基本一致。 四、DELETE请求在RestTemplate中,对DELETE请求可以通过delete方法进行调用实现,比如:RestTemplate restTemplate=new RestTemplate(); Longid=10001L; restTemplate.delete("http://USER-SERVICE/user/{1)",id); delete函数也实现了三种不同的重载方法:delete(String url,Object... urlVariables)delete(String url,Map urlVariables)delete(URI url)由于我们在进行REST请求时,通常都将DELETE请求的唯一标识拼接在url中,所以DELETE请求也不需要request的body信息,就如put()方法一样,返回值类型为void。说明:第三种重载方法,url指定DELETE请求的位置,urlVariables绑定url中的参数即可。五、通用方法exchange()exchange方法可以在发送个服务器端的请求中设置头信息,其重载方法如下:<T> ResponseEntity<T> exchange(URI url, HttpMethod method, HttpEntity<?> requestEntity, Class<T> responseType) throws RestClientException;<T> ResponseEntity<T> exchange(String url, HttpMethod method, HttpEntity<?> requestEntity, Class<T> responseType, Object... uriVariables) throws RestClientException;<T> ResponseEntity<T> exchange(String url, HttpMethod method, HttpEntity<?> requestEntity, Class<T> responseType, Map<String, ?> uriVariables) throws RestClientException;MultiValueMap<String, String> headers = new LinkedMultiValueMap<String, String>(); headers.add("Accept", "application/json"); HttpEntity<Object> requestEntity = new HttpEntity<Object>(headers); ResponseEntity<Spitter> response=rest.exchange("http://localhost:8080/Spitter/spitters/{spitter}", HttpMethod.GET, requestEntity, Spitter.class, spitterId); 补充: 如果期望返回的类型是一个列表,如 List,不能简单调用 xxxForObject,因为存在泛型的类型擦除, RestTemplate 在反序列化的时候并不知道实际反序列化的类型,因此可以使用 ParameterizedTypeReference 来包含泛型类型,代码如下:RestTemplate client= restTemplateBuilder.build(); //根据条件查询一纽订单 String uri = base+"/orders?offset={offset }"; Integer offset = 1; //元参数 HttpEntity body = null; ParameterizedTypeReference<List<Order> typeRef = new ParameterizedTypeReference<List<Order>(){}; ResponseEntity<List<Order> rs=client.exchange(uri, HttpMethod.GET, body, typeRef, offset); List<Order> order = rs.getBody() ; 注意到 typeRef 定义是用{}结束的,这里创建了一个 ParameterizedTypeReference 子类,依 据在类定义中的泛型信息保留的原则, typeRef保留了期望返回的泛型 List。exchange 是一个基础的 REST 调用接口,除了需要指明 HTTP Method,调用方法同其他方法类似。除了使用ParameterizedTypeReference子类对象,也可以先将返回结果映射成 json字符串,然后通过 ObjectMapper 来转为指定类型(笔记<SpringBoot中的JSON>中有介绍 ) 。————————————————原文链接:https://blog.csdn.net/fsy9595887/article/details/86420048
-
【摘要】 Sermant Agent是一种基于JavaAgent的无代理服务网格技术。它利用JavaAgent来检测主机应用程序,并具有增强的服务治理功能,以解决海量微服务架构中的服务治理问题。本文介绍了Sermant Agent的接入原理和如何使用Sermant Agent无修改接入CSE。本文分享自华为云社区《Spring Cloud应用零代码修改接入华为云微服务引擎CSE》,作者: 微服务小助手 。一、 Sermant Agent介绍Sermant Agent是一种基于JavaAgent的无代理服务网格技术。它利用JavaAgent来检测主机应用程序,并具有增强的服务治理功能,以解决海量微服务架构中的服务治理问题。Sermant Agent处于快速发展阶段,当前已支持多种服务治理能力,包含流量治理、注册、优雅上下线及动态配置能力。 二、 为何使用Sermant Agent接入代码零侵入,配置很简单相较于SDK方式接入,基于Sermant Agent的接入会更加快捷高效,配置简单,且应用无需做任何代码改造,仅需在服务启动时附带Sermant Agent即可动态接入到CSE。支持多种治理能力Sermant Agent默认集成流量治理能力,当前支持熔断、限流、隔离仓以及重试治理能力,该能力可基于CSE配置中心进行配置与发布。支持多种注册中心Sermant Agent目前支持业内主流的注册中心,已经支持了ServiceComb ServiceCenter、Naocs,Eureka、Zookeeper等正在开发中。支持应用不停机迁移Sermant Agent支持服务的双注册,可根据配置中心下发的服务订阅策略,动态修改当前服务的订阅策略,并基于该能力帮助线上应用在业务不中断的前提下完成服务迁移。不仅如此,Sermant Agent提供优雅上下线能力,在服务重启、上下线时提供保障,在保护服务的同时,规避服务下线时可能存在的流量丢失问题。三、 接入原理当然,在说明原理之前,我们首先需要了解什么是Java Agent。Java Agent是在JDK1.5之后引入的新特性,它支持JVM将字节码文件读入内存之后,JVM使用对应的字节流在Java堆中生成一个Class对象之前,用户可以对其字节码进行修改的能力,JVM使用修改之后的字节码进行Class对象的创建,从而实现Java应用的非代码侵入的业务逻辑修改和替换。Sermant Agent正是基于动态修改字节码的技术,在服务启动时,动态增强原服务的注册逻辑。那Sermant Agent是如何在不修改代码的前提下接入CSE呢?主要流程如下:Sermant Agent接入CSE的时序图包含以下6个步骤:首先服务携带Sermant Agent启动;服务启动时,针对服务执行字节码增强操作(基于Java Agent的字节码增强),主要针对注册与配置两块,在步骤3-5体现;通过字节码增强,动态识别原应用的注册中心;注入启动配置,动态关闭原应用的注册中心自动配置逻辑;随后通过Spring的SpringFactory机制注入基于Spring Cloud实现的注册CSE的自动配置类,由Spring接管;当应用发起注册时,会通过步骤5注入的注册逻辑向CSE发起注册,最终完成接入。四、 简单零代码修改,轻松接入CSE接入场景分为虚机接入和容器接入,大家可以根据自身需求选择合适的接入方式。虚机场景接入CSE虚机部署的应用可通过Sermant Agent接入到CSE,点击查看虚机接入CSE流程。接入流程基于ECS将应用接入CSE流程如下:容器场景接入CSE容器部署的应用可通过Sermant Injector自动挂载Sermant Agent,从而通过Sermant Agent接入到CSE,点击查看容器接入CSE流程。接入流程基于CCE将应用接入CSE流程如下:五、 更多支持版本当前Sermant已支持大部分业内主流版本,相关Spring及注册中心版本如下:开源方式接入除了上述接入方式,还可基于开源方式接入,您可在Sermant开源社区拉取最新代码,并自行打包,启动步骤可参考虚机接入流程。开源项目Sermant:https://github.com/huaweicloud/Sermant
-
1.Spring Security是怎么鉴权和实现权限认证的呢?一般我们会创建一个Spring Security的配置来进行权限的认证设置、各种拦截策略以及各种自定义的配置。1.1 创建Spring Security配置类只要继承了WebSecurityConfigurerAdapter抽象类并且加上Spring的配置注解@Configuration以及将Bean放到容器中管理的 @EnableWebSecurity注解和@EnableGlobalMethodSecurity(prePostEnabled=true)就可以完成一个Spring Security的配置类的创建,实现全局生效自定义的安全配置。在创建完成后,可以重载里面很多设置配置的方法,如下图所示:1.2 其中有很多public void configure这个的重载方法,用于配置不同的拦截策略。用于根据系统自定义一些拦截策略,provider就实现了AuthenticationProvider接口的自定义配置处理类1.2.1 下面这种AuthenticationManagerBuilder auth参数的是身份验证提供程序是需要自定义配置的。@Override public void configure(AuthenticationManagerBuilder auth) throws Exception { auth.authenticationProvider(provider); }provider是实现了AuthenticationProvider接口的自定义配置处理类,provider自定义类中实现方法public Authentication authenticate(Authentication authentication)中可以设置一些自定义的属性值,也可以从authentication参数中获取登录的用户名和密码。@Override public Authentication authenticate(Authentication authentication) throws AuthenticationException { CustomWebAuthenticationDetails details = (CustomWebAuthenticationDetails) authentication.getDetails(); String username = authentication.getName(); String password = (String) authentication.getCredentials(); String imgCode = details.getImgCode(); ... ... }如果想要配置一些别的参数比如验证码可以通过继承WebAuthenticationDetails类设置自定义的一些属性,可以从请求参数中获取到这些参数,再在上面的安全拦截的配置类中强转为这个自定义的类从中就可以获取我们需要设置的自定义的安全配置了。//比如可以配置验证码 public class CustomWebAuthenticationDetails extends WebAuthenticationDetails { private final String imgCode; public CustomWebAuthenticationDetails(HttpServletRequest request) { super(request); imgCode = request.getParameter("imgCode"); } public String getImgCode() { return imgCode; } }还有一种配置是下面这样的,可以设置内存指定的登录的账号密码,指定角色,不加.passwordEncoder(new MyPasswordEncoder())或者注入该类的Bean就不是以明文的方式进行匹配,会报错,如果加上就需要配置自己myUserService跟数据库配置自定义查询,然后配置自定义的加密解密的配置类MyPasswordEncoder,实现不明文传输 。@Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { auth.inMemoryAuthentication().withUser("admin").password("admin").roles("ADMIN"); auth.inMemoryAuthentication().withUser("demo").password("demo").roles("ADMIN"); auth.inMemoryAuthentication().withUser("testRole").password("testRole").roles("AD"); auth.userDetailsService(myUserService).passwordEncoder(new MyPasswordEncoder()); }其中myUserService是配置自定义的数据库配置 @Component public class MyUserService implements UserDetailsService{ @Override public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException { return null; } } 其中new MyPasswordEncoder()可以配置加密 @Component public class MyPasswordEncoder implements PasswordEncoder { @Override public String encode(CharSequence charSequence) { return charSequence.toString(); } @Override public boolean matches(CharSequence charSequence, String s) { return s.equals(charSequence.toString()); } //private final static String SALT = "这个是自定义的salt"; // @Override // public String encode(CharSequence charSequence) { // // return MD5Util.MD5Encode(charSequence.toString(),SALT); // } // // @Override // public boolean matches(CharSequence rawPassword, String encodedPassword) { // // return MD5Util.isPasswordValid(encodedPassword,rawPassword.toString(),SALT); // } }1.2.2 参数是HttpSecurity http的是主要的接口请求拦截配置这里就是主要的安全拦截配置了,可以在其中配置登录相关的处理,成功和失败的相关响应,配置登录和登出接口的定义(这里配置后可以不用在控制器中再配置相关接口,只需要在拦截器中配置好相关的安全参数的拦截策略就可以,在访问配置登录接口时,会自动进入到自定义的拦截器中进行参数判断)。还有很多其他配置,目前还在探索中。。。 @Override protected void configure(HttpSecurity http) throws Exception { http // 关闭csrf防护 .csrf().disable().headers().frameOptions().disable().and(); http // 登录处理 .formLogin().authenticationDetailsSource(authenticationDetailsSource) // 自定义登陆接受参数名 .usernameParameter("username").passwordParameter("password") // 定义登录页面 .loginPage("/login") // 登录处理接口 .loginProcessingUrl("/admin/login") // 登录成功后处理 .successHandler(new AuthenticationSuccessHandler() { @Override public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException { response.setContentType("application/json;charset=utf-8"); PrintWriter writer = response.getWriter(); writer.append("{\"code\": 0,\"msg\": \"ok\"}"); writer.flush(); writer.close(); } }) // 登录失败后处理 .failureHandler(new AuthenticationFailureHandler() { @Override public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException { String writerStr = "{\"code\": 1,\"msg\": \"error\"}"; if (exception instanceof MyException) { MyException e = (MyException) exception; RespModel resp = new RespModel(e.getCode(), e.getMsg()); writerStr = JSON.toJSONString(resp); } response.setContentType("application/json;charset=utf-8"); PrintWriter writer = response.getWriter(); writer.append(writerStr); writer.flush(); writer.close(); } }).permitAll().and() // 退出登录 .logout().logoutUrl("/logout").permitAll(); http.authorizeRequests().anyRequest().authenticated().and(); // Session的并发控制,这里设为最多一个,只允许一个用户登录,如果同一个账户两次登录,那么第一个账户将被踢下线 http.sessionManagement().maximumSessions(1).expiredUrl("/login");//自己实现的userDetail要重写EqualsAndHashCode 注解:@EqualsAndHashCode(callSuper = true) // 当一个用户已经认证过了,在另外一个地方重新进行登录认证,spring security可以阻止其再次登录认证,从而保持原来的会话可用性 // 存在一个问题,当用户登陆后,没有退出直接关闭浏览器,则再次打开浏览器时,此时浏览器的session若被删除的话,用户只能等到服务器的session过期后,才能再次登录。 //http.sessionManagement().maximumSessions(1).maxSessionsPreventsLogin(true); }1.2.3 参数是WebSecurity web是配置静态资源可以设置忽略拦截静态资源的路径等一些配置。@Override public void configure(WebSecurity web) throws Exception { web.ignoring().antMatchers("/js/**", "/css/**", "/img/**", "/html/**", "/error"); }以上就是目前探索的一些Spring Secrity的一些安全认证和鉴权策略的实现配置方法,继续探索ing...
-
1、简介 Spring Security是一个能够为基于Spring的企业应用系统提供声明式的安全访问控制解决方案的安全框架。框架中主要涉及两个操作,一个是认证,从名字就可以知道,是为了获得一个标识,确认哪些用户可以访问,哪些用户会被拦截;还有一个是授权,从名字中也很容易明白,授权就是赋予一定的权利,可以去做某一些操作,而在授权之前,框架中首先会判断是否是认证的用户,这就是SpringSecurity安全框架中认证和授权相互配合的操作,用这样的操作来保证系统的安全可靠。2、Spring Security默认登录页面 在创建一个Spring Security安全框架后,会默认带一个登录页面,创建的过程也很简单,只要需要创建web项目的时候引入Spring Security的相关依赖就可以完成创建了。 <!--springsecurity 主要依赖--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-test</artifactId> </dependency> 在创建好后会有一个默认的登录页面,默认配置用户为user,密码会输出在控制台上,那么问题来了我们什么都没有写,这个登录页面是哪里来的呢?而且还自带账号和密码。 其实这个登录页面是一个默认filter里面直接写入response的,可以在自定义实现的WebSecurityConfigurerAdapter 中有找到这么一个方法,再在其中找到一个 默认配置DefaultLoginPageConfigurer。它会添加两个默认filter,再点进去,其中的loginPageGeneratingFilter中,就会生成登录页面是怎么写的了。private void applyDefaultConfiguration(HttpSecurity http) throws Exception { http.csrf(); http.addFilter(new WebAsyncManagerIntegrationFilter()); http.exceptionHandling(); http.headers(); http.sessionManagement(); http.securityContext(); http.requestCache(); http.anonymous(); http.servletApi(); http.apply(new DefaultLoginPageConfigurer<>()); http.logout(); } public void configure(H http) { AuthenticationEntryPoint authenticationEntryPoint = null; ExceptionHandlingConfigurer<?> exceptionConf = http.getConfigurer(ExceptionHandlingConfigurer.class); if (exceptionConf != null) { authenticationEntryPoint = exceptionConf.getAuthenticationEntryPoint(); } if (this.loginPageGeneratingFilter.isEnabled() && authenticationEntryPoint == null) { this.loginPageGeneratingFilter = postProcess(this.loginPageGeneratingFilter); http.addFilter(this.loginPageGeneratingFilter); http.addFilter(this.logoutPageGeneratingFilter); } }private String generateLoginPageHtml(HttpServletRequest request, boolean loginError, boolean logoutSuccess) { String errorMsg = "Invalid credentials"; if (loginError) { HttpSession session = request.getSession(false); if (session != null) { AuthenticationException ex = (AuthenticationException) session .getAttribute(WebAttributes.AUTHENTICATION_EXCEPTION); errorMsg = (ex != null) ? ex.getMessage() : "Invalid credentials"; } } String contextPath = request.getContextPath(); StringBuilder sb = new StringBuilder(); sb.append("<!DOCTYPE html>\n"); sb.append("<html lang=\"en\">\n"); sb.append(" <head>\n"); sb.append(" <meta charset=\"utf-8\">\n"); sb.append(" <meta name=\"viewport\" content=\"width=device-width, initial-scale=1, shrink-to-fit=no\">\n"); sb.append(" <meta name=\"description\" content=\"\">\n"); sb.append(" <meta name=\"author\" content=\"\">\n"); sb.append(" <title>Please sign in</title>\n"); sb.append(" <link href=\"https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-beta/css/bootstrap.min.css\" " + "rel=\"stylesheet\" integrity=\"sha384-/Y6pD6FV/Vv2HJnA6t+vslU6fwYXjCFtcEpHbNJ0lyAFsXTsjBbfaDjzALeQsN6M\" crossorigin=\"anonymous\">\n"); sb.append(" <link href=\"https://getbootstrap.com/docs/4.0/examples/signin/signin.css\" " + "rel=\"stylesheet\" crossorigin=\"anonymous\"/>\n"); sb.append(" </head>\n"); sb.append(" <body>\n"); sb.append(" <div class=\"container\">\n"); if (this.formLoginEnabled) { sb.append(" <form class=\"form-signin\" method=\"post\" action=\"" + contextPath + this.authenticationUrl + "\">\n"); sb.append(" <h2 class=\"form-signin-heading\">Please sign in</h2>\n"); sb.append(createError(loginError, errorMsg) + createLogoutSuccess(logoutSuccess) + " <p>\n"); sb.append(" <label for=\"username\" class=\"sr-only\">Username</label>\n"); sb.append(" <input type=\"text\" id=\"username\" name=\"" + this.usernameParameter + "\" class=\"form-control\" placeholder=\"Username\" required autofocus>\n"); sb.append(" </p>\n"); sb.append(" <p>\n"); sb.append(" <label for=\"password\" class=\"sr-only\">Password</label>\n"); sb.append(" <input type=\"password\" id=\"password\" name=\"" + this.passwordParameter + "\" class=\"form-control\" placeholder=\"Password\" required>\n"); sb.append(" </p>\n"); sb.append(createRememberMe(this.rememberMeParameter) + renderHiddenInputs(request)); sb.append(" <button class=\"btn btn-lg btn-primary btn-block\" type=\"submit\">Sign in</button>\n"); sb.append(" </form>\n"); } if (this.openIdEnabled) { sb.append(" <form name=\"oidf\" class=\"form-signin\" method=\"post\" action=\"" + contextPath + this.openIDauthenticationUrl + "\">\n"); sb.append(" <h2 class=\"form-signin-heading\">Login with OpenID Identity</h2>\n"); sb.append(createError(loginError, errorMsg) + createLogoutSuccess(logoutSuccess) + " <p>\n"); sb.append(" <label for=\"username\" class=\"sr-only\">Identity</label>\n"); sb.append(" <input type=\"text\" id=\"username\" name=\"" + this.openIDusernameParameter + "\" class=\"form-control\" placeholder=\"Username\" required autofocus>\n"); sb.append(" </p>\n"); sb.append(createRememberMe(this.openIDrememberMeParameter) + renderHiddenInputs(request)); sb.append(" <button class=\"btn btn-lg btn-primary btn-block\" type=\"submit\">Sign in</button>\n"); sb.append(" </form>\n"); } if (this.oauth2LoginEnabled) { sb.append("<h2 class=\"form-signin-heading\">Login with OAuth 2.0</h2>"); sb.append(createError(loginError, errorMsg)); sb.append(createLogoutSuccess(logoutSuccess)); sb.append("<table class=\"table table-striped\">\n"); for (Map.Entry<String, String> clientAuthenticationUrlToClientName : this.oauth2AuthenticationUrlToClientName .entrySet()) { sb.append(" <tr><td>"); String url = clientAuthenticationUrlToClientName.getKey(); sb.append("<a href=\"").append(contextPath).append(url).append("\">"); String clientName = HtmlUtils.htmlEscape(clientAuthenticationUrlToClientName.getValue()); sb.append(clientName); sb.append("</a>"); sb.append("</td></tr>\n"); } sb.append("</table>\n"); } if (this.saml2LoginEnabled) { sb.append("<h2 class=\"form-signin-heading\">Login with SAML 2.0</h2>"); sb.append(createError(loginError, errorMsg)); sb.append(createLogoutSuccess(logoutSuccess)); sb.append("<table class=\"table table-striped\">\n"); for (Map.Entry<String, String> relyingPartyUrlToName : this.saml2AuthenticationUrlToProviderName .entrySet()) { sb.append(" <tr><td>"); String url = relyingPartyUrlToName.getKey(); sb.append("<a href=\"").append(contextPath).append(url).append("\">"); String partyName = HtmlUtils.htmlEscape(relyingPartyUrlToName.getValue()); sb.append(partyName); sb.append("</a>"); sb.append("</td></tr>\n"); } sb.append("</table>\n"); } sb.append("</div>\n"); sb.append("</body></html>"); return sb.toString(); }这种直接拼接的写法很简单粗暴,虽然不是很优雅但是不会引入别的东西,只用原生的方法拼接,极大的减少了冲突等等一系列问题的产生。这样我们知道了登录页面是怎么来的了。但是这个账号和密码又是怎么来的呢?可以在自己的依赖包中找到相关的Properties的配置F:\maven\org\springframework\boot\spring-boot-autoconfigure\2.6.7\spring-boot-autoconfigure-2.6.7.jar!\org\springframework\boot\autoconfigure\security\SecurityProperties.class当然从上面的properties中我们可以看出这是,框架默认的配置,我们是可以对其进行修改的。只需要在yml配置文件中配置用户名和密码就可以完成spring security登录页面的账号和密码配置了。当然再配置类中也可以进行配置。spring: security: user: name: test password: 123456也可以通过配置类完成很多其他设置如下,其中@Configuration 注解将Bean 放到容器中管理,@EnableWebSecurity注解 用于将Security 生成 Bean,一定记得要加上才能使配置类生效。package com.test.config; import com.test.service.MyUserService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Configuration; import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.builders.WebSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; /** * @author * @date */ @Configuration // 将Bean 放到容器中管理 @EnableWebSecurity // 用于将Security 生成 Bean public class SpringSecurityConfig extends WebSecurityConfigurerAdapter { @Autowired private MyUserService myUserService; /* * 接口请求拦截 **/ @Override protected void configure(HttpSecurity http) throws Exception { http.authorizeRequests() //安全请求策略 .antMatchers("/home").permitAll() //可放行请求配置 .anyRequest().authenticated() //其他请求进行拦截 .and() .logout().permitAll() // 注销任意访问 .and() .formLogin(); http.cors().disable(); } /* * 告诉程序,系统中有个用户 用户名为 admin ,密码为 admin 角色为 ADMIN * */ @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { //可以设置内存指定的登录的账号密码,指定角色 //不加.passwordEncoder(new MyPasswordEncoder())或者注入该类的Bean //就不是以明文的方式进行匹配,会报错 auth.inMemoryAuthentication().withUser("admin").password("admin").roles("ADMIN"); auth.inMemoryAuthentication().withUser("demo").password("demo").roles("ADMIN"); auth.inMemoryAuthentication().withUser("testRole").password("testRole").roles("AD"); auth.userDetailsService(myUserService).passwordEncoder(new MyPasswordEncoder()); } /* * 前端拦截 * */ @Override public void configure(WebSecurity web){ // 忽视 js 、css 、 images 后缀访问 web.ignoring().antMatchers("/js/**","/css/**","/images/**"); } }
-
@ConfigurationProperties(prefix=“ XX”)告诉springboot将该注解修饰的类的所有属性和配置文件中相关的配置进行绑定,prefix属性表示的是从配置文件中为XX的下面的属性进行一一映射。该注解一般和@Component 注解一起使用。 因为是只有这个组件是容器内的组件才能用@ConfigurationProperties 这个注解如果不写@Component,提示在springboot的开启配置属性中未注册,所以需要标记为spring的Component组件或者,在配置扫描中配置,配置值得时候支持数据校验,可以给这个类上面加上@Valiadated注解,然后下面的属性上面就可以就加上类似@Email的校验。注解可以能获取yml配置文件中的复杂类型的值比如数组或者map,默认从全局配置文件中获取值@Lazy使bean懒加载,取消bean预初始化。@Primary自动装配时当出现多个Bean候选者时,被注解为@Primary的Bean将作为首选者,否者将抛出异常。@Value也可以获取配置文件的@Value需要一个个的在类中给属性上面加这个注解 用{xx.bb}的形式单个设置值,但是value注解支持el表达式,但是value注解不能获取yml配置文件中的复杂类型的值比如数组或者map@PropertySource(value={""})加载value数组路径下的配置文件@ImportSource(localtions{}) 导入spring的配置文件,springboot里面没有spring的配置,自己编写的spring文件也不会生效,想使用的话 就需要加入这个注解标准在配置类上,locations数组可以加载多个配置文件。springboot推荐使用配置类来使用配置文件@Cofinguration指明当前类为配置类,等于spring的配置文件,在这个配置下 可以给方法上面加入@Bean注解给容器中加入组件 意思为把方法的返回值放入容器,默认的id为方法名@Profile springboot。1.多个配置文件的。比如application-dev.properties application-pro.properties application-{profile}.properties 可以在配置文件中选择激活哪一个配置文件,用来切换开发生产环境2.spring-profiles-active ={profile}如果使用的是yml格式的配置文件的话 可以使用文档块每个文档块用 --- 隔开在最上面使用 3.命令行:将项目install,然后在jar包所在磁盘地址栏cmd,运行如下命令启动项目java -jar spring-boot-02-config-0.0.1-SNAPSHOT.jar --spring.profiles.active=dev; 酱紫可以直接在测试的时候,配置传入命令行参数,优先级最高,会覆盖上面的或者用idea运行项目,在中配置,效果和级别等同命令行4.虚拟机参数-Dspring.profiles.active=dev 优先级比配置文件高5.Active profiles 设置如果每个位置都有配置文件,每个配置文件都会加载,并且会配置互补。spring.config.location 可以在项目打完包之后指定运行一个配置文件,用在打包完后修改了配置.springBoot也可以从一下位置加载配置,优先级从高到低高优先级覆盖低优先级呃配置,所有的配置回互补1.多个命令行参数可以用空格隔开 --server.port=8081 --server.context-path=/abc6-9优先加载带profile的,由jar包外向jar包内寻找
-
## 责任链模式是什么 责任链模式是一种行为型设计模式,也就是重点是处理数据,假设我们有一份数据,需要经过很多个节点处理,那么就会是以下这个样子: 一个节点处理完之后,交给下一个节点,不知道大家有没有使用过审批流,当我们提完一个审批单后,你的`leader`审批,leader审批通过之后就是总监批,总监后面可能是高级总监,或者`cto`,或者`hr`。他们在同一个链条上,倘若你的`leader`没有审批完,后面的节点是不可能收到信息的。如果你的`leader`拒绝了你的申请,那数据也不会到达后面的审批节点。 如果你接触过前端,JS 中点击某个 `div` 的时候会产生冒泡事件,也就是点击下面的`A`, `A` 在`B`里面,`B`在`C`里面, `A`-> `B` -> `C` 会依次收到点击事件: 再举个例子,在 `SpringMVC`中,我们有时候会定义一些拦截器,对请求进行预处理,也就是请求过来的时候,会依次经历拦截器,通过拦截器之后才会进入我们的处理业务逻辑代码。 之前,在做人员管理的时候,有涉及到人员离职情况的处理流程,要交接工作,解除权限,禁用账号等等,这整个处理流程就很适合使用责任链来处理。当然,自动处理流程是会出错的,保存每一个阶段的状态,针对出错的场景,可以手动去从断开责任链的地方接着执行。这整个流程的框架就是应用了责任链,但是根据实际场景也添加了不少其他的东西。 ## 两点疑问 1. 责任链的每一个节点是不是一定包含下一个节点的引用? 答:不一定,要么把所有责任节点放在一个`list`里面,依次处理;要么每个节点包含下一个责任节点的引用, 1. 责任链到底是不允许中断还是不允许中断? 答:两种都可以,不拘泥于细节,可以根据自己的场景使用。 ## 责任链模式中的角色 责任链一般有以下的角色: - `Client`(客户端):调用责任链处理器的处理方法,或者在第一个链对象中调用`handle`方法。 - `Handler`(处理器):抽象类,提供给实际处理器继承然后实现`handle`方法,处理请求 - `ConcreteHandler`(具体处理器):实现`handler`的类,同时实现`handle`方法,负责处理业务逻辑类,不同业务模块有不同的`ConcreteHandler`。 - `HandlerChain`:负责组合责任链的所有节点以及流程(如果节点包含下一个节点的引用,那么`HandlerChain`可以不存在) ## 审批链的实现 下面我们分别来实现不同的写法,假设现在有一个场景,秦怀入职了一家公司,哼哧哼哧干了一年,但是一直没调薪,又过了一年,总得加薪了吧,不加就要提桶跑路了,于是秦怀大胆去内部系统提了一个申请单:【加薪申请】 ### 不中断模式 先演示不中断模式,得先弄个申请单的实体,里面包含了申请单的名字和申请人: ```java public class Requisition { // 名称 public String name; // 申请人 public String applicant; public Requisition(String name, String applicant) { this.name = name; this.applicant = applicant; } } ``` 责任链中的每个责任节点,也就是处理器,可以抽象成为一个接口: ```java public interface Handler { // 处理申请单 void process(Requisition requisition); } ``` 我们依次实现了三个不同的责任节点,分别代表`leader`,总监,`hr`审批: ```java public class ManagerHandler implements Handler { @Override public void process(Requisition requisition) { System.out.println(String.format("Manager 审批来自[%s]的申请单[%s]...", requisition.applicant, requisition.name)); } } public class DirectorHandler implements Handler{ @Override public void process(Requisition requisition) { System.out.println(String.format("Director 审批来自[%s]的申请单[%s]...", requisition.applicant, requisition.name)); } } public class HrHandler implements Handler{ @Override public void process(Requisition requisition) { System.out.println(String.format("Hr 审批来自[%s]的申请单[%s]...", requisition.applicant, requisition.name)); } } ``` 责任节点都有了,我们需要用一个责任链把它们组合起来: ```java public class HandlerChain { List handlers = new ArrayList(); public void addHandler(Handler handler){ handlers.add(handler); } public void handle(Requisition requisition){ for(Handler handler:handlers){ handler.process(requisition); } System.out.println(String.format("来自[%s]的申请单[%s]审批完成", requisition.applicant, requisition.name)); } } ``` 客户端测试类: ```java public class ClientTest { public static void main(String[] args) { HandlerChain handlerChain = new HandlerChain(); handlerChain.addHandler(new ManagerHandler()); handlerChain.addHandler(new DirectorHandler()); handlerChain.addHandler(new HrHandler()); handlerChain.handle(new Requisition("加薪申请","秦怀")); } } ``` 运行结果: ```java Manager 审批来自[秦怀]的申请单[加薪申请]... Director 审批来自[秦怀]的申请单[加薪申请]... Hr 审批来自[秦怀]的申请单[加薪申请]... 来自[秦怀]的申请单[加薪申请]审批完成 ``` 从结果上来看,申请单确实经历过了每一个节点,形成了一条链条,这就是责任链的核心思想。每个节点拿到的都是同一个数据,同一个申请单。 ### 中断模式 秦怀加薪的想法很美好,但是现实很骨感,上面的审批流程一路畅通,但是万一 Hr 想拒绝掉这个申请单了,上面的代码并没有赋予她这种能力,因此,代码得改!(Hr 内心:我就要这个功能,明天上线)。 既然是支持中断,也就是支持任何一个节点审批不通过就直接返回,不会再走到下一个节点,先给抽象的处理节点方法加上返回值: ```java public interface Handler { // 处理申请单 boolean process(Requisition requisition); } ``` 三个处理节点也同步修改: ```java public class ManagerHandler implements Handler { @Override public boolean process(Requisition requisition) { System.out.println(String.format("Manager 审批通过来自[%s]的申请单[%s]...", requisition.applicant, requisition.name)); return true; } } public class DirectorHandler implements Handler{ @Override public boolean process(Requisition requisition) { System.out.println(String.format("Director 审批通过来自[%s]的申请单[%s]...", requisition.applicant, requisition.name)); return true; } } public class HrHandler implements Handler{ @Override public boolean process(Requisition requisition) { System.out.println(String.format("Hr 审批不通过来自[%s]的申请单[%s]...", requisition.applicant, requisition.name)); return false; } } ``` 处理链调整: ```java public class HandlerChain { List handlers = new ArrayList(); public void addHandler(Handler handler) { handlers.add(handler); } public void handle(Requisition requisition) { for (Handler handler : handlers) { if (!handler.process(requisition)) { System.out.println(String.format("来自[%s]的申请单[%s]审批不通过", requisition.applicant, requisition.name)); return; } } System.out.println(String.format("来自[%s]的申请单[%s]审批完成", requisition.applicant, requisition.name)); } } ``` 修改完成之后的结果: ```java Manager 审批通过来自[秦怀]的申请单[加薪申请]... Director 审批通过来自[秦怀]的申请单[加薪申请]... Hr 审批不通过来自[秦怀]的申请单[加薪申请]... 来自[秦怀]的申请单[加薪申请]审批不通过 ``` 秦怀哭了,加薪的审批被 hr 拒绝了。虽然被拒绝了,但是秦怀也感受到了可以中断的责任链模式,这种写法在处理请求的时候也比较常见,因为我们不希望不合法的请求到正常的处理逻辑中。 ### 包含下一个节点的引用 前面说过,**在责任链模式里,很多对象由每一个对象对其下家的引用而连接起来形成一条链。**上面的写法都是不包含下一个节点引用的写法。下面我们实践一下,如何使用引用写法完成责任链。 改造`Handler`接口为抽象类: ```java public abstract class Handler { private Handler nextHandler; public void setNextHandler(Handler handler) { this.nextHandler = handler; } // 处理申请单 protected abstract boolean process(Requisition requisition); // 暴露方法 public boolean handle(Requisition requisition) { boolean result = process(requisition); if (result) { if (nextHandler != null) { return nextHandler.handle(requisition); } else { return true; } } return false; } } ``` 三个实现类不变: ```java public class ManagerHandler extends Handler{ @Override boolean process(Requisition requisition) { System.out.println(String.format( "Manager 审批通过来自[%s]的申请单[%s]...", requisition.applicant, requisition.name)); return true; } } public class DirectorHandler extends Handler { @Override public boolean process(Requisition requisition) { System.out.println(String.format( "Director 审批通过来自[%s]的申请单[%s]...", requisition.applicant, requisition.name)); return true; } } public class HrHandler extends Handler{ @Override public boolean process(Requisition requisition) { System.out.println(String.format("Hr 审批不通过来自[%s]的申请单[%s]...", requisition.applicant, requisition.name)); return false; } } ``` 测试方法,构造嵌套引用: ```java public class ClientTest { public static void main(String[] args) { HrHandler hrHandler = new HrHandler(); DirectorHandler directorHandler = new DirectorHandler(); directorHandler.setNextHandler(hrHandler); ManagerHandler managerHandler = new ManagerHandler(); managerHandler.setNextHandler(directorHandler); managerHandler.handle(new Requisition("加薪申请","秦怀")); } } ``` 可以看到运行结果也是一样: ```java Manager 审批通过来自[秦怀]的申请单[加薪申请]... Director 审批通过来自[秦怀]的申请单[加薪申请]... Hr 审批不通过来自[秦怀]的申请单[加薪申请]... ``` ### 拓展一下 其实责任链配合上`Spring`更加好用,主要有两点: 1、可以使用注入,自动识别该接口的所有实现类。 ```java @Autowire public List handlers; ``` 2、可以使用`@Order`注解,让接口实现类按照顺序执行。 ```java @Order(1) public class HrHandler extends Handler{ ... } ``` ### 源码中的应用 - `Mybatis` 中的 `Plugin` 机制使用了责任链模式,配置各种官方或者自定义的 `Plugin`,与 `Filter` 类似,可以在执行 `Sql` 语句的时候执行一些操作。 - `Spring`中使用责任链模式来管理`Adviser`。 比如`Mybatis`中可以添加若干的插件,比如`PageHelper`,多个插件对对象的包装采用的动态代理来实现,多层代理。 ```java //责任链插件 public class InterceptorChain { private final List interceptors = new ArrayList(); // 生成代理对象 public Object pluginAll(Object target) { for (Interceptor interceptor : interceptors) { target = interceptor.plugin(target); } return target; } //一层一层的拦截器 public void addInterceptor(Interceptor interceptor) { interceptors.add(interceptor); } public List getInterceptors() { return Collections.unmodifiableList(interceptors); } } ``` ## 总结 责任链模式的优点: - 降低对象直接的耦合度,对象会自动传递到下一个责任节点,不管是引用方式,还是非引用方式。 - 增强拓展性,如果需要添加新的责任节点,也比较方便,实现特定的接口即可。 - 责任节点的顺序可控,可以指定一个顺序属性,排序即可。 - 每个责任节点职责专一,只处理自己的任务,符合类的单一职责原则。 责任链的缺点: - 如果责任链比较长,性能会受影响。 - 责任链可能会中途断掉,请求不一定会被接收。 责任链一般是在流程化的处理中,多个节点处理同一份数据,依次传递,可能有顺序要求,也可能没有,处理器的能力抽象成接口,方便拓展。
-
一、前言前面已经讲解了快速上手SpringBoot入门程序制作的四种方式,相信各位小伙伴们已经可以熟练的使用这些方式来创建一个简单的web程序了,但是仅仅知道这些还是不够的。接下来,带大家一起了解parent、starter、引导类、以及内嵌Tomcat相关的知识!二、百度百科Spring Boot是由Pivotal团队提供的全新框架,其设计目的是用来简化新Spring应用的初始搭建以及开发过程。该框架使用了特定的方式来进行配置,从而使开发人员不再需要定义样板化的配置。通过这种方式,Spring Boot致力于在蓬勃发展的快速应用开发领域(rapid application development)成为领导者。三、简化开发从百度百科中可以看出,其目的是用来简化Spring!那么到底简化在什么地方呢?让我们想想在学习SSM时,做过原始SpringMVC程序的小伙伴应该知道,写SpringMVC程序,最基础的spring-web和spring-webmvc这两个坐标是必须的,这些还不包含我们使用的json啊等等坐标,现在呢?一个坐标搞定!以前写配置类或者配置文件,然后用什么东西就要自己写加载bean这些东西,现在呢?什么都没写,照样能用。有以下优点:简化依赖配置简化常用工程相关配置内置服务器,比如Tomcat别着急,让我们慢慢来探讨探讨其中的奥秘~四、parent介绍打开创建好的springboot程序,可以看见pom.xml文件中的<parent> </parent> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.6.4</version> <relativePath/> </parent>这里的<version>2.6.4<version>就是自己使用的springboot版本,打开后可以发现其中又继承了一个坐标,引入了很多依赖<parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-dependencies</artifactId> <version>2.6.4</version> </parent>再次点击打开,就可以找到其中的奥秘了。从下图我们可以发现各式各样的依赖版本号属性,下面列出依赖版本属性的局部,可以看的出来,定义了若干个技术的依赖版本号再看看下图,各式各样的的依赖坐标信息,可以看出依赖坐标定义中没有具体的依赖版本号,而是引用了第一组信息中定义的依赖版本属性值注意:上面的依赖坐标定义是出现在<dependencyManagement>标签中的,其实是对引用坐标的依赖管理,并不是实际使用的坐标。因此当我们的项目中继承了这组parent信息后,在不使用对应坐标的情况下,前面的这组定义是不会具体导入某个依赖的最后来看看使用不同的springboot版本时,其对应的pom依赖文件有什么不同。我这里对比的是springboot2.5.6版本和springboot2.6.4从图中可以清楚的看到,当我们使用不同的springboot版本时,他们的依赖版本就会不同。这也确保了,在使用springboot时,我们可以在某种程度上避免版本冲突的复杂问题,方便了程序员们的开发!五、starter介绍SpringBoot关注到开发者在实际开发时,对于依赖坐标的使用往往都有一些固定的组合方式,比如使用spring-webmvc就一定要使用spring-web。每次都要固定搭配着写,非常繁琐,而且格式固定,没有任何技术含量。SpringBoot一看这种情况,把所有的技术使用的固定搭配格式都给开发出来,以后我们使用某个技术,就不用一次写一堆依赖了,直接用springboot做好的这个东西就好了,对于这样的固定技术搭配,SpringBoot给它起了个名字叫做starter。starter定义了使用某种技术时对于依赖的固定搭配格式,也是一种最佳解决方案,使用starter可以帮助开发者减少依赖配置 <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency>比如我想开发web应用,就需要引入上面的web对应的starter依赖,并没有写SpringMVC的坐标,点击spring-boot-starter-web我们会发现在spring-boot-starter-web中又定义了若干个具体依赖的坐标通过上图我们可以细心的发现叫做spring-boot-starter-json的名字中也有starter,打开看看里面有什么?我们可以发现,这个starter中又包含了若干个坐标,其实就是使用SpringMVC开发通常都会使用到Json,使用json又离不开这里面定义的这些坐标,看来还真是方便,SpringBoot把我们开发中使用的东西能用到的都给提前做好了。仔细看完会发现,里面有一些我们没用过的。的确会出现这种过量导入的可能性,不过没关系,可以通过maven中的排除依赖剔除掉一部分。不过你不管它也没事,大不了就是过量导入呗。到这里基本上得到了一个信息,使用starter可以帮开发者快速配置依赖关系六、starter与parent的区别朦朦胧胧中感觉starter与parent好像都是帮助我们简化配置的,但是功能又不一样:starter是一个坐标中定了若干个坐标,以前写多个的,现在写一个,是用来减少依赖配置的书写量的parent是定义了几百个依赖版本号,以前写依赖需要自己手工控制版本,现在由SpringBoot统一管理,这样就不存在版本冲突了,是用来减少依赖冲突的温馨提示 SpringBoot官方给出了好多个starter的定义,方便我们使用,而且名称都是如下格式命名规则:spring-boot-starter-技术名称七、引导类介绍配置说完了,我们发现SpringBoot确实帮助我们减少了很多配置工作,下面说一下程序是如何运行的。目前程序运行的入口就是SpringBoot工程创建时自带的那个类了,带有main方法的那个类,运行这个类就可以启动SpringBoot工程的运行,我的是这个:@SpringBootApplication public class Springboot0101Application { public static void main(String[] args) { SpringApplication.run(Springboot0101Application.class, args); }写代码测试一下,先创建一个User类,把它放在容器中@Component public class User { }然后再写一个BookController类,也把它放在容器中@RestController @RequestMapping("/books") public class BookController { @GetMapping("/getBooks") public String getBooks() { System.out.println("springboot程序正在运行呢~"); return "Hello,SpringBoot is running"; } }看看我对应类的目录结构:最后写代码测试一下:@SpringBootApplication public class Springboot0101Application { public static void main(String[] args) { ConfigurableApplicationContext applicationContext = SpringApplication.run(Springboot0101Application.class, args); BookController bookBean = applicationContext.getBean(BookController.class); System.out.println("The message of bookBean : " + bookBean); User userBean = applicationContext.getBean(User.class); System.out.println("The message of userBean : " + userBean); } }运行结果:看到结果,小伙伴们不难猜想了——SpringBoot程序启动是创建了一个Spring容器对象吧?答案就是如此!Springboot0101Application这个类在SpringBoot程序中是所有功能的入口,称这个类为引导类。作为一个引导类最典型的特征就是当前类上方声明了一个注解@SpringBootApplication点击进入@SpringBootApplication,我们可以看到:这里面有我们之前学习SSM时用到的包扫描注解,再点击进入@SpringBootConfiguration内:我们可以发现,它最终使用了@Configuration注解,所以,归根到底,我们使用的引用类,也是一个配置类。八、内嵌Tomcat1、Tomcat定义位置程序现在已经运行了,通过引导类的main方法运行了起来。但是运行java程序不应该是执行完就结束了吗?但是我们现在明显是启动了一个web服务器啊,不然网页怎么能正常访问呢?这个服务器是在哪里写的呢?认真想一想,它就在我们引入的spring-boot-starter-web场景starter中,我们打开它来看一看:这里面有一个核心的坐标,tomcat-embed-core,叫做tomcat内嵌核心。就是这个东西把tomcat功能引入到了我们的程序中。2、Tomcat运行原理再来说第二个问题,这个服务器是怎么运行的?Tomcat服务器是一款软件,而且是一款使用java语言开发的软件,既然是使用java语言开发的,运行的时候肯定符合java程序运行的原理,java程序运行靠的是什么?对象呀,一切皆对象,万物皆对象。那tomcat运行起来呢?也是对象。如果是对象,那Spring容器是用来管理对象的,这个对象能不能交给Spring容器管理呢?答案是可以的!tomcat服务器运行其实是以对象的形式在Spring容器中运行的,怪不得我们没有安装这个tomcat,而且还能用。闹了白天这东西最后是以一个对象的形式存在,保存在Spring容器中悄悄运行的。具体运行的是什么呢?其实就是上前面提到的那个tomcat内嵌核心具体内嵌核心依赖如下:<dependency> <groupId>org.apache.tomcat.embed</groupId> <artifactId>tomcat-embed-core</artifactId> <version>9.0.58</version> <scope>compile</scope> <exclusions> <exclusion> <artifactId>tomcat-annotations-api</artifactId> <groupId>org.apache.tomcat</groupId> </exclusion> </exclusions> </dependency>3、更换内嵌Tomcat那既然是个对象,如果把这个对象从Spring容器中去掉是不是就没有web服务器的功能呢?当然可以,通过依赖排除可以去掉这个web服务器功能。根据SpringBoot的工作机制,用什么技术,加入什么依赖就行了。我选择的是SpringBoot提供的内置服务器jetty更换代码如下: <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> <exclusions> <exclusion> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-tomcat</artifactId> </exclusion> </exclusions> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-jetty</artifactId> </dependency>让我们运行一下看看是什么样的结果:输出结果是没有问题的,但是服务器就不是默认的Tomcat了,而是我选择的jetty服务器链接:https://bbs.huaweicloud.com/blogs/336909
-
什么是 Spring BootSpring Boot 是由 Pivotal 团队提供的基于 Spring 的全新框架,其设计目的是为了简化 Spring 应用的搭建和开发过程。该框架遵循“约定大于配置”原则,采用特定的方式进行配置,从而使开发者无需定义大量的 XML 配置。通过这种方式,Spring Boot 致力于在蓬勃发展的快速应用开发领域成为领导者。Spring Boot 并不重复造轮子,而且在原有 Spring 的框架基础上封装了一层,并且它集成了一些类库,用于简化开发。换句话说,Spring Boot 就是一个大容器。我们可以注意到,没有写任何的配置文件,更没有显示使用任何容器,只需要启动 Main 方法即可开启 Web 服务,从而访问到 HelloController 类里定义的路由地址。这就是 Spring Boot 的强大之处,它默认集成了 Tomcat 容器,通过 Main 方法编写的 SpringApplication.run 方法即可启动内置 Tomcat。它是如何启动的,内部又是如何运行的呢?具体原理我将在《第03课:Spring Boot 启动原理》一节中具体分析。在上面的示例中,我们没有定义应用程序启动端口,可以看到控制台,它开启了 8080 端口,这是 Spring Boot 默认的启动端口。Spring Boot 提供了默认的配置,我们也可以改变这些配置,具体方法将在后面介绍。我们在启动类里加入 @SpringBootApplication 注解,则这个类就是整个应用程序的启动类。如果不加这个注解,启动程序将会报错,读者可以尝试一下。Spring Boot 还有一个特点是利用注解代码繁琐的 XML 配置,整个应用程序只有一个入口配置文件,那就是 application.yml 或 application.properties。接下来,我将介绍其配置文件的用法。properties 和 yaml在前面的示例代码中,我们并没有看到该配置文件,那是因为 Spring Boot 对每个配置项都有默认值。当然,我们也可以添加配置文件,用以覆盖其默认值,这里以 .properties 文件为例,首先在 resources 下新建一个名为 application.properties(注意:文件名必须是 application)的文件,键入内容为:server.port=8081server.servlet.context-path=/api并且启动 Main 方法,这时程序请求地址则变成了:http://localhost:8081/api/hello。Spring Boot 支持 properties 和 yaml 两种格式的文件,文件名分别对应 application.properties 和 application.yml,下面贴出 yaml 文件格式供大家参考:server: port: 8080 servlet: context-path: /api可以看出 properties 是以逗号隔开,而 yaml 则换行+ 两个空格 隔开,这里需要注意的是冒号后面必须空格,否则会报错。yaml 文件格式更清晰,更易读,这里作者建议大家都采用 yaml 文件来配置。以上示例只是小试牛刀,更多的配置将在后面的课程中讲解。本教程的所有配置均采用 yaml 文件。打包、运行Spring Boot 打包分为 war 和 jar 两个格式,下面将分别演示如何构建这两种格式的启动包。在 pom.xml 加入如下配置:<packaging>war</packaging><build> <finalName>api</finalName> <resources> <resource> <directory>src/main/resources</directory> <filtering>true</filtering> </resource> </resources> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> <plugin> <artifactId>maven-resources-plugin</artifactId> <version>2.5</version> <configuration> <encoding>UTF-8</encoding> </configuration> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-surefire-plugin</artifactId> <version>2.18.1</version> <configuration> <skipTests>true</skipTests> </configuration> </plugin> </plugins></build>这个时候运行 mvn package 就会生成 war 包,然后放到 Tomcat 当中就能启动,但是我们单纯这样配置在 Tomcat 是不能成功运行的,会报错,需要通过编码指定 Tomcat 容器启动,修改 DemoApplication 类:import org.springframework.boot.SpringApplication;import org.springframework.boot.autoconfigure.SpringBootApplication;import org.springframework.boot.builder.SpringApplicationBuilder;import org.springframework.boot.web.servlet.support.SpringBootServletInitializer;@SpringBootApplicationpublic class DemoApplication extends SpringBootServletInitializer { public static void main(String[] args) { SpringApplication.run(DemoApplication.class, args); } @Override protected SpringApplicationBuilder configure(SpringApplicationBuilder application) { return application.sources(DemoApplication.class); }}这时再打包放到 Tomcat,启动就不会报错了。在上述代码中,DemoApplication 类继承了 SpringBootServletInitializer,并重写 configure 方法,目的是告诉外部 Tomcat,启动时执行该方法,然后在该方法体内指定应用程序入口为 DemoApplication 类,如果通过外部 Tomcat 启动 Spring Boot 应用,则其配置文件设置的端口和 contextPath 是无效的。这时,应用程序的启动端口即是 Tomcat 的启动端口,contextPath 和 war 包的文件名相同。接下来我们继续看如果达成 jar 包,在 pom.xml 加入如下配置:<!-- 需要将包类型改为 jar 包 --><packaging>jar</packaging><build> <finalName>api</finalName> <resources> <resource> <directory>src/main/resources</directory> <filtering>true</filtering> </resource> </resources> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> <configuration> <fork>true</fork> <!-- 指定 Main 方法所在类 --> <mainClass>com.lynn.DemoApplication</mainClass> </configuration> <executions> <execution> <goals> <goal>repackage</goal> </goals> </execution> </executions> </plugin> <plugin> <artifactId>maven-resources-plugin</artifactId> <version>2.5</version> <configuration> <encoding>UTF-8</encoding> <useDefaultDelimiters>true</useDefaultDelimiters> </configuration> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-surefire-plugin</artifactId> <version>2.18.1</version> <configuration> <skipTests>true</skipTests> </configuration> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>2.3.2</version> <configuration> <source>1.8</source> <target>1.8</target> </configuration> </plugin> </plugins></build>然后通过 mvn package 打包,最后通过 java 命令启动:java -jar api.jar如果是 Linux 服务器,上述命令是前台进程,点击 Ctrl+C 进程就会停止,可以考虑用 nohup 命令开启守护进程,这样应用程序才不会自动停止。
-
Spring Cloud 是什么?在学习本课程之前,读者有必要先了解一下 Spring Cloud。Spring Cloud 是一系列框架的有序集合,它利用 Spring Boot 的开发便利性简化了分布式系统的开发,比如服务发现、服务网关、服务路由、链路追踪等。Spring Cloud 并不重复造轮子,而是将市面上开发得比较好的模块集成进去,进行封装,从而减少了各模块的开发成本。换句话说:Spring Cloud 提供了构建分布式系统所需的“全家桶”。Spring Cloud 现状目前,国内使用 Spring Cloud 技术的公司并不多见,不是因为 Spring Cloud 不好,主要原因有以下几点:Spring Cloud 中文文档较少,出现问题网上没有太多的解决方案。国内创业型公司技术老大大多是阿里系员工,而阿里系多采用 Dubbo 来构建微服务架构。大型公司基本都有自己的分布式解决方案,而中小型公司的架构很多用不上微服务,所以没有采用 Spring Cloud 的必要性。但是,微服务架构是一个趋势,而 Spring Cloud 是微服务解决方案的佼佼者,这也是作者写本系列课程的意义所在。Spring Cloud 优缺点其主要优点有:集大成者,Spring Cloud 包含了微服务架构的方方面面。约定优于配置,基于注解,没有配置文件。轻量级组件,Spring Cloud 整合的组件大多比较轻量级,且都是各自领域的佼佼者。开发简便,Spring Cloud 对各个组件进行了大量的封装,从而简化了开发。开发灵活,Spring Cloud 的组件都是解耦的,开发人员可以灵活按需选择组件。接下来,我们看下它的缺点:项目结构复杂,每一个组件或者每一个服务都需要创建一个项目。部署门槛高,项目部署需要配合 Docker 等容器技术进行集群部署,而要想深入了解 Docker,学习成本高。Spring Cloud 的优势是显而易见的。因此对于想研究微服务架构的同学来说,学习 Spring Cloud 是一个不错的选择。Spring Cloud 和 Dubbo 对比Dubbo 只是实现了服务治理,而 Spring Cloud 实现了微服务架构的方方面面,服务治理只是其中的一个方面。下面通过一张图对其进行比较:可以看出,Spring Cloud 比较全面,而 Dubbo 由于只实现了服务治理,需要集成其他模块,需要单独引入,增加了学习成本和集成成本。Spring Cloud 学习Spring Cloud 基于 Spring Boot,因此在研究 Spring Cloud 之前,本课程会首先介绍 Spring Boot 的用法,方便后续 Spring Cloud 的学习。本课程不会讲解 Spring MVC 的用法,因此学习本课程需要读者对 Spring 及 Spring MVC 有过研究。本课程共分为四个部分:第一部分初识 Spring Boot,掌握 Spring Boot 基础知识,为后续入门 Spring Cloud 打好基础 。第二部分 Spring Cloud 入门篇,主要介绍 Spring Cloud 常用模块,包括服务发现、服务注册、配置中心、链路追踪、异常处理等。第三部分 Spring Cloud 进阶篇,介绍大型分布式系统中事务处理、线程安全等问题,并以一个实例项目手把手教大家搭建完整的微服务系统。第四部分 Spring Cloud 高级篇,解析 Spring Cloud 源码,并讲解如何部署基于 Spring Cloud 的大型分布式系统。本课程的所有示例代码均可在:https://github.com/lynnlovemin/SpringCloudLesson 下载。
-
Spring Cloud是一系列框架的有序集合。它利用Spring Boot的开发便利性巧妙地简化了分布式系统基础设施的开发,如服务发现注册、配置中心、消息总线、负载均衡、断路器、数据监控等,都可以用Spring Boot的开发风格做到一键启动和部署。Spring Cloud并没有重复制造轮子,它只是将各家公司开发的比较成熟、经得起实际考验的服务框架组合起来,通过Spring Boot风格进行再封装屏蔽掉了复杂的配置和实现原理,最终给开发者留出了一套简单易懂、易部署和易维护的分布式系统开发工具包。外文名Spring Cloud性 质一系列框架的有序集合目录1 Spring Cloud组成2 Spring Cloud前景Spring Cloud组成编辑 播报Spring Cloud的子项目,大致可分成两类,一类是对现有成熟框架”Spring Boot化”的封装和抽象,也是数量最多的项目;第二类是开发了一部分分布式系统的基础设施的实现,如Spring Cloud Stream扮演的就是kafka, ActiveMQ这样的角色。对于我们想快速实践微服务的开发者来说,第一类子项目就已经足够使用,如:Spring Cloud Netflix 是对Netflix开发的一套分布式服务框架的封装,包括服务的发现和注册,负载均衡、断路器、REST客户端、请求路由等。Spring Cloud Config 将配置信息中央化保存, 配置Spring Cloud Bus可以实现动态修改配置文件Spring Cloud Stream 分布式消息队列,是对Kafka, MQ的封装Spring Cloud Security 对Spring Security的封装,并能配合Netflix使用Spring Cloud Zookeeper 对Zookeeper的封装,使之能配置其它Spring Cloud的子项目使用Spring Cloud Eureka 是 Spring Cloud Netflix 微服务套件中的一部分,它基于Netflix Eureka 做了二次封装,主要负责完成微服务架构中的服务治理功能。Spring Cloud前景编辑 播报Spring Cloud对于中小型互联网公司来说是一种福音,因为这类公司往往没有实力或者没有足够的资金投入去开发自己的分布式系统基础设施,使用Spring Cloud一站式解决方案能在从容应对业务发展的同时大大减少开发成本。同时,随着近几年微服务架构和Docker容器概念的火爆,也会让Spring Cloud在未来越来越“云”化的软件开发风格中立有一席之地,尤其是在五花八门的分布式解决方案中提供了标准化的、全站式的技术方案,意义可能会堪比当年Servlet规范的诞生,有效推进服务端软件系统技术水平的进步。
-
Spring介绍Spring 是一个开源框架,是一个分层的 JavaEE 一站式框架。所谓一站式框架是指 Spring 有 JavaEE 开发的每一层解决方案。WEB层:SpringMVCService层:Spring的Bean管理,声明式事务DAO层:Spring的JDBC模板,ORM模板优点:IOC:方便解耦合AOP:对程序进行扩展轻量级框架方便与其他框架整合Spring使用Spring开发包解压后的目录介绍:docs: Spring 开发规范和APIlibs: Spring jar 包和源代码schema: Spring 配置文件的约束DataAccess 用于数据访问,WEB 用于页面显示,核心容器也就是IOC部分。控制反转(IOC)控制反转(Inversion of Control)是指将对象的创建权反转(交给)Spring。使用IOC就需要导入IOC相关的包,也就是上图中核心容器中的几个包:beans,context,core,expression四个包。实现原理传统方式创建对象:UserDAO userDAO=new UserDAO();进一步面向接口编程,可以多态:UserDAO userDAO=new UserDAOImpl();这种方式的缺点是接口和实现类高耦合,切换底层实现类时,需要修改源代码。程序设计应该满足OCP元祖,在尽量不修改程序源代码的基础上对程序进行扩展。此时,可以使用工厂模式:class BeanFactory{ public static UserDAO getUserDAO(){ return new UserDAOImpl(); }}此种方式虽然在接口和实现类之间没有耦合,但是接口和工厂之间存在耦合。使用工厂+反射+配置文件的方式,实现解耦,这也是 Spring 框架 IOC 的底层实现。//xml配置文件//class BeanFactory{ public static Object getBean(String id){ //解析XML //反射 Class clazz=Class.forName(); return clazz.newInstance(); }}IOC XML 开发在 docs 文件中包含了 xsd-configuration.hmtl 文件。其中定义了 beans schema。 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> //在此配置bean 调用类:ApplicationContext applicationContext=new ClassPathXmlApplicationContext("applicationContext.xml");UserService userService=(UserService)applicationContext.getBean("userService");userService.save();IOC 和 DIDI 指依赖注入,其前提是必须有 IOC 的环境,Spring 管理这个类的时候将类的依赖的属性注入进来。例如,在UserServiceImpl.java中:public class UserServiceImpl implements UserService{private String name;public void setName(String name){this.name=name;}public void save(){System.out.println("save "+name);}}在配置文件中: xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> 测试代码:@Testpublic void demo2(){//创建Spring工厂ApplicationContext applicationContext=new ClassPathXmlApplicationContext("applicationContext.xml");UserService userService=(UserService)applicationContext.getBean("userService");userService.save();}运行结果:save tony可以看到,在配置文件中配置的属性,在 Spring 管理该类的时候将其依赖的属性成功进行了设置。如果不使用依赖注入,则无法使用接口,只能使用实现类来进行设置,因为接口中没有该属性。Spring 的工厂类BeanFactory: 老版本的工厂类,在调用getBean()方法时,才会生成类的实例。ApplicationContext: 在加载配置文件的时候,就会将 Spring 管理的类都实例化。有两个实现类:ClassPathXmlApplicationContext: 加载类路径下的配置文件FileSystemXmlApplicationContext: 加载磁盘下的配置文件bean标签配置id: 唯一约束,不能出现特殊字符name: 理论上可以重复,但是开发中最好不要。可以出现特殊字符生命周期:init-method: bean被初始化的时候执行的方法destroy-method: bean被销毁的时候执行的方法作用范围:scope: bean的作用范围,有如下几种,常用的是前两种singleton: 默认使用单例模式创建prototype: 多例request: 在web项目中,spring 创建类后,将其存入到 request 范围中session: 在web项目中,spring 创建类后,将其存入到 session 范围中globalsession: 在web项目中,必须用在 porlet 环境属性注入设置构造方法方式的属性注入: Car 类在构造方法中有两个属性,分别为 name 和 price。 set 方法属性注入: Employee 类在有两个 set 方法,分别设置普通类型的 name 和引用类型的 Car (使用 ref 指向引用类型的 id 或 name)。 P名称空间的属性注入: 首先需要引入p名称空间: //引入p名称空间 xmlns:p="http://www.springframework.org/schema/p" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">如果是普通属性:如果是引用类型:SpEL(Spring Expression Language)属性注入(Spring 3.x以上版本) 集合类型属性注入: qirui baoma benchi 多模块开发配置在加载配置文件的时候,加载多个配置文件在一个配置文件中引入多个配置文件,通过实现IOC 注解开发示例引入jar包: 除了要引入上述的四个包之外,还需要引入aop包。创建 applicationContext.xml ,使用注解开发引入 context 约束(xsd-configuration.html) xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> 组件扫描: 使用IOC注解开发,需要配置组件扫描,也就是哪些包下的类使用IOC的注解。在类上添加注解使用注解设置属性的值属性如果有set方法,将属性注入的注解添加到set方法属性没有set方法,将注解添加到属性上。@Component("UserDao")//相当于配置了一个 其id为UserDao,对应的类为该类public class UserDAOImpl implements UserDAO {@Overridepublic void save() {// TODO Auto-generated method stubSystem.out.println("save");} }注解详解@Component组件注解,用于修饰一个类,将这个类交给 Spring 管理。有三个衍生的注解,功能类似,也用来修饰类。@Controller:修饰 web 层类@Service:修饰 service 层类@Repository:修饰 dao 层类2.属性注入普通属性使用 @Value 来设置属性的值对象属性使用 @Autowired ,这个注解是按照类型来进行属性注入的。如果希望按照 bean 的名称或id进行属性注入,需要用 @Autowired 和 @Qualifier 一起使用实际开发中,使用 @Resource(name=" ") 来进行按照对象的名称完成属性注入3.其他注解@PostConstruct 相当于 init-method,用于初始化函数的注解@PreDestroy 相当于 destroy-method,用于销毁函数的注解@Scope 作用范围的注解,常用的是默认单例,还有多例 @Scope("prototype")IOC 的 XML 和注解开发比较适用场景:XML 适用于任何场景;注解只适合自己写的类,不是自己提供的类无法添加注解。可以使用 XML 管理 bean,使用注解来进行属性注入AOP开发AOP 是 Aspect Oriented Programming 的缩写,意为面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术,是OOP的延续。AOP 能够对程序进行增强,在不修改源码的情况下,可以进行权限校验,日志记录,性能监控,事务控制等。也就是说功能分为两大类,一类是核心业务功能,一类是辅助增强功能。两类功能彼此独立进行开发。比如登录功能是核心业务功能,日志功能是辅助增强功能,如果有需要,将日志和登录编制在一起。辅助功能就称为切面,这种能选择性的、低耦合的把切面和核心业务功能结合的编程思想称为切面编程。底层实现JDK 动态代理只能对实现了接口的类产生代理。Cglib 动态代理可以对没有实现接口的类产生代理对象,生成的是子类对象。使用 JDK 动态代理:public interface UserDao {public void insert();public void delete();public void update();public void query();}实现类:public class UserDaoImpl implements UserDao { @Override public void insert() { System.out.println("insert"); } @Override public void delete() { System.out.println("delete"); } @Override public void update() { System.out.println("update"); } @Override public void query() { System.out.println("query"); } }JDK 代理:public class JDKProxy implements InvocationHandler{private UserDao userDao;public JDKProxy(UserDao userDao){this.userDao=userDao;}public UserDao createProxy(){UserDao userDaoProxy=(UserDao)Proxy.newProxyInstance(userDao.getClass().getClassLoader(),userDao.getClass().getInterfaces(), this);return userDaoProxy;}@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {if("update".equals(method.getName())){System.out.println("权限校验");return method.invoke(userDao, args);}return method.invoke(userDao, args);}}通过动态代理增强了 update 函数。 测试类:public class Demo1 {@Testpublic void demo1(){UserDao userDao=new UserDaoImpl();UserDao proxy=new JDKProxy(userDao).createProxy();proxy.insert();proxy.delete();proxy.update();proxy.query();}}运行结果为:insertdelete权限校验updatequeryCglibCglib 是第三方开源代码生成类库,可以动态添加类的属性和方法。与上边JDK代理不同,Cglib的使用方式如下:public class CglibProxy implements MethodInterceptor{//传入增强的对象private UserDao customerDao;public CglibProxy(UserDao userDao){this.userDao=userDao;}public UserDao createProxy(){Enhancer enhancer=new Enhancer();enhancer.setSuperclass(userDao.getClass());enhancer.setCallback(this);UserDao proxy=(UserDao)enhancer.create();return proxy;}@Overridepublic Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {if("save".equals(method.getName())){System.out.println("enhance function");return methodProxy.invokeSuper(proxy, args);}return methodProxy.invokeSuper(proxy, args);}}如果实现了接口的类,底层采用JDK代理。如果不是实现了接口的类,底层采用 Cglib代理。IOC与传统方式的比较获取对象方式:传统通过 new 关键字主动创建一个对象。IOC 方式中,将对象的生命周期交给 Spring 管理,直接从 Spring 获取对象。也就是控制反转————将控制权从自己手中交到了 Spring 手中。Spring 的 AOP 开发(AspectJ 的 XML 方式)AspectJ 是一个 AOP 的框架,Spring 引入 AspectJ,基于 AspectJ 进行 AOP 的开发。相关术语Joinpoint: 连接点,可以被拦截到的点。也就是可以被增强的方法都是连接点。Pointcut: 切入点,真正被拦截到的点,也就是真正被增强的方法Advice: 通知,方法层面的增强。对某个方法进行增强的方法,比如对 save 方法进行权限校验,权限校验的方法称为通知。Introduction: 引介,类层面的增强。Target: 目标,被增强的对象(类)。Weaving: 织入,将 advice 应用到 target 的过程。Proxy: 代理对象,被增强的对象。Aspect: 切面,多个通知和多个切入点的组合。使用方法引入相关包引入配置文件 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">编写目标类并配置:public class ProductDaoImpl implements ProductDao {@Overridepublic void save() {System.out.println("save");}@Overridepublic void update() {System.out.println("update");}@Overridepublic void find() {System.out.println("find");}@Overridepublic void delete() {System.out.println("delete");}}编写切面类,假设用于权限验证并配置public class MyAspectXML {public void checkPri(){System.out.println("check auth");}}通过AOP配置完成对目标类的增强 通知类型前置通知:在目标方法执行前操作,可以获得切入点信息public void checkPri(JoinPoint joinPoint){System.out.println("check auth "+joinPoint);}后置通知:在目标方法执行后操作,可以获得方法返回值public void writeLog(Object result){ System.out.println("writeLog "+result);}环绕通知:在目标方法执行前和后操作,可以阻止目标方法执public Object around(ProceedingJoinPoint joinPoint) throws Throwable{System.out.println("before");Object result=joinPoint.proceed();System.out.println("after");return result;}异常抛出通知:程序出现异常时操作public void afterThrowing(Throwable ex){System.out.println("exception "+ex.getMessage());}最终通知:相当于finally块,无论代码是否有异常,都会执行public void finallyFunc(){System.out.println("finally");}引介通知:不常用Spring 切入点表达式基于 execution 函数完成语法:[访问修饰符] 方法返回值 包名.类名.方法名(参数)其中任意字段可以使用*代替表示任意值Spring 的 AOP 基于 AspectJ 注解开发开发步骤引入jar包设置配置文件:xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns:context="http://www.springframework.org/schema/context"xmlns:aop="http://www.springframework.org/schema/aop"xmlns:tx="http://www.springframework.org/schema/tx"xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/contexthttp://www.springframework.org/schema/context/spring-context.xsdhttp://www.springframework.org/schema/aophttp://www.springframework.org/schema/aop/spring-aop.xsdhttp://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd">编写配置目标类public class OrderDao {public void save(){System.out.println("save order");}public void update(){System.out.println("update order");}public void delete(){System.out.println("delete order");}public void find(){System.out.println("find order");}}开启aop注解自动代理编写切面类并配置@Aspectpublic class MyAspectAnno {@Before(value="execution(* demo1.OrderDao.save(..))")public void before(){System.out.println("before");}}注解通知类型@Before: 前置通知@AfterReturning: 后置通知@AfterReturning(value="execution(* demo1.OrderDao.save(..))",returning="result")public void after(Object result){System.out.println("after "+result);}@Around:环绕通知@Around(value="execution(* demo1.OrderDao.save(..))")public Object around(ProceedingJoinPoint joinPoint) throws Throwable{System.out.println("before");Object obj=joinPoint.proceed();System.out.println("after");return obj;}@AfterThrowing: 抛出异常@AfterThrowing(value="execution(* demo1.OrderDao.save(..))",throwing="e")public void afterThrowing(Throwable e){System.out.println("exception:"+e.getMessage();}@After: 最终通知@After(value="execution(* demo1.OrderDao.save(..))")public void after(){System.out.println("finally");}@PointCut:切入点注解@PointCut(value="execution(* demo1.OrderDao.save(..))")private void pointcut1(){}此时,在上述通知的注解中,value可以替换为该函数名,例如:@After(value="MyAspect.pointcut1()")public void after(){System.out.println("finally");}这个注解的好处是,只需要维护切入点即可,不用在修改时修改每个注解。Spring 的 JDBC 模板Spring 对持久层也提供了解决方案,也就是 ORM 模块和 JDBC 的模板。针对 JDBC ,提供了 org.springframework.jdbc.core.JdbcTemplate 作为模板类。使用 JDBC 模板引入jar包,数据库驱动,Spring 的 jdbc 相关包。基本使用:public void demo1(){ //创建连接池DriverManagerDataSource dataSource=new DriverManagerDataSource();dataSource.setDriverClassName("com.mysql.jdbc.Driver");dataSource.setUrl("jdbc:mysql:///spring4");dataSource.setUsername("root");dataSource.setPassword("123456");//创建JDBC模板 JdbcTemplate jdbcTemplate=new JdbcTemplate(dataSource);jdbcTemplate.update("insert into account values (null,?,?)", "xiaoming",1000d);}将连接池和模板交给 Spring 管理配置文件: 测试文件:@RunWith(SpringJUnit4ClassRunner.class)@ContextConfiguration("classpath:applicationContext.xml")public class JdbcDemo2 {@Resource(name="jdbcTemplate")private JdbcTemplate jdbcTemplate;@Testpublic void demo2(){jdbcTemplate.update("insert into account values (null,?,?)", "xiaolan",1000d);}}使用开源数据库连接池使用 DBCP 的配置:使用 C3P0 的配置:引入外部属性文件首先建立外部属性文件:jdbc.driverClass=com.mysql.jdbc.Driverjdbc.url=jdbc:mysql://192.168.66.128/spring4jdbc.username=rootjdbc.password=123456然后对属性文件进行配置:CRUD操作insert, update, delete 语句都借助模板的 update 方法进行操作。public void demo(){jdbcTemplate.update("insert into account values (null,?,?)", "xiaoda",1000d);jdbcTemplate.update("update account set name=?,money=? where id=?", "xiaoda",1000d,2);jdbcTemplate.update("delete from account where id=?", 6);}查询操作:public void demo3(){String name=jdbcTemplate.queryForObject("select name from account where id=?",String.class,5);long count=jdbcTemplate.queryForObject("select count(*) from account",Long.class);}将返回的结果封装成为类:public void demo4(){Account account=jdbcTemplate.queryForObject("select * from account where id=?", new MyRowMapper(),5);}其中:class MyRowMapper implements RowMapper{@Overridepublic Account mapRow(ResultSet rs, int rowNum) throws SQLException {Account account=new Account();account.setId(rs.getInt("id"));account.setName(rs.getString("name"));account.setMoney(rs.getDouble("money"));return account;}}Spring的事务管理事务事务是指逻辑上的一组操作,组成这组操作的各个单元,要么全部成功,要么全部失败。具有四个特性:原子性:事务不可分一致性:事务执行前后数据完整性保持一致隔离性:一个事务的执行不应该受到其他事务干扰持久性:一旦事务结束,数据就持久化到数据库如果不考虑隔离性会引发安全性问题:读问题:脏读:一个事务读到另一个事务未提交的数据不可重复读:一个事务读到另一个事务已经提交的 update 数据,导致一个事务中多次查询结果不一致幻读:一个事务读到另一个事务已经提交的 insert 数据,导致一个事务中多次查询结果不一致写问题:丢失更新解决读问题:设置事务隔离级别Read uncommitted: 未提交读,无法解决任何读问题Read committed: 已提交读,解决脏读问题Repeatable read: 重复读,解决脏读和不可重复读问题Serializable:序列化,解决所有读问题事务管理APIPlatformTransactionManager: 平台事务管理器这是一个接口,拥有多个不同的实现类,如 DataSourceTransactionManager 底层使用了JDBC 管理事务; HibernateTransactionManager 底层使用了 Hibernate 管理事务。TransactionDefinition: 事务定义信息用于定义事务的相关信息,如隔离级别、超时信息、传播行为、是否只读等TransactionStatus: 事务的状态用于记录在事务管理过程中,事务的状态的对象。上述API的关系: Spring 在进行事务管理的时候,首先平台事务管理器根据事务定义信息进行事务管理,在事务管理过程当中,产生各种此状态,将这些状态信息记录到事务状态的对象当中。事务的传播行为事务的传播行为主要解决业务层(Service)方法相互调用的问题,也就是不同的业务中存在不同的事务时,如何操作。Spring 中提供了7种事务的传播行为,分为三类:保证多个操作在同一个事务中PROPAGATION_REQUIRED: B方法调用A方法,如果A中有事务,使用A中的事务并将B中的操作包含到该事务中;否则新建一个事务,将A和B中的操作包含进来。(默认)PROPAGATION_SUPPORTS:如果A中有事务,使用A的事务;否则不使用事务PROPAGATION_MANDATORY:如果A中有事务,使用A的事务;否则抛出异常保证多个操作不在同一个事务中PROPAGATION_REQUIRES_NEW:如果A中有事务,将其挂起,创建新事务,只包含自身操作。否则,新建一个事务,只包含自身操作。PROPAGATION_NOT_SUPPORTED:如果A中有事务,挂起,不使用事务。PROPAGATION_NEVER:如果A中有事务,抛出异常,也即不能用事务运行。嵌套事务PROPAGATION_NESTED:如果A有事务,按照A的事务执行,执行完成后,设置一个保存点,然后执行B的操作。如果出现异常,可以回滚到最初状态或保存点状态。实例以转账为例,业务层的DAO层类如下:public interface AccountDao {public void outMoney(String from,Double money);public void inMoney(String to,Double money);}public class AccountDaoImpl extends JdbcDaoSupport implements AccountDao{@Overridepublic void outMoney(String from, Double money) { this.getJdbcTemplate().update("update account set money = money - ? where name = ?",money,from);}@Overridepublic void inMoney(String to, Double money) {this.getJdbcTemplate().update("update account set money = money + ? where name = ?",money,to);}}public interface AccountService {public void transfer(String from,String to,Double money);}public class AccountServiceImpl implements AccountService {private AccountDao accountDao;public void setAccountDao(AccountDao accountDao) {this.accountDao = accountDao;}@Overridepublic void transfer(String from, String to, Double money) {accountDao.outMoney(from, money);accountDao.inMoney(to, money);}}在xml中进行类的配置:
-
漏洞描述:作为一种函数计算框架,Spring Cloud Function 是基于Spring Boot而产生的。Spring Cloud Function能抽象出所有传输细节和基础架构,允许开发人员保留所有熟悉的工具和流程,并专注于业务逻辑,提高业务效率。Spring Cloud Function 要实现高级别要求的目标:1.通过函数促进业务逻辑的实现。2.将业务逻辑的开发生命周期与特定运行时分离,以便相同的代码可以作为任务运行。3.支持Serverless 提供商之间的统一编程模型,以及独立运行的能力。4.在Serverless提供商上启用Spring Boot 功能。近期,Spring Cloud官方修复了一个Spring Cloud Function中的SPEL表达式注入漏洞。由于Spring Cloud Function中RoutingFunction类的apply方法将请求头中的“spring.cloud. function.routing-expression”参数作为Spel表达式进行处理,造成了Spel表达式注入漏洞。使得未经授权的远程攻击者可利用该漏洞执行任意代码。漏洞危害:攻击者利用此漏洞,可实现远程代码执行。通过发送恶意制作的请求,从而导致在主机上执行任意恶意代码。使得攻击者在用户运行应用程序时执行恶意程序,并控制这个受影响的系统。攻击者一旦访问该系统后,它会试图提升其权限,甚至控制企业的服务器。影响范围:3.0.0.RELEASE <= Spring Cloud Function<= 3.2.2注:发布时间为2019年11月22日至2022年2月17日漏洞等级: 高危修复方案:目前官方已针对此漏洞发布修复补丁,请受影响的用户尽快更新进行防护,官方链接:https://github.com/spring-cloud/spring-cloud-function/commit/0e89ee27b2e76138c16bcba6f4bca906c4f3744f注:目前官方暂未发布新版本,请持续关注更新链接:https://github.com/spring-cloud/spring-cloud-function/tags
-
Spring介绍Spring 是一个开源框架,是一个分层的 JavaEE 一站式框架。所谓一站式框架是指 Spring 有 JavaEE 开发的每一层解决方案。WEB层:SpringMVCService层:Spring的Bean管理,声明式事务DAO层:Spring的JDBC模板,ORM模板优点:IOC:方便解耦合AOP:对程序进行扩展轻量级框架方便与其他框架整合Spring使用Spring开发包解压后的目录介绍:docs: Spring 开发规范和APIlibs: Spring jar 包和源代码schema: Spring 配置文件的约束DataAccess 用于数据访问,WEB 用于页面显示,核心容器也就是IOC部分。控制反转(IOC)控制反转(Inversion of Control)是指将对象的创建权反转(交给)Spring。使用IOC就需要导入IOC相关的包,也就是上图中核心容器中的几个包:beans,context,core,expression四个包。实现原理传统方式创建对象:UserDAO userDAO=new UserDAO();进一步面向接口编程,可以多态:UserDAO userDAO=new UserDAOImpl();这种方式的缺点是接口和实现类高耦合,切换底层实现类时,需要修改源代码。程序设计应该满足OCP元祖,在尽量不修改程序源代码的基础上对程序进行扩展。此时,可以使用工厂模式:class BeanFactory{ public static UserDAO getUserDAO(){ return new UserDAOImpl(); }}此种方式虽然在接口和实现类之间没有耦合,但是接口和工厂之间存在耦合。使用工厂+反射+配置文件的方式,实现解耦,这也是 Spring 框架 IOC 的底层实现。//xml配置文件//<bean id="userDAO" class="xxx.UserDAOImpl"></bean>class BeanFactory{ public static Object getBean(String id){ //解析XML //反射 Class clazz=Class.forName(); return clazz.newInstance(); }}IOC XML 开发在 docs 文件中包含了 xsd-configuration.hmtl 文件。其中定义了 beans schema。<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> //在此配置bean <bean id="userService" class="x.y.UserServiceImpl"> </bean></beans>调用类:ApplicationContext applicationContext=new ClassPathXmlApplicationContext("applicationContext.xml");UserService userService=(UserService)applicationContext.getBean("userService");userService.save();IOC 和 DIDI 指依赖注入,其前提是必须有 IOC 的环境,Spring 管理这个类的时候将类的依赖的属性注入进来。例如,在UserServiceImpl.java中:public class UserServiceImpl implements UserService{private String name;public void setName(String name){this.name=name;}public void save(){System.out.println("save "+name);}}在配置文件中:<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <bean id="userService" class="spring.demo1.UserServiceImpl"> <!--配置依赖的属性--> <property name="name" value="tony"/> </bean></beans>测试代码:@Testpublic void demo2(){//创建Spring工厂ApplicationContext applicationContext=new ClassPathXmlApplicationContext("applicationContext.xml");UserService userService=(UserService)applicationContext.getBean("userService");userService.save();}运行结果:save tony可以看到,在配置文件中配置的属性,在 Spring 管理该类的时候将其依赖的属性成功进行了设置。如果不使用依赖注入,则无法使用接口,只能使用实现类来进行设置,因为接口中没有该属性。Spring 的工厂类BeanFactory: 老版本的工厂类,在调用getBean()方法时,才会生成类的实例。ApplicationContext: 在加载配置文件的时候,就会将 Spring 管理的类都实例化。有两个实现类:ClassPathXmlApplicationContext: 加载类路径下的配置文件FileSystemXmlApplicationContext: 加载磁盘下的配置文件bean标签配置id: 唯一约束,不能出现特殊字符name: 理论上可以重复,但是开发中最好不要。可以出现特殊字符生命周期:init-method: bean被初始化的时候执行的方法destroy-method: bean被销毁的时候执行的方法作用范围:scope: bean的作用范围,有如下几种,常用的是前两种singleton: 默认使用单例模式创建prototype: 多例request: 在web项目中,spring 创建类后,将其存入到 request 范围中session: 在web项目中,spring 创建类后,将其存入到 session 范围中globalsession: 在web项目中,必须用在 porlet 环境属性注入设置构造方法方式的属性注入: Car 类在构造方法中有两个属性,分别为 name 和 price。<bean id="car" class="demo.Car"> <constructor-arg name="name" value="bmw"> <constructor-arg name="price" value="123"></bean>set 方法属性注入: Employee 类在有两个 set 方法,分别设置普通类型的 name 和引用类型的 Car (使用 ref 指向引用类型的 id 或 name)。<bean id="employee" class="demo.Employee"> <property name="name" value="xiaoming"> <property name="car" ref="car"></bean>P名称空间的属性注入: 首先需要引入p名称空间:<beans xmlns="http://www.springframework.org/schema/beans" //引入p名称空间 xmlns:p="http://www.springframework.org/schema/p" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"></beans>如果是普通属性:<bean id="car" class="demo.Car" p:name="bmv" p:price="123"></bean>如果是引用类型:<bean id="employee" class="demo.Employee" p:name="xiaoming" p:car-ref:"car"></bean>SpEL(Spring Expression Language)属性注入(Spring 3.x以上版本)<bean id="car" class="demo.Car"> <property name="name" value="#{'xiaoming'}"> <property name="car" ref="#{car}"></bean>集合类型属性注入:<bean id="car" class="demo.Car"> <property name="namelist"> <list> <value>qirui</value> <value>baoma</value> <value>benchi</value> </list> </property></bean>多模块开发配置在加载配置文件的时候,加载多个配置文件在一个配置文件中引入多个配置文件,通过实现IOC 注解开发示例引入jar包: 除了要引入上述的四个包之外,还需要引入aop包。创建 applicationContext.xml ,使用注解开发引入 context 约束(xsd-configuration.html)<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> <!-- bean definitions here --></beans>组件扫描: 使用IOC注解开发,需要配置组件扫描,也就是哪些包下的类使用IOC的注解。<context:component-scan base-package="demo1">在类上添加注解使用注解设置属性的值属性如果有set方法,将属性注入的注解添加到set方法属性没有set方法,将注解添加到属性上。@Component("UserDao")//相当于配置了一个<bean> 其id为UserDao,对应的类为该类public class UserDAOImpl implements UserDAO {@Overridepublic void save() {// TODO Auto-generated method stubSystem.out.println("save");} }注解详解@Component组件注解,用于修饰一个类,将这个类交给 Spring 管理。有三个衍生的注解,功能类似,也用来修饰类。@Controller:修饰 web 层类@Service:修饰 service 层类@Repository:修饰 dao 层类2.属性注入普通属性使用 @Value 来设置属性的值对象属性使用 @Autowired ,这个注解是按照类型来进行属性注入的。如果希望按照 bean 的名称或id进行属性注入,需要用 @Autowired 和 @Qualifier 一起使用实际开发中,使用 @Resource(name=" ") 来进行按照对象的名称完成属性注入3.其他注解@PostConstruct 相当于 init-method,用于初始化函数的注解@PreDestroy 相当于 destroy-method,用于销毁函数的注解@Scope 作用范围的注解,常用的是默认单例,还有多例 @Scope("prototype")IOC 的 XML 和注解开发比较适用场景:XML 适用于任何场景;注解只适合自己写的类,不是自己提供的类无法添加注解。可以使用 XML 管理 bean,使用注解来进行属性注入AOP开发AOP 是 Aspect Oriented Programming 的缩写,意为面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术,是OOP的延续。AOP 能够对程序进行增强,在不修改源码的情况下,可以进行权限校验,日志记录,性能监控,事务控制等。也就是说功能分为两大类,一类是核心业务功能,一类是辅助增强功能。两类功能彼此独立进行开发。比如登录功能是核心业务功能,日志功能是辅助增强功能,如果有需要,将日志和登录编制在一起。辅助功能就称为切面,这种能选择性的、低耦合的把切面和核心业务功能结合的编程思想称为切面编程。底层实现JDK 动态代理只能对实现了接口的类产生代理。Cglib 动态代理可以对没有实现接口的类产生代理对象,生成的是子类对象。使用 JDK 动态代理:public interface UserDao {public void insert();public void delete();public void update();public void query();}实现类:public class UserDaoImpl implements UserDao { @Override public void insert() { System.out.println("insert"); } @Override public void delete() { System.out.println("delete"); } @Override public void update() { System.out.println("update"); } @Override public void query() { System.out.println("query"); } }JDK 代理:public class JDKProxy implements InvocationHandler{private UserDao userDao;public JDKProxy(UserDao userDao){this.userDao=userDao;}public UserDao createProxy(){UserDao userDaoProxy=(UserDao)Proxy.newProxyInstance(userDao.getClass().getClassLoader(),userDao.getClass().getInterfaces(), this);return userDaoProxy;}@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {if("update".equals(method.getName())){System.out.println("权限校验");return method.invoke(userDao, args);}return method.invoke(userDao, args);}}通过动态代理增强了 update 函数。 测试类:public class Demo1 {@Testpublic void demo1(){UserDao userDao=new UserDaoImpl();UserDao proxy=new JDKProxy(userDao).createProxy();proxy.insert();proxy.delete();proxy.update();proxy.query();}}运行结果为:insertdelete权限校验updatequeryCglibCglib 是第三方开源代码生成类库,可以动态添加类的属性和方法。与上边JDK代理不同,Cglib的使用方式如下:public class CglibProxy implements MethodInterceptor{//传入增强的对象private UserDao customerDao;public CglibProxy(UserDao userDao){this.userDao=userDao;}public UserDao createProxy(){Enhancer enhancer=new Enhancer();enhancer.setSuperclass(userDao.getClass());enhancer.setCallback(this);UserDao proxy=(UserDao)enhancer.create();return proxy;}@Overridepublic Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {if("save".equals(method.getName())){System.out.println("enhance function");return methodProxy.invokeSuper(proxy, args);}return methodProxy.invokeSuper(proxy, args);}}如果实现了接口的类,底层采用JDK代理。如果不是实现了接口的类,底层采用 Cglib代理。IOC与传统方式的比较获取对象方式:传统通过 new 关键字主动创建一个对象。IOC 方式中,将对象的生命周期交给 Spring 管理,直接从 Spring 获取对象。也就是控制反转————将控制权从自己手中交到了 Spring 手中。Spring 的 AOP 开发(AspectJ 的 XML 方式)AspectJ 是一个 AOP 的框架,Spring 引入 AspectJ,基于 AspectJ 进行 AOP 的开发。相关术语Joinpoint: 连接点,可以被拦截到的点。也就是可以被增强的方法都是连接点。Pointcut: 切入点,真正被拦截到的点,也就是真正被增强的方法Advice: 通知,方法层面的增强。对某个方法进行增强的方法,比如对 save 方法进行权限校验,权限校验的方法称为通知。Introduction: 引介,类层面的增强。Target: 目标,被增强的对象(类)。Weaving: 织入,将 advice 应用到 target 的过程。Proxy: 代理对象,被增强的对象。Aspect: 切面,多个通知和多个切入点的组合。使用方法引入相关包引入配置文件<?xml version="1.0" encoding="UTF-8"?><beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd"> <!-- bean definitions here --></beans>编写目标类并配置:public class ProductDaoImpl implements ProductDao {@Overridepublic void save() {System.out.println("save");}@Overridepublic void update() {System.out.println("update");}@Overridepublic void find() {System.out.println("find");}@Overridepublic void delete() {System.out.println("delete");}}<bean id="productDao" class="demo1.ProductDaoImpl"></bean>编写切面类,假设用于权限验证并配置public class MyAspectXML {public void checkPri(){System.out.println("check auth");}}<bean id="myAspect" class="demo1.MyAspectXML"></bean>通过AOP配置完成对目标类的增强<aop:config><aop:pointcut expression="execution(* demo1.ProductDaoImpl.save(..))" id="pointcut1"/><aop:aspect ref="myAspect"><aop:before method="chechPri" pointcut-ref="pointcut1"/></aop:aspect> </aop:config>通知类型前置通知:在目标方法执行前操作,可以获得切入点信息<aop:before method="chechPri" pointcut-ref="pointcut1"/>public void checkPri(JoinPoint joinPoint){System.out.println("check auth "+joinPoint);}后置通知:在目标方法执行后操作,可以获得方法返回值<aop:after-returning method="writeLog" pointcut-ref="pointcut2" returning="result"/>public void writeLog(Object result){ System.out.println("writeLog "+result);}环绕通知:在目标方法执行前和后操作,可以阻止目标方法执<aop:around method="around" pointcut-ref="pointcut3"/>public Object around(ProceedingJoinPoint joinPoint) throws Throwable{System.out.println("before");Object result=joinPoint.proceed();System.out.println("after");return result;}异常抛出通知:程序出现异常时操作<aop:after-throwing method="afterThrowing" pointcut-ref="pointcut4" throwing="ex"/>public void afterThrowing(Throwable ex){System.out.println("exception "+ex.getMessage());}最终通知:相当于finally块,无论代码是否有异常,都会执行<aop:after method="finallyFunc" pointcut-ref="pointcut4"/>public void finallyFunc(){System.out.println("finally");}引介通知:不常用Spring 切入点表达式基于 execution 函数完成语法:[访问修饰符] 方法返回值 包名.类名.方法名(参数)其中任意字段可以使用*代替表示任意值Spring 的 AOP 基于 AspectJ 注解开发开发步骤引入jar包设置配置文件:<?xml version="1.0" encoding="UTF-8"?><beans xmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns:context="http://www.springframework.org/schema/context"xmlns:aop="http://www.springframework.org/schema/aop"xmlns:tx="http://www.springframework.org/schema/tx"xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/contexthttp://www.springframework.org/schema/context/spring-context.xsdhttp://www.springframework.org/schema/aophttp://www.springframework.org/schema/aop/spring-aop.xsdhttp://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd"></beans>编写配置目标类<bean id="orderDao" class="demo1.OrderDao"></bean>public class OrderDao {public void save(){System.out.println("save order");}public void update(){System.out.println("update order");}public void delete(){System.out.println("delete order");}public void find(){System.out.println("find order");}}开启aop注解自动代理<aop:aspectj-autoproxy/>编写切面类并配置@Aspectpublic class MyAspectAnno {@Before(value="execution(* demo1.OrderDao.save(..))")public void before(){System.out.println("before");}}<bean id="myAspect" class="demo1.MyAspectAnno">注解通知类型@Before: 前置通知@AfterReturning: 后置通知@AfterReturning(value="execution(* demo1.OrderDao.save(..))",returning="result")public void after(Object result){System.out.println("after "+result);}@Around:环绕通知@Around(value="execution(* demo1.OrderDao.save(..))")public Object around(ProceedingJoinPoint joinPoint) throws Throwable{System.out.println("before");Object obj=joinPoint.proceed();System.out.println("after");return obj;}@AfterThrowing: 抛出异常@AfterThrowing(value="execution(* demo1.OrderDao.save(..))",throwing="e")public void afterThrowing(Throwable e){System.out.println("exception:"+e.getMessage();}@After: 最终通知@After(value="execution(* demo1.OrderDao.save(..))")public void after(){System.out.println("finally");}@PointCut:切入点注解@PointCut(value="execution(* demo1.OrderDao.save(..))")private void pointcut1(){}此时,在上述通知的注解中,value可以替换为该函数名,例如:@After(value="MyAspect.pointcut1()")public void after(){System.out.println("finally");}这个注解的好处是,只需要维护切入点即可,不用在修改时修改每个注解。Spring 的 JDBC 模板Spring 对持久层也提供了解决方案,也就是 ORM 模块和 JDBC 的模板。针对 JDBC ,提供了 org.springframework.jdbc.core.JdbcTemplate 作为模板类。使用 JDBC 模板引入jar包,数据库驱动,Spring 的 jdbc 相关包。基本使用:public void demo1(){ //创建连接池DriverManagerDataSource dataSource=new DriverManagerDataSource();dataSource.setDriverClassName("com.mysql.jdbc.Driver");dataSource.setUrl("jdbc:mysql:///spring4");dataSource.setUsername("root");dataSource.setPassword("123456");//创建JDBC模板 JdbcTemplate jdbcTemplate=new JdbcTemplate(dataSource);jdbcTemplate.update("insert into account values (null,?,?)", "xiaoming",1000d);}将连接池和模板交给 Spring 管理配置文件:<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource;"><property name="driverClassName" value="com.mysql.jdbc.Driver"></property><property name="url" value="jdbc:mysql:///spring4"></property><property name="username" value="root"></property><property name="password" value="123456"></property></bean><bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate;"> <property name="dataSource" ref="dataSource"></property></bean>测试文件:@RunWith(SpringJUnit4ClassRunner.class)@ContextConfiguration("classpath:applicationContext.xml")public class JdbcDemo2 {@Resource(name="jdbcTemplate")private JdbcTemplate jdbcTemplate;@Testpublic void demo2(){jdbcTemplate.update("insert into account values (null,?,?)", "xiaolan",1000d);}}使用开源数据库连接池使用 DBCP 的配置:<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource"><property name="driverClassName" value="com.mysql.jdbc.Driver"></property><property name="url" value="jdbc:mysql://192.168.66.128/spring4"></property><property name="username" value="root"></property><property name="password" value="123456"></property>使用 C3P0 的配置:<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"><property name="driverClass" value="com.mysql.jdbc.Driver"></property><property name="jdbcUrl" value="jdbc:mysql://192.168.66.128/spring4"></property><property name="user" value="root"></property><property name="password" value="123456"></property></bean>引入外部属性文件首先建立外部属性文件:jdbc.driverClass=com.mysql.jdbc.Driverjdbc.url=jdbc:mysql://192.168.66.128/spring4jdbc.username=rootjdbc.password=123456然后对属性文件进行配置:<context:property-placeholder location="classpath:jdbc.properties"/><bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"><property name="driverClass" value="${jdbc.driverClass}"></property><property name="jdbcUrl" value="${jdbc.url}"></property><property name="user" value="${jdbc.username}"></property><property name="password" value="${jdbc.password}"></property></bean>CRUD操作insert, update, delete 语句都借助模板的 update 方法进行操作。public void demo(){jdbcTemplate.update("insert into account values (null,?,?)", "xiaoda",1000d);jdbcTemplate.update("update account set name=?,money=? where id=?", "xiaoda",1000d,2);jdbcTemplate.update("delete from account where id=?", 6);}查询操作:public void demo3(){String name=jdbcTemplate.queryForObject("select name from account where id=?",String.class,5);long count=jdbcTemplate.queryForObject("select count(*) from account",Long.class);}将返回的结果封装成为类:public void demo4(){Account account=jdbcTemplate.queryForObject("select * from account where id=?", new MyRowMapper(),5);}其中:class MyRowMapper implements RowMapper<Account>{@Overridepublic Account mapRow(ResultSet rs, int rowNum) throws SQLException {Account account=new Account();account.setId(rs.getInt("id"));account.setName(rs.getString("name"));account.setMoney(rs.getDouble("money"));return account;}}Spring的事务管理事务事务是指逻辑上的一组操作,组成这组操作的各个单元,要么全部成功,要么全部失败。具有四个特性:原子性:事务不可分一致性:事务执行前后数据完整性保持一致隔离性:一个事务的执行不应该受到其他事务干扰持久性:一旦事务结束,数据就持久化到数据库如果不考虑隔离性会引发安全性问题:读问题:脏读:一个事务读到另一个事务未提交的数据不可重复读:一个事务读到另一个事务已经提交的 update 数据,导致一个事务中多次查询结果不一致幻读:一个事务读到另一个事务已经提交的 insert 数据,导致一个事务中多次查询结果不一致写问题:丢失更新解决读问题:设置事务隔离级别Read uncommitted: 未提交读,无法解决任何读问题Read committed: 已提交读,解决脏读问题Repeatable read: 重复读,解决脏读和不可重复读问题Serializable:序列化,解决所有读问题事务管理APIPlatformTransactionManager: 平台事务管理器这是一个接口,拥有多个不同的实现类,如 DataSourceTransactionManager 底层使用了JDBC 管理事务; HibernateTransactionManager 底层使用了 Hibernate 管理事务。TransactionDefinition: 事务定义信息用于定义事务的相关信息,如隔离级别、超时信息、传播行为、是否只读等TransactionStatus: 事务的状态用于记录在事务管理过程中,事务的状态的对象。上述API的关系: Spring 在进行事务管理的时候,首先平台事务管理器根据事务定义信息进行事务管理,在事务管理过程当中,产生各种此状态,将这些状态信息记录到事务状态的对象当中。事务的传播行为事务的传播行为主要解决业务层(Service)方法相互调用的问题,也就是不同的业务中存在不同的事务时,如何操作。Spring 中提供了7种事务的传播行为,分为三类:保证多个操作在同一个事务中PROPAGATION_REQUIRED: B方法调用A方法,如果A中有事务,使用A中的事务并将B中的操作包含到该事务中;否则新建一个事务,将A和B中的操作包含进来。(默认)PROPAGATION_SUPPORTS:如果A中有事务,使用A的事务;否则不使用事务PROPAGATION_MANDATORY:如果A中有事务,使用A的事务;否则抛出异常保证多个操作不在同一个事务中PROPAGATION_REQUIRES_NEW:如果A中有事务,将其挂起,创建新事务,只包含自身操作。否则,新建一个事务,只包含自身操作。PROPAGATION_NOT_SUPPORTED:如果A中有事务,挂起,不使用事务。PROPAGATION_NEVER:如果A中有事务,抛出异常,也即不能用事务运行。嵌套事务PROPAGATION_NESTED:如果A有事务,按照A的事务执行,执行完成后,设置一个保存点,然后执行B的操作。如果出现异常,可以回滚到最初状态或保存点状态。实例以转账为例,业务层的DAO层类如下:public interface AccountDao {public void outMoney(String from,Double money);public void inMoney(String to,Double money);}public class AccountDaoImpl extends JdbcDaoSupport implements AccountDao{@Overridepublic void outMoney(String from, Double money) { this.getJdbcTemplate().update("update account set money = money - ? where name = ?",money,from);}@Overridepublic void inMoney(String to, Double money) {this.getJdbcTemplate().update("update account set money = money + ? where name = ?",money,to);}}public interface AccountService {public void transfer(String from,String to,Double money);}public class AccountServiceImpl implements AccountService {private AccountDao accountDao;public void setAccountDao(AccountDao accountDao) {this.accountDao = accountDao;}@Overridepublic void transfer(String from, String to, Double money) {accountDao.outMoney(from, money);accountDao.inMoney(to, money);}}在xml中进行类的配置:<bean id="accountService" class="tx.demo.AccountServiceImpl"><property name="accountDao" ref="accountDao"/></bean><bean id="accountDao" class="tx.demo.AccountDaoImpl"><property name="dataSource" ref="dataSource"/></bean>
-
### 一、SpringCloud项目架构介绍 #### 1.项目地址: [https://gitee.com/caichunfu/dtse-practice-microservice](https://gitee.com/caichunfu/dtse-practice-microservice) #### 2.项目技术栈如下: 1. 注册中心:Eureka 2. 网关:zuul 3. 声明式调用:feign 4. 存储:OBS、RDS for MySql ![](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/20222/18/1645173154462340346.png) #### 3.项目结构: 1. dtse-commons:项目的公共工具类 2. dtse-eureka-server:eureka注册中心集群 3. dtse-feign-client:feign调用客户端 4. dtse-obs-storage:服务端,完成对obs的操作,包括上传和删除obs对象 5. dtse-system:消费端,实现用户登录、查询用户头像、更新用户头像(调用服务端请求) 6. dtse-zuul-getway:项目网关 ### 二、SpringCloud Huawei改造方案 #### 1.替换技术栈对比 | SpringCloud | SpringCloud-Huawei | | ------------------ | ------------------------------------------------------------ | | Eureka注册中心 | [华为云CSE微服务引擎](https://support.huaweicloud.com/devg-cse/cse_devg_0010.html) | | zuul网关 | SpringCloud Huawei Getway | | SpringCloud config | [CSE Config](https://support.huaweicloud.com/devg-cse/cse_devg_0022.html) | #### 2.SpringCloud Huawei技术文档 [https://github.com/huaweicloud/spring-cloud-huawei](https://github.com/huaweicloud/spring-cloud-huawei) #### 3.SpringCloud Huawei基础Demo [https://github.com/huaweicloud/spring-cloud-huawei-samples/tree/master/basic](https://github.com/huaweicloud/spring-cloud-huawei-samples/tree/master/basic) #### 4.SpringCloud Huawei版本对照 ![](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/20222/18/1645174723327212698.png) #### 5.改造前设置 1. 下载本地CSE,下载地址:[https://support.huaweicloud.com/devg-cse/cse_devg_0036.html](https://support.huaweicloud.com/devg-cse/cse_devg_0036.html) 2. 将maven镜像改为华为镜像仓库: ``` <mirror> <id>huaweicloud</id> <mirrorOf>*</mirrorOf> <url>https://repo.huaweicloud.com/repository/maven/</url> </mirror> ``` 3. 项目版本选定 - SpringBoot:2.3.7.RELEASE - SpringCloud:Hoxton.SR9 - SpringCloud Huawei:1.8.0-Hoxton #### 6.改造方案 1. 移除微服务项目里所有的eureka依赖,删掉application.yml里的eureka的配置 ``` <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId> </dependency> ``` 2. 导入SpringCloud Huawei依赖 - 父工程里导入 ```xml <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>${spring-cloud.version}</version> <type>pom</type> <scope>import</scope> </dependency> ``` - 子工程里导入 ```xml <dependency> <groupId>com.huaweicloud</groupId> <artifactId>spring-cloud-starter-huawei-service-engine</artifactId> </dependency> ``` 3. 添加bootstrap.yaml配置文件 ```yaml spring: application: name: #微服务名 cloud: servicecomb: discovery: enabled: true address: http://127.0.0.1:30100 appName: basic-application serviceName: ${spring.application.name} version: 0.0.1 healthCheckInterval: 30 config: serverAddr: http://127.0.0.1:30110 serverType: kie ``` 4. 解决启动中的报错 4.1. 需要编写IClientConfig配置类: ![](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/20222/18/1645176166364496834.png) ![](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/20222/18/1645176147975776732.png) 4.2. 配置类添加 ![](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/20222/18/1645176207306600820.png) ![](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/20222/18/1645176216784964848.png) 4.3. 微服务里无数据库配置: ![](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/20222/18/1645176276185922690.png) 需要在启动类上加`@SpringBootApplication(exclude={DataSourceAutoConfiguration.class})` 4.4. feign调用报错 ![](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/20222/18/1645176345773477730.png) SpringCloud Huawei当前版本对负载均衡支持不够友好,openfeign自带负载均衡,建议使用RestTemplate *以下解决方法来自本站的帖子[SpringCloud项目接入华为云微服务引擎CSE(一)](https://bbs.huaweicloud.com/forum/thread-178657-1-1.html)* 4.5. 修改openfeign调用为RestTemplate - 在dtse-system--usercontroller--UpdateIconImage路径对应的方法里注释掉openfeign调用代码 - 添加RestTemplate方式调用的代码 ```java //请求url String url = "http://xxxx:xxxx/xx/x"; //构造请求头 HttpHeaders httpHeaders = new HttpHeaders(); HttpHeaders headers = httpHeaders; headers.setContentType(MediaType.MULTIPART_FORM_DATA); //FileSystemResource将文件变成流以发送 File file = MultipartFileToFile.multipartFileToFile(multipartFile); FileSystemResource fileSystemResource = new FileSystemResource(file); //构造请求体,使用LinkedMultiValueMap MultiValueMap<String, Object> resultMap = new LinkedMultiValueMap<>(); resultMap.add("file", fileSystemResource); resultMap.add("obsParamsJson", obsParamsJson); //HttpEntity封装整个请求报文 HttpEntity<MultiValueMap<String, Object>> httpEntity = new HttpEntity<>(resultMap, headers); //postForObject发送请求体 String objURL = restTemplate.postForObject(url, httpEntity, String.class); ``` **MultipartFile转File:** ```java import org.springframework.web.multipart.MultipartFile; import java.io.File; import java.io.FileOutputStream; import java.io.InputStream; import java.io.OutputStream; public class MultipartFileToFile { /** * MultipartFile 转 File * * @param file * @throws Exception */ public static File multipartFileToFile(MultipartFile file) throws Exception { File toFile = null; if (file.equals("") || file.getSize() <= 0) { file = null; } else { InputStream ins = null; ins = file.getInputStream(); toFile = new File(file.getOriginalFilename()); inputStreamToFile(ins, toFile); ins.close(); } return toFile; } //获取流文件 private static void inputStreamToFile(InputStream ins, File file) { try { OutputStream os = new FileOutputStream(file); int bytesRead = 0; byte[] buffer = new byte[8192]; while ((bytesRead = ins.read(buffer, 0, 8192)) != -1) { os.write(buffer, 0, bytesRead); } os.close(); ins.close(); } catch (Exception e) { e.printStackTrace(); } } /** * 删除本地临时文件 * @param file */ public static void delteTempFile(File file) { if (file != null) { File del = new File(file.toURI()); del.delete(); } } } ``` 4.6 网关改造 - 删除原项目zuul的依赖,添加springcloudgateway的依赖和springcloudhuawei提供的网关依赖 ```xml <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-gateway</artifactId> </dependency> <dependency> <groupId>com.huaweicloud</groupId> <artifactId>spring-cloud-starter-huawei-service-engine-gateway</artifactId> </dependency> ``` - 添加网关配置文件(注意uri不要用lb地址,当前版本涉及负载均衡的均有问题,暂时用固定地址) ```yaml spring: main: allow-bean-definition-overriding: true cloud: gateway: # 路由(routes:路由,它由唯一标识(ID)、目标服务地址(uri)、一组断言(predicates)和一组过滤器组成(filters)。filters 不是必需参数。) routes: #路由标识(id:标识,具有唯一性) - id: dtse-system-route # 目标服务地址(uri:地址,请求转发后的地址) #uri: lb://dtse-system #不要用lb地址 uri: http://localhost:9090 filters: args: # 路由条件(predicates:断言,匹配 HTTP 请求内容) predicates: - Path=/** urifiler: login-uri: /login ``` - 定义全局过滤器实现鉴权 ```java @Component @Slf4j public class RouteConfiguration implements GlobalFilter, Ordered { @Autowired JwtUtil jwtUtil; @Autowired URIFilter uriFilter; @Override public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) { ServerHttpRequest request = exchange.getRequest(); RequestPath path = request.getPath(); // 2、登陆请求放行 if(path.value().contains(uriFilter.getLoginuri().get(0))){ System.out.println("登陆请求路经:request.getPath() = " + path.value()); log.info("登录"); return chain.filter(exchange); } //3、非登陆请求用户权限校验 String authorization = request.getHeaders().getFirst("Authorization"); if (!StringUtils.isEmpty((authorization))) { System.out.println("非登陆请求路径:request.getPath() = " + path.value()); //2、获取请求头中Token信息 String token = authorization.replace("Bearer", ""); //3、Token校验 Claims claims = jwtUtil.parseToken(token) ; //4、获取用户id,并将用户id传送到后端 if (claims == null) { try { throw new Exception(String.valueOf(ResultCode.UNAUTHENTICATED)); } catch (Exception e) { e.printStackTrace(); } return null; } String id = claims.getId(); //5、添加用户请求头 request.mutate().header("userId",id).build(); return chain.filter(exchange); } return chain.filter(exchange); } @Override public int getOrder() { return 0; } } ``` - urifiler配置类 ```java @ConfigurationProperties(prefix = "urifiler", ignoreUnknownFields = false) @Data @Component public class URIFilter { private List<String> loginuri; } ``` 4.7 配置中心 在浏览器输入http://127.0.0.1:30103/,进入cse可视化界面,选择配置列表,创建配置项 ![](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/20222/21/1645427195901258209.png)
-
spring boot是Spring Data JPA和mybatis哪个用着更方便?
上滑加载中
推荐直播
-
华为AI技术发展与挑战:集成需求分析的实战指南
2024/11/26 周二 18:20-20:20
Alex 华为云学堂技术讲师
本期直播将综合讨论华为AI技术的发展现状,技术挑战,并深入探讨华为AI应用开发过程中的需求分析过程,从理论到实践帮助开发者快速掌握华为AI应用集成需求的框架和方法。
去报名 -
华为云DataArts+DWS助力企业数据治理一站式解决方案及应用实践
2024/11/27 周三 16:30-18:00
Walter.chi 华为云数据治理DTSE技术布道师
想知道数据治理项目中,数据主题域如何合理划分?数据标准及主数据标准如何制定?数仓分层模型如何合理规划?华为云DataArts+DWS助力企业数据治理项目一站式解决方案和应用实践告诉您答案!本期将从数据趋势、数据治理方案、数据治理规划及落地,案例分享四个方面来助力企业数据治理项目合理咨询规划及顺利实施。
去报名
热门标签