天天看点

Spring核心概念和动态代理解析Spring

Spring

[百度1](http://www.baidu.com/" 百度一下")

Spring解决的问题

  • 之前代码耦合性太高,service层要使用dao,要添加相应的dao实现类作为成员变量,但有一天想换实现类,就需要修改源代码,不符合开闭原则

    开闭原则:程序对于拓展是开放的,对修改关闭

  • 控制事务繁琐,spring的AOP,面向切面编程,方便的对代码进行增强。是我们更关注业务逻辑代码,而不是无关的日志记录等操作
  • 方便集成第三方框架,向上集成表现层,向下集成持久层

Spring 三大概念

  • IoC

    控制反转,把创建对象的权利交给spring去做,我们只需在使用到的时候去从spring容器中去取。spring通过读取配置中的元配置对bean对象进行创建

    配置的三种方式

  1. 基于xml配置,古老,结构清晰,但bean对象过多配置会繁琐。但是有些不能使用注解配置的仍然需要使用xml的配置
  2. 基于注解,简单,但是不直观,较常用
  3. 基于java代码配置,在springboot中很常见,通常配合注解进行使用
  • DI

    依赖注入,通常DI概念应该包含在Ioc中,是指在创建对象的时候把对象依赖的对象给注入进去

  • AOP

    面向切面编程

Ioc和DI

Spring测试框架

传统junit测试是在每一次test去启动spring容器 ,不仅启动开销大,而且每次测试都是非正常关闭容器。

spring测试是在spring容器中去开启test,会自动关闭spring容器。传统测试 需要手动去调用容器的关闭操作

Spring 创建对象的四种方式

  1. 构造器创建(常用)
  2. 静态工厂创建,不推荐
  3. 实例工厂创建,不推荐
  4. 实现FactoryBean接口创建(后面框架会使用)

BeanFactory接口和ApplicationContext接口区别

  • BeanFactory只支持Ioc相关操作,即创建,销毁和管理bean。同时它是在用到bean的时候才会去创建bean
  • applicationContext继承自beanfactory接口,除了ioc相关操作,还包括Aop和国际化,事务等其他操作。它是在spring容器启动的时候就把所管理的bean进行创建

bean对象作用域问题

  1. singleton,单例,在spring容器中仅会存在一个bean实例。spring容器关闭,bean会被销毁
  2. prototype,多例,每次从容器中取出bean的时候都会创建出一个新的bean。容器关闭,但bean不被销毁,因为对象并不被spring容器所管理

bean对象的初始化和销毁(生命周期 )

bean标签中有init-method和destroy-method.前者在构造器执行后即对象创建后执行,后者会在容器关闭之前执行。注意如果如果bean的scope是prototype,spring容器只会去创建和初始化对象,这个对象并不会被容器管理,因此即便容器关闭也不会执行bean的销毁方法

DI注入常用方式(xml)

  • 基于setter,使用标签,类中必须相应的属性(提供setter)
  • 基于构造器,使用标签,类中必须有相应的构造器

xml中引入其他配置文件

区分以下两个

  • 引入properties,下面的配置需要使用属性占位符去取值,比如数据库连接信息
  • 引入其他的spring配置的xml文件,比如后期会有个总的application-context文件,里面会引入其他的service层,dao层配置文件,方便进行管理

补充:

Spring中有很多命名空间,比如context:和aop:等,背后都有相应的一个处理器去处理和解析这些命名空间,这些处理类都实现了一个接口NamespaceHandler,里面每个属性都会有个对应的parser,比如下边的ContextNamespaceHandler

@Override
	public void init() {
		registerBeanDefinitionParser("property-placeholder", new PropertyPlaceholderBeanDefinitionParser());
		registerBeanDefinitionParser("property-override", new PropertyOverrideBeanDefinitionParser());
		registerBeanDefinitionParser("annotation-config", new AnnotationConfigBeanDefinitionParser());
		registerBeanDefinitionParser("component-scan", new ComponentScanBeanDefinitionParser());
		registerBeanDefinitionParser("load-time-weaver", new LoadTimeWeaverBeanDefinitionParser());
		registerBeanDefinitionParser("spring-configured", new SpringConfiguredBeanDefinitionParser());
		registerBeanDefinitionParser("mbean-export", new MBeanExportBeanDefinitionParser());
		registerBeanDefinitionParser("mbean-server", new MBeanServerBeanDefinitionParser());
	}
           

基于注解的配置

  • DI注解配置

    在xml中添加context:annotation-config/可省略,但web环境必须添加

  1. 注入对象

    方式一 @[email protected]

    方式二 @Resource

  2. 注入简单类型数据

    @Value,通常很少为某个属性直接注入常量值,通常用于读取properties文件后,将文件中的key值注入进来,但要注意,解析文件一定要有<context:property-placeholder location=“classpath:db.properties”/> 配置,或者在javaconfig配置场景下,提供相应的读取解析类

  • Ioc注解配置

    在xml中添加<context:component-scan base-package="">,可以猜测底层一定有个处理器处理该注解

    四大Ioc注解@Component,@Repository,@Service,@Controller。四者功能相同,仅是标记不同,不知道属于哪一层,使用@Component

  • aop注解配置

    <aop:aspectj-autoproxy />,这是aop和aspect注解解析器

代理思想

静态代理

静态代理解决责任不分离的问题,业务层就应该做业务逻辑相关,日志记录和权限检查等不应该由业务层来做。所谓静态代理就是代理类的字节码文件在程序运行前就确定,即被代理类和代理类的关系在运行前就确定。使用就是写一个代理类实现和被代理类相同接口,然后再增强,有点像装饰设计模式,用时直接使用代理类就行。静态代理存在很多问题,针对每个被代理类都需要去写一个代理类,而且代理类中每个方法如果都有相同的代码,每个方法又要重复相同操作,因此是二次冗余,配置繁琐,代码冗余,一般不用,期待动态代理。

动态代理

  • 代理类是在运行期间jvm通过反射创建的,就是说在运行前根本不存在代理类的字节码文件,代理类和被代理类的关系是在运行期间才创建的
  • 注意此处配置相应处理器而不是直接配置代理类,代理方式是开发一个handler实现InvocationHandler,这个handler中会包含真实对象和增强对象比如tx,分为jdk动态代理(基于接口)和cglib动态代理(基于继承)。那么代理对象从这个处理器中的一个方法来获得,方法名自己定。那相当于配置时,仅需要配置handler,然后通过handler获得代理对象,然后使用时只需要代理对象去调用相应的方法即可
  • 选用:有接口使用jdk动态代理,没有使用cglib动态代理
  • 怎么理解动态代理过程:

    TxManagerHandler实现InvocationHandler接口,覆盖invoke方法,且类中有增强类对象和真实对象。invoke方法中自己去调用增强类中的方法去增强。另外需要自己写一个方法返回代理对象,方法名随意比如getProxy(),然后jdk动态代理和cglib方式创建过程不同,但最终会把代理对象返回,而且创建时有个特点都会把当前handler对象也就是this传进去用于回调。通过反编译看出动态创建的代理类,会发现此代理类实现了和真实对象实现的相同接口(IemployeeService),里面的save和update方法调用的就是InvocationHandler对象的invoke方法,也就是TxManagerHandler类中我们实现的invoke方法.

    Spring核心概念和动态代理解析Spring
  1. jdk动态代理完整开发:

    配置文件

<bean name="txManager" class="cn.wolfcode.common.tx.TxManager" />
    <bean name="employeeService" class="cn.wolfcode.common.service.EmployeeServiceImpl" />
	<bean name="txManagerHandler" class="cn.wolfcode.jdkproxy.TxManagerHandler">
		<property name="tx" ref="txManager" />
		<property name="target" ref="employeeService" />
	</bean>
           

自定义事务类

public class TxManager {
	public void open(){
		System.out.println("开启事务。。。");
	}
	public void commit(){
		System.out.println("提交事务。。。");
	}
	public void rollback(){
		System.out.println("回滚事务。。。");
	}
	public void close(){
		System.out.println("关闭资源。。。");
	}
}
           

service(被代理对象)

public class EmployeeServiceImpl implements IEmployeeService{
	public void insert(String name, String password) {
		System.out.println("保存员工");
	}
	public void update(String name, String password) {
		int a = 1 / 0;
		System.out.println("修改员工");
	}
	
}
           

动态代理类

public class TxManagerHandler implements InvocationHandler{
	private TxManager tx;   //自定义事务增强对象
	private IEmployeeService target;      //需要增强的对象(被代理对象)

	public void setTx(TxManager tx) {
		this.tx = tx;
	}
	public void setTarget(IEmployeeService target) {
		this.target = target;
	}
	
	/*
	 * 返回代理对象
	 */
	public <T>T getProxy(){
		return (T) Proxy.newProxyInstance(target.getClass().getClassLoader(),
					target.getClass().getInterfaces(), this);
	}

	/*
	 * 真正执行的方法
	 */
	public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
		Object obj = null;
		try {
			tx.open();
			obj = method.invoke(target, args);
			tx.commit();
		} catch (Exception e) {
			// TODO: handle exception
			tx.rollback();
		}finally {
			tx.close();
		}
		return obj;
	}

}

           
  1. cglib动态代理完整开发(其余相同,动态代理类不同):

    cglib动态代理类

public class TxManagerHandler implements InvocationHandler{
	//事务管理器
	private TxManager tx;
	//真实对象
	private IEmployeeService target;

	public void setTx(TxManager tx) {
		this.tx = tx;
	}
	public void setTarget(IEmployeeService target) {
		this.target = target;
	}
	
	/*
	 * 返回代理对象
	 */
	public <T>T getProxy(){
		Enhancer enhancer = new Enhancer();
		enhancer.setSuperclass(target.getClass());
		enhancer.setCallback(this);
		return (T) enhancer.create();
	}

	/*
	 * 真正执行的方法
	 */
	public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
		Object obj = null;
		try {
			tx.open();
			obj = method.invoke(target, args);
			tx.commit();
		} catch (Exception e) {
			tx.rollback();
		}finally {
			tx.close();
		}
		return obj;
	}

}
           

测试保存

@Test
	public void testSave() throws Exception {
		IEmployeeService service = txManagerHandler.getProxy();
		System.out.println(service.getClass());
		service.insert("sss", "666");
	}

	结果:
	开启事务。。。
	保存员工
	提交事务。。。
	关闭资源。。。
           

测试更新:里面故意抛出异常,测试回滚

@Test
	public void testUpdate() throws Exception {
		IEmployeeService service = txManagerHandler.getProxy();
		service.update("sss", "7777");
	}

	结果:
	开启事务。。。
	回滚事务。。。
	关闭资源。。。
           
  • 怎么用

    我们创建的是TxManagerHandler类,那么配置就是配置这个类的对象,然后把真实对象和增强对象依赖注入进来。在真正用到时把handler对象注入,然后handler对象.getProxy()获取代理对象,用真实对象的接口接收即可,因为代理对象和真实对象都实现了相同的接口.然后就可以正常调用service.save()方法即可

IEmployeeService service = txManagerHandler.getProxy();
           
  • 缺陷

    首先动态代理可以解决代理类中方法中重复代码的问题,但无法解决每个类都需要一个代理类的问题,配置文件中仍需要为每一个被代理类进行配置处理器,也就是说handler类我们只需要写一个而且是通用的,但如果每一个被代理类想要用这个handler就要再去配一次,很不友好,期待AOP。

补充:

jvm如何在运行期间动态的创建一个类呢。在运行期间通过字节码文件(.class)的格式和结构,生成相应二进制数据,然后二进制数据转成相应的类

AOP

首先AOP的底层原理就是动态代理,Spring默认是jdk动态代理,我们也可以改为cglib动态代理

面向切面编程,我们想做日志增强,就把日志增强做成一个切面,可以去切多个类中的多个方法,那么这些被切的类中就有了日志增强,这样就不需要动态代理那样为每个类都配置一个handler进行增强

常用术语

  1. JoinPoint: 连接点,就是那些被增强的方法。通过JoinPoint和其子类ProceedingJoinPoint中的某些方法,可以去执行被增强的方法和获取被增强方法的相关信息比如签名参数等,ProceedingJoinPoint只能作为around方法的参数
  2. PointCut: 切入点,表示where,有多个连接点构成。表示对哪些类中的哪些方法进行增强.一般用Aspectj中的表达式来表达
  3. Advice:增强,when+what,表示了在什么时候做什么样的增强
  4. Aspect:切面,Aspect = Pintcut+Advice,在哪些地方在什么时机做什么增强

配置方式

  • XML配置

    注意使用AOP,那些在Spring容器中管理的bean都不再是真实类型了,而是AOP为每个真实类型生成的代理类型。而且只要配置下边这些,在Spring容器时,容器就为每个被切到的类生成了代理类,所以整个应用下来拿到的都是代理对象不是真实对象

<bean name="txManager" class="cn.wolfcode.common.tx.TxManager" />
<aop:config>
        <!-- ref:表示做什么增强,此处是日志增强-->
		<aop:aspect ref="txManager">
            <!-- pointcut: 表示对哪些类中哪些方法做增强-->
			<aop:pointcut expression="execution(* cn.wolfcode.common.service.*ServiceImpl.*(..))" id="txPointCut"/>
            <!-- 下边表示在什么时候增强,注意每个都要联系切点pointcut-ref,可以理解,因为上述切点可以定义多个->
            <!--前置增强-->
			<aop:before method="open" pointcut-ref="txPointCut"/>
            <!--后置增强,正常执行后做的增强-->
			<aop:after-returning method="commit" pointcut-ref="txPointCut"/>
            <!--异常增强-->
			<aop:after-throwing method="rollback" throwing="ex" pointcut-ref="txPointCut"/>
            <!--最终增强,无论有无异常,都要做的增强,比如关闭资源-->
			<aop:after method="close" pointcut-ref="txPointCut"/>
            <!--环绕增强,包含以上几个-->
			<aop:around method="around" pointcut-ref="txPointCut"/>
		</aop:aspect>
	</aop:config>
           
  • 注解配置

    一定在xml中加入<aop:aspectj-autoproxy />,这是aop和aspect注解解析器

    注解配置需要我们额外开发一个Advice类,一般起名xxAdvice.贴上@Aspect标签和@Component,然后写个方法贴上@Pointcut标签,表示where,下边的方法贴上对应时间的标签,具体如下 :

@Component
@Aspect
public class TxManagerAdvice {
	@Pointcut("execution(* cn.wolfcode.common.service.*ServiceImpl.*(..))")
	public void txPoint(){}
	
	@Before("txPoint()")
	public void open(){
		System.out.println("开启事务。。。");
	}
}