[SpringMVC 2]IOC 与 AOP

结合Spring MVC快速搭建SSM框架进行IOC与AOP的进阶,Spring MVC1 搭建SSM项目

本次内容围绕IOC与AOP进行展开,同时介绍了单例模式(饿汉,双重锁机制)、工厂模式、代理模式等重要内容,内容确实非常肝,需要通过大量的实践才能领悟其中的真谛。本次同时对Spring 源码做了些解读,本篇内容确实有一定的难度。

Spring IOC和DI

概述

IOC是控制反转,DI是依赖注入,控制反转是通过依赖注入来实现的。
IOC是实现的对象创建、维护对象间的依赖关系,反转给容器来帮忙实现。那么需要创建一个容器来让其知道需要创建的对象与对象的关系。依赖注入的目的就是为了解耦。
IOC负责创建Bean,通过容器将Bean注入到需要的Bean对象上。

@Service
public class AyUserServiceImpl implements AyUserService {
	//依赖注入,和AutoWired类似
    @Resource
    private AyUserDao ayUserDao;
    public List<AyUser> findAll() {
        return ayUserDao.findAll();
    }
}

声明Bean的注解
@Component 没有明确的角色,类似于中立
@Service 在服务层(业务逻辑层)被使用
@Repository 在数据访问层(dao)被使用
@Controller 在表现层(控制层)被使用

注入Bean的注解
@Autowired Spring 提供的注解
@Resource JSR-250提供的注解
注意 @Resource注解属于J2EE,默认安装名称进行装配,名称通过name来指定,按照名称来进行装配。

@Resource (name = "user")
private userDao user;

而@Autowired 注解属于Spring ,默认按类型装配。默认依赖对象存在,如果运行null值,可以设置为false,例如@Autowired(required = false),如果想用名称来进行装配,可以结合Qualifier

@Autowired
@Qualifier("user")
private userDao user;

单例模式

Spring 依赖注入Bean实例默认是单例的。
单例模式:确保某一个类只有一个实例,而自行实例化并向整个系统提供这个实例,这个类为单例类,是一种对象创建型模式。
传统创建类的实例:


public class Case_1 {
	public static void main(String[] args) {
		Singleton sing = new Singleton();
		Singleton sing2 = new Singleton();
	}
}
/**单例类**/
class Singleton {
	
}

在上述代码中,每次new Singleton(),都会创建一个Singleton实例,显然不符合一个类只有一个实例的要求:注意用的是static修饰

/**单例模式实例**/


public class Case_1{
	public static void main(String[] args) {
		//Singleton sing = new Singleton();
		// 单例
		Singleton sing = Singleton.getInstance();
	}
}

/**单例类(饿汉模式)**/

class Singleton {
	//自行对外提供实例
	private static final Singleton singleton = new Singleton();
	//构造函数私有化
	private Singleton() {
		
	}
	//提供外界可以获得该实例的方法
	public static Singleton getInstance() {
		return singleton;
	}
}

可见,实现一个单例模式总共有三个步骤**(提供实例、初始化、构造方法获得实例)**。与饿汉模式想对应的还有懒汉模式(延迟加载)

/***懒汉模式**/
class Singleton {
	private static Singleton single = null;
	private Singleton(){}
	public static Singleton getInstance() {
		//判断对象是否创建
		if(null == single )  single = new Singleton();
		return single;
	}
}

懒汉模式有个明显的问题,就是没有考虑线程安全的问题,在多线程并发的情况下,会并会调用getInstance()方法,从而导致系统同时创建多个单例类实例,通过getInstance()方法添加锁解决该问题。单例类是全局唯一的,锁的操作会成为系统的瓶颈,需要进行优化,由此引出“双重校验锁”:

class Singleton {
	private static Singleton single = null;
	private Singleton() {}
	
	private static Singleton getInstance() {
		//第一次检验
		if(single == null) {
			synchronized(Singleton.class) {
				//第二次检验
				if(single == null) {
					//创建对象,非原子操作
					single = new Singleton();
				}
				
			}
		}
	}
	return single;
}

双重校验锁会出现指令重排的问题,所谓指令重排就是指JVM为了优化指令,提高程序运行效率,在不影响单线程程序执行结果的前提下,尽可能提高并行度。single = new Singleton()可以抽象为下面几条JVM指令:

//分配对象的内存空间
memory = allocate();
//初始化对象
ctorInstance(memory);
//设置instance 指向分配的内存地址
single = memory;

操作2依赖于操作1,但是操作3不依赖操作2,所以JVM是针对他们进行指令的优化重排序,经过重排序后:

//分配对象的内存空间
memory = allocate();
//设置instance 指向分配的内存地址,此时对象还没有初始化
single = memory;
//初始化对象
ctorInstance(memory);

为解决指令重排的问题,可以使用volatile修饰single字段,此关键字就是禁止指令的重排序优化,阻止JVM对其重排,可以按照顺序执行。

Spring 单例模式
Spring 的BeanFactory工厂如何实现单例模式?Spring 的依赖注入,发生在AbstractBeanFactory的getBean方法里,getBean方法内部调用doGetBean方法。

public Object getBean(String name) throws BeansException {
        return this.doGetBean(name, (Class)null, (Object[])null, false);
    }

 protected <T> T doGetBean(String name, @Nullable Class<T> requiredType, @Nullable Object[] args, boolean typeCheckOnly) throws BeansException {
        String beanName = this.transformedBeanName(name);
        Object sharedInstance = this.getSingleton(beanName);
        Object bean;
        if (sharedInstance != null && args == null) {
            if (this.logger.isDebugEnabled()) {
                if (this.isSingletonCurrentlyInCreation(beanName)) {
                    this.logger.debug("Returning eagerly cached instance of singleton bean '" + beanName + "' that is not fully initialized yet - a consequence of a circular reference");
                } else {
                    this.logger.debug("Returning cached instance of singleton bean '" + beanName + "'");
                }
            }
...
}

从上述来看,Spring依赖注入时候,使用了双重校验锁的单例模式,首先从缓存singletonObjects中获取bean实例,如果为null,对缓存singletonObjects加锁,然后再从缓存earlySingletonObjects获取bean实例,如果继续为null,就创建一个bean。Spring 没有使用私有构造方法来创建bean,而是通过singletonFactory.getObject()返回具体beanName对应的ObjectFactory来创建bean。

简单工厂模式

Spring的Bean工厂大量使用工厂方法模式,工厂方法模式是以简单工厂模式为基础的。简单工厂模式用来定义一个工厂类,可以根据参数不同返回不同类的实例,被创建的实例通常具有共同的父类。因为创建实例的方法是static方法,所以又被称为静态工厂方法模式,属于类创建型模式。
有以下几个角色:
Factory(工厂角色):工厂类,负责实现创建所有产品实例的内部逻辑,可以被外界直接调用,创建所需要的产品对象,提供静态方法:factoryMethod,返回类型就是Product.
Product(抽象产品角色):父类
ConcreteProduct(具体产品角色)子类
在这里插入图片描述

package com.ay.test;

import org.omg.PortableInterceptor.SYSTEM_EXCEPTION;

public class SimpleFactoryPattern {
    public static  void main(String[] args) {
        Vehicle vehicle = Factory.produce("car");
        vehicle.run();
    }
}

class Factory{
    public static Vehicle produce(String type) {
        Vehicle vehicle = null;
        if(type.equals("car")) {
            vehicle = new Car();
            return vehicle;
        }
        if(type.equals("bus")) {
            vehicle = new Bus();
            return vehicle;
        }
        return vehicle;
    }
}


/*
抽象类
 */
interface Vehicle {
    void run();
}

/*
汽车,具体类
 */
class Car implements Vehicle {
    @Override
    public void run() {
        System.out.println("Car Run");
    }
}

/*
自行车,具体类
 */
class  Bus implements Vehicle {
    @Override
    public void run() {
        System.out.println("Bus run" );
    }
}

简单工厂模式优点:根据判断逻辑来决定在什么时候创建哪一个产品类的实例;
缺点:集中了所有产品的创建逻辑,职责过重;增加类的个数与系统的复杂度;

工厂方法模式

在简单工厂模式只提供一个工厂类,需要知道每一个产品对象的创建细节,并决定何时实例化哪一个产品类,最大的缺点就是当有新产品要加入到系统中必须要修改工厂类,需要增加必要的业务逻辑,违背了开闭原则
而工厂方法模式中,不再提供一个统一的工厂类来创建所有的对象,而是针对不同的产品提供不同的工厂,系统提供一个与产品等级结构对应的工厂等级结构
在这里插入图片描述

package com.ay.test;

import org.hamcrest.Factory;

public class FactoryMethodPattern {
    public  static void main(String[] args) {
        //生产汽车
        Factoryc carFactory = new CarFactory();
        Vehicle car = carFactory.produce();
        car.run();
    }
}


/*
抽象工厂类
 */
interface Factoryc {
    // 生产
    Vehicle produce();
}

/*
汽车工厂
 */
class CarFactory implements Factoryc {
    @Override
    public Vehicle produce() {
        return new Car();
    }
}

/*
交通工具
 */
interface Vehicle {
    void run();
}

/*
汽车
 */
class Car implements  Vehicle {
    @Override
    public void run() {
        System.out.println("Car run。。。" );
    }
}

与简单工厂相比,工厂方法模式最重要的区别就是引入抽象工厂角色,可以是接口,可以是抽象类或者具体类。抽象工厂中声明了工厂方法但并未实现,具体产品对象的创建由子类负责,客户端针对抽象工厂编程,可以在运行时候再指定具体工厂类,具体工厂类实现了工厂方法,不同的具体工厂可以创建不同的具体产品。

Spring Bean工厂类

bean工厂类是放在spring-beans-5.0.3.RELEASE.jar包中的,可以使用IDEA查看Spring的类结构图。
BeanFactory:定义获取bean及bean的各种属性
SimpletonBeanRegistry:定义对单例的注册及获取
ListableBeanFactory:获取bean的配置清单
Spring 源码用到了很多工厂模式。IOC容器对于Bean的管理,利用工厂模式来实现更加优良的松耦合设计。
在这里插入图片描述

Spring AOP

概述

AOP:面向切面编程,通过预编译方式运行期动态代理实现程序功能的统一维护的技术。AOP是OOP的延续,也是Spring 框架的重要内容,是函数式编程的一种衍生泛型。利用AOP可以对业务逻辑各个部分进行隔离,从而使得业务逻辑各部分耦合度降低,提高程序可重用性和开发效率。
AOP 主要功能有日志记录,性能统计、安全控制、事务处理、异常处理等。
那什么是切面呢?简单来说,就是代码执行过程中可以随意插入和拔出
在这里插入图片描述
核心概念:
在这里插入图片描述

JDK动态代理实现日志框架

动态代理:对象的执行方法交给代理来负责
AOP内部是使用动态代理来实现的,通过动态代理模式来实现最简单的日志框架。
实现原理:动态创建代理类并通过指定类加载器加载,然后在创建代理对象时将InvokerHandler对象作为构造参数传入,当调用代理对象时候,会调用InvokerHandler.invoke方法,并最终调用真正业务对象的相应方法。
一般实现步骤:
1.创建一个实现InvocationHandler接口类MyLoggerHandler,必须实现invoke方法
2.创建被代理的类BusinessClassService 以及接口 BusinessClassServiceImpl
3.调用Proxy的静态方法newProxyInstance,创建一个代理类。
4.通过代理类调用方法。

结合第一讲在com.ay.test进行创建所需要的代码:
此业务接口类可以理解为日常开发业务创建的接口类,接口中有一个简单的方法。

package com.ay.test;
/*
业务类接口
 */
public interface BusinessClassService {
    void doSomeThing();
}

设计开发业务类BusinessClassServiceImpl
实现了业务接口并实现了方法

package com.ay.test;

public class BusinessClassServiceImpl  implements  BusinessClassService{

    @Override
    public void doSomeThing() {
        System.out.println("do somthing。。。");
    }
}

接着开发日志接口类MyLogger:

package com.ay.test;
import java.lang.reflect.Method;
/*
日志类接口
 */
public interface MyLogger {
    void saveIntoMethodTime(Method method);//记录进入方法的时间
    void saveOutMethodTime(Method method);//记录退出方法的时间
}

接口完成后,用MyLoggerImpl进行实现它:

package com.ay.test;

import java.lang.reflect.Method;
import java.sql.SQLSyntaxErrorException;

public class MyLoggerImpl implements  MyLogger{

    @Override
    public void saveIntoMethodTime(Method method) {
        System.out.println("In"+method.getName()+"Time:"+System.currentTimeMillis());
    }

    @Override
    public void saveOutMethodTime(Method method) {
        System.out.println("Out"+method.getName()+"Time:"+ System.currentTimeMillis());
    }
}

最后实现重要的类MyLogger:
注意InvocationHandler只定义一个方法,在使用时,第一个参数指代理类,method是代理的方法,args是该方法的参数数组,这个抽象方法在代理类中动态实现。

package com.ay.test;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

public class MyLoggerHandler implements InvocationHandler {
    //原始对象
    private  Object objOriginal;
    //关键一步
    private MyLogger myLogger = new MyLoggerImpl();

    public MyLoggerHandler(Object obj) {
        super();
        this.objOriginal = obj;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        Object result = null;
        // 日志类的方法,保存进入方法的时间
        myLogger.saveIntoMethodTime(method);
        //调用代理类方法
        result = method.invoke(this.objOriginal,args);
        //日志类方法
        myLogger.saveOutMethodTime(method);
        return result;
    }
}

代码开发完成后,开发测试类MyLoggerTest进行测试:
Proxy.newProxyInstance: 该类即为动态代理类,返回代理类的一个实例,返回后的代理类可以被当做代理类使用。在其方法中,共有以下三个参数:
ClassLoader loader: targetObj.getClass().getClassLoader()目标对象通过getClass获取类的所有信息后,调用getClassLoader()来获取类加载器,生成的代理类加载到JVM,以便运行使用。
Class[] interfaces:targetObj.getClass().getInterfaces()获取被代理的所有接口信息
InvocationHandler h:使用动态代理为了更好地扩展,回调方法。

package com.ay.test;

import java.lang.reflect.Proxy;

public class MyLoggerTest {
    public static void main(String[] args) {
        //实例化真实项目中的业务类
        BusinessClassService businessClassService = new BusinessClassServiceImpl();
        //日志类的handler
        MyLoggerHandler myLoggerHandler = new MyLoggerHandler(businessClassService);
        //获得代理类对象
        BusinessClassService businessClass = (BusinessClassService) Proxy.newProxyInstance(businessClassService.getClass().getClassLoader(),businessClassService.getClass().getInterfaces(),myLoggerHandler);
        //执行代理类方法
        businessClass.doSomeThing();
    }
}

以下就是简单的日志框架结构图:
在这里插入图片描述

AOP实现日志框架

使用AOP的注解方式实现日志框架很简单,只要在spring-mvc.xml添加配置:
aop:aspectj-autoproxy: 声明自动为Spring容器中那些配置@aspectJ切面的bean创建代理,织入切面。
proxy-target-class为false表示使用JDK动态代理;为true使用CGLib动态代理技术增强,目标类没有声明接口,Spring将自动使用CGLib动态代理。

<aop:aspectj-autoproxy proxy-target-class="true">

配置完成后,需要一个切面LogInterceptor:
Aspect : 识别为一个切面,供容器读取
Before:在拦截方法执行之前执行
After: 之后
Around: 可以同时在所拦截方法前后执行一段逻辑
这里就是说把这个类交给Spring管理,重新起个名字叫userManager,由于不好说这个类属于哪个层面,就用@Component
execution切入点指示符: com.ay.controller..(…)表示在controller包下面所有方法

execution (方法修饰符 *方法返回值 方法所属类 *匹配方法名 方法抛出的异常)
* 一个任意类型的参数
.. 代表0个多个任意类型的参数

():匹配一个无参方法
(..:匹配一个可接受任意数量参数和类型的方法
(*):匹配一个接受一个任意类型参数的方法
(*Integer)匹配一个接受两个参数的方法
package com.ay.proxy;


import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;

@Aspect
@Component
public class LogInterceptor {
    @Before(value="execution(* com.ay.controller.*.*(..))")
    public void before() {
        System.out.println("In"+System.currentTimeMillis());
    }
    @After(value = "execution(* com.ay.controller.*.*(..))")
    public void after() {
        System.out.println("Out:"+System.currentTimeMillis());
    }
}

运行完以后,浏览器输入8080/user/findAll,可以看到控制台打印内容:
在这里插入图片描述

静态代理与动态代理模式

Spring AOP使用的是动态代理模式。其定义如下:
给某一个对象提供一个代理或占位符,由代理对象控制对原对象的访问,一种对象结构型模式;在客户端和目标对象起到了中介作用。其类结构图如下:
在这里插入图片描述
Subject(抽象主题角色):声明了真实主题和代理主题的共同接口
Proxy(代理主题角色):包含了对真实主题的引用
RealSubject(真实主题角色):定义了代理角色所代表的真实对象
下面是静态代理模式的具体代码:
在编译阶段为RealSubject类创建一个Proxy类,需要代理类很多时候,就会出现大量Proxy类,所以使用JDK动态代理来解决。

package com.ay.test;


import java.sql.PreparedStatement;

/**
 * 客户端
 */
public class ProxyPattern {
    public static void main(String[] args) {
        //为每个RealSubject创建代理类Proxy
        Proxyc proxyc = new Proxyc(new RealSubject());
        proxyc.operation();
    }
}

/**
 * 抽象主题类
 */
abstract class Subject {
    abstract void operation();
}

/**
 * 具体主题类
 */
class RealSubject extends Subject {
    void operation() {
        System.out.println("Operation...");
    }
}

/*
代理类
 */

class Proxyc extends  Subject {
    private Subject subject;
    public Proxyc (Subject subject) {
        this.subject = subject;
    }
    void operation() {
        //前置处理
        this.preOperation();
        //具体操作
        subject.operation();
        //后置处理
        this.postOperation();
    }
    void preOperation() {
        System.out.println("pre operation...");
    }
    void postOperation() {
        System.out.println("post operation...");
    }
}

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值