likes
comments
collection
share

常规后端开发

作者站长头像
站长
· 阅读数 13

开发规范-Restful

REST: Representational State Transfer, 表述性状态转换, 是一种软件架构风格

传统风格
	http://localhost/user/getById?id=1 GET
	http://localhost/user/saveUser POST
	http://localhost/user/updateUser POST
	http://localhost/user/deleteUser?id=1 GET

REST风格
	http://localhost/users/1    GET: 查询id为1的用户
	http://localhost/users      POST: 新增用户
	http://localhost/users      PUT: 修改用户
	http://localhost/users/1    DELETE: 删除id=1的用户
 
URL定位资源, HTTP动词描述操作

统一的响应结果Result

@Data
@NoArgsConstructor
@AllArgsConstructor
public class Result {
	private Integer code;
	private String msg;
	private Object data;

	// 成功响应
	public static Result success() { return new Result(1, "success", null); }

	// 查询类的成功
	public static Result success() { return new Result(1, "success", data); }

	// 失败响应
	public static Result error() { return new Result(0, msg, null); }
}

Lombok有一个很有用的注解, 日志 @Slf4j就可以使用 log.info("xxx");具体的使用看提示

指定请求方式

@Slf4j
@RestControler
public class DeptController {

	// @RequestMapping(value="/depts", method = RequestMethod.GET)
	@GetMapping("/depts")
	public Result list() {
		log.info("check all data");

		return Result.success();
	}


	@DeleteMapping("/depts/{id}")
	public Result delete(@PathVariable Integer id) { // @PathVariable 表示路径参数
		log.info("delete dept with id = {}", id);

		return Result.success();
	} 

	@PostMapping("/depts")
	public Result save(@ReequestBody Dept dept) { // @ReequestBody 表示参数是json
		return Result.success();
	}
}

像上面这种情况, 会有一个问题, 同一个Controller中, 基本上会有一个基本相同的路径 /depts , 一般的做法是

@Slf4j
@RestControler
@RequestMapping("/depts") // 添加共同的根路径
public class DeptController {

	// @RequestMapping(value="/depts", method = RequestMethod.GET)
	@GetMapping
	public Result list() {
		log.info("check all data");

		return Result.success();
	}


	@DeleteMapping("/{id}")
	public Result delete(@PathVariable Integer id) { // @PathVariable 表示路径参数
		log.info("delete dept with id = {}", id);

		return Result.success();
	} 

	@PostMapping
	public Result save(@ReequestBody Dept dept) { // @ReequestBody 表示参数是json
		return Result.success();
	}
}

分页查询

定义Mapping的时候可以设置默认值

// page 起始索引   pageSize 每页包含多少条数据
@GetMapping("/emps")
	public Result page(
		@RequestParam(defaultValue = "1") Integer page,
		@RequestParam(defaultValue = "10") Integer pageSize) {

			log.info("分页查询,  {}, {}", page, pageSize);
			return Result.success();
	}

分页插件PageHelper的使用

如果正常开发, 想要使用实现分页查询, 至少需要两条sql语句
		select count(*) from emp;

		selct * from emp limit 0, 5;

基本上所有的分页操作都是这么实现的,  那么可以使用插件来简化这个操作, 只需要保留一个查询所有的语句就行

引入插件
<dependency>
	<groupId>com.github.pagehelper</grouId>
	<artifactId>pagehelper-spring-boot-starter</artifactId>
	<version>1.4.2</version>
</dependency>

		@Override
		public PageBean page(Integer page, Integer pageSize) {
			PageHelper.startPage(page,pageSize);

			List<Emp> empList = empMapper.list();
			Page<Emp> p = (Page<Emp>) empList;

			PageBean pageBean = new PageBean(p.getTotal(), p.getResult());

			return pageBean;
		}

文件上传

// 前端  传递图片数据的设置
MIME type 
application/x-www-form-urlencoded    // default
multipart/form-data    // 文件
text/plain  // 普通文本

@PostMapping("/upload")
public Result upload(String uname, Integer age, MultipartFile image) {
	// MultipartFile 专门用来接收二进制数据, 默认上传只能1M
	// image 要跟参数名保持一致

	// getSize();  文件大小, 字节
	// getBytes(); 文件的内容的字节数组
	// getInputStream(); 文件内容输入流

	// 需要构造一个唯一文件名
	String originalFilename = image.getOriginalFilenaeme();
	int index = originalFilename.lastIndexOf(".");
	String extname = originalFilename.substring(index);
	String newFileName = 	UUID.randomUUID().toString() + extname;

	// 保存到服务器本地
	image.transferTo(new File("/xx/xx/" + newFileName));

	// 上传到阿里云  直接封装到工具类中
	aliOSSUtils.upload(image); // 这个工具类直接使用@Component注解

	return Result.success();
}

修改上传大小限制application.properties

# 单个文件上传大小
spring.servlet.multipart.max-file-size=10MB

# 多个文件单个上传的大小
spring.servlet.multipart.max-request-size=100MB

上传到阿里云

对象存储OSS(Object Storage Service)

  1. 添加依赖, java9以上还需要添加别的依赖

<dependency>
	<groupId>com.aliyun.oss</grouId>
	<artifactId>aliyun-sdk-oss</artifactId>
	<version>3.15.1</version>
</dependency>
  1. 上传文件
 // Endpoint以华东1(杭州)为例,其它Region请按实际情况填写。
		String endpoint = "https://oss-cn-hangzhou.aliyuncs.com";
		// 阿里云账号AccessKey拥有所有API的访问权限,风险很高。强烈建议您创建并使用RAM用户进行API访问或日常运维,请登录RAM控制台创建RAM用户。
		String accessKeyId = "yourAccessKeyId";
		String accessKeySecret = "yourAccessKeySecret";
		// 填写Bucket名称,例如examplebucket。
		String bucketName = "examplebucket";
		// 填写Object完整路径,完整路径中不能包含Bucket名称,例如exampledir/exampleobject.txt。
		String objectName = "exampledir/exampleobject.txt";
		// 填写本地文件的完整路径,例如D:\\localpath\\examplefile.txt。
		// 如果未指定本地路径,则默认从示例程序所属项目对应本地路径中上传文件。
		String filePath= "D:\\localpath\\examplefile.txt";

		// 创建OSSClient实例。
		OSS ossClient = new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret);

		try {
		    // 创建PutObjectRequest对象。
		    PutObjectRequest putObjectRequest = new PutObjectRequest(bucketName, objectName, new File(filePath));
		    // 如果需要上传时设置存储类型和访问权限,请参考以下示例代码。
		    // ObjectMetadata metadata = new ObjectMetadata();
		    // metadata.setHeader(OSSHeaders.OSS_STORAGE_CLASS, StorageClass.Standard.toString());
		    // metadata.setObjectAcl(CannedAccessControlList.Private);
		    // putObjectRequest.setMetadata(metadata);

		    // 设置该属性可以返回response。如果不设置,则返回的response为空。
		    putObjectRequest.setProcess("true");

		    // 上传文件。
		    PutObjectResult result = ossClient.putObject(putObjectRequest);
		    // 如果上传成功,则返回200。
		    System.out.println(result.getResponse().getStatusCode());
		} catch (OSSException oe) {
		    System.out.println("Caught an OSSException, which means your request made it to OSS, "
		            + "but was rejected with an error response for some reason.");
		    System.out.println("Error Message:" + oe.getErrorMessage());
		    System.out.println("Error Code:" + oe.getErrorCode());
		    System.out.println("Request ID:" + oe.getRequestId());
		    System.out.println("Host ID:" + oe.getHostId());
		} catch (ClientException ce) {
		    System.out.println("Caught an ClientException, which means the client encountered "
		            + "a serious internal problem while trying to communicate with OSS, "
		            + "such as not being able to access the network.");
		    System.out.println("Error Message:" + ce.getMessage());
		} finally {
		    if (ossClient != null) {
		        ossClient.shutdown();
		    }
		}

配置文件

application.properties配置

主要作用是集中管理参数配置

1. 在application.properties 中先定义参数并赋值
2. 在具体的类中使用@Value("${配置文件中的key}")注解来进行赋值


application.properties
	aliyun.oss.endpoint=http://xxxx
	aliyun.oss.accessKeyId=xxxx


@Component
public class AliOSSUtils {
	@Value("${aliyun.oss.endpoint}")
	private String endpoint;


	@Value("${aliyun.oss.accessKeyId}")
	private String accessKeyId;
}

yml配置文件

配置方式稍有不同

XML
	<server>
		<port>8080</port>
		<address>127.0.0.1</address>
	</server>

application.properties
	server.port=8080
	server.address=127.0.0.1

application.yml / .yaml (推荐)
	server:
		port:8080
		address:127.0.0.1

yml语法

- 大小写敏感
- 数值前必须有空格, 作为分隔符
- 使用缩进表示层级关系, 缩进`不允许使用tab`, 只能用空格
- 缩进空格数不重要, 只要相同层级元素左对齐就行
- # 表示注释
对象, map 集合
	user:
		name: zz
		age: 18
		pwd: 123

数组list
	hobby:
		- java
		- game
		- sport

Tips: 可以直接删除application.properties, 新建一个application.yml文件就可以了

@ConfigurationProperties注解

这个注解的作用, 主要是在引用配置的端口, 引用的时候是要使用`@Value`注解, 每个属性上都需要使用, 如果使用`@ConfigurationProperties`注解的话, 只需要一个注解, 然后保持配置文件和引用的属性保持一致

// 定义阿里云相关的配置类
@ConfiturationProperties(prefix='aliyun.oss')
public class AliOSSProperties {
	private String ednpoint;
	private String accessKeyId;
	xxxx
	xxxx
}


// 具体的工具类, 直接使用DI即可
@Component
public class AliOSSUtils {
	@Autowired
	private AliOSSProperties aliOSSProperties;

}

// 这里报错的话, 需要引入一个依赖
<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-configuration-processor</artifactId>
</dependency>

拦截技术

会话技术

会话: 用户打开浏览器, 访问web服务器的资源, 会话建立, 在一次会话中可以包含多次请求和响应 会话跟踪: 服务器需要识别多次请求是否来自同一个浏览器, 以便在同一次会话的多次请求间共享数据

会话跟踪技术分三种

客户端: Cookie, Http 天生支持


// 设置响应cookie
public Result cookie1(HttpServletResponse res) {
	res.addCookie(new Cookie("login_name", "kk"));
	return Result.success();
}


// 获取浏览器cookie
public Result cookie2(HttpServletRequest req) {
	Cookie[] cookies = req.getCookies();

	for (Cookie cookie : cookies) {
		xxxx 
	}

	return Result.success();
}

客户端cookie的缺点

- 移动端App无法使用
- 不安全, 用户可以禁用cookie
- Cookie 不能跨域

服务端: Session

浏览器第一次请求的时候, 是没有session, 这个时候服务端会自动创建一个Session添加到cookie中返回给客户端

// 设置session
public Result cookie1(HttpSession session) {
	session.setAtribute(new Cookie("login_name", "kk"));
	return Result.success();
}


// 获取session
public Result cookie2(HttpServletRequest req) {
	
	HttpSession session = reques.getSession();

	Object loginUser = session.getAttribute("login_name");


	return Result.success();
}

优点: 存储在服务器, 安全 缺点: 服务器集群情况下无法使用, Cookie的缺点都有

JWT令牌(Json Web Token) jwt.io

定义了一种简洁的, 自包含格式, 用于在通信双方以json数据格式安全的传输信息, 因为数字签名的存在, 因此是安全的


base64: 是一种基于64个可打印字符(A-Z a-z 0-9 + /)来表示二进制数据的编码格式

JWT 是由三部分组成, 以 . 进行分割

第一部分: 
	Header, 记录令牌类型, 签名算法 

第二部分:
	Payload, 携带自定义信息, 默认信息

第三部分:
	Signature, 防止Token被篡改, 确保安全性, 将header和payload加入指定秘钥, 通过指定签名算法计算而成

JWT令牌的生成

	1. 添加依赖 pom.xml

	<dependency>
		<groupId>io.jsonwebtoken</groupId>
		<artifactId>jjwt</artifactId>
		<version>0.9.1</version>
	</dependency>

	2. 生成

	Map<String, Objec> claims = new HashMap<>();

	claims.put("id", 1);
	claims.put("username", "tom");

	String jwt = Jwts.builder()
		.setClaims(claims) // payload
		.signWith(SignatureAlgorithm.HS256, "kkkk") // 签名算法
		.setExpiration(new Date(System.currentTimeMillis() + 12 * 3600*1000)) // 有效期
		.compact();


	3. 解析
	Jwts.parser()
		.setSigningKey("kkkk")
		.parseClaimsJws(jwt字符串)
		.getBody()

Filter

JavaWeb的三大组件Servlet: 处理请求, Filter: 过滤器, Listener: 监听器, 常用的只有过滤器了

过滤器一般完成一些通用操作, 比如登录校验, 统一编码处理, 敏感字符处理等

Filter快速入门

  1. 定义Filter类, 实现Filter接口, 并重写所有方法
 
// /login 指定拦截, /emp/* 指定目录下的所有,  /* 拦截所有
@WebFilter(urlPatterns="/*")  // 拦截所有url  
public class CusFilter implements Filter {

	@Override
	public void init(FilterConfig filterConfig) throws ServletExecption { // web服务启动, 调用一次
		Filter.super.init(filterConfig);
	}

	@Override
	public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) {

		// 放行前逻辑

		// 这个是放行, 允许访问相对应的资源
		// 过滤器链, 可以存在多个过滤器, 每次访问, 所有的过滤器都会走一遍
		// 执行顺序, 是按照过滤器类名的自然排序来排名的(字符串)
		chain.doFilter(request, response);

		// 放行后, 依然会执行到这里
	}

	@Override
	public void destroy() { // 销毁的时候调用一次
		Filter.super.destroy();
	}
}
  1. 配置Filter, Filter类上加@WebFilter注解, 引导类上加上@ServletComponentScan开启Servlet组件支持
引导类, 就是带有 @SpringBootApplication 的启动类

@ServletComponentScan // 表示支持servlet组件
@SpringBootApplication
public class xxx {

}

登录过滤

	@Override
	public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) {

		HttpServletRequest req = (HttpServletRequest) request;
		HttpServletResponse resp = (HttpServletResponse) response;

		String url = req.getRequestURL().toString();

		if(url.contains("login")) {
			// 放行
			chain.doFilter(request, response);			
			return;
		}

		// 获取token
		String jwt = req.getHeader("token");

		// 不存在, 没有登录
		if(!StringUtils.hasLength(jwt)) {
			Result error = Result.error("NOT_LOGIN");
			// 转成json, 使用fastjson
			String notLogin = JSONObject.toJSONString(error);
			resp.getWriter().write(notLogin);
			return;
		}

		// 存在, 解析失败
		try {
			JwtUtils.parseJWT(jwt);
		} catch (Exception e) {
			e.printStackTrace();

			Result error = Result.error("NOT_LOGIN");
			String notLogin = JSONObject.toJSONString(error);
			resp.getWriter().write(NOT_LOGIN);
		}


		// 没问题, 放行
		chain.doFilter(request, response);

		return;

		// 放行后, 依然会执行到这里
	}

Interceptor

类似过滤器, Spring框架提供的

快速入门

  1. 定义拦截器, 实现HandlerInterceptor接口, 并重写所有方法
@Component
public class LoginCheckInterceptor implements HandlerInterceptor {

	// 处理前, 返回false 则不放心 
	public boolean preHandle(HttpServletRequest req, HttpServletResponse resp, Object handler) throws Exception {
		// 登录校验逻辑

		return true;
	}

	// 拦截后执行
	public void posthandle(HttpServletRequest req, HttpServletResponse resp,Object handler, ModelAndView modelAndView) {

	}

	// 视图渲染完毕后执行
	public void afterCompletion(HttpServletRequest req, HttpServletResponse resp, Object handler, Exception ex) {

	}
}
  1. 注册拦截器
@Configuration
public class WebConfig implements WebMvcConfigurer {
	@Autowired
	private LoginCheckInterceptor loginCheckInterceptor;

	@Override
	public void addInterceptors(InterceptorRegistry registry) {
		registry.addInterceptor(loginCheckInterceptor)
			.addPathPatterns("/**") // 匹配哪些路径  /* 一级路径, /** 任意路径, /depts/* depts下的一级路径, /depts/** depts下的任意路径
			.excludePathPatterns("/login"); // 不需要过滤的路径
	}
}

异常处理

一般的请求响应流程是:

-req-> Controller --> Service --> Mapper 

-res->Mapper --> Service --> Controller

一旦产生异常, 最有效的做法是全局异常处理器, 不然就要每个请求都添加try - catch

@RestControllerAdvice // = @ControllerAdvice + @ResponseBody
public class GlobalExceptionHandler {

	// 表示捕获所有异常
	@ExceptionHandler(Exception.class) 
	public Result ex(Exception ex) {
		ex.printStackTrace();
		return Result.error("操作失误, 请联系管理员");
	}
}

事务 @Transactional

@Transactional: 可以放到 业务的方法上, 类上, 或者接口

将当前的方法提交给spring进行事务管理, 一般是在需要执行多次sql操作的业务方法上添加注解即可

// 删除部门, 那么同时要把部门下的人也干掉 

@Transactional
public void delete(Integer id) {
	// 删除部门
	deptMapper.deleteById(id);

	// 删除部门下的员工
	empMapper.deleteByDeptId(id);

}

开启Spring事务管理日志

logging:
	level:
		org.springframework.jdbc.support.JdbcTransactionmanager:debug

rollbackFor

正常情况下, 只有出现`RuntimeException`才会回滚, `rollbackFor`属性用于控制出现任何异常类型, 都会回滚
`@Transactional(rollbackFor = Exception.class)`

propagation

事务的传播行为, 当一个事务方法被另一个事务方法调用时, 这个事务方法应该如何进行事务控制
a 和 b都开启了事务,  a 内部调用了b 
@Transactional
public void a() {
	userService.b();
}


@Transactional(propagation = Propagation.REQUIRED)
public void b() {
	xxx
}

REQUIRED      默认值, 需要事务, 有则加入, 加入到当前的事务, 无则创建新事务
REQUIRES_NEW  需要新事务, 总是创建新事务
SUPPORTS      支持事务, 有则加入, 无则在无事务状态中运行
NOT_SUPPORTED 不支持事务, 在无事务状态下运行, 如果存在已有事务, 则挂起当前事务
MANDATORY     必须有事务, 否则抛异常
NEVER         必须没有事务, 否则抛异常

例子: 
	删除部门, 无论成功与否要记录到日志
	try {
		删除部门
	} finally {
		// 记录到日志
		insert();
	}

	这么写之后, 就会出现问题, 删除部门的操作会和下面的insert共用一个事务, 如果删除部门出错, 就会导致添加日志的也会回滚掉,所以,  insert()方法上要添加注解


@Transactional(propagation = Propagation.REQUIRES_NEW)  // 开启新事务
public void insert() {  }

控制台日志插件Grep Console 可以高亮显示

AOP Aspect Oriented Programming 面向切面编程, 面向特定方法编程

AOP基本使用

运行耗时计算, 埋点, 权限控制, 记录日志之类的操作, 上面的事务底层也是通过aop来实现的, 类似动态代理技术

统计各个业务层方法执行耗时

  1. 导入AOP依赖
<dependency>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-aop</artifactId>
	</dependency>

  1. 定义模板方法

@Component 
@Aspect
public class TimeAspect {


	@Around("execution(* com.kk.service.*.*(..))") // 指定哪些方法需要  报名下 所有的类所有的方法
	public Object recordTime(ProceedingJoinPoint proceedingJoinPoint) {
		long begin = System.currentTimeMillis();

		Object object = proceedingJoinPoint.proceed(); // 调用原始方法运行

		long end = System.currentTimeMillis();

		log.info(proceedingJoinPoint.getSignature() + " 执行耗时: {} ms", end - begin);

		return object;
	}
}


上面的方法可以使用另一个注解简化@Pointcut抽取出公共的切入点

@Pointcut("execution(* com.kk.service.impl.DeptServiceImpl.*(..))")
public void pt() {} // 这里的public 都能引用  private 只能在这个类里


@Component 
@Aspect
public class TimeAspect {


	@Around("pt()") // 指定哪些方法需要  报名下 所有的类所有的方法
	public Object recordTime(ProceedingJoinPoint proceedingJoinPoint) {
		long begin = System.currentTimeMillis();

		Object object = proceedingJoinPoint.proceed(); // 调用原始方法运行

		long end = System.currentTimeMillis();

		log.info(proceedingJoinPoint.getSignature() + " 执行耗时: {} ms", end - begin);

		return object;
	}
}


连接点 JointPoint: 可以被Aop控制的方法, 也就是业务方法 通知 Advice: 指重复的逻辑, 共性功能, 比如上面那个 recordTime 切入点 PointCut: 匹配连接点的条件, 通知仅会在切入点方法执行时被应用 @Around指定的 切面 Aspect: 描述通知与切入点的对应关系, 也就是以上三个放到一块被称为切面, 或者切面类 TimeAspect 目标对象 Target: 通知所应用的对象, 要对哪个类生效

AOP的执行流程, 在执行的时候, 就相当于生成了一个代理对象, 就是把要还行的代码放到上面recordTime这个方法里, 调用业务代码的时候, 就相当于是执行了代理类组成的方法, 业务层上面是无法感知的

AOP进阶

通知类型

@Around: 环绕通知, 在链接点前后都执行

@Before: 前置通知, 连接点方法之前

@After: 后置通知, 连接点之后执行, 无论是否有异常都会执行

@AfterReturning: 返回后通知, 有异常不会执行

@AfterThrowing: 有异常后执行

通知顺序

多个切面类, 针对一个方法, 执行的顺序是, 类名的字母排序有关系, 谁靠前就先执行谁, 还可以通过Order(1)注解

切入点表达式

execution(访问修饰符? 返回值 包名.类名.?方法名(方法参数) throws 异常?)?表示是可以省略 * :单个的任意一个 execution(* com.*.service.*.update*(*)

第一个*: 任意权限
第二个*: 第二级包是任意的
第三个*: 类或者是接口是任意的
第四个*: 以update开头后面是任意的字符的方法/接口
第五个*: 任意类型的一个参数 

如果是*service, 表示以service结尾的

.. : 多个连续的任意符号execution(* com..DeptService.*(..)

第一个 .. : 表示任意的包名
第二个 .. : 表示任意的参数 

Pointcut 链接多个: Pointcut("execution(* com..DeptService.(..) || " + "execution( com..service..update*(*)"), ! || && 都可以使用

@annotation 表达式: 匹配表示有特定注解的方法

  1. 直接创建一个注解类 Annotaion

    
    @Retention(RetentionPolicy.RUNTIME) // 运行时生效
    @Target(ElementType.METHOD) // 在方法上生效
    public @interface MyLog {
    
    }
    
  2. 在需要使用的方法上添加注解

@MyLog
@Override 
public List<Dept> list() {
	xxxx
}

  1. 切面类的修改
@Component
@Aspect 
public class Aspect1 {
	@Pointcut("@annotaion(com.kk.aop.MyLog)") // 凡是有MyLog注解的, 需要生效
	privatte void pt() { }

	@Before("pt()")
	public void before() {

	}
}

连接点

可以获取方法执行时的相关信息, 目标类名, 方法名, 参数等...

1. 对于@Around , 获取连接点只能使用ProceedingJoinPoint
2. 其他四种可以直接使用JointPoint, 这个是上面的父类型

jointPoint.getTarget().getClass().getName(); // 获取目标类名
jointPoint.getSignature(); // 获取目标方法签名
jointPoint.getSignature().getName(); // 获取目标方法名
jointPoint.getTarget().getArgs(); // 获取目标方法参数
jointPoint.proceed(); // 获取方法本身

Bean对象相关

获取Bean对象

// 主要就是这个类  ApplicationContext

@Autowired
private ApplicationContext applicationContext; // IOC容器对象

application.getBean("deptController"); // 根据名称

application.getBean(DeptController.class); // 根据类型

application.getBean("deptController", DeptController.class); // 根据名称和类型

Bean对象作用域

Spring 支持五中作用域, 后三种在web才生效

singleton: 容器内同名称的bean只有一个实例, 单例, 默认设置

prototype: 每次使用该bean会创建一个新对象

request: 每个请求

session: 么个session

application: 每一个应用范围

通过@Scope注解来进行配置作用域

@Lazy // 延迟, 等到使用的时候才会实例化
@Scope("prototype")
@RestController
public class DeptController {

}

第三方Bean

如果要管理第三方, 比如引用的第三方库, 是无法使用@Component及衍生注解声明bean的, 需要使用@Bean注解

一般是搞一个配置类, 集中管理第三方

@Configuration
public class CommonConfig {
    @Bean // 将返回值交给 IOC容器管理
    public TestReader reader() {
        return new TestReader();
    }
}

Tips: 这里需要注意, 一般情况下, 加入到IOC容器管理的名称默认是首字母小写, 也可以通过name或者value指定, 第三方比较特殊, 不是类名的首字母小写, 而是方法名比如上面的是reader而不是testReader

如果想要在第三方注入自己写的对象, 直接传参即可

@Configuration
public class CommonConfig {
    @Bean // 将返回值交给 IOC容器管理
    public TestReader reader(DeptController contro) {
        return new TestReader();
    }
}

Springboot 简单原理

Spring: java开发框架, 集成了很多优秀的框架, 但是比较麻烦 依赖, 配置

Spring Boot: Spring4.0之后提出来的, 目的是简化Spring, 能够让开发者快速开发部署(起步依赖, 自动配置)

  • 起步依赖: 包管理, 安卓的gradle, iOS的cocoapods
  • 类和配置,个人理解就是IOC和DI相关

自动配置的起点@SpringBootApplication开始, 源码之中有三个比较重要的注解

@SpringBootConfiguration: 用来声明当前类也是一个配置类 @Configuration相同作用
@ComponentScan: 组件扫描, 默认扫描当前引导类所在包及其子包, 就是在这配置的
@EnableAutoConfigation: 实现自动配置的核心注解 会导入👇🏻这两个文件, 该jar包中所包含的类(全类名)
    META-INF/spring.factories // 低版本导入
    META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports

通过这些导入到Ioc容器, 每个插件都有这两个文件

并不是所有在👆🏻两个文件出现的类都会被注入到Ioc容器, 因为类里面都有很多条件注解@Conditional

自动配置中有一个常用的注解@Conditional 条件注解

@ConditionalOnClass: 有对应的字节码文件, 才会注册bean到Ioc容器

@ConditionalOnMissingBean: 判断没有对应的bean, 才会注册到Ioc

@ConditionalOnProperty: 判断环境中是否有对应的属性和值, 才会注册到Ioc