所有的技术框架的发展似乎都遵循了一条主线规律:
- 从一个复杂应用场景衍生一种规范框架, 人们只需要进行各种配置而不需要自己去实现它, 这时候强大的配置功能成了优点;
- 发展到一定程度之后, 人们根据实际生产应用情况, 选取其中实用功能和设计精华, 重构出一些轻量级的框架;
- 之后为了提高开发效率, 嫌弃原先的各类配置过于麻烦, 于是开始提倡 “约定大于配置″, 进而衍生出一些一站式的解决方案。
这就是 Java 企业级应用 ->J2EE-> spring-> spring boot 的过程。
优点:1. 节省了调用资源。2. 每个功能元素的服务都是一个可替换的、可独立升级的软件代码。
优点:易于开发和测试,方便部署;当需要扩展时,只需要将 war 复制多份, 然后放到多个服务器上, 再做个负载均衡就可以了。
缺点:修改一个非常小的地方, 都需要停掉整个服务, 重新打包部署这个应用 War 包;对于一个大型应用,如何维护,如何分工合作都是问题。
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
@SpringBootApplication
自动导包的核心 AutoConfigurationImportSelector 类:选择了什么东西
AutoConfigurationImportSelector 类中的方法:
// 1.获得自动配置实体(调用“获取所有候选配置”的方法获取实体)。
protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata)
// 2.获取所有候选配置(候选配置是哪些配置)。
List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes){
List<String> configurations = SpringFactoriesLoader.loadFactoryNames(this.getSpringFactoriesLoaderFactoryClass(), this.getBeanClassLoader());
Assert.notEmpty(configurations, "No auto configuration classes found in META-INF/spring.factories. If you are using a custom packaging, make sure that file is correct.");
return configurations;
}
// 3.候选配置是所有标注@EnableAutoConfiguration注解的类下的所有配置。(即获取主启动类下的所有配置)
protected Class<?> getSpringFactoriesLoaderFactoryClass() {
return EnableAutoConfiguration.class;
}
// 4.获取所有的配置从哪里来,上面第二个方法调用了loadFactoryNames方法
public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
String factoryTypeName = factoryType.getName();
return (List)loadSpringFactories(classLoader).getOrDefault(factoryTypeName, Collections.emptyList());
}
// 5.loadFactoryNames方法调用了loadSpringFactories方法
// 6.loadSpringFactories方法从项目资源和系统资源中获取配置文件
classLoader.getResources("META-INF/spring.factories") ClassLoader.getSystemResources("META-INF/spring.factories")
META-INF/spring.factories:自动配置的核心文件。
// 遍历所有的资源,封装成properties供我们使用
Properties properties = PropertiesLoaderUtils.loadProperties(resource);
结论: springboot 所有自动配置都是在启动的时候扫描并加载: 所有的自动配置类都在 spring.factories 里面,但是不一定生效;要判断条件是否成立:只要导入了对应的 start,就有对应的启动器,有了启动器,我们自动装配就会生效,然后自动配置就可以成功。
详细步骤:
main 方法中的 run 方法启动会开启一个服务。
@SpringBootApplication
public class SpringbootWeb01Application {
public static void main(String[] args) {
// 该方法返回一个ConfigurableApplicationContext对象
// 参数一:应用入口的类, 参数二:命令行参数
SpringApplication.run(SpringbootWeb01Application.class, args);
}
}
主要两部分
springboot 配置文件能够配置的东西都有一个固有的规律:
他们都有对应的 xxxAutoConfiguration 配置类,这个配置类都会绑定一个 xxxProperties 类,xxxProperties 类和 springboot 的配置文件绑定;这样我们就可以在 springboot 配置文件中自定义配置了。
spring 的底层注解:根据不同的条件,来判断当前配置或者类是否生效。@Conditionalxxx
SpringBoot 使用一个全局的配置文件,配置文件名是固定的。
全局配置文件的作用:修改 SpringBoot 自动配置的默认值,它会在底层帮我们自动配置。
# 对象
student:
name: 'zs'
age: 12
# 对象的行内写法
student1: {name: 'zs',age: 12}
# 数组
pets:
- cat
- dog
- pig
# 数组行内写法
pets1: [cat,dog,pig]
/*
ConfigurationProperties作用
将yml配置文件中配置的每个属性的值,映射到这个组件中;
告诉SpringBoot将本类中的所有属性和yml配置文件中相关的配置进行绑定;
参数prefix="person":将yml配置文件中的person下面的所有属性和本类属性对应。
(只有这个组件是容器中的组件,才能使用容器提供的ConfigurationProperties功能)
*/
// 可以直接拿到复杂类型的值
@Component
@ConfigurationProperties(prefix = "person")
@Data
public class Person {
private String name;
private Integer age;
private Boolean flag;
private Date birth;
private Map<String,Object> map;
private List<Object> list;
private Dog dog;
}
// application.yml
person:
name: zs
age: 12
flag: false
birth: 2021/04/20
map: {k1: v1,k2: v2}
list: [code,music,girl]
dog:
name: 旺财
age: 3
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-configuration-processorartifactId>
<optional>trueoptional>
dependency>
// 复杂类型不能用@Value直接取值
@Configuration
@Data
public class Person {
@Value("${person222.name}")
private String name;
@Value("${person222.age}")
private Integer age;
@Value("${person222.flag}")
private Boolean flag;
@Value("${person222.birth}")
private Date birth;
private Map<String,Object> map;
private List<Object> list;
private Dog dog;
}
// application.yml
person222:
name: zs
age: 12
flag: false
birth: 2021/04/20
map: {k1: v1,k2: v2}
list: [code,music,girl]
dog:
name: 旺财
age: 3
@Component
@PropertySource(value = "classpath:test.properties")//可以这样配置@PropertySource({"classpath:test.properties"})
@Data
public class Person {
// SPEL表达式取出配置文件的值
@Value("${name}")
private String name;
@Value("${age}")
private Integer age;
private Boolean flag;
private Date birth;
private Map<String,Object> map;
private List<Object> list;
private Dog dog;
}
// test.properties
name=zhangsan
age=13
yml 配置属性的 - 可以和实体属性的驼峰对应绑定
@Component
@ConfigurationProperties(prefix = "person")
@Data
public class Person {
private String firstName;
}
// application.yml
person:
first-name: zs
@Component
@ConfigurationProperties(prefix = "person")
@Data
@Validated //数据校验
public class Person {
@Email(message="邮箱格式错误")
private String name;
}
HibernateValidator 是 BeanValidation 的参考实现;HibernateValidator 提供了 JSR 303 规范中所有内置 constraint 的实现;和附加的 constraint
HibernateValidator 附加的 constraint
<dependency>
<groupId>org.thymeleafgroupId>
<artifactId>thymeleaf-spring5artifactId>
dependency>
<dependency>
<groupId>org.thymeleaf.extrasgroupId>
<artifactId>thymeleaf-extras-java8timeartifactId>
dependency>
DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>首页title>
head>
<body>
<div th:text="${msg}">div>
body>
html>
package com.sywl.config;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
// 扩展springMVC:只加上@Configuration注解
// 如果再加上@EnableWebMvc就会全面接管springMVC配置。(默认的配置不生效)
@Configuration
@EnableWebMvc // 就是导入了DelegatingWebMvcConfiguration类:作用是获取容器中所有的WebMvcConfig
public class SpringMvcConfig implements WebMvcConfigurer {
/**
* 视图跳转
* @param registry
*/
@Override
public void addViewControllers(ViewControllerRegistry registry) {
// 访问/test跳转到test.html页面
registry.addViewController("/test").setViewName("test");
}
}
package com.sywl.config;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* 自定义拦截器
*/
public class LoginHandlerInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// 登录成功之后,应该有用户的session
Object loginUser = request.getSession().getAttribute("loginUser");
if (loginUser == null) {// 1.没有登录
// 2.给出提示信息
request.setAttribute("msg", "没有权限,请先登录");
// 3.转发到登录页面
request.getRequestDispatcher("index.html").forward(request, response);
return false;
} else {
return true;
}
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
}
}
package com.sywl.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.LocaleResolver;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class SpringMvcConfig implements WebMvcConfigurer {
/**
* 配置拦截器
*
* @param registry
*/
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new LoginHandlerInterceptor())// 1.添加自定义拦截器
// 2.添加拦截所有的的路径和页面
.addPathPatterns("/**")
// 3.排除不需要拦截的路径和页面
.excludePathPatterns("/index.html", "/", "/user/login","/css/**","/js/**","/img/**");
}
}
package com.sywl.config;
import org.springframework.util.StringUtils;
import org.springframework.web.servlet.LocaleResolver;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Locale;
/**
* 自定义国际化组件
*/
public class MyLocaleResolver implements LocaleResolver {
/**
* 解析请求
* @param httpServletRequest
* @return
*/
@Override
public Locale resolveLocale(HttpServletRequest httpServletRequest) {
// 1.获取请求中的语言参数。
String language = httpServletRequest.getParameter("l");
// 2.如果没有就使用默认的。
Locale locale = Locale.getDefault();
// 3.如果请求的链接携带了国际化的参数。
if (!StringUtils.isEmpty(language)){
// zh_CN
String[] split = language.split("_");
// 国家,地区
locale = new Locale(split[0], split[1]);
}
return locale;
}
@Override
public void setLocale(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Locale locale) {
}
}
package com.sywl.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.LocaleResolver;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class SpringMvcConfig implements WebMvcConfigurer {
/**
* 自定义国际化组件注入到spring容器
*
* @return
*/
@Bean
public LocaleResolver localeResolver() {
return new MyLocaleResolver();
}
}
package com.sywl.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.LocaleResolver;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class SpringMvcConfig implements WebMvcConfigurer {
@Override
public void addViewControllers(ViewControllerRegistry registry) {
// 访问localhost:8080/跳转到index.html
registry.addViewController("/").setViewName("index");
// 访问localhost:8080/index.html跳转到index.html
registry.addViewController("/index.html").setViewName("index");
// 访问localhost:8080/main.html跳转到dashboard.html
// "redirect:/main.html"即跳转到dashboard.html
registry.addViewController("/main.html").setViewName("dashboard");
}
/**
* 自定义国际化组件注入到spring容器
*
* @return
*/
@Bean
public LocaleResolver localeResolver() {
return new MyLocaleResolver();
}
/**
* 配置拦截器
*
* @param registry
*/
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new LoginHandlerInterceptor())// 1.添加自定义拦截器
// 2.添加拦截所有的的路径和页面
.addPathPatterns("/**")
// 3.排除不需要拦截的路径和页面
.excludePathPatterns("/index.html", "/", "/user/login","/css/**","/js/**","/img/**");
}
}
SpringSecurity 是针对 Spring 项目的安全框架,也是 Springboot 底层安全模块默认的技术选型;他可以实现强大的 web 安全控制,对于安全控制,我们仅需要引入 spring-boot-starter-security 模块, 进行少量的配置, 即可实现强大的安全管理。
// 获取当前的用户对象Subject
Subject currentUser = SecurityUtils.getSubject();
// 通过当前用户获取shiro的session
Session session = currentUser.getSession();
// 判断当前用户是否被认证
currentUser.isAuthenticated()
// 获得当前用户的认证
currentUser.getPrincipal()
// 判断当前用户是否拥某些角色
currentUser.hasRole("schwartz")
// 判断当前用户是否拥某些权限(参数不同,产生不同效果)
currentUser.isPermitted("lightsaber:wield")
currentUser.isPermitted("winnebago:drive:eagle5")
// 注销
currentUser.logout();
在 springboot2.x 之后,原来使用的 jedis 被替换为 lettuce
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-jdbcartifactId>
dependency>
<dependency>
<groupId>mysqlgroupId>
<artifactId>mysql-connector-javaartifactId>
<scope>runtimescope>
dependency>
@RestController
public class JDBCController {
@Autowired
JdbcTemplate jdbcTemplate;
@GetMapping("/list")
public List<Map<String, Object>> selectDeptList() {
String sql = "select * from depart";
List<Map<String, Object>> maps = jdbcTemplate.queryForList(sql);
System.out.println(maps);
return maps;
}
@GetMapping("/add")
public void add() {
String sql = "insert into depart values (6,'部门6')";
int update = jdbcTemplate.update(sql);
System.out.println(update);
}
@GetMapping("/delete/{id}")
public void delete(@PathVariable("id") Integer id) {
String sql = "delete from depart where id = ?";
int update = jdbcTemplate.update(sql,id);
System.out.println(update);
}
@GetMapping("/update/{id}")
public void update(@PathVariable("id") Integer id) {
String sql = "update depart set id = ?,dept_name = ? where id = "+id;
Object[] params = new Object[2];
params[0] = 100;
params[1] = "部门100";
int update = jdbcTemplate.update(sql,params);
System.out.println(update);
}
<dependency>
<groupId>mysqlgroupId>
<artifactId>mysql-connector-javaartifactId>
<scope>runtimescope>
dependency>
<dependency>
<groupId>com.alibabagroupId>
<artifactId>druidartifactId>
<version>1.1.21version>
dependency>
<dependency>
<groupId>log4jgroupId>
<artifactId>log4jartifactId>
<version>1.2.12version>
dependency>
spring:
datasource:
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://localhost:3306/z-springboot?useUnicode=true&characterEncoding=utf-8&serverTimezone=
username: root
password: xxx
type: com.alibaba.druid.pool.DruidDataSource
# druid数据源配置
initialSize: 5
minIdle: 5
maxActive: 20
maxWait: 60000
timeBetweenEvictionRunsMillis: 60000
minEvictableIdleTimeMillis: 300000
validationQuery: SELECT 1 FROM DUAL
testWhileIdle: true
testOnBorrow: false
testOnReturn: false
poolPreparedStatements: true
# 配置监控统计拦截的filters:stat(监控统计),log4j(日志记录),wall(防御sql注入)
# 如果允许时报错java.lang.ClassNotFoundException:org.apache.log4j.Priority则需要导入log4j依赖
filters: stat,wall,log4j
maxPoolPreparedStatementPerConnectionSize: 20
useGlobalDataSourceStat: true
connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=500
import com.alibaba.druid.pool.DruidDataSource;
import com.alibaba.druid.support.http.StatViewServlet;
import com.alibaba.druid.support.http.WebStatFilter;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.boot.web.servlet.ServletRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import javax.servlet.Filter;
import javax.sql.DataSource;
import java.util.HashMap;
@Configuration
public class DruidConfig {
/**
* 关联application.yml的druid配置
* @return
*/
@ConfigurationProperties("spring.datasource")
@Bean
public DataSource druidDataSource(){
return new DruidDataSource();
}
// 后台监控:web.xml,ServletRegistrationBean
// 因为springboot内置了Servlet容器,所以没有web.xml;替代方法:ServletRegistrationBean
/**
* druid后台监控配置
* @return
*/
@Bean
public ServletRegistrationBean a(){
// 1.配置访问路径localhost:8080/druid
ServletRegistrationBean<StatViewServlet> bean = new ServletRegistrationBean<>(new StatViewServlet(),"/druid/*");
// 2.后台需要有人登录,账号密码配置
HashMap<String, String> initParameters = new HashMap<>();
initParameters.put("loginUsername","xxx");// 登录的key是固定的:必须是loginUsername,loginPassword
initParameters.put("loginPassword","xxx");
// 3.配置允许谁可以访问:""意思是谁都可以访问
initParameters.put("allow","");
// 4.禁止谁能访问
initParameters.put("sywl","localhost");
// 设置初始化参数
bean.setInitParameters(initParameters);
return bean;
}
/**
* druid过滤器
* @return
*/
@Bean
public FilterRegistrationBean webStatFilter(){
FilterRegistrationBean<Filter> bean = new FilterRegistrationBean<>();
bean.setFilter(new WebStatFilter());
HashMap<String, String> initParameters = new HashMap<>();
// 可以过滤哪些请求(这些东西不进行统计)
initParameters.put("exclusions","*.js,*.css,/druid/*");
bean.setInitParameters(initParameters);
return bean;
}
}
<dependency>
<groupId>org.mybatis.spring.bootgroupId>
<artifactId>mybatis-spring-boot-starterartifactId>
<version>2.1.1version>
dependency>
<dependency>
<groupId>mysqlgroupId>
<artifactId>mysql-connector-javaartifactId>
<scope>runtimescope>
dependency>
// application.properties
server.port=8888
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/z-springboot?useUnicode=true&characterEncoding=utf-8
spring.datasource.username=root
spring.datasource.password=xxx
# mybatis
mybatis.type-aliases-package=com.sywl.entity
mybatis.mapper-locations=classpath:mybatis/mapper/*.xml
# classpath:mybatis/mapper/*.xml是从resources文件夹下开始扫描。
# classpath:/mybatis/mapper/*.xml是从项目的根目录开始扫描。
@Mapper// 这个注解表示这是mybatis的一个mapper类
@Repository
public interface UserMapper {
List<User> selectUserList();
User selectUserById(@Param("id")Integer id);
void insertUser(User user);
void deleteUser(@Param("id")Integer id);
void updateUser(User user);
}
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-securityartifactId>
dependency>
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
/**
* 授权
* @param http
* @throws Exception
*/
@Override
protected void configure(HttpSecurity http) throws Exception {
// 首页所有人可以访问,功能页只有对应有权限的人可以访问。
// 1.请求权限的规则
http.authorizeRequests()
.mvcMatchers().permitAll()
.mvcMatchers("/level1/**").hasRole("vip1")
.mvcMatchers("/level2/**").hasRole("vip2")
.mvcMatchers("/level3/**").hasRole("vip3");
// 2.开启登录页面(security自带的登录页)(没有权限默认跳转到登录页面)
http.formLogin().loginPage("/toLogin")// 定制登录页:localhost:8080/toLogin会访问RouterController的/toLogin请求跳转到登录页
.loginProcessingUrl("/login")// 设置表单提交的地址。(默认就是/login,可以改成其他的路径,比如:/toLogin)
.usernameParameter("user")// 设置表单提交的用户名的name,默认是username
.passwordParameter("pwd");// 设置表单提交的密码的name,默认是password
// 3.开启注销功能(logoutSuccessUrl设置登出成功跳转的页面,一般跳转到首页)
// logoutUrl("/logout")是设置登出访问的请求地址。(默认是logout)
// 防止网站传输:登出是get的请求
http.csrf().disable();// 关闭csrf功能(默认是开启的):登出失败可能的原因就是没有关闭csrf
http.logout().logoutSuccessUrl("/").logoutUrl("/logout");
// 4.开启记住我功能:cookies 默认保存两周;自定义"记住我"的name
http.rememberMe().rememberMeParameter("remember");
}
/**
* 认证:springboot2.1.x可以直接使用
* passwordEncoder:密码编码
* springSecurity 5.0+新增了需要加密算法
* @param auth
* @throws Exception
*/
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
/*User.UserBuilder user = User.withDefaultPasswordEncoder();
auth.jdbcAuthentication()
.dataSource(datasource)
.withDefaultSchema()
.withUser(user.username("user")).password(user.password("password")).roles("ADMIN");*/
// 这些数据正常从数据库中取
auth.inMemoryAuthentication().passwordEncoder(new BCryptPasswordEncoder())
.withUser("sywl").password(new BCryptPasswordEncoder().encode("123456")).roles("vip2","vip3")
.and()
.withUser("root").password(new BCryptPasswordEncoder().encode("123456")).roles("vip1","vip2","vip3")
.and()
.withUser("guest").password(new BCryptPasswordEncoder().encode("123456")).roles("vip1");
}
}
<dependency>
<groupId>org.thymeleaf.extrasgroupId>
<artifactId>thymeleaf-extras-springsecurity4artifactId>
<version>3.0.4.RELEASEversion>
dependency>
<div class="nav" th:fragment="nav-menu">
<div sec:authorize="!isAuthenticated()">
<a th:href="@{/toLogin}">登录a>
div>
<div sec:authorize="isAuthenticated()">
用户名:<span sec:authentication="name">span>
<a th:href="@{/logout}">注销a>
div>
div>
<dependency>
<groupId>org.apache.shirogroupId>
<artifactId>shiro-springartifactId>
<version>1.4.1version>
dependency>
<dependency>
<groupId>com.github.theborakompanionigroupId>
<artifactId>thymeleaf-extras-shiroartifactId>
<version>2.0.0version>
dependency>
public class UserRealm extends AuthorizingRealm {
@Autowired
UserMapper userMapper;
/**
* 授权
* @param principalCollection
* @return
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
System.out.println("执行了授权");
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
// 拿到当前登录对象:subject.getPrincipal()可以获得认证时SimpleAuthenticationInfo传递的user
Subject subject = SecurityUtils.getSubject();
User currentUser = (User) subject.getPrincipal();
info.addStringPermission(currentUser.getPerms());
return info;
}
/**
* 认证
* @param authenticationToken
* @return
* @throws AuthenticationException
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
System.out.println("执行了认证");
// 1.拿到用户输入的用户名和密码
UsernamePasswordToken userToken = (UsernamePasswordToken) authenticationToken;
// 2.连接真实数据库
User user = userMapper.selectUser(userToken.getUsername());
if (user==null){
return null;
}
Session session = SecurityUtils.getSubject().getSession();
session.setAttribute("loginUser",user);
return new SimpleAuthenticationInfo(user,user.getPassword(),"");
}
}
@Configuration
public class ShiroConfig {
@Bean
public ShiroFilterFactoryBean shiroFilterFactoryBean(@Qualifier("securityManager") DefaultWebSecurityManager securityManager) {
ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean();
bean.setSecurityManager(securityManager);
/**
* anon:无需认证就可以访问
* authc:必须认证了才能访问
* user:必须加有"记住我"功能才能用
* perms:拥有对某个资源的权限才能访问
* roLe:拥有某个角色权限才能访问
*/
LinkedHashMap<String, String> filterMap = new LinkedHashMap<>();
filterMap.put("/user/add", "perms[user:add]");
filterMap.put("/user/update", "perms[user:update]");
filterMap.put("/user/*", "authc");// authc配置需要在perms下面
bean.setFilterChainDefinitionMap(filterMap);
// 登录失败设置跳转到登录的请求
bean.setLoginUrl("/toLogin");
// 未授权跳转页面
bean.setUnauthorizedUrl("/unauth");
return bean;
}
@Bean(name = "securityManager")
public DefaultWebSecurityManager securityManager(UserRealm userRealm) {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
securityManager.setRealm(userRealm);
return securityManager;
}
@Bean
public UserRealm userRealm() {
return new UserRealm();
}
/**
* 整合shiroDialect:用来整合shiro thymeleaf
*(需要导入shiro整合thymeleaf的依赖)
* @return
*/
@Bean
public ShiroDialect shiroDialect() {
return new ShiroDialect();
}
}
@RequestMapping("/login")
public String login(String username, String password, Model model){
Subject subject = SecurityUtils.getSubject();
UsernamePasswordToken token = new UsernamePasswordToken(username,password);
try {
subject.login(token);
return "index";
} catch (UnknownAccountException e) {
model.addAttribute("msg","用户名不存在");
return "login";
}catch (IncorrectCredentialsException e){
model.addAttribute("msg","密码错误");
return "login";
}
}
DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org"
xmlns:shiro="http://www.thymeleaf.org/thymeleaf-extras-shiro">
<head>
<meta charset="UTF-8">
<title>首页title>
head>
<body>
<h1>首页h1>
<div th:if="${session.loginUser!=null}">
<a th:href="@{/logout}">注销a>
div>
<div th:if="${session.loginUser==null}">
<a th:href="@{/toLogin}">登录a>
div>
<hr>
<div shiro:hasPermission="user:add">
<a th:href="@{/user/add}">adda>
div>
<div shiro:hasPermission="user:update">
<a th:href="@{/user/update}">updatea>
div>
body>
html>
<dependency>
<groupId>io.springfoxgroupId>
<artifactId>springfox-swagger2artifactId>
<version>2.9.2version>
dependency>
<dependency>
<groupId>io.springfoxgroupId>
<artifactId>springfox-swagger-uiartifactId>
<version>2.9.2version>
dependency>
@Configuration
@EnableSwagger2 // 开启swagger2
public class SwaggerConfig {
}
@Configuration
@EnableSwagger2 // 开启swagger2
public class SwaggerConfig {
// 注册多个Docket实例可以实现分组
@Bean
public Docket docket(){
return new Docket(DocumentationType.SWAGGER_2)
.apiInfo(info())
.groupName("sywl")// 组名
.enable(true)// 是否开启访问swagger;默认true
.select()
// 配置需要扫描接口的方式RequestHandlerSelectors
// basePackage:指定要扫描的包
// any():扫描全部
// none():不扫描
// withClassAnnotation():扫描类上的注解;参数是注解的反射对象
// withMethodAnnotation():扫描方法上的注解
.apis(RequestHandlerSelectors.basePackage("com.sywl.controller"))
// paths:过滤路径(只扫描这些请求下的接口)
.paths(PathSelectors.ant("/user/**"))
.build();
}
/**
* 配置swagger信息
* @return
*/
@Bean
public ApiInfo info(){
// 作者信息
Contact contact = new Contact("sywl", "", "[email protected]");
return new ApiInfo("SwaggerApi文档",
"这是一个文档描述",
"1.0",
"http://www.xxx.com",
contact,
"Apache 2.0",
"http://www.apache.org/licenses/LICENSE-2.0",
new ArrayList());
}
}
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-data-redisartifactId>
dependency>
// application.yml
spring:
redis:
host: 127.0.0.1
port: 6379
username: root
password: xxx
@SpringBootApplication
@EnableAsync // 开启异步任务
public class Springboot05taskApplication {
public static void main(String[] args) {
SpringApplication.run(Springboot05taskApplication.class, args);
}
}
@Service
public class AsyncService {
@Async
public void hello(){
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
@RestController
public class AsyncController {
@Autowired
private AsyncService asyncService;
@RequestMapping("/hello")
public String Async(){
asyncService.hello(); // 睡眠3秒
System.out.println("正在处理数据");
return "OK";
}
}
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-mailartifactId>
dependency>
// application.yml
spring:
mail:
username: [email protected]
password: xxx
host: smtp.qq.com
# QQ邮箱需要开启加密验证;其他的邮箱不需要。
properties: {mail.smtp.ssl.enable: true}
@SpringBootTest
class Springboot05taskApplicationTests {
@Autowired
private JavaMailSenderImpl mailSender;
@Test
void contextLoads01() {
SimpleMailMessage message = new SimpleMailMessage();
message.setSubject("你好啊!");
message.setText("这是一个问候邮件");
message.setFrom("[email protected]");
message.setTo("[email protected]");
mailSender.send(message);
}
@Test
void contextLoads02() throws MessagingException {
// 1.一个复杂的邮件。
MimeMessage mimeMessage = mailSender.createMimeMessage();
// 2.组装。
MimeMessageHelper helper = new MimeMessageHelper(mimeMessage,true);
helper.setSubject("你好啊!");
helper.setText("这一个问候邮件!
",true);
// 附件
helper.addAttachment("1.jpg",new File("C:\\Users\\SYWL\\Desktop\\2.jpg"));
helper.setFrom("[email protected]");
helper.setTo("[email protected]");
mailSender.send(mimeMessage);
}
}
在《分布式系统原理与范型》一书中有如下定义:“分布式系统是若干独立计算机的集合, 这些计算机对于用户来说就像单个相关系统”。
分布式系统是由一组通过网络进行通信、为了完成共同的任务而协调工作的计算机节点组成的系统。
为了用廉价的、普通的机器完成单个计算机无法完成的计算、存储任务。(其目的是利用更多的机器,处理更多的数据)
RPC【Remote procedure cal】是指远程过程调用, 是—种进程间通信方式, 他是一种技术的思想, 而不是规范。它允许程序调用另一个地址空间 (通常是共亨网络的另一台机器上) 的过程或函数而不用程序员显式编码这个远程调用的细节。(即程序员无论是调用本地的还是远程的函数, 本质上编写的调用代码基本相同)
RPC 两个核心模块:
Apache dubbob 是一款高性能、轻量级的开源 Java rpc 框架。
它提供了三大核心能力:
zookeeper 是注册中心
dubbo-admin 是一个监控管理后台,查看我们注册了哪些服务,哪些服务被消费了
<dependency>
<groupId>org.apache.dubbogroupId>
<artifactId>dubbo-spring-boot-starterartifactId>
<version>2.7.3version>
dependency>
<dependency>
<groupId>com.github.sgroschupfgroupId>
<artifactId>zkclientartifactId>
<version>0.1version>
dependency>
<dependency>
<groupId>org.apache.curatorgroupId>
<artifactId>curator-frameworkartifactId>
<version>5.1.0version>
dependency>
<dependency>
<groupId>org.apache.curatorgroupId>
<artifactId>curator-recipesartifactId>
<version>5.1.0version>
dependency>
<dependency>
<groupId>org.apache.zookeepergroupId>
<artifactId>zookeeperartifactId>
<version>3.6.3version>
<exclusions>
<exclusion>
<groupId>org.slf4jgroupId>
<artifactId>slf4j-log4j12artifactId>
exclusion>
exclusions>
dependency>
// application.yml
server:
port: 8081
dubbo:
application:
name: provider-server # 注册的服务应用名称
registry:
address: zookeeper://localhost:2181 # 注册中心地址
scan:
base-packages: com.sywl.service # 哪些服务要被注册
import org.apache.dubbo.config.annotation.Service;
// zookeeper:服务注册与发现
@Service //dubbo的@sevice注解:可以被扫描到,在项目一启动就自动注册到注册中心
public class TickerServiceImpl implements TicketService {
@Override
public String getTicket() {
return "你好啊";
}
}
<dependency>
<groupId>org.apache.dubbogroupId>
<artifactId>dubbo-spring-boot-starterartifactId>
<version>2.7.3version>
dependency>
<dependency>
<groupId>com.github.sgroschupfgroupId>
<artifactId>zkclientartifactId>
<version>0.1version>
dependency>
<dependency>
<groupId>org.apache.curatorgroupId>
<artifactId>curator-frameworkartifactId>
<version>5.1.0version>
dependency>
<dependency>
<groupId>org.apache.curatorgroupId>
<artifactId>curator-recipesartifactId>
<version>5.1.0version>
dependency>
<dependency>
<groupId>org.apache.zookeepergroupId>
<artifactId>zookeeperartifactId>
<version>3.6.3version>
<exclusions>
<exclusion>
<groupId>org.slf4jgroupId>
<artifactId>slf4j-log4j12artifactId>
exclusion>
exclusions>
dependency>
// application.yml
server:
port: 8082
dubbo:
application:
name: consumer-server # 消费者去拿服务需要暴露自己的名字
registry:
address: zookeeper://localhost:2181 # 注册中心地址
import org.springframework.stereotype.Service;
@Service // 注入容器
public class UserService {
// 去注册中心拿provider-server提供的票
@Reference //引用(远程调用),需要定义服务提供者路径相同的接口名
TicketService tiketService;
public void buyTiket(){
String ticket = tiketService.getTicket();
System.out.println("在注册中心拿到服务:"+ ticket);
}
}