spring
IoC是什么
Ioc—Inversion of Control,即“控制反转”,不是什么技术,而是一种设计思想。在Java开发中,Ioc意味着将你设计好的对象交给容器控制,而不是传统的在你的对象内部直接控制。
我们来深入分析一下:
- 谁控制谁,控制什么?
传统Java SE程序设计,我们直接在对象内部通过new进行创建对象,是程序主动去创建依赖对象;而IoC是有专门一个容器来创建这些对象,即由Ioc容器来控制对 象的创建;谁控制谁?当然是IoC 容器控制了对象;控制什么?那就是主要控制了外部资源获取(不只是对象包括比如文件等)。
- 为何是反转,哪些方面反转了?
有反转就有正转,传统应用程序是由我们自己在对象中主动控制去直接获取依赖对象,也就是正转;而反转则是由容器来帮忙创建及注入依赖对象;为何是反转?因为由容器帮我们查找及注入依赖对象,对象只是被动的接受依赖对象,所以是反转;哪些方面反转了?依赖对象的获取被反转了。
简单来说就是类的创建被ioc容器控制了,然后应用程序的所依赖的对象也是由ioc注入的。
IoC能做什么
IoC 不是一种技术,只是一种思想,一个重要的面向对象编程的法则,它能指导我们如何设计出松耦合、更优良的程序。
传统应用程序都是由我们在类内部主动创建依赖对象,从而导致类与类之间高耦合,难于测试;有了IoC容器后,把创建和查找依赖对象的控制权交给了容器,由容器进行注入组合对象,所以对象与对象之间是 松散耦合,这样也方便测试,利于功能复用,更重要的是使得程序的整个体系结构变得非常灵活。
IoC和DI是什么关系
控制反转是通过依赖注入实现的,其实它们是同一个概念的不同角度描述。通俗来说就是IoC是设计思想,DI是实现方式。
DI—Dependency Injection,即依赖注入:组件之间依赖关系由容器在运行期决定,形象的说,即由容器动态的将某个依赖关系注入到组件之中。
- 谁依赖于谁?
当然是应用程序依赖于IoC容器;
- 为什么需要依赖?
应用程序需要IoC容器来提供对象需要的外部资源;
- 谁注入谁?
很明显是IoC容器注入应用程序某个对象,应用程序依赖的对象;
- 注入了什么?
就是注入某个对象所需要的外部资源(包括对象、资源、常量数据)。
- IoC和DI由什么关系呢? 通俗来说就是IoC是设计思想,DI是实现方式。
AOP是什么
AOP为Aspect Oriented Programming的缩写,意为:面向切面编程
AOP最早是AOP联盟的组织提出的,指定的一套规范,spring将AOP的思想引入框架之中,通过预编译方式和运行期间动态代理实现程序的统一维护的一种技术.
我们将记录日志功能解耦为日志切面,它的目标是解耦。进而引出AOP的理念:就是将分散在各个业务逻辑代码中相同的代码通过横向切割的方式抽取到一个独立的模块中!
面向切面编程
AOP术语
首先让我们从一些重要的AOP概念和术语开始。这些术语不是Spring特有的。
下面是我认为常见的
- 连接点(Jointpoint) :表示需要在程序中插入横切关注点的扩展点,连接点可能是类初始化、方法执行、方法调用、字段调用或处理异常等等,Spring只支持方法执行连接点,在AOP中表示为在哪里干;
- 切入点(Pointcut) : 选择一组相关连接点的模式,即可以认为连接点的集合,Spring支持perl5正则表达式和AspectJ切入点模式,Spring默认使用AspectJ语法,在AOP中表示为在哪里干的集合;
- 通知(Advice) :在连接点上执行的行为,通知提供了在AOP中需要在切入点所选择的连接点处进行扩展现有行为的手段;包括前置通知(before advice)、后置通知(after advice)、环绕通知(around advice),在Spring中通过代理模式实现AOP,并通过拦截器模式以环绕连接点的拦截器链织入通知;在AOP中表示为干什么;
- 方面/切面(Aspect) :横切关注点的模块化,比如上边提到的日志组件。可以认为是通知、引入和切入点的组合;在Spring中可以使用Schema和@AspectJ方式进行组织实现;在AOP中表示为在哪干和干什么集合;
通知类型:
- 前置通知(Before advice) :在某连接点之前执行的通知,但这个通知不能阻止连接点之前的执行流程(除非它抛出一个异常)。
- 后置通知(After returning advice) :在某连接点正常完成后执行的通知:例如,一个方法没有抛出任何异常,正常返回。
- 异常通知(After throwing advice) :在方法抛出异常退出时执行的通知。
- 最终通知(After (finally) advice) :当某连接点退出的时候执行的通知(不论是正常返回还是异常退出)。
- 环绕通知(Around Advice) :包围一个连接点的通知,如方法调用。这是最强大的一种通知类型。环绕通知可以在方法调用前后完成自定义的行为。它也会选择是否继续执行连接点或直接返回它自己的返回值或抛出异常来结束执行。
通过注解方式使用AOP
@EnableAspectJAutoProxy @Component @Aspect public class LogAspect {
/**
* define point cut.
*/
@Pointcut("execution(* tech.pdai.springframework.service.*.*(..))")
private void pointCutMethod() {
}
/**
* 环绕通知.
*
* @param pjp pjp
* @return obj
* @throws Throwable exception
*/
@Around("pointCutMethod()")
public Object doAround(ProceedingJoinPoint pjp) throws Throwable {
System.out.println("-----------------------");
System.out.println("环绕通知: 进入方法");
Object o = pjp.proceed();
System.out.println("环绕通知: 退出方法");
return o;
}
/**
* 前置通知.
*/
@Before("pointCutMethod()")
public void doBefore() {
System.out.println("前置通知");
}
/**
* 后置通知.
*
* @param result return val
*/
@AfterReturning(pointcut = "pointCutMethod()", returning = "result")
public void doAfterReturning(String result) {
System.out.println("后置通知, 返回值: " + result);
}
/**
* 异常通知.
*
* @param e exception
*/
@AfterThrowing(pointcut = "pointCutMethod()", throwing = "e")
public void doAfterThrowing(Exception e) {
System.out.println("异常通知, 异常: " + e.getMessage());
}
/**
* 最终通知.
*/
@After("pointCutMethod()")
public void doAfter() {
System.out.println("最终通知");
}
代理模式
我们知道AOP就是通过动态代理实现的。
代理模式和装饰器模式很像。基本就是,只是关注点不一样。
代理关注:通过代理类访问目标对象。
装饰器关注:增强对象某些方法的能力。
代理模式的结构比较简单,主要是通过定义一个继承抽象主题的代理来包含真实主题,从而实现对真实主题的访问,下面来分析其基本结构。
代理模式的主要角色如下。
- 抽象主题(Subject)类(业务接口类):通过接口或抽象类声明真实主题和代理对象实现的业务方法,服务端需要实现该方法。
- 真实主题(Real Subject)类(业务实现类):实现了抽象主题中的具体业务,是代理对象所代表的真实对象,是最终要引用的对象。
- 代理(Proxy)类:提供了与真实主题相同的接口,其内部含有对真实主题的引用,它可以访问、控制或扩展真实主题的功能。
模式实现
根据代理的创建时期,代理模式分为静态代理和动态代理。
- 静态:由程序员创建代理类或特定工具自动生成源代码再对其编译,在程序运行前代理类的 .class 文件就已经存在了。
- 动态:在程序运行时,运用反射机制动态创建而成。
静态代理
静态代理服务于单个接口,我们来考虑实际工程中的一个例子,现在已经有业务代码实现一个增删功能,原有的业务代码由于仍有大量程序无法改变,现在新增需求,即以后每执行一个方法输出一个日志。
我们不改变原有代码而添加一个代理来实现:
//业务接口
interface DateService {
void add();
void del();
}
class DateServiceImplA implements DateService {
@Override
public void add() {
System.out.println("成功添加!");
}
@Override
public void del() {
System.out.println("成功删除!");
}
}
class DateServiceProxy implements DateService {
DateService server;
public DateServiceProxy(DateService server) {
this.server = server;
}
@Override
public void add() {
server.add();
System.out.println("程序执行add方法,记录日志.");
}
@Override
public void del() {
server.del();
System.out.println("程序执行del方法,记录日志.");
}
}
//客户端
public class Test {
public static void main(String[] args) {
DateServiceImplA server = new DateServiceImplA();
DateService service = new DateServiceProxy(server);
service.add();
service.del();
}
}
动态代理
我们为什么需要动态代理?要理解这一点,我们必须要知道静态代理有什么不好,要实现静态代理,我们必须要提前将代理类硬编码在程序中,这是固定死的,上面也提到过,有一些代理一个代理就必须要负责一个类,这种情况下代理类的数量可能是非常多的。
动态代理可以帮助我们仅仅在需要的时候再创建代理类,减少资源浪费,此外由于动态代理是一个模板的形式,也可以减少程序的代码量
动态代理采用反射的机制,在运行时创建一个接口类的实例。在JDK的实现中,我们需要借助Proxy类和InvocationHandler接口类。
在运行期动态创建一个interface实例的方法如下:
-
定义一个类去实现
InvocationHandler接口,这个接口下有一个invoke(Object proxy, Method method, Object[] args)方法,它负责调用对应接口的接口方法;调用代理类的方法时,处理程序会利用反射,将代理类、代理类的方法、要调用代理类的参数传入这个函数,并运行这个函数,这个函数是实际运行的,我们在这里编写代理的核心代码。
-
通过
Proxy.newProxyInstance()创建某个interface实例,它需要3个参数:-
使用的
ClassLoader,通常就是接口类的ClassLoader; -
需要实现的接口数组,至少需要传入一个接口进去;
-
一个处理程序的接口。
这个方法返回一个代理类$Proxy0,它有三个参数,第一个通常是类本身的ClassLoader,第二个是该类要实现的接口,例如这里我们要实现增删接口,第三个是一个处理程序接口,即调用这个类的方法时,这个类的方法会被委托给该处理程序,该处理程序做一些处理,这里对应了上面这个方法,通常设置为this。
-
//业务接口
interface DateService {
void add();
void del();
}
class DateServiceImplA implements DateService {
@Override
public void add() {
System.out.println("成功添加!");
}
@Override
public void del() {
System.out.println("成功删除!");
}
}
class ProxyInvocationHandler implements InvocationHandler {
private DateService service;
public ProxyInvocationHandler(DateService service) {
this.service = service;
}
public Object getDateServiceProxy() {
return Proxy.newProxyInstance(this.getClass().getClassLoader(), service.getClass().getInterfaces(), this);
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
var result = method.invoke(service, args); // 让service调用方法,方法返回值
System.out.println(proxy.getClass().getName() + "代理类执行" + method.getName() + "方法,返回" + result + ",记录日志!");
return result;
}
}
//客户端
public class Test {
public static void main(String[] args) {
DateService serviceA = new DateServiceImplA();
DateService serviceProxy = (DateService) new ProxyInvocationHandler(serviceA).getDateServiceProxy();
serviceProxy.add();
serviceProxy.del();
}
}
/*
成功添加!
$Proxy0代理类执行add方法,返回null,记录日志!
成功删除!
$Proxy0代理类执行del方法,返回null,记录日志!
*/
Bean 的作用域有哪些?
Spring 中 Bean 的作用域通常有下面几种:
- singleton : IoC 容器中只有唯一的 bean 实例。Spring 中的 bean 默认都是单例的,是对单例设计模式的应用。
- prototype : 每次获取都会创建一个新的 bean 实例。也就是说,连续
getBean()两次,得到的是不同的 Bean 实例。 - request (仅 Web 应用可用): 每一次 HTTP 请求都会产生一个新的 bean(请求 bean),该 bean 仅在当前 HTTP request 内有效。
- session (仅 Web 应用可用) : 每一次来自新 session 的 HTTP 请求都会产生一个新的 bean(会话 bean),该 bean 仅在当前 HTTP session 内有效。
- application/global-session (仅 Web 应用可用): 每个 Web 应用在启动时创建一个 Bean(应用 Bean),,该 bean 仅在当前应用启动时间内有效。
- websocket (仅 Web 应用可用):每一次 WebSocket 会话产生一个新的 bean。
如何配置 bean 的作用域呢?
xml 方式:
<bean id="..." class="..." scope="singleton"></bean>
注解方式:
@Bean
@Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public Person personPrototype() {
return new Person();
}
单例 Bean 的线程安全问题了解吗?
大部分时候我们并没有在项目中使用多线程,所以很少有人会关注这个问题。单例 Bean 存在线程问题,主要是因为当多个线程操作同一个对象的时候是存在资源竞争的。
常见的有两种解决办法:
- 在 Bean 中尽量避免定义可变的成员变量。
- 在类中定义一个
ThreadLocal成员变量,将需要的可变成员变量保存在ThreadLocal中(推荐的一种方式)。
不过,大部分 Bean 实际都是无状态(没有实例变量)的(比如 Dao、Service),这种情况下, Bean 是线程安全的。
bean的单例作用域
单例模式1.0: 懒汉式加载,需要的时候才加载。
很显然这里是线程不安全的。
public class Singleton{
private static Singleton singleton;
//注意:私有的构造方法,不让外部类调用创建对象
private Singleton(){}
public static Singleton getInstance(){
if(singleton == null){
singleton = new Singleton();
}
return singleton;
}
}
单例模式2.0:
1.这里存在指令重排的问题
2.这种方式也很辣鸡,因为多线程环境下每个线程执行getInstance()都要阻塞,效率很低。如果我们使用双重判断,就可以避免很多线程被阻塞
sInstance =new Singleton();这句话创建了一个对象,他可以分解成为如下3行代码:
| 123 | memory = allocate(); ``// 1.分配对象的内存空间``ctorInstance(memory); ``// 2.初始化对象``sInstance = memory; ``// 3.设置sInstance指向刚分配的内存地址 |
|---|
上述伪代码中的2和3之间可能会发生重排序,重排序后的执行顺序如下
| 123 | memory = allocate(); ``// 1.分配对象的内存空间``sInstance = memory; ``// 2.设置sInstance指向刚分配的内存地址,此时对象还没有被初始化``ctorInstance(memory); ``// 3.初始化对象 |
|---|
因为这种重排序并不影响Java规范中的规范:intra-thread sematics允许那些在单线程内不会改变单线程程序执行结果的重排序。
但是多线程并发时可能会出现以下情况
线程B访问到的是一个还未初始化的对象。
public class Singleton{
private static Singleton singleton;
//注意:私有的构造方法,不让外部类调用创建对象
private Singleton(){}
public static synchronized Singleton getInstance(){
if(singleton == null){
singleton = new Singleton();
}
return singleton;
}
}
单例模式3.0: 双重锁+懒加载
完美解决前面的问题。线程安全,且性能高
public class Singleton{
private static volatile Singleton singleton;
//注意:私有的构造方法,不让外部类调用创建对象
private Singleton(){}
public static Singleton getInstance(){
if(singleton == null){
synchronized(Singleton.class){
if(singleton == null){
singleton = new Singleton();
}
}
}
return singleton;
}
}
前面全是懒加载,写个饿汉加载的。启动就加载
public class Singleton {
private static Singleton instance = new Singleton();
private Singleton (){
}
public static Singleton getInstance() {
return instance;
}
}
Spring,Spring MVC,Spring Boot 之间什么关系?
Spring 包含了多个功能模块,Spring MVC 是 Spring 中的一个很重要的模块,主要赋予 Spring 快速构建 MVC 架构的 Web 程序的能力。
使用 Spring 进行开发各种配置过于麻烦比如开启某些 Spring 特性时,需要用 XML 或 Java 进行显式配置。于是,Spring Boot 诞生了!
Spring Boot 只是简化了配置,SpringBoot的设计策略是通过开箱即用和约定大于配置 来解决配置重的问题的。
springmvc
随着 Spring 轻量级开发框架的流行,Spring 生态圈出现了 Spring MVC 框架, Spring MVC 是当前最优秀的 MVC 框架。相比于 Struts2 , Spring MVC 使用更加简单和方便,开发效率更高,并且 Spring MVC 运行速度更快。
MVC 是一种设计模式,Spring MVC 是一款很优秀的 MVC 框架。Spring MVC 可以帮助我们进行更简洁的 Web 层的开发,并且它天生与 Spring 框架集成。Spring MVC 下我们一般把后端项目分为 Service 层(处理业务)、Dao 层(数据库操作)、Entity 层(实体类)、Controller 层(控制层,返回数据给前台页面)。
适配器模式
比如你想,后端返回给前端的数据900,500,400都代表成功,那前端就很难了,每次都要加判断代码。然而前端希望我们成功返回的code统一是200.所以我们可以通过适配器把各种成功的code转换成200.
适配器组成要素:
·Target:客户期望获得的功能接口(220V电压供电)。
·Cilent:客户,期望访问Target接口(客户期望能有220V电压)。
·Adaptee:现有接口,这个接口需要被适配(现有110V电压供电,需要被适配至220V)。
·Adapter:适配器类,适配现有接口使其符合客户需求接口(适配110V电压,使其变为220V电
简单说就是通过适配器adapter把目前有的接口adaptee转换成甲方希望的接口target。
其中client就是我们要run的类,他直接调用适配器,参数里面传入想要被适配的类。然后用适配器调用方法就行了。
加入有个例子:
我们现在有个查找用户的业务。UserService接口,以及它的实现类UserServiceImpl,其中有个findUser()的方法。方法的返回值是个Map,这是我们有的接口,也就是adapter。
我们的甲方希望我们返回一个json数据,也就是Target接口。类名TargetUserService。此接口也有一个抽象方法,findUser(),返回值是String类型
那么自身的接口有了,怎么使用适配器模式转成甲方的需求呢??
首先我们建一个类UserAdapter,实现TargetUserService目标接口,然后重写接口的方法,同时定义成员变量UserService userService = null,这个值在此类的构造方法中赋值。
UserAdapter(UserService userService){ this.userService = userService; }
然后在重写的方法中,调用userService.findUser();在转成json返回就行了。
最后在客户端client,new UserServiceImpl(),传入UserAdapter的构造方法中,就可以调用UserAdapter中的方法了满足甲方需求了。
为什么要这么麻烦使用适配器?
1.解耦,降低了对象与对象之间的耦合性。
2.增加了类的复用,这点是比较重要的。
3.灵活性和扩展性都非常好,通过使用配置文件,可以很方便地更换适配器,也可以在不修改原有代码的基础上增加新的适配器类,完全符合“开闭原则”。
springmvc中使用的适配器
我们都知道SpringMVC定义一个映射的方式很简单,使用@RequestMapping注解,如下所示:
@RestController
public class PayController {
@RequestMapping("/pay")
public String pay(String channel,String amount)throws Exception{
return "";
}
}
实际上除了上面这种常用的方式外,还有其他的方式定义:
实现
Controller接口
@org.springframework.stereotype.Controller("/path")
public class TestController implements Controller {
@Override
public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {
return null;
}
}
实现
HttpRequestHandler接口
@Controller("/httpPath")
public class HttpController implements HttpRequestHandler {
@Override
public void handleRequest(HttpServletRequest request,
HttpServletResponse response
) throws ServletException, IOException {
//业务处理,页面跳转,返回响应结果等等
}
}
实现
Servlet接口
@Controller("/servletPath")
public class ServletController implements Servlet {
//Servlet生命周期函数
//重写init()方法
//重写getServletConfig()方法
@Override
public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {
//业务处理
}
//重写getServletInfo()方法
//重写destroy()方法
}
每个不同的controller都要对应的适配器,只要找到合适的适配器,然后通过相应适配器调用handler方法就能执行controller中的方法了。每当我们自己定义一个controller,就能在定义一个适配器执行。这就是很好的拓展性
自定义SpringMVC适配器
首先要定义一个适配器MyHandlerAdapter,实现HandlerAdapter接口。
public class MyHandlerAdapter implements HandlerAdapter {
@Override
public boolean supports(Object handler) {
return handler instanceof MyController;
}
@Override
public ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
return ((MyController) handler).handleRequest(request, response);
}
@Override
public long getLastModified(HttpServletRequest request, Object handler) {
//不使用浏览器缓存,返回-1
return -1;
}
}
接着定义一个MyController接口。
public interface MyController {
/**
* 处理请求
*/
@Nullable
ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception;
}
注册适配器到Spring容器中。
@Configuration
@EnableWebMvc
public class WebMvcConfig extends WebMvcConfigurerAdapter {
//注册自定义的适配器
@Bean
public MyHandlerAdapter myHandlerAdapter() {
return new MyHandlerAdapter();
}
}
最后创建一个MyTestController实现MyController进行测试。
@Controller("/myTest")
public class MyTestController implements MyController {
@Override
public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {
response.getWriter().println("MyTestController Test success!!!");
return null;
}
}
SpringMVC 工作原理了
流程说明(重要):
- 客户端(浏览器)发送请求,
DispatcherServlet拦截请求。 DispatcherServlet根据请求信息调用HandlerMapping。HandlerMapping根据 uri 去匹配查找能处理的Handler(也就是我们平常说的Controller控制器) ,并会将请求涉及到的拦截器和Handler一起封装。DispatcherServlet调用HandlerAdapter适配执行Handler。Handler完成对用户请求的处理后,会返回一个ModelAndView对象给DispatcherServlet,ModelAndView顾名思义,包含了数据模型以及相应的视图的信息。Model是返回的数据对象,View是个逻辑上的View。ViewResolver会根据逻辑View查找实际的View。DispaterServlet把返回的Model传给View(视图渲染)。- 把
View返回给请求者(浏览器)
Spring MVC拦截器(Interceptor)
在 Spring MVC 框架中定义一个拦截器需要对拦截器进行定义和配置,主要有以下 2 种方式。
- 通过实现 HandlerInterceptor 接口或继承 HandlerInterceptor 接口的实现类(例如 HandlerInterceptorAdapter)来定义;
- 通过实现 WebRequestInterceptor 接口或继承 WebRequestInterceptor 接口的实现类来定义。
public class TestInterceptor implements HandlerInterceptor {
@Override
public void afterCompletion(HttpServletRequest request,
HttpServletResponse response, Object handler, Exception ex)
throws Exception {
System.out.println("afterCompletion方法在控制器的处理请求方法执行完成后执行,即视图渲染结束之后执行");
}
@Override
public void postHandle(HttpServletRequest request,
HttpServletResponse response, Object handler,
ModelAndView modelAndView) throws Exception {
System.out.println("postHandle方法在控制器的处理请求方法调用之后,解析视图之前执行");
}
@Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response, Object handler) throws Exception {
System.out.println("preHandle方法在控制器的处理请求方法调用之前执行");
return false;
}
}
上述拦截器的定义中实现了 HandlerInterceptor 接口,并实现了接口中的 3 个方法,说明如下。
- preHandle( ):该方法在控制器的处理请求方法前执行,其返回值表示是否中断后续操作,返回 true 表示继续向下执行,返回 false 表示中断后续操作。结合session可以用来判断登录状态
- postHandle( ):该方法在控制器的处理请求方法调用之后、解析视图之前执行,可以通过此方法对请求域中的模型和视图做进一步的修改。
- afterCompletion( ):该方法在控制器的处理请求方法执行完成后执行,即视图渲染结束后执行,可以通过此方法实现一些资源清理、记录日志信息等工作。
SpringBoot 自动装配原理详解
每次问到 Spring Boot, 面试官非常喜欢问这个问题:“讲述一下 SpringBoot 自动装配原理?”。
我觉得我们可以从以下几个方面回答:
- 什么是 SpringBoot 自动装配?
- SpringBoot 是如何实现自动装配的?如何实现按需加载?
- 如何实现一个 Starter?
前言
使用过 Spring 的小伙伴,一定有被 XML 配置统治的恐惧。即使 Spring 后面引入了基于注解的配置,我们在开启某些 Spring 特性或者引入第三方依赖的时候,还是需要用 XML 或 Java 进行显式配置。
举个例子。没有 Spring Boot 的时候,我们写一个 RestFul Web 服务,还首先需要进行如下配置。
@Configuration
public class RESTConfiguration
{
@Bean
public View jsonTemplate() {
MappingJackson2JsonView view = new MappingJackson2JsonView();
view.setPrettyPrint(true);
return view;
}
@Bean
public ViewResolver viewResolver() {
return new BeanNameViewResolver();
}
}
spring-servlet.xml
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context/ http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/mvc/ http://www.springframework.org/schema/mvc/spring-mvc.xsd">
<context:component-scan base-package="com.howtodoinjava.demo" />
<mvc:annotation-driven />
<!-- JSON Support -->
<bean name="viewResolver" class="org.springframework.web.servlet.view.BeanNameViewResolver"/>
<bean name="jsonTemplate" class="org.springframework.web.servlet.view.json.MappingJackson2JsonView"/>
</beans>
但是,Spring Boot 项目,我们只需要添加相关依赖,无需配置,通过启动下面的 main 方法即可。
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
并且,我们通过 Spring Boot 的全局配置文件 application.properties或application.yml即可对项目进行设置比如更换端口号,配置 JPA 属性等等。
为什么 Spring Boot 使用起来这么酸爽呢? 这得益于其自动装配。自动装配可以说是 Spring Boot 的核心,那究竟什么是自动装配呢?
什么是 SpringBoot 自动装配?
所有 Spring Boot Starter 下的META-INF/spring.factories都会被读取到。
spring.factories中这么多配置,每次启动都要全部加载么?”。
很明显,这是不现实的。我们 debug 到后面你会发现,configurations 的值变小了。
因为,这一步有经历了一遍筛选,@ConditionalOnXXX 中的所有条件都满足,该类才会生效。
如何实现一个 Starter
(21条消息) 【SpringBoot】手写一个简单的SpringBoot-starter_灯盏菜的博客-CSDN博客
主要步骤:
1.创建一个xx-starter的项目,引入需要的依赖
2.编写想要实现xx功能的业务类
3.通过注解 @ConfigurationProperties(prefix = "ziyou.json"使得类与properties绑定
4.创建自动化配置类 ,创建一个AutoConfiguration,引用定义好的配置信息;在AutoConfiguration中实现所有starter应该完成的操作,如果想与properties 配置挂钩,需要用这个注解
@EnableConfigurationProperties(MyJsonProperties.class)
@Configuration
@ConditionalOnClass({MyJsonService.class})
@EnableConfigurationProperties(MyJsonProperties.class)
public class MyJsonAutoConfiguration {
/**
* 注入属性类
*/
@Autowired
private MyJsonProperties myJsonProperties;
/**
* 当当前上下文中没有 MyJsonService 类时创建类
*
* @return 返回创建的实例
*/
@Bean
@ConditionalOnMissingBean(MyJsonService.class)
public MyJsonService myJsonService() {
MyJsonService myJsonService = new MyJsonService();
myJsonService.setName(myJsonProperties.getName());
return myJsonService;
}
}
并且把这个类加入spring.factories配置文件中进行声明
org.springframework.boot.autoconfigure.EnableAutoConfiguration
=com.ziyou.starter.MyJsonAutoConfiguration
6.然后我们通过运行mvn install命令,将这个项目打包成 jar 部署到本地仓库中,提供让另一个服务调用。
7.创建一个新的 SpringBoot web 项目test-myjson-spring-boot-starter,提供一个接口去访问。
Spring Boot 通过@EnableAutoConfiguration开启自动装配,通过 SpringFactoriesLoader 最终加载META-INF/spring.factories中的自动配置类实现自动装配,自动配置类其实就是通过@Conditional按需加载的配置类,想要其生效必须引入spring-boot-starter-xxx包实现起步依赖