框架就是程序的架子,在这个程序的架子中,搭建起程序的几本的骨架,针对程序的通用问题给出了便捷的解决方案,可以使开发人员 基于框架快速开发具体的应用程序
常见的框架SSH(Struts2、Spring、Hibernate) SSM(Spring、Spring、MyBatis)
Spring是一个Service层的框架,可以整合许多其他框架进行工作
Spring的主要技术是IOC和AOP
Spring本身是基于xml配置来工作的,在使用Spring过成中,不可避免的要边写大量xml配置,Spring官方提供了这些xml文件的编写规范,这是通过提供xml的约束文件来实现的
控制反转,简单来说就是将对象的创建的权力及对象的声明周期的管理过程交由Spring框架来处理,从此在开发过程中不在需要关注对象的创建和声明周期的管理,而是在需要时由Spring框架提供,这个由Spring框架管理对象创建和声明周期的机制称为控制反转
IOC不是技术,而是一种设计思想,IOC意味着将你设计好的对象给容器控制,而不是传统的在你的对象内部直接控制,
IOC控制了对象,主要控制了外部资源获取(不只是对象)
因为由容器帮我们查找及注入依赖对象,对象只是被动的接受依赖对象,所以是反转依赖对象的获取被反转了
IOC对编程带来的最大改变不是从代码上,而是从思想上,发生了“主从换位”的变化。应用程序原本是老大,要获取什么资源都是主动出击,但是在IOC/DI思想中,应用程序就编程被动的了,被动的等待IOC容器来创建并注入它需要的资源
IOC很好的体现了面向对象的设计法则之一——好莱坞法则:“别找我们,我们找你”;即由IOC容器帮对象找相应的依赖对象并注入,而不是由对象主动去找
依赖注入:组件之间依赖关系由容器在运行期决定,形象的说,即由容器动态的将某个依赖关系注入到组件之中。依赖注入的目的并非为软件系统带来更多的功能,而是为了提升组件重用的频率。并未系统搭建一个灵活、可扩展的平台。通过依赖注入机制,我们只需要简单的配置,而无需任何代码就可以指定目标需要的资源,完成自身的业务逻辑,而不需要关心具体的资源来自何处,由谁实现
应用程序依赖于IOC容器
应用程序需要IOC容器来提供对象需要的外部资源
IOC容器注入应用程序某个对象,应用程序依赖的对象
注入某个对象所需要的外部资源(包括对象、资源、常量数据)
IOC和DI是一个概念的不同概念的不用角度描述,“依赖注入”明确描述了“被注入对象依赖IOC容器配置依赖对象”
IOC是Spring的核心,所谓IoC,对于spring框架来说,就是由spring来负责控制对象的生命周期和对象间的关系。
Spring所倡导的开发方式就是,所有的类都会在spring容器中登记,告诉spring你是个什么东西,你需要什么东西,然后spring会在系统运行到适当的时候,把你要的东西主动给你,同时也把你交给其他需要你的东西。所有的类的创建、销毁都由 spring来控制,也就是说控制对象生存周期的不再是引用它的对象,而是spring。对于某个具体的对象而言,以前是它控制其他对象,现在是所有对象都被spring控制,所以这叫控制反转。
IoC的一个重点是在系统运行中,动态的向某个对象提供它所需要的其他对象。这一点是通过DI(Dependency Injection,依赖注入)来实现的
AOP面向切面编程,OOP面向对象编程
OOP引入封装、继承和多台性等概念来建立一种对象层次结构,用以模拟公共行为的一个集合。当我们需要为分散的对象引入公共行为的时候,OOP显得无能为力,oop允许你定义从上到下的关系,但并不适合定义从左到右的关系。例如日志功能。日志代码往往水平地散布在所有对象层次中,而与它所散布到的对象的核心功能毫无关系。对于其他类型的代码,如安全性、异常处理和透明的持续性也是如此。这种散布在各处的五官的代码被称为横切代码,在oop设计中,它导致了大量代码的重复,模块间的耦合度高,而不利于各个模块的重用
AOP利用横切技术,剖解开封装的对象内部,并将那些影响力多个类的公共行为封装到一个可重用模块,并将其明明为“Aspect”,即切面。所谓“切面”,
切面:一个关注点的模块化,这个关注点可能会横切多个对象。事务管理是J2EE应用中一个关于横切关注点的很好的例子。在Spring AOP中,切面可以使用基于模式)或者基于@Aspect注解的方式来实现。
简单地说,就是将那些与业务无关,却为业务模块所共同调用的逻辑或责任封装起来,便于减少系统的重复代码,降低模块间的耦合度,并有利于未来的可操作性和可维护性。AOP代表的是一个横向的关系,如果说“对象”是一个空心的圆柱体,其中封装的是对象的属性和行为;那么面向方面编程的方法,就仿佛一把利刃,将这些空心圆柱体剖开,以获得其内部的消息。而剖开的切面,也就是所谓的“方面”了。然后它又以巧夺天功的妙手将这些剖开的切面复原,不留痕迹。
使用“横切”技术,AOP把软件系统分为两个部分:核心业务逻辑组件和横切关注点。横切关注点模块化为特殊的类,这些类被称为“切面”,好处:1.横切关注点都集中于一块,不会出现大量重复代码;2.核心模块只关注核心功能的代码,模块间藕合度降低
如图:AOP 实际上是由目标类的代理类实现的。AOP 代理其实是由 AOP 框架动态生成的一个对象,该对象可作为目标对象使用。AOP 代理包含了目标对象的全部方法,但 AOP 代理中的方法与目标对象的方法存在差异,AOP 方法在特定切入点添加了增强处理,并回调了目标对象的方法。
连接点(Joinpoint):在程序执行过程中某个特定的点,比如某方法调用的时候或者处理异常的时候。在Spring AOP中,一个连接点总是表示一个方法的执行。通俗的说就是加入切点的那个点
通俗讲:
层于层之间调用的过程中,目标层中可供调用的方法,就称之为连接点。
通知(Advice):在切面的某个特定的连接点上执行的动作。其中包括了“around”、“before”和“after”等不同类型的通知(通知的类型将在后面部分进行讨论)。许多AOP框架(包括Spring)都是以拦截器做通知模型,并维护一个以连接点为中心的拦截器链。
通俗讲:
在spring底层的代理拦截下切入点后,将切入点交给切面类,切面类中就要有处理这些切入点的方法,这些方法就称之为通知(也叫增强 增强方法)。针对于切入点执行的过程,通知还分为不同的类型,分别关注切入点在执行过程中的不同的时机。
切入点(Pointcut):匹配连接点的断言。通知和一个切入点表达式关联,并在满足这个切入点的连接点上运行(例如,当执行某个特定名称的方法时)。切入点表达式如何和连接点匹配是AOP的核心:Spring缺省使用AspectJ切入点语法。
通俗讲:
在连接点的基础上 增加上切入规则 选择出需要进行增强的切入点 这些基于切入规则选出来的连接点 就称之为切入点。
引入(Introduction):用来给一个类型声明额外的方法或属性(也被称为连接类型声明(inter-type declaration))。Spring允许引入新的接口(以及一个对应的实现)到任何被代理的对象。例如,你可以使用引入来使一个bean实现IsModified接口,以便简化缓存机制。
织入(Weaving):将切面应用到目标对象来创建新的代理对象的过程。这些可以在编译时(例如使用AspectJ编译器),类加载时和运行时完成。Spring和其他纯Java AOP框架一样,在运行时完成织入。
增强(Advice):是织入到目标类连接点上的一段程序代码。Spring使用增强类定义横切逻辑,同时由于Spring只支持方法连接点,增强还包括了在方法上的哪一点加入横切代码的方位信息,所以增强既包括横切逻辑、还包含部分连接点的信息。
引介(Introduction):是一种特殊的增强,为类添加一些属性和方法。
切面(Advisor):代表一般切面,包含了横切代码和连接点信息,本身是一个简单的切面,横切的连接点是目标类的所有方法。3种类型:一般切面(advisor)、切点切面(PointcutAdvisor)、引介切面(IntroductionAdvisor)。
通俗讲:
狭义上就是 当spring拦截下切入点后 将这些切入点 交给 处理类 进行功能的增强,这个处理类就称之为切面。
广义上来讲 将spring底层的代理 切入点 和 处理类 加在一起 实现的 对层与层之间调用过程进行增强的机制 称之为切面。
目标对象(Target Object): 被一个或者多个切面所通知的对象。也被称做被通知(advised)对象。 既然Spring AOP是通过运行时代理实现的,这个对象永远是一个被代理(proxied)对象。
通俗讲:
就是真正希望被访问到的对象。spring底层的动态代理对他进行了代理,具体能不能真的访问到目标对象,或在目标对象真正执行之前和之后是否做一些额外的操作,取决于切面。
第一种是基于xml配置文件方式的实现,
第二种是基于注解方式的实现。
pring AOP使用动态代理技术在运行期织入增强代码。使用两种代理机制:基于JDK的动态代理(JDK本身只提供接口的代理);基于CGlib的动态代理。
1. JDK的动态代理主要涉及java.lang.reflect包中的两个类:Proxy和InvocationHandler。其中InvocationHandler只是一个接口,可以通过实现该接口定义横切逻辑,并通过反射机制调用目标类的代码,动态的将横切逻辑与业务逻辑织在一起。而Proxy利用InvocationHandler动态创建一个符合某一接口的实例,生成目标类的代理对象。(只能为接口创建代理实例)
CGLib采用底层的字节码技术,为一个类创建子类,并在子类中采用方法拦截的技术拦截所有父类的调用方法,并顺势织入横切逻辑。
案例:
在业务方法执行时,如果有异常抛出,则根据异常信息记录日志
@Component
@Aspect
public class ExceptionAspect {
private FileWriter writer = null;
{
try {
writer = new FileWriter("err.log");
} catch (IOException e) {
e.printStackTrace();
}
}
@AfterThrowing(value="execution(public * *(..))",throwing="e")
public void afterThrowing(JoinPoint ji ,Throwable e) throws Exception{
Class> clz = ji.getTarget().getClass();
String mName = ji.getSignature().getName();
String msg = e.getMessage();
String err = "--["+clz+"]--["+mName+"]--["+msg+"]--";
writer.write(err);
writer.flush();
}
}
统计所有业务方法执行时的耗时
package cn.tedu.aop;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;
@Component
@Aspect
public class UseTimeAspect {
@Around("execution(* add*(..))")
public void around(ProceedingJoinPoint jp) throws Throwable{
//--获取目标类
Class> clz = jp.getTarget().getClass();
//--获取目标方法
String mName = jp.getSignature().getName();
//--开始时间
long begin = System.currentTimeMillis();
//--执行目标方法
jp.proceed();
//--获取结束时间
long end = System.currentTimeMillis();
//--计算耗时
long use = end - begin;
System.out.println("--["+clz+"]["+mName+"]方法共用时["+use+"]毫秒--");
}
}
通过AOP实现事务控制,通过注解来标识方法是否需要事务
package cn.tedu.anno;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Trans {
}
package cn.tedu.service;
import org.springframework.stereotype.Service;
import cn.tedu.anno.Trans;
@Service("userService")
public class UserServiceImpl implements UserService {
@Trans
@Override
public String addUser(String name) {
int i = 1/0;
System.out.println("增加用户。。");
return "zs";
}
@Trans
@Override
public void updateUser() {
System.out.println("修改用户。。");
}
@Trans
@Override
public void deleteUser() {
System.out.println("删除用户。。");
}
@Override
public void query() {
System.out.println("查询用户。。");
}
}
package cn.tedu.aop;
import java.lang.reflect.Method;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;
import cn.tedu.anno.Trans;
import cn.tedu.utils.TransactionManager;
@Component
@Aspect
public class TransAspect {
@Pointcut("execution(* cn.tedu.service.*.*(..))")
public void pc(){
}
@Around("pc()")
public void around(ProceedingJoinPoint jp) throws Throwable{
//--获取当前的目标方法
MethodSignature signature = (MethodSignature) jp.getSignature();
//--!!!!此方法 如果底层使用的是jdk的动态动态代理 得到的将是接口上的方法
Method method = signature.getMethod();
//--获取实现类上的method
Class extends Object> clz = jp.getTarget().getClass();
Method realMethod = clz.getMethod(method.getName(), method.getParameterTypes());
//--判断目标方法上是否有@Trans
if(realMethod.isAnnotationPresent(Trans.class)){
//--有@Trans,走事务
TransactionManager.startTran();
jp.proceed();
TransactionManager.commitTran();
}else{
//--没有@Trans,不走事务
jp.proceed();
}
}
@AfterThrowing("pc()")
public void throwing(JoinPoint jp) throws Exception{
//--获取当前的目标方法
MethodSignature signature = (MethodSignature) jp.getSignature();
//--!!!!此方法 如果底层使用的是jdk的动态动态代理 得到的将是接口上的方法
Method method = signature.getMethod();
//--获取实现类上的method
Class extends Object> clz = jp.getTarget().getClass();
Method realMethod = clz.getMethod(method.getName(), method.getParameterTypes());
//--判断目标方法上是否有@Trans
if(realMethod.isAnnotationPresent(Trans.class)){
//--有@Trans,走事务
TransactionManager.rollbackTran();
}else{
//--没有@Trans,不走事务
}
}
}
package cn.tedu.test;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import cn.tedu.service.UserService;
public class AOPTest {
@Test
public void test01(){
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
UserService userService = (UserService) context.getBean("userService");
String result = userService.addUser("zsf");
userService.updateUser();
userService.deleteUser();
userService.query();
}
}
package cn.tedu.aop;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;
import cn.tedu.anno.Trans;
import cn.tedu.utils.TransactionManager;
@Component
@Aspect
public class TransAspect2 {
@Around("execution(* cn.tedu.service.*.*(..)) && @annotation(ax)")
public void around(ProceedingJoinPoint jp,Trans ax) throws Throwable{
TransactionManager.startTran();
jp.proceed();
TransactionManager.commitTran();
}
@AfterThrowing("execution(* cn.tedu.service.*.*(..)) && @annotation(ax)")
public void throwing(JoinPoint jp,Trans ax) throws Exception{
TransactionManager.rollbackTran();
}
}
通过aop来实现权限控制,通过自定义注解声明业务方法是否需要权限控制,通过权限注解上的属性声明需要什么样的权限
package cn.tedu.anno;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Priv {
String value();
}
package cn.tedu.service;
import org.springframework.stereotype.Service;
import cn.tedu.anno.Priv;
@Service("userService")
public class UserServiceImpl implements UserService {
@Override
@Priv("add")
public String addUser(String name) {
System.out.println("增加用户。。");
return "zs";
}
@Override
@Priv("update")
public void updateUser() {
System.out.println("修改用户。。");
}
@Override
@Priv("delete")
public void deleteUser() {
System.out.println("删除用户。。");
}
@Override
public void query() {
System.out.println("查询用户。。");
}
}
package cn.tedu.aop;
import java.util.List;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;
import cn.tedu.anno.Priv;
import cn.tedu.test.AOPTest;
@Component
@Aspect
public class PrivAspect {
@Around("execution(* cn.tedu.service.*.*(..)) && @annotation(anno)")
public void around(ProceedingJoinPoint jp,Priv anno) throws Throwable{
//检查权限
//--方法要啥权限
String needPriv = anno.value();
//--用户权限是啥
List userPrivList = AOPTest.userPrivList;
//判断是否匹配
if(userPrivList.contains(needPriv)){
//--有权限就执行目标
System.out.println("恭喜您有权限。。");
jp.proceed();
}else{
//--没有权限就不执行目标方法
System.out.println("不好意思,没有权限,禁止访问。。");
}
}
}
package cn.tedu.test;
import java.util.ArrayList;
import java.util.List;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import cn.tedu.service.UserService;
public class AOPTest {
public static List userPrivList = new ArrayList();
@Test
public void test01(){
userPrivList.add("add");
//userPrivList.add("update");
userPrivList.add("delete");
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
UserService userService = (UserService) context.getBean("userService");
String result = userService.addUser("zsf");
userService.updateUser();
userService.deleteUser();
userService.query();
}
}
思考:
此处我们使用类中静态的List作为用户权限列表,但是在真正的web开发中,同时会有很多用户并发访问,如果使用这种方案,所有用户使用同一个权限列表,就乱掉了,怎样让每个人都有一个自己的权限列表呢?
提示 -- ThreadLocal机制
使用这个工具类可以很简洁地编写出优美的多线程程序。ThreadLocal并不是一个Thread,而是Thread的局部变量。当使用ThreadLocal维护变量时,ThreadLocal为每个使用该变量的线程提供独立的变量副本。
hreadLocal类(TL 代替)主要是为应用提供线程作用域的局部变量,即这些变量的值的获取只在其存放的线程里有效。TL对象并不是这个局部变量,其更像一个引导者。
当某个线程还在运行且ThreadLocal对象还可以访问,那么该线程将持有ThreadLocal对象的一个隐式引用,线程回收,则TL回收。一个线程可以具有多个TL对象。
每个Thread都将维护一个ThreadLocalMap类,即线程局部变量容器,这才是真正线程局部变量存放的地方,并和当前线程一一对应。而TL的存在,主要是为了存取该map中的数据,一方面充当变量的键,完成线程域内的对象变量的存取;另一方面根据TL自身属性threadLocalHashCode,来优化局部变量容器的存取。注:threadLocalHashCode由Atomic Integer类型动态控制,即全局不会有threadLocalHashCode相同的TL对象。
SpringMVC是一个WEB层框架,主要用来负责与页面的交互
Spring整合SpringMVC可以做到无缝集成。特点就是简单易用性能佳
SpringMVC和Spring的关系:
SPringMVC是SpringWeb框架的一部分
SpringMVC是一个MVC的Web层框架
SpringMVC和Spring是父子容器,浑然天成;而struts2等是需要单独进行整合struts2-spring-plugin插件支持;
SpringMVC执行过程
前端控制器(DispatcherServlet)
本质上是一个Servlet,相当于一个中转站,所有的访问都会走到这个Servlet中,再根据配置进行中转到相应的Handler(Controller)中进行处理,获取到数据和视图后,在使用相应视图做出响应。
处理器映射器(HandlerMapping)
本质上就是一段映射关系,将访问路径和对应的Handler(Controller)存储为映射关系,在需要时供前端控制器查阅。
处理器适配器(HandlerAdapter)
本质上是一个适配器,可以根据要求找到对应的Handler(Controller)来运行。前端控制器通过处理器映射器找到对应的Handler信息之后,将请求响应和对应的Handler信息交由处理器适配器处理,处理器适配器找到真正handler执行后,将结果即model和view返回给前端控制器
视图解析器(ViewResolver)
本质上也是一种映射关系,可以将视图名称映射到真正的视图地址。前端控制器调用处理器适配完成后得到model和view,将view信息传给视图解析器得到真正的view。
MyBatis是数据访问层(DAO)框架
JDBC
java原生的关系型数据库访问方式
Hibernate
基于面向对象理念设计的DAO层框架,基本理念就是维护对象到表的映射关系,通过操作对象操作表中的数据,从而可以减少甚至杜绝sql的使用
MyBatis
是一种半自动对象-表映射关系的DAO层框架,可以自动的进行对象的封装,但是sql仍然需要自己来写。
多表设计 多表查询
多表查询
MyBatis的缓存机制
缓存机制可以减轻数据库的压力,原理是在第一查询时,将查询结果缓存起来,之后再查询同样的sql,不是真的去查询数据库,而是直接返回缓存中的结果。
缓存可以降低数据库的压力,但同时可能无法得到最新的结果数据。
数据库缓存的实现:
通过第三方工具实现缓存:
Redis内存数据库 - 可以实现缓存
通过MyBatis提供的缓存机制来实现缓存:
一级缓存:
缓存只在一个事务中有效,即同一个事务中先后执行多次同一个查询,只在第一次真正去查库,并将结果缓存,之后的查询都直接获取缓存中的中数据。如果是不同的事务,则缓存无效。
二级缓存:
缓存在全局有效,一个事务查询一个sql得到结果,会被缓存起来,之后只要缓存未被清楚,则其他事务如果查询同一个sql,得到的将会是之前缓存的结果。二级缓存作用范围大,作用时间长,可能造成的危害也更大,所以在开发中一般很少启用Mybatis的二级缓存。
MyBatis的一级缓存
MyBatis的一级缓存默认就是开启的
也可以通过配置禁用一级缓存
MyBatis的二级缓存
MyBatis的二级缓存默认是关闭的
接口的使用
为了简化MyBatis的使用,MyBatis提供了接口方式 自动化 生成调用过程,可以大大简化MyBatis的开发
开发接口:接口的名字和映射文件的名字相同,接口中方法的名字和要调用的映射文件中的标签的id相同,方法的参数和被调用的标签中的sql中需要的参数对应
真正的开发中,都是使用这种接口+配置文件方式,实现MyBatis的使用。