摘要
设计模式作为工作学习中的枕边书,却时常处于勤说不用的尴尬境地,也不是我们时常忘记,只是一直没有记忆。Spring作为业界的经典框架,无论是在架构设计方面,还是在代码编写方面,都堪称行内典范。spring中常用的设计模式达到九种,我们举例说明。
一、工厂模式
BeanFactory就是工厂模式的体现。Spring使用工厂模式可以通过 BeanFactory
或 ApplicationContext
创建 bean 对象。
两者对比:
BeanFactory
:延迟注入(使用到某个 bean 的时候才会注入),相比于BeanFactory
来说会占用更少的内存,程序启动速度更快。ApplicationContext
:容器启动的时候,不管你用没用到,一次性创建所有 bean 。BeanFactory
仅提供了最基本的依赖注入支持,ApplicationContext
扩展了BeanFactory
,除了有BeanFactory
的功能还有额外更多功能,一般开发人员使用ApplicationContext
会更多。
ApplicationContext的三个实现类:
ClassPathXmlApplication
:把上下文文件当成类路径资源。FileSystemXmlApplication
:从文件系统中的 XML 文件载入上下文定义信息。XmlWebApplicationContext
:从Web系统中的XML文件载入上下文定义信息。
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.FileSystemXmlApplicationContext;
public class App {
public static void main(String[] args) {
ApplicationContext context = new FileSystemXmlApplicationContext(
"C:/work/IOC Containers/springframework.applicationcontext/src/main/resources/bean-factory-config.xml");
HelloApplicationContext obj = (HelloApplicationContext) context.getBean("helloApplicationContext");
obj.getMsg();
}
}
二、单例设计模式
在我们的系统中,有一些对象其实我们只需要一个,比如说:线程池、缓存、对话框、注册表、日志对象、充当打印机、显卡等设备驱动程序的对象。事实上,这一类对象只能有一个实例,如果制造出多个实例就可能会导致一些问题的产生,比如:程序的行为异常、资源使用过量、或者不一致性的结果。
使用单例模式的好处:
- 对于频繁使用的对象,可以省略创建对象所花费的时间,这对于那些重量级对象而言,是非常可观的一笔系统开销;
- 由于 new 操作的次数减少,因而对系统内存的使用频率也会降低,这将减轻 GC 压力,缩短 GC 停顿时间。
Spring中bean的默认作用域就是 singleton(单例)的。 除了 singleton作用域,Spring中bean 还有下面几种作用域:
- prototype : 每次请求都会创建一个新的 bean 实例。
- request : 每一次HTTP请求都会产生一个新的bean,该bean仅在当前HTTP request内有效。
- session : 每一次HTTP请求都会产生一个新的 bean,该bean仅在当前 HTTP session 内有效。
- global-session: 全局session作用域,仅仅在基于portlet的web应用中才有意义,Spring5已经没有了。Portlet是能够生成语义代码(例如:HTML)片段的小型Java Web插件。它们基于portlet容器,可以像servlet一样处理HTTP请求。但是,与 servlet 不同,每个 portlet 都有不同的会话
Spring 实现单例的方式:
- xml :
<bean id="userService" class="top.snailclimb.UserService" scope="singleton"/>
- 注解:
@Scope(value = "singleton")
Spring 通过 ConcurrentHashMap
实现单例注册表的特殊方式实现单例模式。Spring 实现单例的核心代码如下
// 通过 ConcurrentHashMap(线程安全) 实现单例注册表
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<String, Object>(64);
public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {
Assert.notNull(beanName, "'beanName' must not be null");
synchronized (this.singletonObjects) {
// 检查缓存中是否存在实例
Object singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null) {
//...省略了很多代码
try {
singletonObject = singletonFactory.getObject();
}
//...省略了很多代码
// 如果实例对象在不存在,我们注册到单例注册表中。
addSingleton(beanName, singletonObject);
}
return (singletonObject != NULL_OBJECT ? singletonObject : null);
}
}
//将对象添加到单例注册表
protected void addSingleton(String beanName, Object singletonObject) {
synchronized (this.singletonObjects) {
this.singletonObjects.put(beanName, (singletonObject != null ? singletonObject : NULL_OBJECT));
}
}
}
三、装饰器模式
装饰者模式可以动态地给对象添加一些额外的属性或行为。相比于使用继承,装饰者模式更加灵活。简单点儿说就是当我们需要修改原有的功能,但我们又不愿直接去修改原有的代码时,设计一个Decorator套在原有代码外面。其实在 JDK 中就有很多地方用到了装饰者模式,比如 InputStream
家族,InputStream
类下有 FileInputStream
(读取文件)、BufferedInputStream
(增加缓存,使读取文件速度大大提升)等子类都在不修改InputStream
代码的情况下扩展了它的功能。
Spring 中配置 DataSource 的时候,DataSource 可能是不同的数据库和数据源。我们能否根据客户的需求在少修改原有类的代码下动态切换不同的数据源?这个时候就要用到装饰者模式(这一点我自己还没太理解具体原理)。Spring 中用到的包装器模式在类名上含有 Wrapper
或者 Decorator
。这些类基本上都是动态地给一个对象添加一些额外的职责。
- 装饰器模式:
- 动态地给对象添加一些额外的属性或者行为
- 和继承相比,装饰器模式更加灵活
- 装饰器模式使用场景:
- 当需要修改原有的功能,但是不想直接修改原有的代码,就可以设计一个装饰器Decorator类在原有的代码的外面,这样可以在不修改原有的类的基础上扩展新的功能
四、代理模式
动态代理,现在主要是用于增强类的功能,同时由于是具有动态性,所以避免了需要频繁创建类的操作,同时,也使得原有的代码在不需要改变的情况下,对类的功能进行增强,主要的动态代理技术有:通过实现目标接口,重写其方法,以增强其能力,典型的以JDK动态代理为代表;或者,通过继承类,重写其方法以增强其能力,典型的以CGLib为代表,这两种技术分别从不同的方向来对类的能力进行扩充,采用动态代理,可以在不知道该类要实现什么功能的情况下去,去适应类的变化,减少框架的耦合。
- 1、当程序在需要为一些类的方法添加新的功能。
- 2、又不想大量修改这个类。需要增加额外的功能,而且增加额外功能的类本身不确定。
- 3、在类的方法运行之时,能和其互动。
spring AOP实现分为静态AOP和动态AOP。静态AOP是指AspectJ实现的AOP,他是将切面代码直接编译到Java类文件中。动态AOP是指将切面代码进行动态织入实现的AOP。Spring的AOP为动态AOP,实现的技术为:JDK提供的动态代理技术 和 CGLIB(动态字节码增强技术)。尽管实现技术不一样,但都是基于代理模式,都是生成一个代理对象。
4.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的原理。我们看到该方式有一个要求,被代理的对象必须实现接口,而且只有接口中的方法才能被代理。
4.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的。
4.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);
}
}
4.4 Spring AOP的配置
Spring中AOP的配置一般有两种方法,一种是使用aop:Config的标签在xml中进行配置,一种是使用注解以及@Aspect风格的配置。
<tx:advice id="transactionAdvice" transaction-manager="transactionManager"?>
<tx:attributes >
<tx:method name="add*" propagation="REQUIRED" />
<tx:method name="append*" propagation="REQUIRED" />
<tx:method name="insert*" propagation="REQUIRED" />
<tx:method name="save*" propagation="REQUIRED" />
<tx:method name="update*" propagation="REQUIRED" />
<tx:method name="get*" propagation="SUPPORTS" />
<tx:method name="find*" propagation="SUPPORTS" />
<tx:method name="load*" propagation="SUPPORTS" />
<tx:method name="search*" propagation="SUPPORTS" />
<tx:method name="*" propagation="SUPPORTS" />
</tx:attributes>
</tx:advice>
<aop:config>
<aop:pointcut id="transactionPointcut" expression="execution(* net.aazj.service..*Impl.*(..))" />
<aop:advisor pointcut-ref="transactionPointcut" advice-ref="transactionAdvice" />
</aop:config>
<bean id="aspectBean" class="net.aazj.aop.DataSourceInterceptor"/>
<aop:config>
<aop:aspect id="dataSourceAspect" ref="aspectBean">
<aop:pointcut id="dataSourcePoint" expression="execution(public * net.aazj.service..*.getUser(..))" />
<aop:pointcut expression="" id=""/>
<aop:before method="before" pointcut-ref="dataSourcePoint"/>
<aop:after method=""/>
<aop:around method=""/>
</aop:aspect>
<aop:aspect></aop:aspect>
</aop:config>
<aop:aspect> 配置一个切面;<aop:pointcut>配置一个切点,基于切点表达式;<aop:before>,<aop:after>,<aop:around>是定义不同类型的advise. aspectBean 是切面的处理bean:
public class DataSourceInterceptor {
public void before(JoinPoint jp) {
DataSourceTypeManager.set(DataSources.SLAVE);
}
}
基于注解和@Aspect风格的AOP配置。
我们以事务配置为例:首先我们启用基于注解的事务配置
<!-- 使用annotation定义事务 -->
<tx:annotation-driven transaction-manager="transactionManager" />
然后扫描Service包:
<context:component-scan base-package="com.zhuangxiaoyan.spring.service.aop" />
最后在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注解定义的切面:
<context:component-scan base-package="com.zhuangxiaoyan.spring.aop" />
启用@AspectJ风格的注解:
<aop:aspectj-autoproxy />
这里有两个属性,<aop:aspectj-autoproxy proxy-target-class="true" expose-proxy="true"/>, 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的缺陷。
解决方法如下:首先:将 <aop:aspectj-autoproxy /> 改为:
<aop:aspectj-autoproxy expose-proxy="true"/>
然后,修改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...
五、观察者模式
观察者模式是一种对象行为型模式。它表示的是一种对象与对象之间具有依赖关系,当一个对象发生改变的时候,这个对象所依赖的对象也会做出反应。Spring 事件驱动模型就是观察者模式很经典的一个应用。Spring 事件驱动模型非常有用,在很多场景都可以解耦我们的代码。比如我们每次添加商品的时候都需要重新更新商品索引,这个时候就可以利用观察者模式来解决这个问题。
5.1 事件驱动模型的角色
事件角色:ApplicationEvent
(org.springframework.context
包下)充当事件的角色,这是一个抽象类,它继承了java.util.EventObject
并实现了 java.io.Serializable
接口。Spring 中默认存在以下事件,他们都是对 ApplicationContextEvent
的实现(继承自ApplicationContextEvent
):
ContextStartedEvent
:ApplicationContext
启动后触发的事件;ContextStoppedEvent
:ApplicationContext
停止后触发的事件;ContextRefreshedEvent
:ApplicationContext
初始化或刷新完成后触发的事件;ContextClosedEvent
:ApplicationContext
关闭后触发的事件。
事件监听者角色:ApplicationListener
充当了事件监听者角色,它是一个接口,里面只定义了一个 onApplicationEvent()
方法来处理ApplicationEvent
。ApplicationListener
接口类源码如下,可以看出接口定义看出接口中的事件只要实现了 ApplicationEvent
就可以了。所以,在 Spring中我们只要实现 ApplicationListener
接口实现 onApplicationEvent()
方法即可完成监听事件
package org.springframework.context;
import java.util.EventListener;
@FunctionalInterface
public interface ApplicationListener<E extends ApplicationEvent> extends EventListener {
void onApplicationEvent(E var1);
}
事件发布者角色:ApplicationEventPublisher
充当了事件的发布者,它也是一个接口。
@FunctionalInterface
public interface ApplicationEventPublisher {
default void publishEvent(ApplicationEvent event) {
this.publishEvent((Object)event);
}
void publishEvent(Object var1);
}
ApplicationEventPublisher
接口的publishEvent()
这个方法在AbstractApplicationContext
类中被实现,阅读这个方法的实现,你会发现实际上事件真正是通过ApplicationEventMulticaster
来广播出去的。
5.2 Spring的事件流程总结
- 定义一个事件: 实现一个继承自
ApplicationEvent
,并且写相应的构造函数; - 定义一个事件监听者:实现
ApplicationListener
接口,重写onApplicationEvent()
方法; - 使用事件发布者发布消息: 可以通过
ApplicationEventPublisher
的publishEvent()
方法发布消息。
// 定义一个事件,继承自ApplicationEvent并且写相应的构造函数
public class DemoEvent extends ApplicationEvent{
private static final long serialVersionUID = 1L;
private String message;
public DemoEvent(Object source,String message){
super(source);
this.message = message;
}
public String getMessage() {
return message;
}
// 定义一个事件监听者,实现ApplicationListener接口,重写 onApplicationEvent() 方法;
@Component
public class DemoListener implements ApplicationListener<DemoEvent>{
//使用onApplicationEvent接收消息
@Override
public void onApplicationEvent(DemoEvent event) {
String msg = event.getMessage();
System.out.println("接收到的信息是:"+msg);
}
}
// 发布事件,可以通过ApplicationEventPublisher 的 publishEvent() 方法发布消息。
@Component
public class DemoPublisher {
@Autowired
ApplicationContext applicationContext;
public void publish(String message){
//发布事件
applicationContext.publishEvent(new DemoEvent(this, message));
}
}
当调用 DemoPublisher
的 publish()
方法的时候,比如 demoPublisher.publish("你好")
,控制台就会打印出:接收到的信息是:你好
。
六、策略模式
策略模式(Strategy Pattern):定义一系列算法,将每一个算法封装起来,并让它们可以相互替换。策略模式让算法独立于使用它的客户而变化,也称为政策模式(Policy)
主要角色
- Context: 环境类
- Strategy: 抽象策略类
- ConcreteStrategy: 具体策略类
//策略接口:
public interface IStrategy {
/**
* 策略模式的运算法则
*/
public void doSomething();
}
// 然后是具体的策略实现类:
public class ConcreteStrategy1 implements IStrategy {
@Override
public void doSomething() {
System.out.println("具体策略的策略方法1");
}
}
public class ConcreteStrategy2 implements IStrategy {
@Override
public void doSomething() {
System.out.println("具体策略的策略方法2");
}
}
// 封装角色的类:
public class Context {
// 抽象策略
private IStrategy strategy;
/**
* 构造函数设置具体策略
* @param strategy
*/
public Context(IStrategy strategy) {
this.strategy = strategy;
}
/**
* 封装后的策略方法
*/
public void doAnything() {
this.strategy.doSomething();
}
}
// 客户端的调用策略类:
public class Client {
public static void main(String[] args) {
// 声明一个具体的策略
IStrategy strategy = new ConcreteStrategy1();
// 声明上下文对象
Context context = new Context(strategy);
// 执行封装后的方法
context.doAnything();
}
}
优点:
- 策略模式提供了对“开闭原则”的完美支持,用户可以在不修改原有系统的基- 础上选择算法或行为,也可以灵活地增加新的算法或行为。
- 策略模式提供了管理相关的算法族的办法。
- 策略模式提供了可以替换继承关系的办法。
- 使用策略模式可以避免使用多重条件转移语句。
缺点
- 客户端必须知道所有的策略类,并自行决定使用哪一个策略类。
- 策略模式将造成产生很多策略类,可以通过使用享元模式在一定程度上减少对象的数量。
适用场景
- 如果在一个系统里面有许多类,它们之间的区别仅在于它们的行为,那么使用策略模式可以动态地让一个对象在许多行为中选择一种行为。
- 一个系统需要动态地在几种算法中选择一种。
- 如果一个对象有很多的行为,如果不用恰当的模式,这些行为就只好使用多重的条件选择语句来实现。
- 不希望客户端知道复杂的、与算法相关的数据结构,在具体策略类中封装算法和相关的数据结构,提高算法的保密性与安全性。
Spring的初始化采用了策略模式,不同类型的类采用不同的初始化策略。
public interface InstantiationStrategy {
Object instantiate(RootBeanDefinition var1, String var2, BeanFactory var3) throws BeansException;
Object instantiate(RootBeanDefinition var1, String var2, BeanFactory var3, Constructor<?> var4, Object... var5) throws BeansException;
Object instantiate(RootBeanDefinition var1, String var2, BeanFactory var3, Object var4, Method var5, Object... var6) throws BeansException;
}
七、模板方法模式
7.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分别输出了对应的请假流程。细心的同学可能已经发现了,做为模版方法中里面除了可以有抽象方法外,还可以有具体的实现方法以及钩子方法。所以大家在应用的过程可以多考虑考虑在内部定义模版方法时,应该定义成抽象方法还是其它的。
模板方法设计模式的特点
- 模板方法不能被子类重写,可用
final
修饰 - 一个模板方法有确定的步骤组成,这些步骤可以被不同的子类实现,也可以自己实现。
7.2 模板方法模式的应用
模版方法模式在我们常见的Java的框架中也是非常常见的,只是可能我们平时没有注意到这一点而已。第一个:首先我们学SpringMVC的时候,最开始都会写一些Servlet来作为处理一些post或者get请求等。
这里直接看这个源码大家就可以发现这也是直接使用模版方法模式的思想,期间在HttpServlet 继承GenericServlet中也还是模版方法的体现,这说明了可以多次抽象构建模版。
第二个:常见问的文件流中,Java IO 类中的InputStream、OutputStream、Reader、Writer等都能看到模版方法模式的身影。
上面是我贴出的部分InputStream的源码,主要看这个read模版方法,也就是模版方法模式的体现。
在业务中怎么使用模版方法?首先需要理解模版方法它是为了增加代码的复用性,以及扩展性而存在的,所以本着这个思想我还是给大家举一个例子吧。商品详情展示我们可以是分模块展示的,比如头图,商品信息,sku信息,配送地址,分期付费等等。那么怎么进行组装到商品详情的展示呢?
可以看到一个请求过来,可以有模块组装器选择组装返回结果。提一个点,在第二步请求的模块的时候为了减少整个链路的请求时间可以考虑是串行,或者并行(开线程池处理)。
public abstract class AbstractTemplateBlock<T> {
// 初始化构建返回结果模型
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<ItemInfoBlock.ItemInfo> {
@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> T execute(StatementCallback<T> var1) throws DataAccessException;
StatementCallback 接口
@FunctionalInterface
public interface StatementCallback<T> {
@Nullable
T doInStatement(Statement var1) throws SQLException, DataAccessException;
}
execute实现
@Nullable
public <T> T execute(StatementCallback<T> 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<Users> findAll() {
JdbcTemplate jdbcTemplate = DataSourceConfig.getTemplate();
String sql = "select nickname,comment,age from users";
return jdbcTemplate.query(sql, new BeanPropertyRowMapper<Users>(Users.class));
}
query实现
public <T> List<T> query(String sql, RowMapper<T> 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<String> 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(适配者类):适配者即被适配的角色,它定义了一个已经存在的接口,这个接口需要适配,适配者类一般是一个具体类,包含了客户希望使用的业务方法,在某些情况下可能没有适配者类的源代码。
8.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目标方法
被适配者的方法
8.2 对象适配器模式
对象适配器与类适配器不同之处在于,类适配器通过继承来完成适配,对象适配器则是通过关联来完成,这里稍微修改一下 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<DC5Adapter> adapters = new LinkedList<DC5Adapter>();
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
8.3 spring中适配器模式的典型应用
8.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<AdvisorAdapter> 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<MethodInterceptor> 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
对象是从外部传进来,而不是成员属性。
8.3.2 spring JPA中的适配器模式
在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<String, ?> getJpaPropertyMap();
// 返回JpaDialect
public abstract JpaDialect getJpaDialect();
// 返回持久层管理器工厂
public abstract Class<? extends EntityManagerFactory> getEntityManagerFactoryInterface();
// 返回持久层管理器
public abstract Class<? extends EntityManager> getEntityManagerInterface();
// 自定义回调方法
public abstract void postProcessEntityManagerFactory(EntityManagerFactory paramEntityManagerFactory);
}
我们来看其中一个适配器实现类 HibernateJpaVendorAdapter
public class HibernateJpaVendorAdapter extends AbstractJpaVendorAdapter {
//设定持久层提供者
private final PersistenceProvider persistenceProvider;
//设定持久层方言
private final JpaDialect jpaDialect;
public HibernateJpaVendorAdapter() {
this.persistenceProvider = new HibernatePersistence();
this.jpaDialect = new HibernateJpaDialect();
}
//返回持久层方言
public PersistenceProvider getPersistenceProvider() {
return this.persistenceProvider;
}
//返回持久层提供者
public String getPersistenceProviderRootPackage() {
return "org.hibernate";
}
//返回JPA的属性
public Map<String, Object> getJpaPropertyMap() {
Map jpaProperties = new HashMap();
if (getDatabasePlatform() != null) {
jpaProperties.put("hibernate.dialect", getDatabasePlatform());
} else if (getDatabase() != null) {
Class databaseDialectClass = determineDatabaseDialectClass(getDatabase());
if (databaseDialectClass != null) {
jpaProperties.put("hibernate.dialect",
databaseDialectClass.getName());
}
}
if (isGenerateDdl()) {
jpaProperties.put("hibernate.hbm2ddl.auto", "update");
}
if (isShowSql()) {
jpaProperties.put("hibernate.show_sql", "true");
}
return jpaProperties;
}
//设定数据库
protected Class determineDatabaseDialectClass(Database database)
{
switch (1.$SwitchMap$org$springframework$orm$jpa$vendor$Database[database.ordinal()])
{
case 1:
return DB2Dialect.class;
case 2:
return DerbyDialect.class;
case 3:
return H2Dialect.class;
case 4:
return HSQLDialect.class;
case 5:
return InformixDialect.class;
case 6:
return MySQLDialect.class;
case 7:
return Oracle9iDialect.class;
case 8:
return PostgreSQLDialect.class;
case 9:
return SQLServerDialect.class;
case 10:
return SybaseDialect.class; }
return null;
}
//返回JPA方言
public JpaDialect getJpaDialect() {
return this.jpaDialect;
}
//返回JPA实体管理器工厂
public Class<? extends EntityManagerFactory> getEntityManagerFactoryInterface() {
return HibernateEntityManagerFactory.class;
}
//返回JPA实体管理器
public Class<? extends EntityManager> getEntityManagerInterface() {
return HibernateEntityManager.class;
}
}
配置文件中可以这样指定
<bean id="jpaVendorAdapter" class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter">
<property name="generateDdl" value="false" />
<property name="database" value="HSQL"/>
</bean>
<bean id="jpaDialect" class="org.springframework.orm.jpa.vendor.HibernateJpaDialect"/>
8.3.3 spring MVC中的适配器模式
Spring MVC中的适配器模式主要用于执行目标 Controller
中的请求处理方法。在Spring MVC中,DispatcherServlet
作为用户,HandlerAdapter
作为期望接口,具体的适配器实现类用于对目标类进行适配,Controller
作为需要适配的类。
为什么要在 Spring MVC 中使用适配器模式?Spring MVC 中的 Controller
种类众多,不同类型的 Controller
通过不同的方法来对请求进行处理。如果不利用适配器模式的话,DispatcherServlet
直接获取对应类型的 Controller
,需要的自行来判断,像下面这段代码一样:
if(mappedHandler.getHandler() instanceof MultiActionController){
((MultiActionController)mappedHandler.getHandler()).xxx
}else if(mappedHandler.getHandler() instanceof XXX){
...
}else if(...){
...
}
这样假设如果我们增加一个 HardController
,就要在代码中加入一行 if(mappedHandler.getHandler() instanceof HardController)
,这种形式就使得程序难以维护,也违反了设计模式中的开闭原则 – 对扩展开放,对修改关闭。
public interface HandlerAdapter {
boolean supports(Object var1);
ModelAndView handle(HttpServletRequest var1, HttpServletResponse var2, Object var3) throws Exception;
long getLastModified(HttpServletRequest var1, Object var2);
}
现该接口的适配器每一个 Controller
都有一个适配器与之对应,这样的话,每自定义一个 Controller
需要定义一个实现 HandlerAdapter
的适配器。springmvc 中提供的 Controller
实现类有如下
springmvc 中提供的 HandlerAdapter
实现类如下:
HttpRequestHandlerAdapter
这个适配器代码如下
public class HttpRequestHandlerAdapter implements HandlerAdapter {
public HttpRequestHandlerAdapter() {
}
public boolean supports(Object handler) {
return handler instanceof HttpRequestHandler;
}
public ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
((HttpRequestHandler)handler).handleRequest(request, response);
return null;
}
public long getLastModified(HttpServletRequest request, Object handler) {
return handler instanceof LastModified ? ((LastModified)handler).getLastModified(request) : -1L;
}
}
当Spring容器启动后,会将所有定义好的适配器对象存放在一个List集合中,当一个请求来临时,DispatcherServlet
会通过 handler
的类型找到对应适配器,并将该适配器对象返回给用户,然后就可以统一通过适配器的 hanle()
方法来调用 Controller
中的用于处理请求的方法。
public class DispatcherServlet extends FrameworkServlet {
private List<HandlerAdapter> handlerAdapters;
//初始化handlerAdapters
private void initHandlerAdapters(ApplicationContext context) {
//..省略...
}
// 遍历所有的 HandlerAdapters,通过 supports 判断找到匹配的适配器
protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {
for (HandlerAdapter ha : this.handlerAdapters) {
if (logger.isTraceEnabled()) {
logger.trace("Testing handler adapter [" + ha + "]");
}
if (ha.supports(handler)) {
return ha;
}
}
}
// 分发请求,请求需要找到匹配的适配器来处理
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
HttpServletRequest processedRequest = request;
HandlerExecutionChain mappedHandler = null;
// Determine handler for the current request.
mappedHandler = getHandler(processedRequest);
// 确定当前请求的匹配的适配器.
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
ha.getLastModified(request, mappedHandler.getHandler());
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
}
// ...省略...
}
通过适配器模式我们将所有的 controller
统一交给HandlerAdapter
处理,免去了写大量的 if-else
语句对 Controller
进行判断,也更利于扩展新的 Controller
类型。
博文参考
谈一谈我对‘模板方法’设计模式的理解(Template) - 掘金
面试官:“谈谈Spring中都用到了那些设计模式?”。 - 掘金
Spring框架中都用到了哪些设计模式?详细解析设计模式在Spring框架中的应用 - 掘金