@TOC
Springboot项目打包
SpringBoot项目可以是jar类型的maven项目,也可以是一个war类型的maven项目,取决于我们要不要整合jsp使用。但是不管是哪种项目类型,已经不是我们传统意义上的项目结构了
在本地使用SpringBoot的启动器即可访问我们开发的项目。如果我们将项目功能开发完成后,需要使用SpringBoot的打包功能来将项目进行打包。
SpringBoot项目打包在linux服务器中运行:
①jar类型项目会打成jar包:
jar类型项目使用SpringBoot打包插件打包时,会在打成的jar中内置一个tomcat的jar。所以我们可以使用jdk直接运行该jar项目可,jar项目中有一个功能,将功能代码放到其内置的tomcat中运行。我们直接使用浏览器访问即可。
②war类型项目会打成war包:
在打包时需要将内置的tomcat插件排除,配置servlet的依赖。将war正常的放到tomcat服务器中运行即可。
打包插件
<build>
<plugins>
<plugin><!-- 打包插件-->
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<fork>true</fork>
</configuration>
</plugin>
</plugins>
</build>
项目打成jar包运行
.jar.original文件里的内容
打包好之后就可以直接在JDK环境中运行了;
其实在打包的时候可以指定打包的类型----jar/war
默认为jar,打包的jar可以直接运行,如果打包成war可以直接部署在服务器上;(打包时已经继承了tomcat)
项目导出war包运行;
打成war包由于是要将war包放在服务器上去运行,而服务器上是有自己的tomcat的,所以在打包war时要将spring boot自带的tomcat排除,(如果代码中需要req与resp,那么可以再次单独导入tomcat依赖,并将作用范围指定为编译时生效—provided);
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<exclusions>
<!-- 排除自带的tomcat-->
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
<!--打包的时候可以不用包进去,别的设施会提供。事实上该依赖理论上可以参与编译,测试,运行等周期。
相当于compile,但是打包阶段做了exclude操作-->
<scope>provided</scope>
</dependency>
打包好的项目目录结构
war.original文件中的内容
接着将war包放到本地tomcat服务器webapp中
然后运行本地tomcat,启动之后会自动扫描war项目.并将该war解压;
这样可以访问我们的项目吗?----404找不到,什么原因?项目路径的问题,
我们来看tomcat解压后的项目上下文路径,
所以访问时的路径如下
打成war项目的上下文路径由实际路径决定(wa包名)而不是按照我们指定的上下文路径,同时如果我们在项目中指定端口,那么这时候也会失效,而是按照服务器的配置去运行;
如果我们使用的是tomcat7则需要将javax.el-api-3.0.0.jar包放到tomcat下 的lib目录中。
SpringBoot对异常的友好处理
我们在写代码时有时候会遇到一些异常,如果直接返回异常的代码,对用户来讲并不是很友好,这事后我们你可以在templates文件夹下建立一个专门用于存放反馈异常信息的页面
异常返回页面的命名规则—是404错误,就将该页面命名为404,则发生404错误时会跳到这个页面;同理500
目录结构---->>
这时候在controller中手动添加一个异常 int i=1/0;
但是又可能会遇到别的4或者5的错误,这样的话,我们可以将 页面命名为4xx,5xx,即可
还可以定义一个统一的页面—error.html
异常跳转的优先级---->>
当template/error文件夹下有相应的错误反馈页面时会优先走这里,如果没有则会走template下的error.html;
按照有异常就要处理的原则,我们可通过注解的方式来完成异常的处理;
@ExceptionHandler(value = {ArithmeticException.class,NullPointerException.class})
public String errorHandler(){
return "forward:/templates/error";
}
这是局部异常处理,如果要做到全局异常处理
定义一个全局异常处理—>>
此处优先级低于局部异常处理器
@ControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(value = {ArithmeticException.class,NullPointerException.class})
public String ExceptionHandler(){
return "forward:/templates/error";
}
}
自定义异常处理
import java.util.Properties;
/**
* @author Gavin
*/
@Configuration
public class GlobalExceptionHandler {
@Bean
public SimpleMappingExceptionResolver getSimpleMappingExceptionResolver(){
SimpleMappingExceptionResolver exceptionResolver= new SimpleMappingExceptionResolver();
Properties properties= new Properties();
properties.put("java.lang.NullPointException","../static/error.html");
properties.put("java.lang.ArithmeticException","../static/error.html");
exceptionResolver.setExceptionMappings(properties);
return exceptionResolver;
}
}
还有一种是通过配置xml了来实现,这里看链接吧;
/**
* @author Gavin
*/
@Configuration
public class GlobalExceptionHandler implements HandlerExceptionResolver {
@Override
public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
ModelAndView modelAndView= new ModelAndView();
if(ex instanceof ArithmeticException ){
modelAndView.setViewName("../static/error.html");
}
if(ex instanceof NullPointerException){
modelAndView.setViewName("../static/error.html");
}
return modelAndView;
}
}
还是springboot中自带的异常处理比较方便;
springboot中的测试单元
test单元的运行逻辑----->>当运行测试单元时会加载spring的运行环境;
package com.gavin.zzy;
import com.gavin.pojo.Goods;
import com.gavin.service.GoodsService;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import java.util.List;
@SpringBootTest
class ZzyApplicationTests {
@Autowired
private GoodsService goodsService;
@Test
void contextLoads() {
List<Goods> goods = goodsService.showGoods();
goods.forEach(System.out::println);
}
}
在测试的时候一般测试类的目录结构跟项目的目录结构保持一致;
若不这样可能由于版本的问题会出现一些错误;
- 测试类不能叫做Test,会和注解同名
- 测试方法必须是public
- 测试方法返回值必须是void
- 测试方法必须没有参数
Springboot对Bean的管理
Spring Boot 由于没有XML文件,所有的Bean管理都放入在一个配置类中实现。
配置类就是类上具有@Configuration的类。这个类就相当于之前的applicationContext.xml
1 新建一个配置类
com.codemar.config.MyConfig , 在springboot中最好是单独建一个配置类文件夹,方便维护(虽然可以直接放在resources文件夹下);
注意:配置类要有@Configuration,方法要有@Bean
@Configuration
public class MyConfig {
//访问权限修饰符没有强制要求,一般是protected
//返回值就是注入到Spring容器中实例类型。
// 方法名没有强制要求,相当于<bean >中id属性。
@Bean
protected User getUser(){
User user = new User();
user.setId(1L);
user.setName("张三");
return user;
}
//自定义bean名称
@Bean("user2")
protected User getUser2(){
User user = new User();
user.setId(2L);
user.setName("李四");
return user;
}
}
如果Spring容器中存在同类型的Bean通过Bean的名称获取到Bean对象。或结合@Qualifier使用
@SpringBootTest
public class TestGetBean {
@Autowired
@Qualifier("user2")
private User user;
@Test
public void testGetUser(){
System.out.println(user);
}
}
在配置类的方法中通过方法参数让Spring容器把对象注入。
//自定义bean名称
@Bean("user1")
public User getUser(){
User user = new User();
user.setId(2L);
user.setName("李四");
return user;
}
@Bean
//可以直接从方法参数中取到。
public People getPeople(User user1){
People p = new People();
p.setUser(user1);
return p;
}
springboot拦截器
首先配置一个拦截器
package com.gavin.intercepter;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* @author Gavin
*/
@Component//bean注入
public class MyIntercepter implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("拦截器执行了");
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 {
}
}
拦截器准备完毕后,拦截器不像其他的bean需要的时候注入即可,拦截器要的是全局,所以要配置一个配置类使得拦截器能够全局生效;
package com.gavin.intercepter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration//拦截器配置类
public class INTERCEPTERCONFIG implements WebMvcConfigurer {
@Autowired //自动装配
private MyIntercepter intercepter;
@Override
public void addInterceptors(InterceptorRegistry registry) {
//注入拦截器,添加拦截路径与放行路径
InterceptorRegistration interceptorRegistration = registry.addInterceptor(intercepter).addPathPatterns("/**") .excludePathPatterns("/login");
}
}
springboot中的注解
@SpringBootApplication
查看源码可以发现该注解其实是一个符合注解,该注解等同于
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(“com.gavin”)
三个注解,
前面的文章已将对此做了解释,不在赘述了,只看结果;
@Configration
定义一个配置类,在配置类中注册一个bean
在启动的时候就会将user的信息加载到上下文的域中,这个时候我们可以从上下文中取出这些信息;
@SpringBootApplication
public class ZzyApplication {
public static void main(String[] args) {
ConfigurableApplicationContext context = SpringApplication.run(ZzyApplication.class, args);
User getUser = context.getBean("getUser", User.class);
System.out.println(getUser);
}
}
这个注解跟前面文章提到的import注解还不太一样,improt是通过无参构造来完成注入的;
比如说---->>
package com.gavin.config;
import com.gavin.pojo.User;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
/**
* @author Gavin
*/
@Configuration
@Import(value = User.class)
public class MyConfig {
@Bean
public User getUser(){
return new User("张三",23);
}
}
package com.gavin;
import com.gavin.config.MyConfig;
import com.gavin.pojo.User;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.SpringBootConfiguration;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.ComponentScan;
/**
* @author Gavin
*/
@SpringBootApplication
public class ZzyApplication {
public static void main(String[] args) {
ConfigurableApplicationContext context = SpringApplication.run(ZzyApplication.class, args);
User getUser = context.getBean("getUser", User.class);
System.out.println(getUser);
AnnotationConfigApplicationContext annotationConfigApplicationContext = new AnnotationConfigApplicationContext(MyConfig.class);
User getUser1 = annotationConfigApplicationContext.getBean("getUser", User.class);
System.out.println(getUser1);
String[] beanDefinitionNames = annotationConfigApplicationContext.getBeanDefinitionNames();
for (String b :
beanDefinitionNames) {
System.out.println(b);
}
}
}
可以看到当我们注入bean时默认的注入bean的名为方法名------此时是按照类的type来装配的;
如果要按照id来装配,需要指定bean的id值;
初始化加载的类----->>org.springframework.context.annotation.internalConfigurationAnnotationProcessor
org.springframework.context.annotation.internalAutowiredAnnotationProcessor
org.springframework.context.annotation.internalCommonAnnotationProcessor
org.springframework.context.event.internalEventListenerProcessor
org.springframework.context.event.internalEventListenerFactory
代码分析---->>
MyConfig bean = context.getBean(MyConfig.class);
MyConfig bean2 = context.getBean(MyConfig.class);
// spring中的单例设计 true
System.out.println(bean==bean2);
System.out.println("-----------------------");
//从context中获取 true
User user = bean.getUser();
User user1 = bean.getUser();
System.out.println(user==user1);
System.out.println("-----------------------");
// 自己new色结果为false,不做解释
MyConfig M= new MyConfig();
User user2 = M.getUser();
User user3 = M.getUser();
System.out.println(user2==user3);
在@Configration注解中我们发现
package org.springframework.context.annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.core.annotation.AliasFor;
import org.springframework.stereotype.Component;
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Configuration {
@AliasFor(
annotation = Component.class
)
String value() default "";
boolean proxyBeanMethods() default true;
}
boolean proxyBeanMethods() default true;
代理Bean方法默认为true,所以 我们在容器中获取Myconfig对象并非是一个真实的对象,而是一个代理对象,所以多次从容器中取出时会先判断有没有,如果有则返回该对象,否则就先建一个代理对象;
如果设置代理bean方法为false,其结果就会发生变化;
我们查看一下获得的对象真实名
MyConfig bean = context.getBean(MyConfig.class);
String simpleName = bean.getClass().getSimpleName();
System.out.println(simpleName);
MyConfig$$EnhancerBySpringCGLIB$$6c794284
当我们设置@Configuration(proxyBeanMethods = false)
返回结果
MyConfig配置类本身也是一个spring容器中的bean
- proxyBeanMethods=true 属性,给MyConfig对象产生一个代理对象
- 通过代理对象控制反复调用MyConfig里面的方法返回的是容器中的一个单实例
- 如果proxyBeanMethods=false 那么我们拿到的MyConfig对象就不是一个代理对象, 那么这个时候反复调用MyConfig中的方法返回的就是多实例
proxyBeanMethods=false 称之为Lite模式 特点启动快
proxyBeanMethods=true 称之为Full模式 特点依赖spring容器控制bean单例