前言:
编程不是为了完成某个功能或者提供某种服务,而是为了使得程序要更加简洁,性能高效,高内聚,低耦合,符合规范性。
在写某个接口时要注意是什么调用什么,是在什么样的上下文环境执行(只进行必要的检查和判断),整个业务逻辑是什么,解决问题的关键点是什么。
Spring Boot支持
–spring-jms提供了对JMS的支持
–spring-rabbit提供了对AMQP的支持
–需要ConnectionFactory的实现来连接消息代理
–提供JmsTemplate、RabbitTemplate来发送消息
–@JmsListener(JMS)、@RabbitListener(AMQP)注解在方法上监听消息代理发布的消息
–@EnableJms、@EnableRabbit开启支持
Spring Boot自动配置
–JmsAutoConfiguration
–RabbitAutoConfiguration
简单springboot项目
项目整体结构
从大的构造来看,主要有controller,domain,filter,model,repository,service,test,util这个几个包,还有一些配置类和在外层的xxxApplication类,这个能够将我们的项目驱动,整个跑起来,所有包里面写的各种类都必须添加到容器中来,就是在xxxApplication这个类中注册,才能够运行。
controller:主要是写一些接口,接口通过调用service中处理业务中的方法,将结果返回给前端,以在终端上实现具体功能。
domain:主要是与数据库中各个表映射的实体类,这里的映射必须是严格的一对一映射,就是数据库的表有哪些字段,这个实体类就只能有哪些属性,属性名和数据类型必须和表里的字段相同。
model:主要是一些自定义的类,可以联立几个表查询结果存储打包这些类中,整体返回。因为无法将分别来自不同表,不同字段存储在单个实体类中。
repository:主要是继承与JPARepository类,可以自定义一些方法,自己写sql语句,可以用jpa给出的方法做数据库的各种事务处理。
service:写处理业务逻辑的类。
test:写一些测试类,测试写好的接口。
filter:一些FilterBean和JWT组件,用来实现安全验证和登陆拦截。
util:工具类,写一些自动记录下载开发日志的类。
重要的注解
在xxxDeviceApplication,驱动类的注解
@SpringBootApplication
@EnableSwagger2
@EnableEurekaClient
public class DeviceApplication {
public static void main(String[] args) {
SpringApplication.run(DeviceApplication.class, args);
}
}
启动注解@SpringBootApplication:
发现@SpringBootApplication是一个复合注解,包括@ComponentScan,和@SpringBootConfiguration,@EnableAutoConfiguration。
- @SpringBootConfiguration继承自@Configuration,二者功能也一致,标注当前类是配置类,并会将当前类内声明的一个或多个以@Bean注解标记的方法的实例纳入到srping容器中,并且实例名就是方法名。
- @EnableAutoConfiguration的作用启动自动的配置,@EnableAutoConfiguration注解的意思就是Springboot根据你添加的jar包来配置你项目的默认配置,比如根据spring-boot-starter-web ,来判断你的项目是否需要添加了webmvc和tomcat,就会自动的帮你配置web项目中所需要的默认配置。在下面博客会具体分析这个注解,快速入门的demo实际没有用到该注解。
- @ComponentScan,扫描当前包及其子包下被@Component,@Controller,@Service,@Repository注解标记的类并纳入到spring容器中进行管理。是以前的context:component-scan(以前使用在xml中使用的标签,用来扫描包配置的平行支持)。所以本demo中的User为何会被spring容器管理。
@EnableSwagger2:可以使用Swagger来调试接口。
@EnableEurekaClient:Eureka client
负责与Eureka Server
配合向外提供注册与发现服务接口。
Controller类的注解
@CrossOrigin
@RestController
@RequestMapping(value = Constants.URI_API_PREFIX + Constants.URI_POSITION)
@Api(value = "安装位置获取服务")
public class PositionController {
@Autowired
private PositionService positionService;
@Autowired
private DeviceService deviceService;
@Autowired
private UserTokenService tokenService;
private Integer customerId=null;
private UUID userId=null;
@ApiOperation(value = "获取按区域汇总的位置数据", response = PositionSumByRegion.class, responseContainer = "List")
@PostMapping(value = "/inbounds/sum/{level}", produces = "application/json")
public PositionSumByRegion[] getSumInBounds(@RequestBody BoundsDevice boundsDevice, @PathVariable int level) {
customerId=tokenService.getCustomerId();
List<PositionSumByRegion> positionSumByRegions = positionService.countBoundsByRegion(boundsDevice, level,customerId);
return positionSumByRegions.toArray(new PositionSumByRegion[positionSumByRegions.size()]);
}
}
@CrossOrigin:
用来处理跨域请求的注解。
先来说一下什么是跨域:
跨域,指的是浏览器不能执行其他网站的脚本。它是由浏览器的同源策略造成的,是浏览器对JavaScript施加的安全限制。
所谓同源是指,域名,协议,端口均相同,不明白没关系,举个栗子:
http://www.123.com/index.html 调用 http://www.123.com/server.PHP (非跨域)
http://www.123.com/index.html 调用 http://www.456.com/server.php (主域名不同:123/456,跨域)
http://abc.123.com/index.html 调用 http://def.123.com/server.php(子域名不同:abc/def,跨域)
http://www.123.com:8080/index.html调用 http://www.123.com:8081/server.php(端口不同:8080/8081,跨域)
http://www.123.com/index.html 调用 https://www.123.com/server.php(协议不同:http/https,跨域)
请注意:localhost和127.0.0.1虽然都指向本机,但也属于跨域。
浏览器执行javascript脚本时,会检查这个脚本属于哪个页面,如果不是同源页面,就不会被执行。
当域名www.abc.com下的js代码去访问www.def.com域名下的资源,就会受到限制。
@RestController:
将这个Controller类注册到容器中,表明当前类是一个Controller控制类。相当于@Controller+@ResponseBody。
@RestController 一般用于接口或前后端分离,如果只是使用@RestController注解Controller,则Controller中的方法无法返回jsp,html页面,配置的视图解析器 InternalResourceViewResolver不起作用,返回的内容就是return 里的内容。
@Contrller 一般用于后台页面,如果需要返回到指定页面,则需要用 @Controller 配合视图解析器 InternalResourceViewResolver 才行。
如果需要返回JSON,XML或自定义mediaType内容到页面,则需要在对应的方法上加上@ResponseBody注解。
@RequestMapping:
value
属性说明这个服务下各个接口访问的url根目录是什么。
在类的级别上的注解会将一个特定请求或者请求模式映射到一个控制器之上。之后你还可以另外添加方法级别的注解来进一步指定到处理方法的映射关系。
@Api:
value
属性说明这个接口提供怎么的服务。
@ApiOperation:
value
说明接口提供服务;
response
返回什么相应类型;
responseContainer
响应对象是包含在什么样数据结构中。
@PostMapping、@GetMapping、@PutMapping、@DeleteMapping:
value
访问该接口的url路径,根目录+此处的value中的值。
produces
produces中指明传参的类型,说明该接口处理一个怎样的http请求。
@Autowired:自动注入Service类的服务。
Service类的注解
@Service
public interface AirMonitorService {
..............
}
@Service
@Transactional
public class DeviceServiceImpl implements DeviceService {
.................
}
@Serive:一个类带了@Service注解,将自动注册到Spring容器,不需要再在applicationContext.xml文件定义bean了,用于标注业务层组件。
@Transactional:当标于类前时, 标示类中所有方法都进行数据库事务处理,主要就是对表的增删改查。
Repository包的注解
public interface DeviceDetailRepository extends JpaRepository<DeviceDetailEntity, Integer> {
List<DeviceDetailEntity>findByPositionIdInAndDisabledFalse(@Param(value = "ids") List<Integer> ids);
List<DeviceDetailEntity> findByPositionId(int positionId);
@Query(value="select p from DeviceDetailEntity as p where (p.positionId=?1)" +
"and (p.modelId in ?2) and (p.customerId=?3 or ?3 is null)")
List<DeviceDetailEntity> findByPositionIdAndModelIdInAndCustomerId(int positionId, List<Integer> modelId,Integer customerId);
..................
}
@Query:主要是重写jpa根据方法名自动生成的sql语句。这里有三种方法来进行数据库事务处理,一种直接写方法名用JPA自动生成的sql语句;第二种自定义sql查询语句;第三种直接调用JPA已经定义好的查询方法,如getOne(),findAll,save()等等。
Domain包下Entity的注解
@Entity
@Table(name = "air_monitor_log")
public class AirMonitorLogEntity {
private int id;
private int deviceId;
private Double pm25;
private Double pm10;
private Double tvoc;
private Double temperature;
private Double humidity;
private Timestamp lastTime;
@Id
@Column(name = "id")
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
@Basic
@Column(name = "device_id")
public int getDeviceId() {
return deviceId;
}
public void setDeviceId(int deviceId) {
this.deviceId = deviceId;
}
...............
@ManyToOne(fetch = FetchType.LAZY)
@NotFound(action = NotFoundAction.IGNORE)
@JoinColumn(name = "position_id", insertable = false, updatable = false)
public DevicePositionEntity getPosition() {
return Position;
}
public void setPosition(DevicePositionEntity position) {
Position = position;
}
}
@Entity:说明这是一个和数据库中某个表严格映射的实体类
@Table:说明该实体类是映射数据库的哪个表,name
指明表的名字
@Id:指明表的主键
@Column:指明这是一个表里的字段,name
指明对映字段的名字。
@ManyToOne:说明此表方法名指明的另一个表(对应另一个类)是多对一的关系。fetch = FetchType.LAZY
是指懒查询,即是只要每次是JPA自动生成的查询语句就会将两个表联立查询。
@NotFound:
action = NotFoundAction.IGNORE
联立两个表查询时,如果有不完全外键对映的记录时就忽视,这样就不会抛出异常。
如A表是主表,B表是子表,外键是aid。A中有条记录是aid为89,但是B表中没有aid为89的记录,那这时不忽视这种找不到的情况就会抛出异常。
@JoinColumn:
指明该表中是哪个字段为另一个的表中的外键。
name = "position_id"
,指明外键关联的字段名。insertable = false
,指明在联立查询时不能插入。updatable = false
,指明在联立查询时不能修改