设计模式作为工作学习中的枕边书,却时常处于勤说不用的尴尬境地,也不是我们时常忘记,只是一直没有记忆。Spring作为业界的经典框架,无论是在架构设计方面,还是在代码编写方面,都堪称行内典范。spring中常用的设计模式达到九种,我们举例说明。以后再也不怕面试官问我:Spring中用了哪些设计模式了。
一、简单工厂模式 二、工厂方法模式 三、单例设计模式 四、包装器模式 五、代理模式动态代理,现在主要是用于增强类的功能,同时由于是具有动态性,所以避免了需要频繁创建类的操作,同时,也使得原有的代码在不需要改变的情况下,对类的功能进行增强,主要的动态代理技术有:通过实现目标接口,重写其方法,以增强其能力,典型的以JDK动态代理为代表;或者,通过继承类,重写其方法以增强其能力,典型的以CGLib为代表,这两种技术分别从不同的方向来对类的能力进行扩充,采用动态代理,可以在不知道该类要实现什么功能的情况下去,去适应类的变化,减少框架的耦合。
- 1、当程序在需要为一些类的方法添加新的功能。
- 2、又不想大量修改这个类。需要增加额外的功能,而且增加额外功能的类本身不确定。
- 3、在类的方法运行之时,能和其互动。
spring AOP实现分为静态AOP和动态AOP。静态AOP是指AspectJ实现的AOP,他是将切面代码直接编译到Java类文件中。动态AOP是指将切面代码进行动态织入实现的AOP。Spring的AOP为动态AOP,实现的技术为:JDK提供的动态代理技术 和 CGLIB(动态字节码增强技术)。尽管实现技术不一样,但都是基于代理模式,都是生成一个代理对象。
5.1JDK的动态代理主要使用到 InvocationHandler 接口和 Proxy.newProxyInstance() 方法。JDK动态代理要求被代理实现一个接口,只有接口中的方法才能够被代理。其方法是将被代理对象注入到一个中间对象,而中间对象实现InvocationHandler接口,在实现该接口时,可以在 被代理对象调用它的方法时,在调用的前后插入一些代码。而 Proxy.newProxyInstance() 能够利用中间对象来生产代理对象。插入的代码就是切面代码。所以使用JDK动态代理可以实现AOP。我们看个例子:
被代理对象实现的接口,只有接口中的方法才能够被代理:
public interface UserService {
public void addUser(User user);
public User getUser(int id);
}
被代理对象:
public class UserServiceImpl implements UserService {
public void addUser(User user) {
System.out.println("add user into database.");
}
public User getUser(int id) {
User user = new User();
user.setId(id);
System.out.println("getUser from database.");
return user;
}
}
代理中间类:
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
public class ProxyUtil implements InvocationHandler {
private Object target; // 被代理的对象
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("do sth before....");
Object result = method.invoke(target, args);
System.out.println("do sth after....");
return result;
}
ProxyUtil(Object target){
this.target = target;
}
public Object getTarget() {
return target;
}
public void setTarget(Object target) {
this.target = target;
}
}
测试:
import java.lang.reflect.Proxy;
import net.aazj.pojo.User;
public class ProxyTest {
public static void main(String[] args){
Object proxyedObject = new UserServiceImpl();// 被代理的对象
ProxyUtil proxyUtils = new ProxyUtil(proxyedObject);
}
}
执行结果:
do sth before....
getUser from database.
do sth after....
do sth before....
add user into database.
do sth after....
我们看到在UserService接口中的方法addUser 和 getUser方法的前面插入了我们自己的代码。这就是JDK动态代理实现AOP的原理。我们看到该方式有一个要求,被代理的对象必须实现接口,而且只有接口中的方法才能被代理。
5.2 CGLIB动态代理字节码生成技术实现AOP,其实就是继承被代理对象,然后Override需要被代理的方法,在覆盖该方法时,自然是可以插入我们自己的代码的。因为需要Override被代理对象的方法,所以自然CGLIB技术实现AOP时,就必须要求需要被代理的方法不能是final方法,因为final方法不能被子类覆盖。我们使用CGLIB实现上面的例子:
import java.lang.reflect.Method;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
public class CGProxy implements MethodInterceptor{
private Object target; // 被代理对象
public CGProxy(Object target){
this.target = target;
}
public Object intercept(Object arg0, Method arg1, Object[] arg2, MethodProxy proxy) throws Throwable {
System.out.println("do sth before....");
Object result = proxy.invokeSuper(arg0, arg2);
System.out.println("do sth after....");
return result;
}
public Object getProxyObject() {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(this.target.getClass()); // 设置父类
// 设置回调
enhancer.setCallback(this); // 在调用父类方法时,回调 this.intercept()
// 创建代理对象
return enhancer.create();
}
}
public class CGProxyTest {
public static void main(String[] args){
Object proxyedObject = new UserServiceImpl(); // 被代理的对象
CGProxy cgProxy = new CGProxy(proxyedObject);
UserService proxyObject = (UserService) cgProxy.getProxyObject();
proxyObject.getUser(1);
proxyObject.addUser(new User());
}
}
输出结果:
do sth before....
getUser from database.
do sth after....
do sth before....
add user into database.
do sth after....
我们看到达到了同样的效果。它的原理是生成一个父类enhancer.setSuperclass(this.target.getClass())的子类enhancer.create(),然后对父类的方法进行拦截enhancer.setCallback(this). 对父类的方法进行覆盖,所以父类方法不能是final的。
5.3 spring实现AOP的相关源码@SuppressWarnings("serial")
public class DefaultAopProxyFactory implements AopProxyFactory, Serializable {
@Override
public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException {
if (config.isOptimize() || config.isProxyTargetClass() || hasNoUserSuppliedProxyInterfaces(config)) {
Class targetClass = config.getTargetClass();
if (targetClass == null) {
throw new AopConfigException("TargetSource cannot determine target class: " +
"Either an interface or a target is required for proxy creation.");
}
if (targetClass.isInterface()) {
// 如果是实现了某一个接口那就采用的JDK的动态代理
return new JdkDynamicAopProxy(config);
}
// 否者那就采用的的CGLIB的到动态代理
return new ObjenesisCglibAopProxy(config);
}
else {
return new JdkDynamicAopProxy(config);
}
}
5.4 Spring AOP的配置
Spring中AOP的配置一般有两种方法,一种是使用aop:Config的标签在xml中进行配置,一种是使用注解以及@Aspect风格的配置。
配置一个切面;配置一个切点,基于切点表达式;,,是定义不同类型的advise. aspectBean 是切面的处理bean:
public class DataSourceInterceptor {
public void before(JoinPoint jp) {
DataSourceTypeManager.set(DataSources.SLAVE);
}
}
基于注解和@Aspect风格的AOP配置。
我们以事务配置为例:首先我们启用基于注解的事务配置
然后扫描Service包:
最后在service上进行注解:
@Service("userService")
@Transactional
public class UserServiceImpl implements UserService{
@Autowired
private UserMapper userMapper;
@Transactional (readOnly=true)
public User getUser(int userId) {
System.out.println("in UserServiceImpl getUser");
System.out.println(DataSourceTypeManager.get());
return userMapper.getUser(userId);
}
public void addUser(String username){
userMapper.addUser(username);
// int i = 1/0; // 测试事物的回滚
}
public void deleteUser(int id){
userMapper.deleteByPrimaryKey(id);
// int i = 1/0; // 测试事物的回滚
}
@Transactional (rollbackFor = BaseBusinessException.class)
public void addAndDeleteUser(String username, int id) throws BaseBusinessException{
userMapper.addUser(username);
this.m1();
userMapper.deleteByPrimaryKey(id);
}
private void m1() throws BaseBusinessException {
throw new BaseBusinessException("xxx");
}
public int insertUser(User user) {
return this.userMapper.insert(user);
}
}
这种事务配置方式,不需要我们书写pointcut表达式,而是我们在需要事务的类上进行注解。但是如果我们自己来写切面的代码时,还是要写pointcut表达式。
下面看一个例子(自己写切面逻辑):
首先去扫描@Aspect注解定义的切面:
启用@AspectJ风格的注解:
这里有两个属性,, proxy-target-class="true" 这个最好不要随便使用,它是指定只能使用CGLIB代理,那么对于final方法时会抛出错误,所以还是让spring自己选择是使用JDK动态代理,还是CGLIB. expose-proxy="true"的作用后面会讲到。
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
@Aspect // for aop
@Component // for auto scan
@Order(0) // execute before @Transactional
public class DataSourceInterceptor {
@Pointcut("execution(public * net.aazj.service..*.get*(..))")
public void dataSourceSlave(){};
@Before("dataSourceSlave()")
public void before(JoinPoint jp) {
DataSourceTypeManager.set(DataSources.SLAVE);
}
}
我们使用到了 @Aspect 来定义一个切面;@Component是配合context:component-scan/,不然扫描不到;@Order定义了该切面切入的顺序,因为在同一个切点,可能同时存在多个切面,那么在这多个切面之间就存在一个执行顺序的问题。该例子是一个切换数据源的切面,那么他应该在 事务处理 切面之前执行,所以我们使用 @Order(0) 来确保先切换数据源,然后加入事务处理。@Order的参数越小,优先级越高,默认的优先级最低:
切点表达式(pointcut):上面我们看到,无论是aop:config风格的配置,还是@Aspect风格的配置,切点表达式都是重点。都是我们必须掌握的。
execution(modifiers-pattern? ret-type-pattern declaring-type-pattern? name-pattern(param-pattern)throws-pattern?)带有 ? 号的部分是可选的,所以可以简化成:ret-type-pattern name-pattern(param_pattern) 返回类型,方法名称,参数三部分来匹配。
配置起来其实也很简单: * 表示任意返回类型,任意方法名,任意一个参数类型; .. 连续两个点表示0个或多个包路径,还有0个或多个参数。就是这么简单。看下例子:
- execution(* com.zhuangxiaoyan.spring.service...get(..)) :表示com.zhuangxiaoyan.spring.service包或者子包下的以get开头的方法,参数可以是0个或者多个(参数不限);
- execution(* com.zhuangxiaoyan.spring.AccountService.*(..)): 表示AccountService接口下的任何方法,参数不限;
- 注意这里,将类名和包路径是一起来处理的,并没有进行区分,因为类名也是包路径的一部分。
- 参数param-pattern部分比较复杂: () 表示没有参数,(..)参数不限,(*,String) 第一个参数不限类型,第二参数为String.
within() 语法: within()只能指定(限定)包路径(类名也可以看做是包路径),表示某个包下或者子报下的所有方法:
within(net.aazj.service.*), within(net.aazj.service..*),within(net.aazj.service.UserServiceImpl.*)
this() 与 target(): this是指代理对象,target是指被代理对象(目标对象)。所以 this() 和 target() 分别限定 代理对象的类型和被代理对象的类型:
this(net.aazj.service.UserService): 实现了UserService的代理对象(中的所有方法);
target(net.aazj.service.UserService): 被代理对象实现了UserService(中的所有方法);
args(): 限定方法的参数的类型:args(net.aazj.pojo.User): 参数为User类型的方法。
@target(), @within(), @annotation(), @args(): 这些语法形式都是针对注解的,比如带有某个注解的类,带有某个注解的方法,参数的类型带有某个注解:
@within(org.springframework.transaction.annotation.Transactional)
@target(org.springframework.transaction.annotation.Transactional)
两者都是指被代理对象类上有 @Transactional 注解的(类的所有方法),(两者似乎没有区别???)。
@annotation(org.springframework.transaction.annotation.Transactional): 方法 带有@Transactional 注解的所有方法
@args(org.springframework.transaction.annotation.Transactional):参数的类型 带有@Transactional 注解的所有方法
bean(): 指定某个bean的名称
bean(userService): bean的id为 "userService" 的所有方法;
bean(*Service): bean的id为 "Service"字符串结尾的所有方法;
另外注意上面这些表达式是可以利用 ||, &&, ! 进行自由组合的。比如:execution(public * net.aazj.service..*.getUser(..)) && args(Integer,..)
向注解处理方法传递参数:有时我们在写注解处理方法时,需要访问被拦截的方法的参数。此时我们可以使用 args() 来传递参数,下面看一个例子:
@Aspect
@Component // for auto scan
//@Order(2)
public class LogInterceptor {
@Pointcut("execution(public * com.zhuangxiaoyan.spring.service..*.getUser(..))")
public void myMethod(){};
@Before("myMethod()")
public void before() {
System.out.println("method start");
}
@After("myMethod()")
public void after() {
System.out.println("method after");
}
@AfterReturning("execution(public * com.zhuangxiaoyan.spring.mapper..*.*(..))")
public void AfterReturning() {
System.out.println("method AfterReturning");
}
@AfterThrowing("execution(public * com.zhuangxiaoyan.spring.mapper..*.*(..))")
// @Around("execution(public * net.aazj.mapper..*.*(..))")
public void AfterThrowing() {
System.out.println("method AfterThrowing");
}
@Around("execution(public * com.zhuangxiaoyan.spring..*.*(..))")
public Object Around(ProceedingJoinPoint jp) throws Throwable {
System.out.println("method Around");
SourceLocation sl = jp.getSourceLocation();
Object ret = jp.proceed();
System.out.println(jp.getTarget());
return ret;
}
@Before("execution(public * com.zhuangxiaoyan.spring..*.getUser(..)) && args(userId,..)")
public void before3(int userId) {
System.out.println("userId-----" + userId);
}
@Before("myMethod()")
public void before2(JoinPoint jp) {
Object[] args = jp.getArgs();
System.out.println("userId11111: " + (Integer)args[0]);
System.out.println(jp.getTarget());
System.out.println(jp.getThis());
System.out.println(jp.getSignature());
System.out.println("method start");
}
}
@Before("execution(public * com.zhaungxiaoyan.spring.service..*.getUser(..)) && args(userId,..)")
public void before3(int userId) {
System.out.println("userId-----" + userId);
}
它会拦截 com.zhuangxiaoyan.spring.service 包下或者子包下的getUser方法,并且该方法的第一个参数必须是int型的,那么使用切点表达式args(userId,..)就可以使我们在切面中的处理方法before3中可以访问这个参数。
before2方法也让我们知道也可以通过 JoinPoint 参数来获得被拦截方法的参数数组。JoinPoint 是每一个切面处理方法都具有的参数,@Around类型的具有的参数类型为ProceedingJoinPoint。通过JoinPoint或者ProceedingJoinPoint参数可以访问到被拦截对象的一些信息(参见上面的before2方法)。
Spring AOP的缺陷,因为Spring AOP是基于动态代理对象的,那么如果target中的方法不是被代理对象调用的,那么就不会织入切面代码,
@Service("userService")
@Transactional
public class UserServiceImpl implements UserService{
@Autowired
private UserMapper userMapper;
@Transactional (readOnly=true)
public User getUser(int userId) {
return userMapper.getUser(userId);
}
public void addUser(String username){
getUser(2);
userMapper.addUser(username);
}
}
看到上面的addUser() 方法中,我们调用了 getUser() 方法,而getUser() 方法是谁调用的呢?是UserServiceImpl的实例,不是代理对象,那么getUser()方法就不会被织入切面代码。
@Aspect
@Component
public class AOPTest {
@Before("execution(public * com.zhuangxiaoyan.spring.service..*.getUser(..))")
public void m1(){
System.out.println("in m1...");
}
@Before("execution(public * com.zhuangxiaoyan.spring.service..*.addUser(..))")
public void m2(){
System.out.println("in m2...");
}
}
public class Test {
public static void main(String[] args){
ApplicationContext context = new ClassPathXmlApplicationContext(
new String[]{"config/spring-mvc.xml","config/applicationContext2.xml"});
UserService us = context.getBean("userService", UserService.class);
if(us != null){
us.addUser("aaa");
输出结果如下:
in m2...
虽然getUser()方法被调用了,但是因为不是代理对象调用的,所以AOPTest.m1()方法并没有执行。这就是Spring aop的缺陷。
解决方法如下:首先:将 改为:
然后,修改UserServiceImpl中的 addUser() 方法:
@Service("userService")
@Transactional
public class UserServiceImpl implements UserService{
@Autowired
private UserMapper userMapper;
@Transactional (readOnly=true)
public User getUser(int userId) {
return userMapper.getUser(userId);
}
public void addUser(String username){
((UserService)AopContext.currentProxy()).getUser(2);
userMapper.addUser(username);
}
((UserService)AopContext.currentProxy()).getUser(2); 先获得当前的代理对象,然后在调用 getUser() 方法,就行了。
expose-proxy="true" 表示将当前代理对象暴露出去,不然 AopContext.currentProxy() 或得的是 null .
修改之后的运行结果:
in m2...
in m1...
六、观察者模式
七、策略模式
八、模板方法模式
8.1 模板方法模式的介绍
定义:模板方法模式在一个方法中定义一个算法骨架,并将某些步骤推迟到子类中实现。模板方法模式可以让子类在不改变算法整体结构的情况下,重新定义算法中的某些步骤。
目的:1.使用模版方法模式的目的是避免编写重复代码,以便开发人员可以专注于核心业务逻辑的实现
解决:接口与接口实现类之间继承矛盾问题。
- AbstractTemplate(抽象模版):定义一系列抽象方法,或者实现的方法,又或者是钩子方法。即:定义流程
- ConcreteTemplate(具体模版):实现父类抽象方法,基于本身不同的模版业务逻辑,实现不同的业务逻辑代码。即:抽象方法实现相同,内部逻辑不同
以上面的请假举例吧,假设现在A公司请假需要直属领导审批以及通知HR有人请假了就可以了,B公司需要直属领导,部门负责人审批最后通知HR,方能完成整个请假流程。那作为OA办公流程怎么去处理这个问题嘛?直接看代码实现吧!
public abstract class AskForLeaveFlow {
// 一级组长直接审批
protected abstract void firstGroupLeader(String name);
// 二级组长部门负责人审批
protected void secondGroupLeader(String name) {
}
// 告知HR有人请假了
private final void notifyHr(String name) {
System.out.println("当前有人请假了,请假人:" + name);
}
// 请假流模版
public void askForLeave(String name) {
firstGroupLeader(name);
secondGroupLeader(name);
notifyHr(name);
}
}
首先还是定义一个请假流程,其中:
firstGroupLeader方法为abstract修饰,则作为子类都是必须要实现的
secondGroupLeader 二级领导审批,在子类中可以重写,也可不重写
notifyHr 方法为通知HR,已经内部实现
最后一个askForLeave请假流程方法,把以上模版方法串起来
public class CompanyA extends AskForLeaveFlow {
@Override
protected void firstGroupLeader(String name) {
System.out.println("CompanyA 组内有人请假,请假人:" + name);
}
}
public class CompanyB extends AskForLeaveFlow {
@Override
protected void firstGroupLeader(String name) {
System.out.println("CompanyB 组内有人请假,请假人:" + name);
}
@Override
protected void secondGroupLeader(String name){
System.out.println("CompanyB 部门有人请假,请假人:" + name);
}
}
在CompanyA以及CompanyB中,secondGroupLeader二级领导可以选择重写或者不重写,这个类模版方法简称为钩子方法。
public class testTemplate {
public static void main(String[] args) {
// 公司A请假流程模版
AskForLeaveFlow companyA = new CompanyA();
companyA.askForLeave("庄小焱");
// 结果:CompanyA 组内有人请假,请假人:庄小焱
// 当前有人请假了,请假人:庄小焱
AskForLeaveFlow companyB = new CompanyB();
companyB.askForLeave("庄小焱");
// 结果:CompanyB 组内有人请假,请假人:庄小焱
// CompanyB 部门有人请假,请假人:庄小焱
// 当前有人请假了,请假人:庄小焱
}
}
最后就是看测试dome结果了。companyA和companyB分别输出了对应的请假流程。细心的同学可能已经发现了,做为模版方法中里面除了可以有抽象方法外,还可以有具体的实现方法以及钩子方法。所以大家在应用的过程可以多考虑考虑在内部定义模版方法时,应该定义成抽象方法还是其它的。
8.2 模板方法模式的应用模版方法模式在我们常见的Java的框架中也是非常常见的,只是可能我们平时没有注意到这一点而已。第一个:首先我们学SpringMVC的时候,最开始都会写一些Servlet来作为处理一些post或者get请求等。
这里直接看这个源码大家就可以发现这也是直接使用模版方法模式的思想,期间在HttpServlet 继承GenericServlet中也还是模版方法的体现,这说明了可以多次抽象构建模版。
第二个:常见问的文件流中,Java IO 类中的InputStream、OutputStream、Reader、Writer等都能看到模版方法模式的身影。
上面是我贴出的部分InputStream的源码,主要看这个read模版方法,也就是模版方法模式的体现。
在业务中怎么使用模版方法?首先需要理解模版方法它是为了增加代码的复用性,以及扩展性而存在的,所以本着这个思想我还是给大家举一个例子吧。商品详情展示我们可以是分模块展示的,比如头图,商品信息,sku信息,配送地址,分期付费等等。那么怎么进行组装到商品详情的展示呢?
可以看到一个请求过来,可以有模块组装器选择组装返回结果。提一个点,在第二步请求的模块的时候为了减少整个链路的请求时间可以考虑是串行,或者并行(开线程池处理)。
public abstract class AbstractTemplateBlock {
// 初始化构建返回结果模型
protected abstract T initBlock();
// 定义抽象模版
protected abstract void doWork(ModelContainer modelContainer, T block) throws Exception;
// 组装结果
public T template(ModelContainer modelContainer) {
T block = initBlock();
try {
this.doWork(modelContainer, block);
} catch (Exception e) {
// 可以选择捕获异常,是中断流程,还是只打印日志,不中断流程
}
return block;
}
}
@Component
public class ItemInfoBlock extends AbstractTemplateBlock {
@Override
protected ItemInfoBlock.ItemInfo initBlock() {
return new ItemInfoBlock.ItemInfo();
}
// 模拟业务逻辑,组装返回商品信息模块数据
@Override
protected void doWork(ModelContainer modelContainer, ItemInfo block) throws Exception {
block.setItemId(123L);
block.setItemName("测试");
}
@Data
public static class ItemInfo {
private Long itemId;
private String itemName;
}
}
public static void main(String[] args) {
// 1.模拟获取SpringBean
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("classpath:applicationContext.xml");
ItemInfoBlock itemInfoBlock = (ItemInfoBlock) applicationContext.getBean("itemInfoBlock");
// 2. ModelContainer可以理解为贯穿上下文中的请求参数,或者一些组装数据需要的预加载数据
ModelContainer modelContainer = new ModelContainer();
// 3. 获取返回结果
ItemInfoBlock.ItemInfo itemInfo = itemInfoBlock.template(modelContainer);
System.out.println(JSON.toJSONString(itemInfo));
// 结果:{"itemId":123,"itemName":"测试"}
}
最后就是看测试demo了,可以看到再每一个模块中都是有一个AbstractTemplateBlock,内部包含doWork抽象方法,由子类去实现当前自己的业务逻辑。同时第三步获取返回结果时,我只是单独列出来,大家可以根据实际情况还能做改造。比如说返回map结构等 mapKey 是模块名称,value是数据。当前这种组装商品详情的模式也是比较常见的一种方式。代码的复用性高,同时扩展性也有一定的体现,符合模版方法模式的思想。
模板方法模式在JDBCTemplate中的应用
public class JdbcTemplate extends JdbcAccessor implements JdbcOperations {
//查询前缀
private static final String RETURN_RESULT_SET_PREFIX = "#result-set-";
//计数前缀
private static final String RETURN_UPDATE_COUNT_PREFIX = "#update-count-";
//是否跳过警告
private boolean ignoreWarnings = true;
//查询大小
private int fetchSize = -1;
//最大行
private int maxRows = -1;
//查询超时
private int queryTimeout = -1;
//是否跳过结果集处理
private boolean skipResultsProcessing = false;
//是否跳过非公共结果集处理
private boolean skipUndeclaredResults = false;
//map结果集是否大小写敏感
private boolean resultsMapCaseInsensitive = false;
public JdbcTemplate() {
}
//调用父类方法设置数据源和其他参数
public JdbcTemplate(DataSource dataSource) {
this.setDataSource(dataSource);
this.afterPropertiesSet();
}
//调用父类方法设置数据源,懒加载策略和其他参数
public JdbcTemplate(DataSource dataSource, boolean lazyInit) {
this.setDataSource(dataSource);
this.setLazyInit(lazyInit);
this.afterPropertiesSet();
}
}
public abstract class JdbcAccessor implements InitializingBean {
protected final Log logger = LogFactory.getLog(this.getClass());
//数据源
@Nullable
private DataSource dataSource;
//异常翻译
@Nullable
private volatile SQLExceptionTranslator exceptionTranslator;
//懒加载策略
private boolean lazyInit = true;
public JdbcAccessor() {
}
public void setDataSource(@Nullable DataSource dataSource) {
this.dataSource = dataSource;
}
@Nullable
public DataSource getDataSource() {
return this.dataSource;
}
protected DataSource obtainDataSource() {
DataSource dataSource = this.getDataSource();
Assert.state(dataSource != null, "No DataSource set");
return dataSource;
}
public void setDatabaseProductName(String dbName) {
this.exceptionTranslator = new SQLErrorCodeSQLExceptionTranslator(dbName);
}
public void setExceptionTranslator(SQLExceptionTranslator exceptionTranslator) {
this.exceptionTranslator = exceptionTranslator;
}
}
JdbcTemplate 继承了JdbcAccessor实现了JdbcOperations,JdbcAccessor主要封装了数据源的操作,JdbcOperations主要定义了一些操作接口。我们一起看一下JdbcOperations类;
public abstract class JdbcAccessor implements InitializingBean {
protected final Log logger = LogFactory.getLog(this.getClass());
//数据源
@Nullable
private DataSource dataSource;
//异常翻译
@Nullable
private volatile SQLExceptionTranslator exceptionTranslator;
//懒加载策略
private boolean lazyInit = true;
public JdbcAccessor() {
}
public void setDataSource(@Nullable DataSource dataSource) {
this.dataSource = dataSource;
}
@Nullable
public DataSource getDataSource() {
return this.dataSource;
}
protected DataSource obtainDataSource() {
DataSource dataSource = this.getDataSource();
Assert.state(dataSource != null, "No DataSource set");
return dataSource;
}
public void setDatabaseProductName(String dbName) {
this.exceptionTranslator = new SQLErrorCodeSQLExceptionTranslator(dbName);
}
public void setExceptionTranslator(SQLExceptionTranslator exceptionTranslator) {
this.exceptionTranslator = exceptionTranslator;
}
}
之所以前面提到spring让我们更方便的处理异常就是这里他包装了一个SQLExceptionTranslator,其他的代码都是做数据源的检查之类的设置数据源,我们看一下其中getExceptionTranslator()方法。
public SQLExceptionTranslator getExceptionTranslator() {
SQLExceptionTranslator exceptionTranslator = this.exceptionTranslator;
if (exceptionTranslator != null) {
return exceptionTranslator;
} else {
synchronized(this) {
SQLExceptionTranslator exceptionTranslator = this.exceptionTranslator;
if (exceptionTranslator == null) {
DataSource dataSource = this.getDataSource();
if (dataSource != null) {
exceptionTranslator = new SQLErrorCodeSQLExceptionTranslator(dataSource);
} else {
exceptionTranslator = new SQLStateSQLExceptionTranslator();
}
this.exceptionTranslator = (SQLExceptionTranslator)exceptionTranslator;
}
return (SQLExceptionTranslator)exceptionTranslator;
}
}
}
这是一个标准的单例模式,我们在学习模板方法模式的路途中有捕获了一个野生的单例;我们继续看JdbcOperations接口我们调其中一个接口进行解析;
@Nullable
T execute(StatementCallback var1) throws DataAccessException;
StatementCallback 接口
@FunctionalInterface
public interface StatementCallback {
@Nullable
T doInStatement(Statement var1) throws SQLException, DataAccessException;
}
execute实现
@Nullable
public T execute(StatementCallback action) throws DataAccessException {
//参数检查
Assert.notNull(action, "Callback object must not be null");
//获取连接
Connection con = DataSourceUtils.getConnection(this.obtainDataSource());
Statement stmt = null;
Object var11;
try {
//创建一个Statement
stmt = con.createStatement();
//设置查询超时时间,最大行等参数(就是一开始那些成员变量)
this.applyStatementSettings(stmt);
//执行回调方法获取结果集
T result = action.doInStatement(stmt);
//处理警告
this.handleWarnings(stmt);
var11 = result;
} catch (SQLException var9) {
//出现错误优雅退出
String sql = getSql(action);
JdbcUtils.closeStatement(stmt);
stmt = null;
DataSourceUtils.releaseConnection(con, this.getDataSource());
con = null;
throw this.translateException("StatementCallback", sql, var9);
} finally {
JdbcUtils.closeStatement(stmt);
DataSourceUtils.releaseConnection(con, this.getDataSource());
}
return var11;
}
这一个方法可谓是展现的淋漓尽致,这是一个典型的模板方法+回调模式,我们不需要再写过多的重复代码只需要实现自己获取result的方法就好(StatementCallback)事实上我们自己也不需要实现这个方法,继续向上看,我们是如何调用execute方法的,以查询为例,我们看他是如何一步步调用的:
查询方法
public List findAll() {
JdbcTemplate jdbcTemplate = DataSourceConfig.getTemplate();
String sql = "select nickname,comment,age from users";
return jdbcTemplate.query(sql, new BeanPropertyRowMapper(Users.class));
}
query实现
public List query(String sql, RowMapper rowMapper) throws DataAccessException {
return (List)result(this.query((String)sql, (ResultSetExtractor)(new RowMapperResultSetExtractor(rowMapper))));
}
模板方法模式HttpServlet中的应用
HttpServlet的所有方法,我们看到HttpServlet继承了GenericServlet,我们继续看:
public abstract class GenericServlet
implements Servlet, ServletConfig, java.io.Serializable
{
private static final String LSTRING_FILE = "javax.servlet.LocalStrings";
private static ResourceBundle lStrings =
ResourceBundle.getBundle(LSTRING_FILE);
private transient ServletConfig config;
public GenericServlet() { }
//没有实现钩子
public void destroy() {
}
public String getInitParameter(String name) {
ServletConfig sc = getServletConfig();
if (sc == null) {
throw new IllegalStateException(
lStrings.getString("err.servlet_config_not_initialized"));
}
return sc.getInitParameter(name);
}
public Enumeration getInitParameterNames() {
ServletConfig sc = getServletConfig();
if (sc == null) {
throw new IllegalStateException(
lStrings.getString("err.servlet_config_not_initialized"));
}
return sc.getInitParameterNames();
}
public ServletConfig getServletConfig() {
return config;
}
public ServletContext getServletContext() {
ServletConfig sc = getServletConfig();
if (sc == null) {
throw new IllegalStateException(
lStrings.getString("err.servlet_config_not_initialized"));
}
return sc.getServletContext();
}
public String getServletInfo() {
return "";
}
public void init(ServletConfig config) throws ServletException {
this.config = config;
this.init();
}
public void init() throws ServletException {
}
public void log(String msg) {
getServletContext().log(getServletName() + ": "+ msg);
}
public void log(String message, Throwable t) {
getServletContext().log(getServletName() + ": " + message, t);
}
public abstract void service(ServletRequest req, ServletResponse res)
throws ServletException, IOException;
public String getServletName() {
ServletConfig sc = getServletConfig();
if (sc == null) {
throw new IllegalStateException(
lStrings.getString("err.servlet_config_not_initialized"));
}
return sc.getServletName();
}
}
九、适配器模式
适配器模式(Adapter Pattern):将一个接口转换成客户希望的另一个接口,使接口不兼容的那些类可以一起工作,其别名为包装器(Wrapper)。适配器模式既可以作为类结构型模式,也可以作为对象结构型模式。在适配器模式中,我们通过增加一个新的适配器类来解决接口不兼容的问题,使得原本没有任何关系的类可以协同工作。
根据适配器类与适配者类的关系不同,适配器模式可分为对象适配器和类适配器两种,在对象适配器模式中,适配器与适配者之间是关联关系;在类适配器模式中,适配器与适配者之间是继承(或实现)关系。
适配器角色
Target(目标抽象类):目标抽象类定义客户所需接口,可以是一个抽象类或接口,也可以是具体类。
Adapter(适配器类):适配器可以调用另一个接口,作为一个转换器,对Adaptee和Target进行适配,适配器类是适配器模式的核心,在对象适配器中,它通过继承Target并关联一个Adaptee对象使二者产生联系。
Adaptee(适配者类):适配者即被适配的角色,它定义了一个已经存在的接口,这个接口需要适配,适配者类一般是一个具体类,包含了客户希望使用的业务方法,在某些情况下可能没有适配者类的源代码。
9.1 类适配器模式首先有一个已存在的将被适配的类
public class Adaptee {
public void adapteeRequest() {
System.out.println("被适配者的方法");
}
}
定义一个目标接口
public interface Target {
void request();
}
怎么才可以在目标接口中的 request()
调用 Adaptee
的 adapteeRequest()
方法呢?
public class ConcreteTarget implements Target {
@Override
public void request() {
System.out.println("concreteTarget目标方法");
}
}
但是如果直接实现 Target
是不行的?还有其他的方式吗?
如果通过一个适配器类,实现 Target
接口,同时继承了 Adaptee
类,然后在实现的 request()
方法中调用父类的 adapteeRequest()
即可实现。
public class Adapter extends Adaptee implements Target{
@Override
public void request() {
//...一些操作...
super.adapteeRequest();
//...一些操作...
}
}
-------------------------------------------------------------------------------------------
// 测试代码
public class Test {
public static void main(String[] args) {
Target target = new ConcreteTarget();
target.request();
Target adapterTarget = new Adapter();
adapterTarget.request();
}
}
-----------------------------------------------------------------------------------------
输出
concreteTarget目标方法
被适配者的方法
对象适配器与类适配器不同之处在于,类适配器通过继承来完成适配,对象适配器则是通过关联来完成,这里稍微修改一下 Adapter
类即可将转变为对象适配器。
public class Adapter implements Target{
// 关联属性设置
// 适配者是对象适配器的一个属性
private Adaptee adaptee = new Adaptee();
@Override
public void request() {
//...
adaptee.adapteeRequest();
//...
}
}
注意这里的 Adapter
是将 Adaptee
作为一个成员属性,而不是继承它。
电压适配器demo:我们国家的民用电都是 220V,美国是 110V,而我们的手机充电一般需要 5V,这时候要充电,就需要一个电压适配器,将 220V 或者 100V 的输入电压变换为 5V 输出。定义输出交流电接口,输出220V交流电类和输出110V交流电类。
public interface AC {
int outputAC();
}
public class AC110 implements AC {
public final int output = 110;
@Override
public int outputAC() {
return output;
}
}
public class AC220 implements AC {
public final int output = 220;
@Override
public int outputAC() {
return output;
}
}
适配器接口,其中 support()
方法用于检查输入的电压是否与适配器匹配,outputDC5V()
方法则用于将输入的电压变换为 5V 后输出。
public interface DC5Adapter {
boolean support(AC ac);
int outputDC5V(AC ac);
}
实现中国变压适配器和美国变压适配器
public class ChinaPowerAdapter implements DC5Adapter {
public static final int voltage = 220;
@Override
public boolean support(AC ac) {
return (voltage == ac.outputAC());
}
@Override
public int outputDC5V(AC ac) {
int adapterInput = ac.outputAC();
//变压器...
int adapterOutput = adapterInput / 44;
System.out.println("使用ChinaPowerAdapter变压适配器,输入AC:" + adapterInput + "V" + ",输出DC:" + adapterOutput + "V");
return adapterOutput;
}
}
public class AmericanPowerAdapter implements DC5Adapter {
public static final int voltage = 110;
@Override
public boolean support(AC ac) {
return (voltage == ac.outputAC());
}
@Override
public int outputDC5V(AC ac) {
int adapterInput = ac.outputAC();
//变压器...
int adapterOutput = adapterInput / 22;
System.out.println("使用JapanPowerAdapter变压适配器,输入AC:" + adapterInput + "V" + ",输出DC:" + adapterOutput + "V");
return adapterOutput;
}
}
测试,准备中国变压适配器和日本变压适配器各一个,定义一个方法可以根据电压找到合适的变压器,然后进行测试
public class Test {
private List adapters = new LinkedList();
public Test() {
this.adapters.add(new ChinaPowerAdapter());
this.adapters.add(new AmericanPowerAdapter());
}
// 根据电压找合适的变压器
public DC5Adapter getPowerAdapter(AC ac) {
DC5Adapter adapter = null;
for (DC5Adapter ad : this.adapters) {
if (ad.support(ac)) {
adapter = ad;
break;
}
}
if (adapter == null){
throw new IllegalArgumentException("没有找到合适的变压适配器");
}
return adapter;
}
public static void main(String[] args) {
Test test = new Test();
AC chinaAC = new AC220();
DC5Adapter adapter = test.getPowerAdapter(chinaAC);
adapter.outputDC5V(chinaAC);
// 去美国旅游,电压是 110V
AC AmericanAC = new AC110();
adapter = test.getPowerAdapter(AmericanAC);
adapter.outputDC5V(AmericanAC);
}
}
输出
------------------------------------------------------------
使用ChinaPowerAdapter变压适配器,输入AC:220V,输出DC:5V
使用JapanPowerAdapter变压适配器,输入AC:110V,输出DC:5V
9.3 spring中适配器模式的典型应用
9.3.1 spring AOP中的适配器模式
在Spring的Aop中,使用的 Advice(通知)
来增强被代理类的功能。Advice
的类型有:MethodBeforeAdvice
、AfterReturningAdvice
、ThrowsAdvice。
在每个类型 Advice
都有对应的拦截器,MethodBeforeAdviceInterceptor
、AfterReturningAdviceInterceptor
、ThrowsAdviceInterceptor。
Spring需要将每个 Advice
都封装成对应的拦截器类型,返回给容器,所以需要使用适配器模式对 Advice
进行转换。
public interface MethodBeforeAdvice extends BeforeAdvice {
void before(Method var1, Object[] var2, @Nullable Object var3) throws Throwable;
}
public interface AfterReturningAdvice extends AfterAdvice {
void afterReturning(@Nullable Object var1, Method var2, Object[] var3, @Nullable Object var4) throws Throwable;
}
public interface ThrowsAdvice extends AfterAdvice {
}
目标接口Target,有两个方法,一个判断 Advice
类型是否匹配,一个是工厂方法,创建对应类型的 Advice
对应的拦截器。
public interface AdvisorAdapter {
boolean supportsAdvice(Advice var1);
MethodInterceptor getInterceptor(Advisor var1);
}
三个适配器类 Adapter 分别如下,注意其中的 Advice、Adapter、Interceptor之间的对应关系
class MethodBeforeAdviceAdapter implements AdvisorAdapter, Serializable {
@Override
public boolean supportsAdvice(Advice advice) {
return (advice instanceof MethodBeforeAdvice);
}
@Override
public MethodInterceptor getInterceptor(Advisor advisor) {
MethodBeforeAdvice advice = (MethodBeforeAdvice) advisor.getAdvice();
return new MethodBeforeAdviceInterceptor(advice);
}
}
@SuppressWarnings("serial")
class AfterReturningAdviceAdapter implements AdvisorAdapter, Serializable {
@Override
public boolean supportsAdvice(Advice advice) {
return (advice instanceof AfterReturningAdvice);
}
@Override
public MethodInterceptor getInterceptor(Advisor advisor) {
AfterReturningAdvice advice = (AfterReturningAdvice) advisor.getAdvice();
return new AfterReturningAdviceInterceptor(advice);
}
}
class ThrowsAdviceAdapter implements AdvisorAdapter, Serializable {
@Override
public boolean supportsAdvice(Advice advice) {
return (advice instanceof ThrowsAdvice);
}
@Override
public MethodInterceptor getInterceptor(Advisor advisor) {
return new ThrowsAdviceInterceptor(advisor.getAdvice());
}
}
客户端 DefaultAdvisorAdapterRegistry
public class DefaultAdvisorAdapterRegistry implements AdvisorAdapterRegistry, Serializable {
private final List adapters = new ArrayList(3);
public DefaultAdvisorAdapterRegistry() {
// 这里注册了适配器
this.registerAdvisorAdapter(new MethodBeforeAdviceAdapter());
this.registerAdvisorAdapter(new AfterReturningAdviceAdapter());
this.registerAdvisorAdapter(new ThrowsAdviceAdapter());
}
public MethodInterceptor[] getInterceptors(Advisor advisor) throws UnknownAdviceTypeException {
List interceptors = new ArrayList(3);
Advice advice = advisor.getAdvice();
if (advice instanceof MethodInterceptor) {
interceptors.add((MethodInterceptor)advice);
}
Iterator var4 = this.adapters.iterator();
while(var4.hasNext()) {
AdvisorAdapter adapter = (AdvisorAdapter)var4.next();
if (adapter.supportsAdvice(advice)) {
// 这里调用适配器方法
interceptors.add(adapter.getInterceptor(advisor));
}
}
if (interceptors.isEmpty()) {
throw new UnknownAdviceTypeException(advisor.getAdvice());
} else {
return (MethodInterceptor[])interceptors.toArray(new MethodInterceptor[0]);
}
}
// ...省略...
}
这里应该属于对象适配器模式,关键字 instanceof
可看成是 Advice
的方法,不过这里的 Advice
对象是从外部传进来,而不是成员属性。
在Spring的ORM包中,对于JPA的支持也是采用了适配器模式,首先定义了一个接口的 JpaVendorAdapter
,然后不同的持久层框架都实现此接口。jpaVendorAdapter:用于设置实现厂商JPA实现的特定属性,如设置Hibernate的是否自动生成DDL的属性generateDdl;这些属性是厂商特定的,因此最好在这里设置;目前Spring提供 HibernateJpaVendorAdapter
、OpenJpaVendorAdapter
、EclipseLinkJpaVendorAdapter
、TopLinkJpaVendorAdapter
四个实现。其中最重要的属性是 database,用来指定使用的数据库类型,从而能根据数据库类型来决定比如如何将数据库特定异常转换为Spring的一致性异常,目前支持如下数据库(DB2、DERBY、H2、HSQL、INFORMIX、MYSQL、ORACLE、POSTGRESQL、SQL_SERVER、SYBASE)。
public interface JpaVendorAdapter
{
// 返回一个具体的持久层提供者
public abstract PersistenceProvider getPersistenceProvider();
// 返回持久层提供者的包名
public abstract String getPersistenceProviderRootPackage();
// 返回持久层提供者的属性
public abstract Map getJpaPropertyMap();
// 返回JpaDialect
public abstract JpaDialect getJpaDialect();
// 返回持久层管理器工厂
public abstract Class
关注
打赏
最近更新
- 深拷贝和浅拷贝的区别(重点)
- 【Vue】走进Vue框架世界
- 【云服务器】项目部署—搭建网站—vue电商后台管理系统
- 【React介绍】 一文带你深入React
- 【React】React组件实例的三大属性之state,props,refs(你学废了吗)
- 【脚手架VueCLI】从零开始,创建一个VUE项目
- 【React】深入理解React组件生命周期----图文详解(含代码)
- 【React】DOM的Diffing算法是什么?以及DOM中key的作用----经典面试题
- 【React】1_使用React脚手架创建项目步骤--------详解(含项目结构说明)
- 【React】2_如何使用react脚手架写一个简单的页面?