前言

由于现在开发中使用注解的比较多,下面演示示例以注解为主
SpringBoot 基于 Spring 开发。继承了Spring框架原有的优秀特性和 Spring 框架紧密 结合进一步简化了Spring应用的整个搭建和开发过程。其设计目的是用来简化 Spring 应用的初始搭建以及开发过程。大部分的 SpringBoot 应用都只需要非常少量的配置代码,开发者能够更加专注于业务逻辑。

springboot基础

springboot的默认结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
src
└── main
├── java
│ └── com
│ └── example
│ └── demo
│ ├── config
│ ├── controller
│ ├── model
│ ├── service
│ └── DemoApplication.java
├── resources
│ ├── application.properties//全局的配置文件
│ ├── static
│ ├── templates//spring默认视图路径以 /templates/ 为前缀,以 .html 为后缀
│ └── ...
└── ...
生成的classes目录中com与resources下的子文件平行

主程序类

@SpringBootApplication,它是一个复合注解。可以用下面三个代替

  1. @SpringBootConfiguration 通过@Configuration 与@Bean结合,注册到Spring ioc 容器。
  2. @ComponentScan 通过范围扫描的方式,扫描特定注解类,将其注册到Spring ioc 容器。如果不写则扫描@SpringBootApplication注解的所在的包及其下级包
  3. @EnableAutoConfiguration 通过spring.factories的配置,来实现bean的注册到Spring ioc 容器。

全局的配置文件

配置文件可以是端口配置,数据库设计,日志设计
application.properties
语法结构 :key=value
server.port=8081

application.yml
语法结构 :
server:
port: 8081

配置类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32

/**
* 1、配置类里面使用@Bean标注在方法上给容器注册组件,默认也是单实例的
* 2、配置类本身也是组件
* 3、proxyBeanMethods:代理bean的方法
* Full(proxyBeanMethods = true)、【保证每个@Bean方法被调用多少次返回的组件都是单实例的】
* Lite(proxyBeanMethods = false)【每个@Bean方法被调用多少次返回的组件都是新创建的】
* 组件依赖必须使用Full模式默认。其他默认是否Lite模式
*
*
*
*/
@Configuration(proxyBeanMethods = false) //告诉SpringBoot这是一个配置类 == 配置文件,启动时会自动注册
public class MyConfig {

/**
* Full:外部无论对配置类中的这个组件注册方法调用多少次获取的都是之前注册容器中的单实例对象
* @return
*/
@Bean //给容器中添加组件。以方法名作为组件的id。返回类型就是组件类型。返回的值,就是组件在容器中的实例
public User user01(){
User zhangsan = new User("zhangsan", 18);
//user组件依赖了Pet组件
zhangsan.setPet(tomcatPet());
return zhangsan;
}

@Bean("tom")
public Pet tomcatPet(){
return new Pet("tomcat");
}
}

路由设计

SpringMVC

@Component:注解是一个通用的注解,被Spring容器扫描并纳入管理,下面三个可以看作其的具体划分
@Controller:修饰web层的类
@Service:修饰service层的类
@Repository:修饰dao层的类

1
2
3
4
5
6
7
8
9
10
11
<!--自动扫描包,让指定包下的注解生效,由IOC容器统一管理-->
<context:component-scan base-package="com.ssl.controller"/>
<!--让SpringMvc不处理静态资源。让.css,.js等不进视图解析器-->
<mvc:default-servlet-handler/>
<!--注解加载映射器、适配器,不用之前那么麻烦配置了-->
<mvc:annotation-driven/>
<!--配置视图解析器-->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver" id="internalResourceViewResolver">
<property name="prefix" value="/WEB-INF/jsp/" />
<property name="suffix" value=".jsp" />
</bean>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Controller
@RequestMapping("/controller")//路径可以不断的叠加
public class HelloController {
/**
* @param model 模型
* @return 被视图解析器处理:访问"/WEB-INF/jsp/hello.jsp资源
* 访问的url:RequestMapping("/hello")
*/
@GetMapping("/hello")//可以设置请求方法
@ResponseBody //是一个 Spring MVC 注解,用于指示方法的返回值将直接作为 HTTP 响应的内容。
public String hello(User user) {//@param user SpringMvc 会自动封装数据到参数里的pojo,不匹配的属性=null/0

return user.getDepartment().getName1().contains("njust");
}

Interceptor

自定义拦截器必须实现HandlerInterceptor接口或者继承Handler InterceptorAdapter类,功能与Servlet Filter类似

1
2
3
4
5
6
7
8
9
public interface HandlerInterceptor {
//在请求进入到Controller进行拦截,return true放行,执行下一个拦截器,如果没有拦截器,执行controller中的方法
return false不放行,不会执行controller中的方法
boolean preHandle(HttpServletRequest var1, HttpServletResponse var2, Object var3) throws Exception;
//在Controller控制器执行完成但是还没有返回模板进行渲染拦截
void postHandle(HttpServletRequest var1, HttpServletResponse var2, Object var3, ModelAndView var4) throws Exception;
//在ModelAndView返回给前端渲染后执行
void afterCompletion(HttpServletRequest var1, HttpServletResponse var2, Object var3, Exception var4) throws Exception;
}

Swagger2

不同与Structs框架,springmvc的路由多有注解实现,不便于通用管理,于是需要有一个维护接口的组件,swagger应用而生,它可以快速帮助我们编写最新的API接口文档,从而提升了团队开发的沟通效率。

创建Swagger配置类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Configuration 
@EnableSwagger2// 注解表示开启swagger
@Profile({"dev", "test"})
// dev(开发)、test(测试)、prod(生产)
public class Swagger2Api {

@Bean
Docket createApi() {
Docket build = new Docket(DocumentationType.SWAGGER_2)
.apiInfo(getApiInfo())
.select()
.apis(RequestHandlerSelectors.basePackage("com.wenbin.springboot"))// 包扫描的地址
.paths(PathSelectors.any())
.build();
return build;

在要分析的control类前加上一下注释

1
2
3
4
@Api(value = "helloController的api文档",description = "helloController的api文档2") // 描述一个类
@ApiOperation(value = "hello的入口方法,基本数据类型参数") // 描述一个方法
@ApiParam(name = "name", value = "姓名",required = true)
@ApiImplicitParam(name = "id",value = "用户id",paramType = "path",dataType = "Long",required = true)

访问http://localhost:8080/swagger-ui.html即可看到分析页面

上线系统时,修改application.yml文件防止不必要的信息泄露

1
2
3
4
spring:
#配置swagger2生产和测试环境不可用
profiles:
active: dev

spring的编程设计

spring有两个重要的设计思想,分别为控制反转(IOC)面向切面编程(AOP)

ioc

IoC的核心思想是将对象之间的依赖关系交给容器来管理,通过配置文件(bean)或注解等方式告诉容器哪些对象需要被创建和注入,容器会根据配置信息自动创建对象,并将依赖注入到相应的位置。这样可以降低对象之间的耦合度(两个子系统或类之间的关联程度),提高代码的可维护性和可扩展性。
IOC是一种编程思想,由主动的编程变成被动的接收
类似与thinkphp中的利用__call根据类名调用自动加载

依赖注入

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
//利用set注入
public class Customer {
private String name;
private int age;

public void setName(String name) {
this.name = name;
}

public void setAge(int age) {
this.age = age;
}

@Override
public String toString() {
return "Customer{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}

<bean id="customerBean" class="com.powernode.spring6.beans.Customer" p:name="zhangsan" p:age="20"/>

@Test
public void testP(){
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring-p.xml");
Customer customerBean = applicationContext.getBean("customerBean", Customer.class);
System.out.println(customerBean);
}
//利用construct注入
public class MyTime {
private int year;
private int month;
private int day;

public MyTime(int year, int month, int day) {
this.year = year;
this.month = month;
this.day = day;
}

@Override
public String toString() {
return "MyTime{" +
"year=" + year +
", month=" + month +
", day=" + day +
'}';
}
}
<bean id="myTimeBean" class="com.powernode.spring6.beans.MyTime" c:_0="2008" c:_1="8" c:_2="8"/>

使用注解实现自动装配

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32

@Repository("userDao")//@Repository主要是方便其余两层调用,其他两个作用一样都是修饰一个类并将这个类交给Spring管理,只是修饰的类的层不同!
public class UserDaoImpl implements UserDao {
@Value("李四")
private String name;

@Override
public void save() {
// TODO Auto-generated method stub
System.out.println("DAOImpl保存用户的方法执行了。。。。" + name);
}
}

@Service("UserService") // 相当于配置了<bean id="userService"
// class="com.itzheng.spring.demo1.UserServiceImpl">
public class UserServiceImpl implements UserService {
// 注入DAO
@Autowired//在Service当中调用Dao层的UserDaoImpl实现类
private UserDao userDao;
//也可以强制按名称来完成属性的注入
//@Autowired
//@Qualifier(value="user123Dao")
//private UserDao userDao;
//@Resource和@Autowired的区别:@Resource默认通过byName(id)的方式实现,如果找不到名字,则通过byType(class)实现!如果两个都找不到的情况下,就报错!
@Override
public void save() {
// TODO Auto-generated method stub
System.out.println("UserServiceImpl执行了");
userDao.save();
}
}

AOP

AOP可以让开发者在不修改原代码的情况下,在程序执行过程中插入一些行为,从而实现某些横向功能的需求
这个类似与thinkphp中的hook,servlet的fliter,常用于日志,行为检测,插件开发等

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public class DiyPointCut {
public void before(){
System.out.println("======方法执行前======");
}

public void after(){
System.out.println("======方法执行后======");
}
}
<aop:pointcut id="point" expression="execution(* com.kuang.service.UserServiceImpl.*(..))"/>
<aop:before method="before" pointcut-ref="point"/>
<aop:after method="after" pointcut-ref="point"/>
//使用注解
@Aspect //标注这个类是一个切面
public class AnnotationPointCut {

@Before("execution(* com.kuang.service.UserServiceImpl.*(..))")
public void before(){
System.out.println("====方法执行前====");
}

@After("execution(* com.kuang.service.UserServiceImpl.*(..))")
public void after(){
System.out.println("====方法执行后====");
}
}

Spring Boot actuator

Actuator 是 springboot 提供的用来对应用系统进行自省和监控的功能模块,借助于 Actuator 开发者可以很方便地对应用系统某些监控指标进行查看、统计等。

编写配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# actuator 监控配置
management:
#actuator端口 如果不配置做默认使用上面8080端口
server:
port: 8080
endpoints:
web:
exposure:
#默认值访问health,info端点 用*可以包含全部端点
include: "*"
#修改访问路径 2.0之前默认是/; 2.0默认是/actuator可以通过这个属性值修改
base-path: /actuator
endpoint:
shutdown:
enabled: true #打开shutdown端点
health:
show-details: always #获得健康检查中所有指标的详细信息

端点总结

Spring Boot 1.x 版本的 Actuator 端点在根 URL 下注册
Spring Boot 2.x 版本的 Actuator 端点移动到 /actuator/ 路径下
Spring Boot < 1.5 默认未授权访问所有端点;
Spring Boot >= 1.5 默认只允许访问 /health 和 /info 端点

1
2
3
4
5
6
7
8
9
10
/actuator/env	     露出Spring的属性的各种环境变量
/actuator/refresh 通过调用这个端点来更新应用中的配置属性,而不需要重启应用
/actuator/restart
/actuator/heapdump 返回的 GZip 压缩 堆转储文件,可使用 Eclipse MemoryAnalyzer 加载,通过站点泄露的内存信息,有机会查看到后台账号信息和数据库账号等
/actuator/mappings 显示所有@RequestMapping路径的整理列表
/actuator/httptrace 显示最近的HTTP请求和响应信息,路径包含一些 http 请求包访问跟踪信息,有可能在其中发现内网应用系统的一些请求信息详情、以及有效用户或管理员的 authorization(token、JWT、cookie)等字段
/actuator/info 显示任意应用信息,是在配置文件里自己定义的
/actuator/health 显示应用健康信息

/actuator/jolokia 通过这个端点,你可以以 JSON 格式与 JMX MBeans 进行交互,从而监控和管理应用程序的各个方面,查看/actuator/jolokia/list 是否存在关键词

Thymeleaf模板引擎

Spring Boot默认是不支持JSP的,而是推荐使用Thymeleaf、Freemarker等模板引擎,默认情况下,如果你添加了 Thymeleaf 或 FreeMarker 的依赖,Spring Boot 将自动配置对应的模板引擎。
SpringBoot默认在static 目录中存放静态资源,而 templates 中放动态页面。
我们只需要把我们的html页面放在类路径下的templates下,thymeleaf就可以帮我们自动渲染了。

如果页面是Thymeleaf生成的就会在html上加上这样的标识<html xmlns:th="http://www.thymeleaf.org">

SpEL表达式

使用场景

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class user {
@Value("${spring.user.username}")//在注解@Value里面
private String name;

@Value("${spring.user.age} }")
private Long age;
}

public String getindex(Model model)
{
user user1=new user("bigsai",22);
model.addAttribute("user",user1);//储存javabean
return "index";//与templates中index.html对应
}


<td th:text="${user.name}"></td>//在模板中嵌入逻辑
<td th:text="${user['name']}"></td>
<td th:text="${user.getname()}"></td>
1
2
3
4
<bean id="xxx" class="com.java.XXXXX.xx">
<!--在xml配置里-->
<property name="arg" value="#{表达式}">
</bean>

在注解@Value里面

用法

1
2
3
4
5
6
    ${} 用于从上下文中获取数据(如模型对象的属性)。
#{} 用于调用 Spring bean 的方法或访问特定功能。
@{} 用于引用外部资源

#{…} 和${…} 可以混合使用,但是必须#{}外面,${}在里面,#{ ‘${}’ } ,注意单引号,注意不能反过来"
基础用法
1
2
3
#{12*12}
#{T(java.lang.Runtime).getRuntime().exec("calc")} //调用静态方法
#{new java.lang.ProcessBuilder('cmd','/c','calc').start()}

运算符

1
2
3
4
#{my name is+' '+user}
#{shape.kind == 'circle' and shape.perimeter gt 10000}
#{songSelector.selectSong() == 'May Rain' ? piano:saxphone}
#{admin.email matches '[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.com'}

SpringSecurity

Spring Security是一个功能强大且高度可定制的身份验证和访问控制框架
配置类授权

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
//配置文件中的账号和密码失效
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.
//基于内存完成认证和授权
inMemoryAuthentication()
// 表示用户名
.withUser("张三")
//表示密码
.password("123456")
//当前用户具有的角色
.roles("vip1")
//表示具有的权限
.authorities("user:select","user:delete","user:insert","user:update")
.and()
.withUser("李四")
.password("123456")
.roles("vip2")
.authorities("user:select","user:export");

}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/").permitAll()
.antMatchers("/level1/**").hasAnyRole("vip1")
.antMatchers("/level2/**").hasAnyRole("vip2")
.antMatchers("/level3/**").hasAnyRole("vip3");
http.csrf().disable();//禁用跨域伪造请求的过滤器
//除了上的请求,其他请求都需要认证

}
}

身份校验

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
 // springsecurity默认把当前用户的信息保存SecurityContext上下文中.
@GetMapping("info")
public Authentication info(){
//获取SecurityContext对象
SecurityContext context = SecurityContextHolder.getContext();
//把用户得到信息封装到Authontication类中--用户名---角色以及权限---状态[]
Authentication authentication = context.getAuthentication();
UserDetails principal = (UserDetails) authentication.getPrincipal();
System.out.println(principal.getUsername());
return authentication;
}
@GetMapping("select")
@PreAuthorize("hasAuthority('user:select')") //资源执行前判断当前用户是否拥有user:select权限
public String select(){
System.out.println("查询用户");
return "查询用户";
}

thymeleaf注入

在<= thymeleaf-spring5:3.0.12组件中,thymeleaf结合模板注入中的特定场景可能会导致远程代码执行。详细描述参见 https://github.com/thymeleaf/thymeleaf-spring/issues/256

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
//URL路径可控

@RequestMapping("/hello")
public class HelloController {
@RequestMapping("/whoami/{name}/{sex}")
public String hello(@PathVariable("name") String name,
@PathVariable("sex") String sex){
return "Hello" + name + sex;
}
}

//return内容可控

@PostMapping("/getNames")
public String getCacheNames(String fragment, ModelMap mmap)
{
mmap.put("cacheNames", cacheService.getCacheNames());
return prefix + "/cache::" + fragment;
}

shiro

简单配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
[main]
shiro.loginUrl = /login.jsp

[users]
# format: username = password, role1, role2, ..., roleN
root = secret,admin
guest = guest,guest

[roles]
# format: roleName = permission1, permission2, ..., permissionN
admin = *

[urls]
#anon:无需认证即可访问。authc:需要认证才可访问
/user/** = authc
/admin/list = authc, roles[admin]
/admin/** = authc
/audit/** = authc, perms["audit:list"] #需要身份验证(authc)和audit:list操作的权限
/** = anon #** 匹配0或者更多的目录