IoC—Inversion of Control,即“控制反转”,不是什么技术,而是一种设计思想。在 Java 开发中,IoC 意味着将你设计好的对象交给容器控制,而不是传统的在你的对象内部直接控制。
谁控制谁,控制什么:传统 Java SE 程序设计,我们直接在对象内部通过 new 进行创建对象,是程序主动去创建依赖对象;而 IoC 是有专门一个容器来创建这些对象,即由 IoC 容器来控制对 象的创建;谁控制谁?当然是 IoC 容器控制了对象;控制什么?那就是主要控制了外部资源获取。
为何是反转,哪些方面反转了:传统应用程序是由我们自己在对象中主动控制去直接获取依赖对象,而反转则是由容器来帮忙创建及注入依赖对象;为何是反转?因为由容器帮我们查找及注入依赖对象,对象只是被动的接受依赖对象,所以是反转;哪些方面反转了?依赖对象的获取被反转了。
Spring 启动时读取应用程序提供的 Bean 配置信息,并在 Spring 容器中生成一份相应的 Bean 配置注册表,然后根据这张注册表实例化 Bean,装配好 Bean 之间的依赖关系,为上层应用提供准备就绪的运行环境。
DI—Dependency Injection,即“依赖注入”:组件之间依赖关系由容器在运行期决定,形象的说,即由容器动态的将某个依赖关系注入到组件之中。依赖注入的目的并非为软件系统带来更多功能,而是为了提升组件重用的频率,并为系统搭建一个灵活、可扩展的平台。通过依赖注入机制,我们只需要通过简单的配置,而无需任何代码就可指定目标需要的资源,完成自身的业务逻辑,而不需要关心具体的资源来自何处,由谁实现。
IOC 和 DI 由什么关系呢?其实它们是同一个概念的不同角度描述,由于控制反转概念比较含糊(可能只是理解为容器控制对象这一个层面,很难让人想到谁来维护对象关系),所以 2004 年大师级人物 Martin Fowler 又给出了一个新的名字:“依赖注入”,相对 IOC 而言,“依赖注入”明确描述了“被注入对象依赖 IOC 容器配置依赖对象”。
AOP 是 OOP 的延续,是 Aspect Oriented Programming 的缩写,意思是面向切面编程。可以通过预编译方式和运行期动态代理实现在不修改源代码的情况下给程序动态统一添加功能的一种技术。
AOP 设计模式孜孜不倦追求的是调用者和被调用者之间的解耦,AOP 可以说也是这种目标的一种实现。我们现在做的一些非业务,如:日志、事务、安全等都会写在业务代码中(也即是说,这些非业务类横切于业务类),但这些代码往往是重复,复制——粘贴式的代码会给程序的维护带来不便,AOP 就实现了把这些业务需求与系统需求分开来做。
官方的抽象定义为“一个关注点的模块化,这个关注点可能会横切多个对象”。“切面”在 ApplicationContext 中 <aop:aspect> 来配置。
程序执行过程中的某一行为,例如,MemberService.get 的调用或者 MemberService.delete 抛出异常等行为。
“切面”对于某个“连接点”所产生的动作。其中,一个“切面”可以包含多个 “Advice”。
匹配连接点的断言,在 AOP 中通知和一个切入点表达式关联。切面中的所有通知所关注的连接点,都由切入点表达式来决定。
被一个或者多个切面所通知的对象。当然在实际运行时,SpringAOP 采用代理实现,实际 AOP 操作的是 TargetObject 的代理对象。
在 Spring AOP 中有两种代理方式,JDK 动态代理和 CGLib 代理。默认情况下,TargetObject 实现了接口时,则采用 JDK 动态代理,反之,采用 CGLib 代理。强制使用 CGLib 代理需要将 <aop:config> 的 proxy-target-class 属性设为 true。
在某连接点(JoinPoint)之前执行的通知,但这个通知不能阻止连接点前的执行。ApplicationContext 中在 <aop:aspect> 里面使用 <aop:before> 元素进行声明。
当某连接点退出的时候执行的通知(不论是正常返回还是异常退出)。ApplicationContext 中在 <aop:aspect> 里面使用 <aop:after> 元素进行声明。
在某连接点正常完成后执行的通知,不包括抛出异常的情况。ApplicationContext 中在 <aop:aspect> 里面使用 <after-returning> 元素进行声明。
包围一个连接点的通知,类似 Web 中 Servlet 规范中的 Filter 的 doFilter 方法。可以在方法的调用前后完成自定义的行为,也可以选择不执行。ApplicationContext 中在 <aop:aspect> 里面使用 <aop:around> 元素进行声明。例如,ServiceAspect 中的 around 方法。
在方法抛出异常退出时执行的通知 。 ApplicationContext 中在 <aop:aspect> 里面使用 <aop:after-throwing> 元素进行声明。
//声明这是一个组件
@Component
//声明这是一个切面 Bean,AnnotaionAspect 是一个面,由框架实现的
@Aspect
public class AnnotaionAspect {
private final static Logger log = Logger.getLogger(AnnotaionAspect.class);
//配置切入点,该方法无方法体,主要为方便同类中其他方法使用此处配置的切入点
//切点的集合,这个表达式所描述的是一个虚拟面(规则)
//就是为了 Annotation 扫描时能够拿到注解中的内容
@Pointcut("execution(* com.stu.aop.service..*(..))")
public void aspect(){}
/*
* 配置前置通知,使用在方法 aspect() 上注册的切入点
* 同时接受 JoinPoint 切入点对象,可以没有该参数
*/
@Before("aspect()")
public void before(JoinPoint joinPoint){
log.info("before " + joinPoint);
}
//配置后置通知,使用在方法 aspect() 上注册的切入点
@After("aspect()")
public void after(JoinPoint joinPoint){
log.info("after " + joinPoint);
}
//配置环绕通知,使用在方法 aspect() 上注册的切入点
@Around("aspect()")
public void around(JoinPoint joinPoint){
long start = System.currentTimeMillis();
try {
((ProceedingJoinPoint) joinPoint).proceed();
long end = System.currentTimeMillis();
log.info("around " + joinPoint + "\tUse time : " + (end - start) + " ms!");
} catch (Throwable e) {
long end = System.currentTimeMillis();
log.info("around " + joinPoint + "\tUse time : " + (end - start) + " ms with exception : " + e.getMessage());
}
}
//配置后置返回通知,使用在方法 aspect() 上注册的切入点
@AfterReturning("aspect()")
public void afterReturn(JoinPoint joinPoint){
log.info("afterReturn " + joinPoint);
}
//配置抛出异常后通知,使用在方法 aspect() 上注册的切入点
@AfterThrowing(pointcut="aspect()", throwing="ex")
public void afterThrow(JoinPoint joinPoint, Exception ex){
log.info("afterThrow " + joinPoint + "\t" + ex.getMessage());
}
}
@Component 组件,没有明确的角色
@Service 在业务逻辑层使用(service 层)
@Repository 在数据访问层使用(dao 层)
@Controller 在展现层使用,控制器的声明(controller 层)
@Autowired:属性注入
@Configuration 声明当前类为配置类,相当于 xml 形式的 Spring 配置(类上)
@Bean 注解在方法上,声明当前方法的返回值为一个 bean,替代 xml 中的方式(方法上)
@ComponentScan 用于对 Component 进行扫描,相当于 xml 中的(类上)
@WishlyConfiguration 为@Configuration 与@ComponentScan 的组合注解,可以替代这两个注解
@After 在方法执行之后执行(方法上)
@Before 在方法执行之前执行(方法上)
@Around 在方法执行之前与之后执行(方法上)
1、客户端发出一个 http 请求给 web 服务器,web 服务器对 http 请求进行解析,如果匹配 DispatcherServlet 的请求映射路径(在 web.xml 中指定),web 容器将请求转交给 DispatcherServlet.
2、DipatcherServlet 接收到这个请求之后将根据请求的信息(包括 URL、Http 方法、请求报文头和请求参数 Cookie 等)以及 HandlerMapping 的配置找到处理请求的处理器(Handler 也就是 Controller)。其中一种非常重要的 HandlerMapping 是 RequestMappingHandlerMapping, 我们通过在 Controller 方面上加@RequestMapping 注释来配合使用,系统会将我们配置的 RequestMapping 信息注册到其中。
3-4、DispatcherServlet 根据 HandlerMapping 找到对应的 Handler, 将处理权交给 Handler(Handler 将具体的处理进行封装),再由具体的 HandlerAdapter 对 Handler 进行具体的调用。HandlerAdapter 包含一个 handle 方法,负责调用真实的页面处理器进行请求处理并返回一个 ModelAndView。HandlerAdpter 里面有一些常见的处理,比如消息转移,参数处理等
5、Handler 对数据处理完成以后将返回一个 ModelAndView 对象给 DispatcherServlet。
6、Handler 返回的 ModelAndView 只是一个逻辑视图并不是一个正式的视图,DispatcherSevlet 通过 ViewResolver 将逻辑视图转化为真正的视图 View。
7、Dispatcher 通过 model 解析出 ModelAndView 中的参数进行解析最终展现出完整的 view 并返回给客户端。
@RequestMapping:是一个用来处理请求地址映射的注解,可用于类或方法上。
@RequestParam:参数绑定注解
@ResponseBody:不进行视图跳转,直接进行数据响应
@PathVariable:可以将 URL 中占位符参数{xxx}绑定到处理器类的方法形参中@PathVariable(“xxx“)
@SessionAttributes:即将值放到 session 作用域中,写在 class 上面。
@CookieValue:可以把 Request header 中关于 cookie 的值绑定到方法的参数上
@RequestHeader:可以把 Request 请求 header 部分的值绑定到方法的参数上
ORM 的全称是 Object Relational Mapping,即对象关系映射。它的实现思想就是将关系数据库中表的数据映射成为对象,以对象的形式展现,这样开发人员就可以把对数据库的操作转化为对这些对象的操作。因此它的目的是为了方便开发人员以面向对象的思想来实现对数据库的操作。
如果没有配置文件,我们的应用程序将只能沿着固定的姿态运行,几乎不能做任何动态的调整,那么这不是一套完美的设计,因为我们希望拥有更宽更灵活的操作空间和更多的兼容度,同时也能解决硬编码等问题,所以我们需要有配置文件,对应用程序进行参数预设和设置初始化工作。
MyBatis 框架本身,理论上就一个配置文件,其实也只需要一个配置文件,即 MyBatis-config.xml (当然文件名允许自由命名),只不过这个配置文件其中的一个属性 mappers(映射器),由于可能产生过多的 SQL 映射文件,于是我们物理上单独拓展出来,允许使用者定义任意数量的 xxxMapper.xml 映射文件。
常用标签:
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE configuration PUBLIC "-//MyBatis.org//DTD Config 3.0//EN"
"http://MyBatis.org/dtd/MyBatis-3-config.dtd">
<configuration>
<!-- 1、属性:例如 jdbc.properties -->
<properties resource="jdbc.properties"></properties>
<!-- 2、设置:定义全局性设置,例如开启二级缓存 -->
<settings>
<setting name="cacheEnabled" value="true"/>
</settings>
<!-- 3、类型名称:为一些类定义别名 -->
<typeAliases>
<typeAlias type="com.panshenlian.pojo.User" alias="user"></typeAlias>
</typeAliases>
<!-- 4、类型处理器:定义 Java 类型与数据库中的数据类型之间的转换关系 -->
<typeHandlers></typeHandlers>
<!-- 5、对象工厂 -->
<objectFactory type=""></objectFactory>
<!-- 6、插件:MyBatis 的插件,支持自定义插件 -->
<plugins>
<plugin interceptor=""></plugin>
</plugins>
<!-- 7、环境:配置 MyBatis 的环境 -->
<environments default="development">
<!-- 环境变量:支持多套环境变量,例如开发环境、生产环境 -->
<environment id="development">
<!-- 事务管理器:默认 JDBC -->
<transactionManager type="JDBC" />
<!-- 数据源:使用连接池,并加载 mysql 驱动连接数据库 -->
<dataSource type="POOLED">
<property name="driver" value="com.mysql.jdbc.Driver" />
<property name="url" value="jdbc:mysql://localhost:3306/MyBatis" />
<property name="username" value="root" />
<property name="password" value="123456" />
</dataSource>
</environment>
</environments>
<!-- 8、数据库厂商标识 -->
<databaseIdProvider type=""></databaseIdProvider>
<!-- 9、映射器:指定映射文件或者映射类 -->
<mappers>
<mapper resource="UserMapper.xml" />
</mappers>
</configuration>
MyBatis 的真正强大在于它的映射语句,也是它的魔力所在。由于它的异常强大,映射器的 XML 文件就显得相对简单。如果拿它跟具有相同功能的 JDBC 代码进行对比,你会立即发现省掉了将近 95% 的代码。MyBatis 就是针对 SQL 构建的,并且比普通的方法做的更好。
常用标签:
select : 用于查询,支持传参,返回指定结果集;
insert : 用于新增,支持传参,返回指定结果集;
update : 用于更新,支持传参,返回指定结果集;
delete : 用于删除,支持传参,返回指定结果集;
sql : 被其它语句引用的可复用语句块;
cache : 当前命名空间缓存配置;
cache-ref : 引用其它命名空间的缓存配置;
parameterMap : 参数映射,已弃用;
resultMap : 结果集映射;
#{}: 解析为一个 JDBC 预编译语句(prepared statement)的参数标记符,一个 #{ } 被解析为一个参数占位符 。
${}: 仅仅为一个纯碎的 string 替换,在动态 SQL 解析阶段将会进行变量替换。
MyBatis 提供了一级缓存和二级缓存的支持。默认情况下,MyBatis 只开启一级缓存。
一级缓存是基于 HashMap 的本地缓存,作用范围为 session 域内。当 session flush(刷新)或者 close(关闭)之后,该 session 中所有的 cache(缓存)就会被清空。
在参数和 SQL 完全一样的情况下,我们使用同一个 SqlSession 对象调用同一个 mapper 的方法,往往只执行一次 SQL。因为使用 SqlSession 第一次查询后,MyBatis 会将其放在缓存中,再次查询时,如果没有刷新,并且缓存没有超时的情况下,SqlSession 会取出当前缓存的数据,而不会再次发送 SQL 到数据库。
由于 SqlSession 是相互隔离的,所以如果你使用不同的 SqlSession 对象,即使调用相同的 Mapper、参数和方法,MyBatis 还是会再次发送 SQL 到数据库执行,返回结果。
二级缓存是全局缓存,作用域超出 session 范围之外,可以被所有 SqlSession 共享。一级缓存缓存的是 SQL 语句,二级缓存缓存的是结果对象。
二级缓存配置:
MyBatis 的全局缓存配置需要在 MyBatis-config.xml 的 settings 元素中设置,代码如下:
<settings>
<setting name="cacheEnabled" value="true" />
</settings>
在 mapper 文件中设置缓存,默认不开启缓存。需要注意的是,二级缓存的作用域是针对 mapper 的 namescape 而言,即只有再次在 namescape 内的查询才能共享这个缓存,代码如下:
<mapper namescape="net.biancheng.WebsiteMapper">
<!-- cache配置 -->
<cache
eviction="FIFO"
flushInterval="60000"
size="512"
readOnly="true" />
...
</mapper>
以上属性说明如下:
属性 | 说明 |
---|---|
eviction | 代表的是缓存回收策略,目前 MyBatis 提供以下策略。LRU:使用较少,移除最长时间不用的对象;FIFO:先进先出,按对象进入缓存的顺序来移除它们;SOFT:软引用,移除基于垃圾回收器状态和软引用规则的对象;WEAK:弱引用,更积极地移除基于垃圾收集器状态和弱引用规则的对象。 |
flushInterval | 刷新间隔时间,单位为毫秒,这里配置的是 100 秒刷新,如果省略该配置,那么只有当 SQL 被执行的时候才会刷新缓存。 |
size | 引用数目,正整数,代表缓存最多可以存储多少个对象,不宜设置过大。设置过大会导致内存溢出。这里配置的是 1024 个对象。 |
readOnly | 只读,默认值为 false,意味着缓存数据只能读取而不能修改,这样设置的好处是可以快速读取缓存,缺点是没有办法修改缓存。 |
在 Maven 中,坐标是 Jar 包的唯一标识,Maven 通过坐标在仓库中找到项目所需的 Jar 包。? 如下代码中,groupId 和 artifactId 构成了一个 Jar 包的坐标。
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.1.1</version>
</dependency>