结合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...");
}
}