ssm--spring3

1.spring整合junit
1.1测试类中的问题和解决思路
	jdk自带的junit无法识别spring框架,因此当使用spring整合junit后,导致了异常。依靠spring框架提供的junit,替换掉jdk自带的运行器。

1.2配置步骤
1.2.1导入junit的坐标,junit版本4.12以上
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-test</artifactId>
    <version>5.0.2.RELEASE</version>
</dependency>

1.2.2使用@RunWith注解替换原有运行器,作用于单元测试类类名称上
	@RunWith(SpringJUnit4ClassRunner.class)

1.2.3使用@ContextConfiguration 指定 Spring 配置文件的位置
这个注解可以代替ApplicationContext ac = new AnnotationConfigApplicationContext(a.class);作用在类上,只要注解配置类是对的,即便main方法中没有任何方法体也能加载注解配置类
	@ContextConfiguration(classes=SpringConfiguration.class)	//表示使用注解配置
	@ContextConfiguration(locations="classpath:applicationContext.xml")		//表示使用xml配置
locations 属性: 用于指定配置文件的位置。如果是类路径下,需要用 classpath:表明
classes 属性: 用于指定注解的类。当不使用 xml 配置时,需要用此属性指定注解类的位置。
替换掉jdk自带的junit运行器后,applicationContext context = new ....这行代码将不再被支持,也就是说@RunWith必须搭配@ContextConfiguration注解使用
2.基于同一个线程下的Connection控制事物
2.1ThreadLocal对象
ThreadLocal<Connection> threadLocal = new ThreadLocal<>();
    threadLocal.set(connection); //底层会创建一个ThreadLocalMap,key:ThreadLocal,value:Connection
    threadLocal.get(); //底层会将ThreadLocal作为键,去map集合中查找Connection
    threadLocal.remove(); //将ThreadLocal,connection解绑,通常释放连接时,调用此方法
    
2.2QueryRunner对象
2.2.1DML
    int runner.update(sql,args...);
    eg: runner.update("insert into account values(id,name)",id,name);
    
2.2.2DQL
    <T> T runner.query(sql,T,args...);
List<Account>	T = new BeanListHandler<Account>(Account.class) 
Account		    T = new BeanHandler<Account>(Account.class)
    
2.2.3AccountDaoImpl连接不再是直接注入QueryRunner,而是通过ConnectionUtils获取,从而实现一组事物共用一个连接
    <!--改造前-->
	QueryRunner runner = new QueryRunner(DataSource ds);
    runner.query(sql,args...);
    <!--改造后-->
    QueryRunner runner = new QueryRunner();
    runner.query(connection,sql,args...);
3.动态代理
3.1基于接口的动态代理
	IProduct proxyProduct = (IProduct)Proxy.newProcyInstance(Classloader classLoader,Class[] interface,new InvocationHandler(){ ... });
ClassLoader:目标对象的类加载器   
Class[]:目标对象实现接口的字节码数组
InvocationHandler: 具体的增强的代码

3.2基于子类的动态代理
	Product proxyProduct = (Product)Enhancer.create(Product.class,new MethodInterceptor(){ ... });
Class:目标对象的字节码
MethodInterceptor: 具体的增强的代码

3.3接口动态代理和子类动态代理
1.前者需要接口,后者不需要接口
2.前者,代理对象与被代理对象是兄弟关系
cglib动态代理,代理对象与被代理对象是父子关系,代理对象继承了被代理对象。
3.注意在使用method.invoke(obj,args)时,绝对不要把obj对象指定为代理对象,这样就是递归
4.AOP
4.1 AOP:全称是 Aspect Oriented Programming 即: 面向切面编程。
	把我们程序重复的代码抽取出来,在需要执行的时候,使用动态代理的技术,在不修改源码的基础上,对我们的已有方法进行增强。

4.2 AOP 的实现方式
	使用动态代理技术,在Spring中,框架会根据目标类是否实现了接口来决定采用哪种动态代理的方式。 

4.3 AOP中的概念
4.3.1核心概念:
	目标类(target): 要被增强的类,这个类必须被spring容器管理
    代理类(proxy): 使用动态代理产生目标类的代理
    切入点(pointcut):目标类中需要增强的方法,这些方法都称为切入点   
    通知(advice): 增强类中定义的方法,这些方法用于增强目标方法
    切面(aspect): 切入点+通知

4.3.2其他概念:
    连接点(joinpoint):目标类中的所有方法,连接点包含切入点
    织入(weaving): 将通知方法加到目标方法中的过程,spring aop整个过程就是织入
    引入(introduction): 在目标类引入新的属性或者新的方法

4.4基于XML的AOP配置
<!--配置通知类bean对象,配置目标类bean对象-->
	......
<!--开启AOP配置-->
<aop:config> 
    <!--配置切入点表达式-->
    <aop:pointcut id="pc" expression="execution(* com.baidu.utils.Loggle.*(..))">		    	</aop:pointcut>
    <!--配置切面,将通知和切入点关联-->
    <aop:aspect id="loggleAdvice" ref="loggle">
        <!--前置通知-->
       <aop:before method="beforeLoggle" pointcut="execution(* com.baidu.utils.Loggle.*(..))">	   	 </aop:before>
        <!--后置通知-->
    	<aop:after-returning method="afterReturningLoggle" pointcut-ref="pc" >
        </aop:after-returning>
        <!--异常通知-->
        <aop:after-throwing ... ></aop:after-throwing>
        <!--最终通知-->
        <aop:after ... ></aop:after>
    </aop:aspect>
</aop:config>

4.5注意事项:
4.5.1 目标类(切入点方法对应的类)和通知类都需要被spring容器管理;
4.5.2 aop:aspect > id : 切入面的唯一标识; aop:aspect > ref : 指定通知类的Bean对象的id;
4.5.3 aop:before > method : 指定具体的前置通知增强的方法名;
aop:before > pointcut属性:用于指定切入点表达式,该表达式的含义指的是对业务层中哪些方法增强;

实验:在目标类的构造方法中调用本类的方法,同时在测试代码中从spring容器获取目标类bean对象,然后通过bean对象调用刚刚的方法,结果先走构造方法中的调用方法,不能触发通知的方法;再走bean对象的调用方法,可以完成通知;验证了目标类和通知类都必须被spring容器管理

4.6切入点表达式的写法:
	访问修饰符 返回值类型 全限定类名.方法名(参数列表)
4.6.1标准的表达式写法:
 public void com.baidu.service.impl.AccountServiceImpl.saveAccount()
4.6.2访问修饰符可以省略
void com.baidu.service.impl.AccountServiceImpl.saveAccount()
4.6.3返回值可以使用通配符,表示任意返回值
* com.baidu.service.impl.AccountServiceImpl.saveAccount()
4.6.4包名可以使用通配符,表示任意包。但是有几级包,就需要写几个*
 * *.*.*.*.AccountServiceImpl.saveAccount())
4.6.5包名可以使用..表示当前包及其子包
* *..AccountServiceImpl.saveAccount()
4.6.6类名和方法名都可以使用*来实现通配
* *..*.*()
4.6.7参数列表:
可以直接写数据类型:
基本类型直接写名称           int
引用类型写包名.类名的方式   java.lang.String
可以使用通配符表示任意类型,但是必须有参数
可以使用..表示有无参数均可,有参数可以是任意类型
4.6.8全通配符写法: * *..*.*(..)
4.6.8实际开发中切入点表达式的通常写法:
 切到业务层实现类下的所有方法
* com.baidu.service.impl.*.*(..)
总结:可以用通配符表示任意一个包或者类或者方法,用..表示当前包及其子包,用..表示方法参数可有可无且任意类型

4.7环绕通知的配置:
4.7.1XML中的配置
<aop:aspect id="around" ref="loggle">
    <aop:around method="aroundLoggle" pointcut="execution(* com.baidu..*.*(..))">
</aop:aspect>
4.7.2环绕通知的方法的配置,环绕通知需要手动调用切入点方法
    public void aroundLoggle(ProceedingJoinPoint pjp){
        try{
            //调用前置通知方法
            <!--这一段相当于调用切入点方法-->
            Object[] args = pjp.getArgs();
            pjp.proceed(args);
            //调用后置通知方法
        }catvh(Throw t){
            //调用异常通知方法
            t.printStackTrace()
        }finally{
            //调用最终通知方法
        }
    }
4.7.3注意事项:
     当我们配置了环绕通知之后,切入点方法没有执行,而通知方法执行了。Spring框架为我们提供了一个接口:ProceedingJoinPoint。该接口有一个方法proceed(),此方法就相当于明确调用切入点方法。    
    
4.8各种通知总结
	前置通知 before   目标方法被调用之前,就执行该前置通知方法
	后置通知 after-returning  目标方法return返回之后,就执行该返回通知方法
    异常通知 after-throwing  当目标方法在执行异常的时候,就会执行该异常通知方法
	最终通知 after    目标方法被调用完之后,不关心返回结果,就执行该最终通知方法
	环绕通知 around   包裹了目标方法,在目标方法之前和在目标方法之后整个过程,经常使用proceedingJoinPoint.proceed(args)来执行目标方法
    每次最多出现3种方法
5.基于注解的AOP配置
5.1导入文档声明
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/aop
        http://www.springframework.org/schema/aop/spring-aop.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context.xsd">

5.2XML中的配置
    <!--告知spring容器,需要扫描注解配置的包-->
    <context:component-scan base-package="com.baidu"></context:component-scan>
    <!--配置spring开启注解AOP支持-->
    <aop:aspectj-autoproxy></aop:aspectj-autoproxy>
  
5.3常用注解:
5.3.1目标类和通知类需要被spring容器管理,使用@Component注解,ElementType.TYPE
5.3.2在通知类的@Component下再加一个@Aspect注解,表示这是切面类,ElementType.TYPE
5.3.3在通知类的相应通知方法上加注解,ElementType.METHOD
@Before  @AfterReturning  @AfterThrowing  @After @Around value属性值必须指定,指定为调用切入点方法
    @pointcut("execution(* com.baidu..*.*(..))") 
    public void pc(){}	 //定义一个切入点方法,无论方法体有没有内容,方法体都不执行,即便故意在方法体中制造异常也没有任何效果。
使用通知注解时,需要在value属性值中调用切入点方法,即:@Before("pc()")
注意导包:import org.aspectj.lang.annotation.*;有可能会导入jdk自带的包
    
 5.4spring中的BUG
    通知的执行顺序,正确应该是: @Befor-->@AfterReturning/@AfterThrowing-->@After		
而spring容器存在一个BUG,使用注解配置AOP,执行顺序为: 
@Befor-->@After-->@AfterReturning/@AfterThrowing

猜你喜欢

转载自blog.csdn.net/qq_42514129/article/details/84642963