springboot服务相关
1springboot常见相关配置
1 spring.profiles.active=PROD //设置环境
2 server.port=8009 //设置内置tomcat服务启动窗口
3 server.context-path=/notify //设置服务路径
4 management.security.enabled=false //springboot服务接口访问暴露是因为secrutiy,但是已废弃
5 management.endpoints.web.exposure.include=*
6 security.basic.enabled=false
读取配置问题
遇到这个问题第一个可能性是配置文件出错,比如yml文件冒号后面少了一个空格,或者属性拼写错误。
第二种可能性是 resources目录没有Mark as resources root,导致读取不到对应目录下的配置文件(idea),这个问题的解决办法是根据target目录下查看是否有对应的配置文件
在ConfigFileApplicationListener类中,可以看到 DEFAULT_SEARCH_LOCATIONS 默认加载位置有:"classpath:/,classpath:/config/,file:./,file:./config/",DEFAULT_NAMES 默认配置文件名:"application"
第三种可能性是因为idea配置问题,当出现这种问题的时候,一般yml文件是粉色的
需要在project structure->对应的module中添加spring即可,之后变成
第四种,resource下的配置文件是绿色的,但是在target/class目录下却没有对应的application.yml配置文件,这种情况下springboot也是无法读取application.yml配置文件的,
这种情况造成的原因是因为在该module下<packaging>标签使用了pom打包,导致在resources下存在application.yml但是在target不存在该配置文件。
5.This View Is Not Available Until Indices Are Built
这种情况下是因为清除了缓存,需要等待编译才能显示相关配置
通常情况下我们将配置配置在application开头的主配置文件中,这样随着项目的增大配置项的增多会使文件变得非常臃肿,其实SpringBoot早已考虑到了该问题,SpringBoot提供了**@PropertySource和@ImportResource**两个注解用于加载外部配置文件使用。
- @PropertySource通常用于属性加载配置文件,注意@PropertySource注解不支持加载yaml文件,支持properties文件。
- @ImportResource通常用于加载Spring的xml配置文件
2.springboot相关配置区域
springboot api服务启动改变端口有下面几种方式:
1)通过Java程序启动参数传入:可以通过在Program arguments中添加--server.port=9000传入;(idea在edit configuration->environment->program agruments)
2)通过配置文件传入:可以通过在配置文件application.properties中添加server.port=9000传入。
3)通过JVM启动命令传入:可以通过-Dserver.port=9000传入(idea在edit configuration->environment->vm options)。
3.引入多个properties文件
SpringBoot的application.yml中内容较多,想按照内容的类别进行划分,比如数据库的配置文件,缓存的配置文件等等,这时候在appliacrion.yml中引用这些的配置主要有两种方式
1多个配置文件:在appliacrion.yml同级目录下新建:appliacrion-aaa.yml、appliacrion-bbb.yml两个文件;
在appliacrion.yml配置
spring:
profiles:
include:
aaa,bbb
就能加进来了。。
2.使用@PropertySource(values={"classpath:/com/xxxx/xxxxx.properties"})【注意@ConfigurationProperties(prefix = "xxx")注解指定自定义配置文件中哪个层级属性需绑定,而PropertySource是指定配置文件路径,这两个配置文件可以配合使用】
4.springboot 不配置数据库
一般情况下springboot 服务都会有数据库相关的操作,因为配置文件缺少数据库连接相关配置启动的过程会报错,原因在于springboot在启动过程中会默认加载org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration类,DataSourceAutoConfiguration类使用了@Configuration注解向spring注入了dataSource bean。因为工程中没有关于dataSource相关的配置信息,当spring创建dataSource bean因缺少相关的信息就会报错。
解决方法:
在Application类上增加:
@EnableAutoConfiguration(exclude={DataSourceAutoConfiguration.class, HibernateJpaAutoConfiguration.class})
5.springboot接口访问安全访问关闭
springboot在启动后会因为安全设置问题,而无法访问,返回信息如下
{
"timestamp": 1595312892656,
"status": 401,
"error": "Unauthorized",
"message": "Full authentication is required to access this resource",
"path": "/demo/greeting"
}
原因:有引入spring-boot-starter-security的jar包,显然这个是没有获取当登录信息,即是没有用户登录,没有登录就肯定没有对应的权限了
解决办法:在properties文件中设置security.basic.enabled=false
6 springboot项目实现热部署
文档:https://www.cnblogs.com/chenkeyu/p/10296588.html
7.spring api requestBody编码问题
spring在api接口层面,在调用过程中,机器发来的请求没有设置content-type,于是默认就是content-type:application/x-www-form-urlencoded,这样会导致请求的body会经过urlEncode编码,导致一些符号字符例如%\"和'等字符会经过编码成为%3A%22传给接口参数,所以需要指定Content-Type为application/json这样就可以避免字符串被url encode了。
原理是spring自作主张对内容进行urlencode,在ServletServerHttpRequest类的getBodyFromServletRequestParameters方法中
/**
* Use {@link javax.servlet.ServletRequest#getParameterMap()} to reconstruct the
* body of a form 'POST' providing a predictable outcome as opposed to reading
* from the body, which can fail if any other code has used the ServletRequest
* to access a parameter, thus causing the input stream to be "consumed".
*/
private static InputStream getBodyFromServletRequestParameters(HttpServletRequest request) throws IOException {
ByteArrayOutputStream bos = new ByteArrayOutputStream(1024);
Writer writer = new OutputStreamWriter(bos, FORM_CHARSET);
Map<String, String[]> form = request.getParameterMap();
for (Iterator<String> nameIterator = form.keySet().iterator(); nameIterator.hasNext();) {
String name = nameIterator.next();
List<String> values = Arrays.asList(form.get(name));
for (Iterator<String> valueIterator = values.iterator(); valueIterator.hasNext();) {
String value = valueIterator.next();
writer.write(URLEncoder.encode(name, FORM_CHARSET));
if (value != null) {
writer.write('=');
writer.write(URLEncoder.encode(value, FORM_CHARSET));
if (valueIterator.hasNext()) {
writer.write('&');
}
}
}
if (nameIterator.hasNext()) {
writer.append('&');
}
}
writer.flush();
return new ByteArrayInputStream(bos.toByteArray());
}
Spring AOP相关
参考:https://blog.csdn.net/javazejian/article/details/56267036 关于 Spring AOP (AspectJ) 你该知晓的一切
https://www.jianshu.com/p/5b9a0d77f95f spring aop 及实现方式
AOP 是英文Aspect Oriented Programming 即被称为面向切面编程,它是OOP(Object Oriented Programming面向对象编程)的补充和完善,AOP的实现技术有很多种,其中与Java无缝对接的时AspectJ,AOP的出现解决了外围业务代码与核心业务代码的分离问题,AspectJ是一个Java实现的AOP框架,它能够在编译期中对Java进行AOP编译,而Spring AOP是采用在运行时增加的代理技术,aop的概念如下:
切点(PointCut)是需要在哪些目标方法中进行执行通知的定义,可以是直接定义,也可以是@PointCut
通知(Advice):在定义的切点上实现的增强处理,通知分为5种,前置通知,后置通知,环绕通知,后置返回通知以及异常通知。
连接点(JointPoint):即目标函数
切面(Aspect):一般包含了若干个配套的切点和通知,切面一般是类,例如日志记录切面类,权限校验切面类等
织入(Weaving):将切面应用到目标函数的过程,织入方式分为两种,一种是静态织入,另一种是动态织入。Aspect J采用的是静态织入的技术,ApectJ主要采用的是编译期织入,在编译期间使用AspectJ的ajc编译器(类似javac,是采用Java编写的编译器,能够识别aspect语法,而Java编译器并不识别aspect语法)把aspect类编译成class字节码后,在java目标类编译时织入,即先编译aspect类再编译目标类。而Spring AOP采用的时动态织入的技术(JDK动态代理或者CGLIB)
注意:编写AspectJ类,注意关键字为aspect(MyAspectJDemo.aj,其中aj为AspectJ的后缀),含义与class相同,即定义一个AspectJ的类,这种定义是典型的A,也可以是通过@Aspect注解定义切面类,现在很多都是切点与通知放在一起的定义方式
/**
* Created by zejian on 2017/2/15.
* Blog : http://blog.csdn.net/javazejian [原文地址,请尊重原创]
* 切面类
*/
public aspect MyAspectJDemo {
/**
* 定义切点,日志记录切点
*/
pointcut recordLog():call(* HelloWord.sayHello(..));
/**
* 定义切点,权限验证(实际开发中日志和权限一般会放在不同的切面中,这里仅为方便演示)
*/
pointcut authCheck():call(* HelloWord.sayHello(..));
/**
* 定义前置通知!
*/
before():authCheck(){
System.out.println("sayHello方法执行前验证权限");
}
/**
* 定义后置通知
*/
after():recordLog(){
System.out.println("sayHello方法执行后记录日志");
}
}
Spring AOP与AspectJ
Spring AOP 与ApectJ 的目的一致,都是为了统一处理横切业务,但与AspectJ不同的是,Spring AOP 并不尝试提供完整的AOP功能(即使它完全可以实现),Spring AOP 更注重的是与Spring IOC容器的结合,并结合该优势来解决横切业务的问题,因此在AOP的功能完善方面,相对来说AspectJ具有更大的优势,同时,Spring注意到AspectJ在AOP的实现方式上依赖于特殊编译器(ajc编译器),因此Spring很机智回避了这点,转向采用动态代理技术的实现原理来构建Spring AOP的内部机制(动态织入),这是与AspectJ(静态织入)最根本的区别。在AspectJ 1.5后,引入@Aspect形式的注解风格的开发,Spring也非常快地跟进了这种方式,因此Spring 2.0后便使用了与AspectJ一样的注解。请注意,Spring 只是使用了与 AspectJ 5 一样的注解,但仍然没有使用 AspectJ 的编译器,底层依是动态代理技术的实现,因此并不依赖于 AspectJ 的编译器。
@Aspect
public class MyAspect {
/**
* 前置通知
*/
@Before("execution(* com.zejian.spring.springAop.dao.UserDao.addUser(..))")
public void before(){
System.out.println("前置通知....");
}
/**
* 后置通知
* returnVal,切点方法执行后的返回值
*/
@AfterReturning(value="execution(* com.zejian.spring.springAop.dao.UserDao.addUser(..))",returning = "returnVal")
public void AfterReturning(Object returnVal){
System.out.println("后置通知...."+returnVal);
}
/**
* 环绕通知
* @param joinPoint 可用于执行切点的类
* @return
* @throws Throwable
*/
@Around("execution(* com.zejian.spring.springAop.dao.UserDao.addUser(..))")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println("环绕通知前....");
Object obj= (Object) joinPoint.proceed();
System.out.println("环绕通知后....");
return obj;
}
/**
* 抛出通知
* @param e
*/
@AfterThrowing(value="execution(* com.zejian.spring.springAop.dao.UserDao.addUser(..))",throwing = "e")
public void afterThrowable(Throwable e){
System.out.println("出现异常:msg="+e.getMessage());
}
/**
* 无论什么情况下都会执行的方法
*/
@After(value="execution(* com.zejian.spring.springAop.dao.UserDao.addUser(..))")
public void after(){
System.out.println("最终通知....");
}
}
除了上述方式外,还可采用与ApectJ中使用pointcut关键字类似的方式定义切入点表达式如下,使用@Pointcut注解:
/**
* 使用Pointcut定义切点
*/
@Pointcut("execution(* com.zejian.spring.springAop.dao.UserDao.addUser(..))")
private void myPointcut(){}
/**
* 应用切入点函数
*/
@After(value="myPointcut()")
public void afterDemo(){
System.out.println("最终通知....");
}
切入点指示符
为了方法通知应用到相应过滤的目标方法上,SpringAOP提供了匹配表达式,这些表达式也叫切入点指示符。
通配符
常见的通配符有* + ..
.. : 匹配方法定义中的任意数量的参数,此外还匹配类定义中的任意数量包
//任意返回值,任意名称,任意参数的公共方法
execution(public * *(..))
//匹配com.zejian.dao包及其子包中所有类中的所有方法
within(com.zejian.dao..*)
+ :匹配给定类的任意子类
//匹配实现了DaoUser接口的所有子类的方法
within(com.zejian.dao.DaoUser+)
* :匹配任意数量的字符
//匹配com.zejian.service包及其子包中所有类的所有方法
within(com.zejian.service..*)
//匹配以set开头,参数为int类型,任意返回值的方法
execution(* set*(int))
类型签名表达式
为了方便类型(如接口、类名、包名)过滤方法,Spring AOP 提供了within关键字。其语法格式如下:within(<type name>)
type name 则使用包名或者类名替换即可
//匹配com.zejian.dao包及其子包中所有类中的所有方法
@Pointcut("within(com.zejian.dao..*)")
//匹配UserDaoImpl类中所有方法
@Pointcut("within(com.zejian.dao.UserDaoImpl)")
//匹配UserDaoImpl类及其子类中所有方法
@Pointcut("within(com.zejian.dao.UserDaoImpl+)")
//匹配所有实现UserDao接口的类的所有方法
@Pointcut("within(com.zejian.dao.UserDao+)")
方法签名表达式
如果想根据方法签名进行过滤,关键字execution可以帮到我们,语法表达式如下
//scope :方法作用域,如public,private,protect
//returnt-type:方法返回值类型
//fully-qualified-class-name:方法所在类的完全限定名称
//parameters 方法参数
execution(<scope> <return-type> <fully-qualified-class-name>.*(parameters))
对于给定的作用域、返回值类型、完全限定类名以及参数匹配的方法将会应用切点函数指定的通知
//匹配UserDaoImpl类中的所有方法
@Pointcut("execution(* com.zejian.dao.UserDaoImpl.*(..))")
//匹配UserDaoImpl类中的所有公共的方法
@Pointcut("execution(public * com.zejian.dao.UserDaoImpl.*(..))")
//匹配UserDaoImpl类中的所有公共方法并且返回值为int类型
@Pointcut("execution(public int com.zejian.dao.UserDaoImpl.*(..))")
//匹配UserDaoImpl类中第一个参数为int类型的所有公共的方法
@Pointcut("execution(public * com.zejian.dao.UserDaoImpl.*(int , ..))")
原文链接:https://blog.csdn.net/javazejian/article/details/56267036
其他指示符
bean:Spring AOP扩展的,AspectJ没有对于指示符,用于匹配特定名称的Bean对象的执行方法;
//匹配名称中带有后缀Service的Bean。
@Pointcut("bean(*Service)")
private void myPointcut1(){}
this :用于匹配当前AOP代理对象类型的执行方法;请注意是AOP代理对象的类型匹配,这样就可能包括引入接口也类型匹配
//匹配了任意实现了UserDao接口的代理对象的方法进行过滤
@Pointcut("this(com.zejian.spring.springAop.dao.UserDao)")
private void myPointcut2(){}
target :用于匹配当前目标对象类型的执行方法;
//匹配了任意实现了UserDao接口的目标对象的方法进行过滤
@Pointcut("target(com.zejian.spring.springAop.dao.UserDao)")
private void myPointcut3(){}
@within:用于匹配所以持有指定注解类型内的方法;请注意与within是有区别的, within是用于匹配指定类型内的方法执行;
//匹配使用了MarkerAnnotation注解的类(注意是类)
@Pointcut("@within(com.zejian.spring.annotation.MarkerAnnotation)")
private void myPointcut4(){}
@annotation(com.zejian.spring.MarkerMethodAnnotation) : 根据所应用的注解进行方法过滤
//匹配使用了MarkerAnnotation注解的方法(注意是方法)
@Pointcut("@annotation(com.zejian.spring.annotation.MarkerAnnotation)")
private void myPointcut5(){}
这里最后说明一点,切点指示符可以使用运算符语法进行表达式的混编,如and、or、not(或者&&、||、!)
//匹配了任意实现了UserDao接口的目标对象的方法并且该接口不在com.zejian.dao包及其子包下
@Pointcut("target(com.zejian.spring.springAop.dao.UserDao) !within(com.zejian.dao..*)")
private void myPointcut6(){}
//匹配了任意实现了UserDao接口的目标对象的方法并且该方法名称为addUser
@Pointcut("target(com.zejian.spring.springAop.dao.UserDao)&&execution(* com.zejian.spring.springAop.dao.UserDao.addUser(..))")
private void myPointcut7(){}