整合营销服务商

电脑端+手机端+微信端=数据同步管理

免费咨询热线:

阿锐分享Spring依赖注入

阿锐分享Spring依赖注入

、Spring简介

1.Spring简化Java开发

Spring Framework是一个应用框架,框架一般是半成品,我们在框架的基础上可以不用每个项目自己实现架构、基础设施和常用功能性组件,而是可以专注业务逻辑。因此学习Spring Framework在架构和模式方面的结构和原理,对我们在架构和模块级别的理解帮助极大。Spring Framework(参考1)的宗旨是简化Java开发,主要的手段如下:

(1)在架构上解耦:通过DI(依赖注入)管理类型依赖,通过AOP分离关注点,减少重复代码。

(2)在设计上广泛采用DIP(依赖倒置)和ISP(接口隔离)等原则和Facade(外观)等模式:提供简化的调用接口并封装了众多出色的第三方组件。

(3)在语言层面上采用注解:通过配置文件和Annotation(参考.NET Attribute)简化应用配置。

2.Spring Framework的架构和模块:

Spring Framework本身的架构是典型的松散分层,外层可以按需引用全部内层,内层不能引用外层。Spring的基础组件如下图所示:

从图中可以看出,开始的模块只有从core\beans\aop\context四个组件,后来添加了context-support【1.2】扩展模块、expression【3.0】扩展模块和beans-groovy【4.0】扩展模块。

Spring上述模块的基础上,内建和封装了众多的实用的通用组件,主要的组件如图所示:

从图中可以看出,spring-oxm、spring-jdbc和spring-web是众多模块依赖的核心,spring-oxm提供了Object和XML的映射支持。

二、基础知识

1.DIP:DIP(依赖倒置原则)是DI(依赖注入)的核心(参考2)。

(1)高层模块不应该依赖于低层模块。两者都应该依赖于抽象。

(2)抽象不应该依赖于细节。细节应该依赖于抽象。

说人话就是:将对具体类的引用转换成对其接口的引用,具体类只引用接口(引用==依赖,接口==接口或抽象类)。事实上我们调用具体类的时候在头脑里也是只关心其提供的API而非实现,DIP则通过在设计和重构阶段在技术手段上保证了解耦。

2.DI:DI(依赖注入)让我们不必手写工厂代码来管理接口和实现类的映射、对象的创建和生命周期的管理。

(1)接口注入:必须实现特定的接口才可以,侵入性太强,现在已经无人关心和使用。

(2)构造函数注入:依赖体现在构造函数的参数上。

(3)属性注入:依赖体现在属性上。

由于在实现时,可以将类型注册为自己的兼容类型,这样依赖注入就可以直接替代new实例化对象,这样理解和使用依赖注入工具还不如不使用或手写工厂了。依赖注入工具在实现时肯定会实现成一个支持不同配置和不同生命周期的对象工厂,但即使没有提供一套添加依赖倒置原则限制的API,也不意味着我们把它当成new的替代品。如同映射工具虽然在实现时可以任意映射,但不是用来取代赋值的,而是用来处理领域实体和视图模型等有实际对应关系的对象之间的映射。

(1)依赖配置:依赖配置是依赖注入实现的基础。依赖注入工具都至少支持代码配置和文件配置。Java中可以通过Annotation(.NET中通过Attribute)简化配置。

(2)对象工厂:根据配置返回一个或多个对象。这是核心功能。

(3)生命周期管理:一般提供至少4种级别的支持:作用域、单例、线程、HTTP请求范围。

大多数依赖注入工具在支持依赖倒置原则的基础上,在技术手段上实现了更多的功能,如类型的兼容转换、对依赖命名、在配置时直接传入对象等。

三、Spring依赖注入的要点

Bean在Spring中就是POJO(.NET的POCO)。

Spring依赖注入需要掌握的核心是3个类型BeanDefinition、BeanFactory和ApplicationContext。

1.BeanFactory

BeanFactory是spring中依赖注入的核心接口,其设计主要采用了ISP(接口隔离原则),通过多层次的接口继承即保证了单个接口的内聚又保证了整个体系的简洁。这里我们要关注的核心是DefaultListableBeanFactory。

如图所示,查看XmlBeanFactory代码,可以看到XmlBeanFactory只是通过XmlBeanDefinitionReader载入了BeanDefinition配置,XmlBeanDefinitionReader负责将配置解析到BeanDefinition。DefaultListableBeanFactory是真正的实现类,其中定义了类型为Map<String, BeanDefinition>的beanDefinitionMap列表用于存储依赖配置。#p#分页标题#e#

2.BeanDefinition:

BeanDefinition定义了配置元数据,无论使用java code、xml、Annotation还是Groovy脚本方式,不同的配置方式通过不同的BeanDefinitionReader解析为BeanDefinition。

3.ApplicationContext

ApplicationContext的核心都是将对象工厂功能委托给BeanFactory的实现类DefaultListableBeanFactory。目前最常用的是基于注解的AnnotationConfigApplicationContext和AnnotationConfigWebApplicationContext。

四、Spring依赖注入快速上手

1.使用Java配置代替xml配置

Java配置的核心是@Configuration和@Bean。定义生命周期使用@Scope,需要引入其他配置文件时使用@Import。

(1)@Configuration:应用了@Configuration注解的POCO成为了配置类。相当于xml配置文件。

(2)@Bean:配置类中应用了@Bean注解的方法成为了配置项。相当于xml中的Bean节点。

package me.test.spring_ioc;

import org.springframework.context.annotation.AnnotationConfigApplicationContext;

import org.springframework.context.annotation.Bean;

import org.springframework.context.annotation.Configuration;

public class App {

public static void main(String[] args) {

AnnotationConfigApplicationContext container=new AnnotationConfigApplicationContext(AppConfig.class);

String message=container.getBean(IAppService.class).Test();

System.out.println(message);

container.close();

}

}

@Configuration

class AppConfig {

@Bean

public IAppService IAppService() {

return new AppService(new Repository<SimpleEntity>());

}

}

class SimpleEntity {

private String name;

public String getName() {

return name;

}

public void setName(String name) {

this.name=name;

}

}

interface IAppService {

String Test();

}

interface IRepository<T> {

String Test();

}

class AppService implements IAppService {

private IRepository<SimpleEntity> _repo;

public AppService(IRepository<SimpleEntity> repo) {

_repo=repo;

}

@Override

public String Test() {

return this._repo.Test();

}

}

class Repository<T> implements IRepository<T> {

@Override

public String Test() {

return this.getClass().getName();

}

}

#p#分页标题#e#

如果是Web应用程序,应该使用AnnotationConfigWebApplicationContext,在JSP中可通过WebApplicationContextUtils获取ApplicationContext对象。

<%@page import="swp.IAppService"%>

<%@page import="org.springframework.web.context.WebApplicationContext"%>

<%@page

import=" org.springframework.web.context.support.WebApplicationContextUtils"%>

<html>

<body>

<%

WebApplicationContext context=WebApplicationContextUtils

.getRequiredWebApplicationContext(this.getServletContext());

String message=context.getBean(IAppService.class).print();

out.print(message);

%>

</body>

</html>

2.基于Annotation的自动装配

自动装配主要使用@ComponentScan、@Component和@Autowired。

(1)@ComponentScan:作用在配置类上,启用组件扫描。扫描并注册标注了@Component(@Controller\@Service\@Repository)的类型。@Configuration已经应用了@Component注解。

(2)@Autowired:按类型自动装配。@Autowired和使用@Inject(JSR-330)或@Resource(JSR-250)的效果是类似的。@Autowired和@Inject默认按类型注入,@Resource默认按名称注入。

package me.test.spring_ioc;

import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.context.annotation.AnnotationConfigApplicationContext;

import org.springframework.context.annotation.Bean;

import org.springframework.context.annotation.ComponentScan;

import org.springframework.context.annotation.Configuration;

import org.springframework.stereotype.Component;

public class App {

public static void main(String[] args) {

AnnotationConfigApplicationContext container=new AnnotationConfigApplicationContext(AppConfig.class);

String message=container.getBean(IAppService.class).Test();

System.out.println(message);

container.close();

}

}

@Configuration

@ComponentScan

class AppConfig {

/*@Bean

public IAppService IAppService() {

return new AppService(new Repository<SimpleEntity>());

}*/

}

class SimpleEntity {

private String name;

public String getName() {

return name;

}

public void setName(String name) {

this.name=name;

}

}

interface IAppService {

String Test();

}

interface IRepository<T> {

String Test();

}

@Component

class AppService implements IAppService {

private IRepository<SimpleEntity> _repo;

@Autowired

public AppService(IRepository<SimpleEntity> repo) {

_repo=repo;

}

@Override

public String Test() {

return this._repo.Test();

}

}

@Component

class Repository<T> implements IRepository<T> {

@Override

public String Test() {

return this.getClass().getName();

}

}

#p#分页标题#e#

小结:

你至少应该知道的:

(1)BeanFactory和BeanDefinition

(2)DefaultListableBeanFactory和ApplicationContext

(3)AnnotationConfigApplicationContext和AnnotationConfigWebApplicationContext

(4)@Configuraton和@Bean

Spring的依赖注入在配置上是基于对象而不是类型,最先支持的是xml而不是注解,到现在也没有比较方便的代码配置机制。虽然现在已经从xml过渡到了注解方式,但基于对象的基础仍然是影响很多方面的缺点。

前在手机微信浏览器中打开的 H5 页面,是无法通过 JavaScript 的 API 实现图片或文件下载的。

为什么在手机的默认浏览器就可以下载,但我们在微信里面打开浏览器,就下不了了呢?

我觉得是微信 APP 的权限问题,下载的权限是掌握在当前使用的 APP 手上的。微信拒绝了内置浏览器的下载文件请求,即使浏览器自身有对应的下载 API,但你就是下载不了。

微信为什么这么做?是因为怕以前常见的浏览器弹窗广告一样的情况出现。如果对图片下载权限不做限制的话,很多搞营销的就会诱导用户打开一些网页,然后自动下载一些广告宣传图片之类的东西。

但长按图片保存还是可以的

但也不是完全没办法,微信还是提供了一些 API 来实现 受限 的下载功能。

使用微信的 JS-SDK

我们可以使用微信提供的 JS-SDK 去和微信通信,获得一些手机系统的能力。

官方说法为:

微信JS-SDK是微信公众平台面向网页开发者提供的基于微信内的网页开发工具包。

用法就是在页面引入这个 jssdk 的 js 文件,然后你就能拿到一个 wx 的对象。然后调用 config 方法,传入 appId、签名等信息(这个需要在微信公众号平台获取)。之后我们就可以通过 wx 对象的方法去和微信通信,使用手机系统的能力。

其中有个 wx.downloadImage() 的方法,是用来下载图片的。但其实我觉得应该是用来耍猴的

该方法不接受一个 url 参数,而是接受一个 serverId。

serverId 从哪来,从另一个方法 wx.uploadImage() 里面获得。wx.uploadImage() 方法用于上传本地的图片,但上传成功后,回调函数就能拿到一个 serverId。

上传到哪?微信自己的服务器。如果你想保存到自己公司的服务器,你还得让公司服务器调用多媒体接口,从微信服务器拿回图片。而且,微信服务器只给你保存 3 天时间

是的,你能下载的图片的图片来自用户的上传,且只有三天有效期。另外,下载的图片貌似也不会保存到相册里,而是保存在用户找不到的地方。

结论就是完全没法用。

微信小程序

小程序提供了一个简单、高效的应用开发框架和丰富的组件及API,帮助开发者在微信中开发具有原生 APP 体验的服务。

微信小程序算不上是 H5,因为它是微信下的原生程序,只是语法上类似网页开发的那一套。

微信小程序的 Hello World 项目模板的入口文件为下面代码。

// app.ts
App<IAppOption>({
  globalData: {},
  onLaunch() {
    // 展示本地存储能力
    const logs=wx.getStorageSync('logs') || []
    logs.unshift(Date.now())
    wx.setStorageSync('logs', logs)

    // 登录
    wx.login({
      success: res=> {
        console.log(res.code)        
        // 发送 res.code 到后台换取 openId, sessionKey, unionId
      },
    })
  },
})

可以看到,这里同样有一个 wx 对象。

但需要注意的是,这个 wx 对象和 JS-SDK 的 wx 是完全不同的东西,千万别混为一谈。

调用 wx.downloadFile() 方法并传入一个 url 就能下载文件。但有一些限制,比如单次下载大小最大为 200 MB、只能访问开发设置配置好的指定域名、域名需 ICP 备案等。

下载的文件会保存在一个本地临时路径。如果是图片,可以再调用 wx.saveImageToPhotosAlbum() 方法保存到相册。

讲了半天,这个微信小程序也不是 H5 啊,如果我的微信小程序通过 web-view 元素嵌入了一个 H5 页面,那我这个 H5 页面是不是会更牛逼一点,是不是可以用小程序的 wx 对象啊。

不行

尽管是内嵌的 H5 页面,但其实它和小程序是隔离的,小程序内嵌的 H5 页面其实和普通的 H5 页面没有区别,还是得用傻不拉几的 JS-SDK。

讲这么多,主要是想让大家认识到:wx.downloadFile() 是微信小程序才能使用的,不可以用到 H5 上,即使这个 H5 是内嵌到小程序里的

话说回来,为什么微信小程序的能力要比 JS-SDK 受到的限制小很多呢?

我认为是因为开发者开发的微信小程序是受到微信监管的,发布也需要通过微信的审核,微信也有权限将任何小程序下架。

所以微信不怕小程序开发者滥用功能,抓到了直接就给你封了。而用 H5 页面则是完全开放的,

那么方案是?

首先,微信浏览器中的 H5 页面,我们无法让用户通过点击 “下载” 按钮来下载图片、视频以及文档文件(pdf、word 等),而是打开一个新的页面让你预览。

但对于其他微信不能预览的文件(如果 zip 压缩包)来说,则会将其下载下来。

但是对于展示出来的图片,用户可以长按图片保存下来。

所以我们的常规方案是:

首先要判断浏览器是否为微信浏览器,如果不是微信浏览器,下载保持原来逻辑。

如果是图片,点击下载按钮时弹出弹窗,提示用户长按图片下载;

如果是视频或文档文件,提示用户使用默认浏览器去下载文件。考虑到用户用新的浏览器又要登陆很麻烦,引导用户点击右上角将链接用默认浏览器打开并不是好方案。

我们可以在点击下载按钮时,复制文件链接,然后让用户自行打开默认浏览器粘贴链接下载。这种占用用户剪贴板的行为其实不太好。

或者直接将下载按钮屏蔽掉算了,又或是弹出只能在 PC 端下载的提示。

如果是其他类型文件,倒是可以下载。

结尾

今天讲了微信浏览器不能下载图片的一些原因,误解和解决方案,希望对你有所帮助。

我是前端西瓜哥,欢迎关注我。

应用经济和数字化经济并生的潮流下,通过数字化转型加强数字创新能力、提升市场竞争力势在必行。敏捷为数字化转型提供了一个出色的框架。


The Open Group《发布时刻》系列网络研讨会重磅发布了开放敏捷架构相关的《O-AA?安全手册》指南《订单到回款——流程轮胎行业领域驱动设计案例研究》


活动邀请到天邦股份CIO姚凯老师,以及翌擎智能科技咨询部经理刘亮老师进行正式发布和解读,从发布物本身切入,与大家一起了解如何有效利用开放敏捷架构助力数字化企业转型。同时,对案例的研究也将助力推动对标准的理解,也为最佳实践提供指引。


本期发布物



无论探讨何种敏捷架构,都离不开“安全”这个关键词,其中,最小安全架构(MSA)是敏捷开发的关键要素。不仅要将安全性在最一开始就构建在架构中,同时还必须通过不需要大量前期规划的定期反馈循环快速响应、必须考虑到远超出当前架构的未来状态。


本指南因此提出了需要遵循的三个核心理念:

  • 敏捷安全架构必须在短而快速的周期内构建。
  • 安全架构的任何变化都必须应用于每个团队的环境。
  • 安全架构师和卫士将确保任何架构符合组织预先定义的风险偏好。


对此,架构师、特别是敏捷安全架构师所应当扮演的角色、所应该履行的职责都是什么,在本文第二章进行了详细说明。如何能淋漓尽致发挥上述职责、而不是每次都“重新发明轮子”,从而实现敏捷安全架构治理,详见指南第三章。“敏捷安全架构师必须与负责风险管理的业务组织以及解决方案的功能用户”共同组成团队,方能一展身手。第四章、第五章则分别从架构安全的特征(即,特别针对安全方面的策略与控制措施)、作为制品的敏捷安全架构(即,其所含内容与质量属性)两方面进行了阐述。



本案例研究基于米其林的真实案例(两位作者同样来自该公司),聚集于这一轮胎生产企业的订单到回款流程,体现了在数字化转型的大背景环境下、领域驱动设计(DDD)和事件风暴是如何发挥实际作用的。


作者们在其实际工作中发现:一方面,单体系统不足以灵活适应流程的局部优化;另一方面,即便引入业务流程管理(BPM)工具作为“中央编排器”,但几年后,编排器本身也会出现故障、反而变成了另一种单体,其维护也日渐难以为继。


此时,DDD成为了重构架构的好方法:通过定义领域边界、开启事件风暴(并在描述依赖关系时使用战略集成模式、体现上下文映射)、识别业务事件、深入实施细节、描述边界上下文等五个主要步骤,将从订单到回款领域清晰而完整地展现出来。


发布嘉宾

获得中欧国际工商学院MBA学位,持有CISA、CISM、CGEIT、CRISC、CISSP、CCSP、CSSLP、CEH、CIPT、CIPM、CIPP/US和EXIN DPO等认证。现任天邦食品有限公司CIO职务,负责IT战略规划、政策程序制定、IT架构设计及数字化推进工作。是ISACA、(ISC)2和IAPP协会的会员。


“数字化转型更多指战略和运营模式的改变,利用技术进步改善人类体验和运营效率,并发展客户将保持忠诚的产品和服务。数字企业指的是具有以下特征的企业:创建完全数字化交付的数字化产品或服务;客户通过数字方式获得实物产品和服务。敏捷安全架构必须在短而快速的周期内构建;安全架构的任何变化都必须应用于每个团队的环境;确保任何架构符合组织预先定义的风险偏好。”


在汽车数字化方面经验丰富,始终活跃在奥迪中国市场敏捷转型的一线。他专注于变革时代的创新企业数字化架构设计、开发和管理,以确保价值符合不断进取的业务场景。作为数字服务提供商的背景和敏捷方面的经验,他始终坚持从业务和用户出发,让数据助力营销,软件赋能汽车。


“以业务服务为起点,通过业务服务识别限界上下文,同时,对业务服务进行细化,编写包含领域知识的业务服务规约。这属于领域驱动设计统一过程的全局分析阶段。进入架构映射阶段。可以通过业务服务识别限界上下文,使用界限上下文画布可清晰列出了领域实体或值对象(“统一语言”)和 “业务决策”(规则、策略和决策),然后在此基础上,得到各个限界上下文的服务契约,包括服务契约的定义,确定上下文映射关系。由此进入领域建模阶段。”


通过与在线观众的互动问答,两位老师的解答帮助大家对“敏捷安全架构”以及“领域驱动设计“主题有了更加清晰的理解:


Q1:敏捷安全架构与传统安全架构的区别是什么?

姚凯:敏捷安全架构是对传统安全架构在敏捷环境下的优化。传统安全架构的部署可以认为是瀑布式的,需求事先确认,然后根据需求完成设计、实施,整个流程周期比较长,期间任何变更都比较难于直接实现。敏捷安全架构引入了MSA的概念,可以先提供最小的安全架构,满足基本的要求,而后在这个基础上逐步迭代和加强,这样这个安全体系的响应速度大为加强。同时,敏捷安全架构可以与更有效地冲刺和MVP集成,不至于造成安全性能的过度或不足。敏捷安全架构与敏捷开发有效融合,在DevOps的每个环节,都融入了安全要素和活动。


Q2:安全架构与信息系统架构如何融合?

姚凯:从TOGAF?标准的观点来说,我们是从业务架构出发,推导出信息系统架构,在往下到数据架构、技术架构和安全架构的。当然,TOGAF?标准也指出了,这个次序不是一成不变的。

安全需求同样来源于业务。因此,我们必须从业务出发,了解组织对风险的态度,并结合具体的业务,识别可能存在的风险点,在去检查我们的信息系统架构,看看在哪些位置有针对性的增强。比方,我们做一个电商系统,存在的风险点可能有注册环节的个人信息,有商品展示环节的价格被篡改,下单环境的“薅羊毛”,支付环节的资金安全等等。针对其中的某些关注点,我们可以检查信息系统架构设计,有没有缓解相应的威胁,比方个人信息的通知和对同意的存储,存储的个人信息类型等等。基于这些设计和我们的安全要求,在某些环节进行加强,并把这个需求传导到技术架构,例如要求数据加密存储,个人信息进行混淆等等。最后,从整体看,信息系统架构中已经包含了安全的内容,就像一个建筑设计图,单独有建筑设计、结构设计、水电、暖通,但合在一起又是彼此融为一体的。


Q3:应用端到端的流程梳理的优势和劣势分别是什么?

刘亮:通过事件风暴梳理端到端的流程是架构规划里面常见的方法。优势显而易见,包括可有效梳理及可视化业务流程和架构;利于可视化管理业务流程的变更;为未来IT业务和目标架构也提供更好的方向。

但任何事物都有正收益和负收益。负收益方面,具体体现在清晰的流程可能会使得业务和IT产生路径依赖,不利于颠覆式创新,所以我们需要定期反思和回顾流程和架构。


Q4:可以展开讲述上下文画布的应用吗?

刘亮:上下文画布广泛应用在敏捷开发和架构的多个方面,是一个战略和战术的结合工具。通过这个工具,有助于把相关的元素、信息进行有序的整合。这里我们说的限界上下文画布,将帮助我们把领域的上下文信息、关系、属性进行集中式模块化的展现,是个设计和文档类的工具。