简介
当积累的知识点到一定量的时候,学新知识就变得容易多了。希望再接下来的学习顺利进行下去。今天知识也是挺简单的,主要就是AOP面向切面编程。其中牵涉到了JDKProxy和CGLIB两个代理类,如何使用好,加以深刻理解。学起Spring切面编程也就简单多了
代理模式
1. 代理模式介绍 代理模式的英文叫做Proxy或Surrogate,中文都可译为”代理“,所谓代理,就是一个人或者一个机构代表另一个人或者另一个机构采取行动。在一些情况下,一个客户不想或者不能够直接引用一个对象,而代理对象可以在客户端和目标对象之间起到中介的作用 2. 代理分了三个角色 抽象主题角色:就是代理类和被代理的类共同的接口 代理主题角色:就是代理类中持有了被代理类的引用,代理类通常在将客户端调用传递给现在创建的对象之前或之后,都要执行某个操作,而不是单纯地将调用传递给现有的对象 真实主题:就是代理类产生的真正的对象 3. JDK动态代理 public static <T> T getBean(final Class<T> clazz) { IUserService proxyService = (IUserService) Proxy.newProxyInstance(clazz.getClassLoader(), clazz.getInterfaces(), new InvocationHandler() { Object retVal = null; @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { if("saveUser".equals(method.getName()) || "updateUser".equals(method.getName())) { security(); retVal = method.invoke(clazz.newInstance(), args); //调用目标对象 } return retVal; } }); } 总结: 1、因为利用JDKProxy生成的代理类实现了接口,所以目标类中所有的方法在代理类中都有。 2、生成的代理类的所有的方法都拦截了目标类的所有的方法。而拦截器中invoke方法的内容正好就是代理类的各个方法的组成体。 3、利用JDKProxy方式必须有接口的存在。 4、invoke方法中的三个参数可以访问目标类的被调用方法的API、被调用方法的参数、被调用方法的返回类型 4. CGLIB代理 private Class clazz; public <T> T getEnhancer(Class<T> clazz) { this.clazz = clazz; Enhancer enhancer = new Enhancer(); enhancer.setClassLoader(clazz.getClassLoader()); enhancer.setSuperclass(clazz); enhancer.setCallback(this); return (T) enhancer.create(); } @Override public Object intercept(Object proxy, Method method, Object[] args, MethodProxy proxyMethod) throws Throwable { Object retVal = null; if("saveUser".equals(method.getName()) || "updateUser".equals(method.getName())) { security(); retVal = method.invoke(clazz.newInstance(), args); System.out.println(proxyMethod.getSignature().getName()+">>>>>>>>>"); } return retVal; } 总结: cglib代理 1. 首先要实现MethodInterceptor,重写intercept方法 2. 创建Enhancer对象,类加载器,加载类,回调方法setCallback,创建对象
Spring面向切面编程
1. Spring面向切面编程介绍 Spring提供2个代理模式,一个是jdk代理,另一个cglib代理 1.若目标对象实现了若干接口,spring使用JDK的java.lang.reflect.Proxy类代理。 2.若目标对象没有实现任何接口,spring使用CGLIB库生成目标对象的子类。 注意:开发时尽量使用接口的编程, (1)对接口创建代理优于对类创建代理,因为会产生更加松耦合的系统。 (2)标记为final的方法不能够被通知。spring是为目标类产生子类。任何需要被通知的方法都被复写,将通知织入。final方法是不允许重写的。 (3) spring只支持方法连接点,不支持属性的连接点 2. AOP概念 JDKProxy代理 SpringAop 目标对象 目标对象(Target) 拦截器类 切面(Aspect) 拦截器类中的方法 通知(Advice) 被拦截到的目标类中方法的集合 切入点(Pointcut) 在客户端调用的方法(目标类目标方法) 连接点(joinpoint) 代理类 AOP代理(JDK&CGLIB) 代理类的代理方法生成的过程 织入(Weaving) ***通知根据拦截目标类中的目标方法的位置不一样可以分为:前置通知、后置通知、最终通知、环绕通知、异常通知
AOP编程实现方式
1. XML形式实现 1). SpringAOP编程,需要引入的jar包 2). 配置applicationContext.xml文件 a. 引入aop的约束文件 b. 声明切面(其实就是定义一个bean节点,class为切面类) c. 声明切面配置<aop:config> d. 定义切面,相当于给切面注入灵魂<aop:aspect id="" ref=""> e. 声明切入点<aop:pointcut id="" expression=""> 指定项目中哪个作为切入点 f. 定义通知<aop:before pointcut-ref="" method=""> 注入切入点,声明哪个方法要进行通知 2. 注解方式实现 1). SpringAOP编程,需要引入的jar包 2). 配置applicationContext.xml文件 a. 引入aop的约束文件 b. 声明切面对象/声明接口实现类对象<bean> c. /**在类上面加入切面*/@Aspect //表示在spring容器中定义:<aop:aspect id="aa" ref="security"> d. 声明切入点 @Pointcut(value="execution(* cn.itcast.f_aspectJ.a_before.UserServiceImpl.saveUser(..))") public void save(){}; e. 加入通知@AfterReturning(value="save() || find()",returning="returnValue") 3. 通知详解 1). 任何通知方法可以将第一个参数定义为org.aspectj.lang.JoinPoint类型 ( * 环绕通知需要定义第一个参数为ProceedingJoinPoint类型, 它是 JoinPoint 的一个子类) 2). 前置通知(before):在访问目标对象方法之前,先执行通知定义的方法 特点:如果代理对象(切面)中的方法(通知)抛出异常,此时不会执行目标对象 3). 后置通知(after-returning):在访问目标对象方法之后,再执行通知定义的方法 特点:1:如果在目标对象中抛出异常,此时不会执行通知 2:因为是先执行目标对象中的方法,再执行通知,所以能不能在通知中获取目标对象的方法的返回值?能 第一步:在spring容器中定义:returning="returnValue" 第二步:在通知的方法中的第二个参数,可以指定Object类型 4). 异常通知:在访问目标对象方法之后,前提是目标对象方法中抛出异常,此时才会执行通知定义的方法 特点:1:只有目标对象方法中抛出异常,此时才会执行通知 2:在通知的方法中捕获异常 第一步:在spring容器中定义 第二步:在通知的方法中的第二个参数的位置,可以指定,例如public void checkSecurity(JoinPoint joinPoint,Throwable throwingValue){ * 要求一:获取目标对象抛出的异常的参数要放置在第二个参数的位置 * 要求二:类型必须指定Throwable类型 * 要求三:Throwable对应的属性值要和spring容器中定义的throwing="throwingValue"值要相匹配 5). 最终通知:在访问目标对象方法之后,不管是否抛出异常,此时都会执行通知定义的方法 特点:1:不管目标对象是否抛出异常,都会执行通知的方法 6). 环绕通知: a. 最后一种通知是环绕通知。环绕通知在一个目标对象方法执行之前和之后执行。它使得通知有机会 b. 在一个方法执行之前和执行之后运行。而且它可以决定这个方法在什么时候执行,如何执行,甚至是否执行。 环绕通知经常在某线程安全的环境下,你需要在一个方法执行之前和之后共享某种状态的时候使用。 请尽量使用最简单的满足你需求的通知。(比如如果简单的前置通知也可以适用的情况下不要使用环绕通知)
切入点表达式(Expression)
1). 其实execution中方法全名,5个参数:带?号表示非必填项 execution( modifiers-pattern? ret-type-pattern declaring-type-pattern? name-pattern(param-pattern) throws-pattern? ) 2). 例子: 任意公共方法的执行: execution(public * *(..)) 任何一个名字以“set”开始的方法的执行: execution(* set*(..)) AccountService接口定义的任意方法的执行: execution(* com.xyz.service.AccountService.*(..)) 在service包中定义的任意方法的执行: execution(* com.xyz.service.*.*(..)) 重点: 在service包或其子包中定义的任意方法的执行: execution(* com.xyz.service..*.*(..)) 总结: * 代表所有,可以理解成通配符 *(..) 代表所有方法 save*(*,String) 代表以save开头的方法,第一个参数为任意类型,第二个参数为string类型
Spring+JDBC
Jdbc编程特点 静态代码+动态变量 = jdbc编程。在spring中动态变量可以用注入的形式给予。这样的编程方式适合包装成模板。静态代码构成了模板,而动态变量则是需要传入的参数 JDBCTemplate编码 1. 导包在spring中依赖包和核心包中找 com.springsource.org.apache.commons.dbcp-1.2.2.osgi.jar com.springsource.org.apache.commons.pool-1.5.3.jar com.springsource.org.junit-4.7.0.jar mysql-connector-java-5.0.8-bin.jar spring-jdbc-3.2.0.RELEASE.jar spring-tx-3.2.0.RELEASE.jar 2. 在applicationContext.xml中配置DBCP连接池 <!-- 配置DBCP连接池 --> <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close"> <property name="driverClassName" value="com.mysql.jdbc.Driver"></property> <property name="url" value="jdbc:mysql://localhost:3306/testspring?useUnicode=true&characterEncoding=utf8"></property> <property name="username" value="root"></property> <property name="password" value="root"></property> <!-- 连接池启动时的初始值 --> <property name="initialSize" value="1"/> <!-- 连接池的最大值 --> <property name="maxActive" value="500"/> <!-- 最大空闲值.当经过一个高峰时间后,连接池可以慢慢将已经用不到的连接慢慢释放一部分,一直减少到maxIdle为止 --> <property name="maxIdle" value="2"/> <!-- 最小空闲值.当空闲的连接数少于该值时,连接池就会预申请一些连接,以避免洪峰来时再申请而造成的性能开销 --> <property name="minIdle" value="1"/> 3. 注入accountDao属性 <!-- 创建Dao的对象 --> <bean id="accountDao" class="cn.itcast.b_crud.AccountDaoImpl"> <property name="jdbcTemplate" ref="jdbcTemplate"></property> </bean> <!-- 创建spring提供的Jdbc模板,用来操作数据库 --> <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate"> <property name="dataSource" ref="dataSource"></property> </bean> 4. 在DAO中操作数据库 private JdbcTemplate jdbcTemplate; CUD使用udpate方法,查询使用query方法 #Spring整合junit,完成测试 1. 导入•org.springframework.test-3.0.2.RELEASE.jar 2. 加入注解,@RunWith(value=SpringJUnit4ClassRunner.class) @ContextConfiguration(value="classpath:cn/itcast/b_crud/beans.xml") 3. @Resource(name="accountDao")